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); Model.Messages.Add(message);
Messages.Add(new MessageViewModel(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 sealed class ChatViewModel : ViewModelBase
{ {
public Action? LoadMoreRequested;
public ObservableCollection<MessageViewModel> Messages public ObservableCollection<MessageViewModel> Messages
{ {
get; get;
@@ -57,6 +59,17 @@ public sealed class ChatViewModel : ViewModelBase
public string CurrentChannelName { get; private set; } = string.Empty; public string CurrentChannelName { get; private set; } = string.Empty;
public bool IsLoadingMore
{
get;
set
{
if (field == value) return;
field = value;
this.RaisePropertyChanged();
}
}
public ChatViewModel() public ChatViewModel()
{ {
Composer = new MessageComposerViewModel(); Composer = new MessageComposerViewModel();
@@ -57,6 +57,7 @@ public sealed class MainWindowViewModel : ViewModelBase
Chat.Composer.SendRequested += HandleSendRequested; Chat.Composer.SendRequested += HandleSendRequested;
Chat.Composer.CommandRequested += HandleCommandAsync; Chat.Composer.CommandRequested += HandleCommandAsync;
Chat.Composer.FileUploadRequested += HandleFileUploadRequested; Chat.Composer.FileUploadRequested += HandleFileUploadRequested;
Chat.LoadMoreRequested += HandleLoadMoreRequested;
WireCommandHandlerEvents(); WireCommandHandlerEvents();
WireConnectionServiceEvents(); 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) private void HandleChannelSelected(ChannelViewModel? channel)
{ {
if (channel is null) if (channel is null)
+39
View File
@@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
@@ -12,6 +13,8 @@ public partial class MessageListView : UserControl
private ChatViewModel? _chat; private ChatViewModel? _chat;
private bool _hasPendingScroll; private bool _hasPendingScroll;
private bool _wasAtBottom; private bool _wasAtBottom;
private bool _loadingMore;
private double _loadingMoreExtent;
public MessageListView() public MessageListView()
{ {
@@ -25,6 +28,7 @@ public partial class MessageListView : UserControl
{ {
_chat.Messages.CollectionChanged -= OnMessagesChanged; _chat.Messages.CollectionChanged -= OnMessagesChanged;
_chat.PropertyChanged -= OnViewModelPropertyChanged; _chat.PropertyChanged -= OnViewModelPropertyChanged;
_chat.LoadMoreRequested -= OnLoadMoreRequested;
} }
ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer"); ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer");
@@ -35,10 +39,20 @@ public partial class MessageListView : UserControl
{ {
_chat.Messages.CollectionChanged += OnMessagesChanged; _chat.Messages.CollectionChanged += OnMessagesChanged;
_chat.PropertyChanged += OnViewModelPropertyChanged; _chat.PropertyChanged += OnViewModelPropertyChanged;
_chat.LoadMoreRequested += OnLoadMoreRequested;
scroll?.ScrollChanged += OnScrollChanged; 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) private void OnScrollChanged(object? sender, ScrollChangedEventArgs e)
{ {
if (e.ExtentDelta.Y > 0 && _wasAtBottom) if (e.ExtentDelta.Y > 0 && _wasAtBottom)
@@ -48,6 +62,14 @@ public partial class MessageListView : UserControl
} }
ScrollViewer scrollViewer = (ScrollViewer)sender!; 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; _wasAtBottom = scrollViewer.Offset.Y + scrollViewer.Viewport.Height >= scrollViewer.Extent.Height - 30;
} }
@@ -58,6 +80,10 @@ public partial class MessageListView : UserControl
_chat!.Messages.CollectionChanged += OnMessagesChanged; _chat!.Messages.CollectionChanged += OnMessagesChanged;
ScrollToBottom(); ScrollToBottom();
} }
else if (e.PropertyName == nameof(ChatViewModel.IsLoadingMore) && !_chat!.IsLoadingMore && _loadingMore)
{
RestoreScrollPosition();
}
} }
private void OnMessagesChanged(object? sender, NotifyCollectionChangedEventArgs e) 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() private void TryAutoScroll()
{ {
ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer"); ScrollViewer? scroll = this.FindControl<ScrollViewer>("MessageScrollViewer");