Auto load more messages when scrolling up

This commit is contained in:
Stone_Red
2026-07-17 18:02:33 +02:00
parent 831ffe46d2
commit 75c1ee4d49
5 changed files with 107 additions and 1 deletions
+16
View File
@@ -111,4 +111,20 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase
Model.Messages.Add(message);
Messages.Add(new MessageViewModel(message));
}
public void InsertMessages(IList<MessageModel> olderMessages)
{
HashSet<string> existingIds = Messages.Select(m => m.Model.Id).ToHashSet();
List<MessageModel> fresh = olderMessages.Where(m => !existingIds.Contains(m.Id)).ToList();
if (fresh.Count == 0)
{
return;
}
foreach (MessageModel msg in fresh)
{
Model.Messages.Insert(0, msg);
Messages.Insert(0, new MessageViewModel(msg));
}
}
}
+13
View File
@@ -7,6 +7,8 @@ namespace Decho.ViewModels;
public sealed class ChatViewModel : ViewModelBase
{
public Action? LoadMoreRequested;
public ObservableCollection<MessageViewModel> Messages
{
get;
@@ -57,6 +59,17 @@ public sealed class ChatViewModel : ViewModelBase
public string CurrentChannelName { get; private set; } = string.Empty;
public bool IsLoadingMore
{
get;
set
{
if (field == value) return;
field = value;
this.RaisePropertyChanged();
}
}
public ChatViewModel()
{
Composer = new MessageComposerViewModel();
@@ -57,6 +57,7 @@ public sealed class MainWindowViewModel : ViewModelBase
Chat.Composer.SendRequested += HandleSendRequested;
Chat.Composer.CommandRequested += HandleCommandAsync;
Chat.Composer.FileUploadRequested += HandleFileUploadRequested;
Chat.LoadMoreRequested += HandleLoadMoreRequested;
WireCommandHandlerEvents();
WireConnectionServiceEvents();
@@ -879,6 +880,43 @@ public sealed class MainWindowViewModel : ViewModelBase
}
}
private async void HandleLoadMoreRequested()
{
string serverUrl = Chat.CurrentServerUrl;
string channelName = Chat.CurrentChannelName;
if (string.IsNullOrEmpty(serverUrl) || string.IsNullOrEmpty(channelName))
{
return;
}
ChannelViewModel? channel = FindChannelViewModel(serverUrl, channelName);
if (channel is null)
{
return;
}
Chat.IsLoadingMore = true;
try
{
int offset = channel.Messages.Count;
List<MessageModel> older = await ConnectionService.GetHistoryAsync(serverUrl, channelName, HubConstants.DefaultHistoryCount, offset);
if (older.Count > 0)
{
channel.InsertMessages(older);
}
}
catch
{
// silently ignore
}
finally
{
Chat.IsLoadingMore = false;
}
}
private void HandleChannelSelected(ChannelViewModel? channel)
{
if (channel is null)
+39
View File
@@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Threading;
@@ -12,6 +13,8 @@ public partial class MessageListView : UserControl
private ChatViewModel? _chat;
private bool _hasPendingScroll;
private bool _wasAtBottom;
private bool _loadingMore;
private double _loadingMoreExtent;
public MessageListView()
{
@@ -25,6 +28,7 @@ public partial class MessageListView : UserControl
{
_chat.Messages.CollectionChanged -= OnMessagesChanged;
_chat.PropertyChanged -= OnViewModelPropertyChanged;
_chat.LoadMoreRequested -= OnLoadMoreRequested;
}
ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer");
@@ -35,10 +39,20 @@ public partial class MessageListView : UserControl
{
_chat.Messages.CollectionChanged += OnMessagesChanged;
_chat.PropertyChanged += OnViewModelPropertyChanged;
_chat.LoadMoreRequested += OnLoadMoreRequested;
scroll?.ScrollChanged += OnScrollChanged;
}
}
private void OnLoadMoreRequested()
{
ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer");
if (scroll is null) return;
_loadingMore = true;
_loadingMoreExtent = scroll.Extent.Height;
}
private void OnScrollChanged(object? sender, ScrollChangedEventArgs e)
{
if (e.ExtentDelta.Y > 0 && _wasAtBottom)
@@ -48,6 +62,14 @@ public partial class MessageListView : UserControl
}
ScrollViewer scrollViewer = (ScrollViewer)sender!;
if (!_loadingMore && _chat is not null && !_chat.IsLoadingMore
&& scrollViewer.Offset.Y <= 0
&& scrollViewer.Extent.Height > scrollViewer.Viewport.Height)
{
_chat.LoadMoreRequested?.Invoke();
}
_wasAtBottom = scrollViewer.Offset.Y + scrollViewer.Viewport.Height >= scrollViewer.Extent.Height - 30;
}
@@ -58,6 +80,10 @@ public partial class MessageListView : UserControl
_chat!.Messages.CollectionChanged += OnMessagesChanged;
ScrollToBottom();
}
else if (e.PropertyName == nameof(ChatViewModel.IsLoadingMore) && !_chat!.IsLoadingMore && _loadingMore)
{
RestoreScrollPosition();
}
}
private void OnMessagesChanged(object? sender, NotifyCollectionChangedEventArgs e)
@@ -68,6 +94,19 @@ public partial class MessageListView : UserControl
}
}
private void RestoreScrollPosition()
{
ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer");
if (scroll is null) return;
Dispatcher.UIThread.Post(() =>
{
double delta = scroll.Extent.Height - _loadingMoreExtent;
scroll.Offset = new Vector(scroll.Offset.X, scroll.Offset.Y + delta);
_loadingMore = false;
}, DispatcherPriority.Background);
}
private void TryAutoScroll()
{
ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer");