From 75c1ee4d4910dd33e0bd4b058f30c615853960fc Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:33:35 +0200 Subject: [PATCH] Auto load more messages when scrolling up --- src/Decho/ViewModels/ChannelViewModel.cs | 16 +++++++++ src/Decho/ViewModels/ChatViewModel.cs | 13 +++++++ src/Decho/ViewModels/MainWindowViewModel.cs | 38 ++++++++++++++++++++ src/Decho/Views/ImageViewerWindow.axaml.cs | 2 +- src/Decho/Views/MessageListView.axaml.cs | 39 +++++++++++++++++++++ 5 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/Decho/ViewModels/ChannelViewModel.cs b/src/Decho/ViewModels/ChannelViewModel.cs index df74915..cc94a56 100644 --- a/src/Decho/ViewModels/ChannelViewModel.cs +++ b/src/Decho/ViewModels/ChannelViewModel.cs @@ -111,4 +111,20 @@ public sealed class ChannelViewModel(ChannelModel model) : ViewModelBase Model.Messages.Add(message); Messages.Add(new MessageViewModel(message)); } + + public void InsertMessages(IList olderMessages) + { + HashSet existingIds = Messages.Select(m => m.Model.Id).ToHashSet(); + List 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)); + } + } } \ No newline at end of file diff --git a/src/Decho/ViewModels/ChatViewModel.cs b/src/Decho/ViewModels/ChatViewModel.cs index 0c30102..d485a98 100644 --- a/src/Decho/ViewModels/ChatViewModel.cs +++ b/src/Decho/ViewModels/ChatViewModel.cs @@ -7,6 +7,8 @@ namespace Decho.ViewModels; public sealed class ChatViewModel : ViewModelBase { + public Action? LoadMoreRequested; + public ObservableCollection 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(); diff --git a/src/Decho/ViewModels/MainWindowViewModel.cs b/src/Decho/ViewModels/MainWindowViewModel.cs index 963f6ac..1902562 100644 --- a/src/Decho/ViewModels/MainWindowViewModel.cs +++ b/src/Decho/ViewModels/MainWindowViewModel.cs @@ -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 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) diff --git a/src/Decho/Views/ImageViewerWindow.axaml.cs b/src/Decho/Views/ImageViewerWindow.axaml.cs index befc64d..b0031e5 100644 --- a/src/Decho/Views/ImageViewerWindow.axaml.cs +++ b/src/Decho/Views/ImageViewerWindow.axaml.cs @@ -31,4 +31,4 @@ public sealed partial class ImageViewerWindow : Window { Close(); } -} +} \ No newline at end of file diff --git a/src/Decho/Views/MessageListView.axaml.cs b/src/Decho/Views/MessageListView.axaml.cs index 73ff212..d93e5eb 100644 --- a/src/Decho/Views/MessageListView.axaml.cs +++ b/src/Decho/Views/MessageListView.axaml.cs @@ -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("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("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("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("MessageScrollViewer");