mirror of
https://github.com/Stone-Red-Code/Decho.git
synced 2026-09-07 15:56:12 +02:00
Auto load more messages when scrolling up
This commit is contained in:
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -31,4 +31,4 @@ public sealed partial class ImageViewerWindow : Window
|
|||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user