fix(ui): batch UI updates for bulk mark-as-read to prevent UI freeze (#786)
This commit is contained in:
@@ -13,22 +13,26 @@ public record MarkFolderAsReadRequest(MailItemFolder Folder, List<MailCopy> Mail
|
|||||||
{
|
{
|
||||||
public override void ApplyUIChanges()
|
public override void ApplyUIChanges()
|
||||||
{
|
{
|
||||||
|
if (MailsToMarkRead == null || MailsToMarkRead.Count == 0) return;
|
||||||
|
|
||||||
foreach (var item in MailsToMarkRead)
|
foreach (var item in MailsToMarkRead)
|
||||||
{
|
{
|
||||||
item.IsRead = true;
|
item.IsRead = true;
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new BulkMailUpdatedMessage(MailsToMarkRead));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void RevertUIChanges()
|
public override void RevertUIChanges()
|
||||||
{
|
{
|
||||||
|
if (MailsToMarkRead == null || MailsToMarkRead.Count == 0) return;
|
||||||
|
|
||||||
foreach (var item in MailsToMarkRead)
|
foreach (var item in MailsToMarkRead)
|
||||||
{
|
{
|
||||||
item.IsRead = false;
|
item.IsRead = false;
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new BulkMailUpdatedMessage(MailsToMarkRead));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Guid> SynchronizationFolderIds => [Folder.Id];
|
public List<Guid> SynchronizationFolderIds => [Folder.Id];
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using System.Collections.Generic;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Models.Folders;
|
using Wino.Core.Domain.Models.Folders;
|
||||||
@@ -11,6 +12,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
IRecipient<MailAddedMessage>,
|
IRecipient<MailAddedMessage>,
|
||||||
IRecipient<MailRemovedMessage>,
|
IRecipient<MailRemovedMessage>,
|
||||||
IRecipient<MailUpdatedMessage>,
|
IRecipient<MailUpdatedMessage>,
|
||||||
|
IRecipient<BulkMailUpdatedMessage>,
|
||||||
IRecipient<MailDownloadedMessage>,
|
IRecipient<MailDownloadedMessage>,
|
||||||
IRecipient<DraftCreated>,
|
IRecipient<DraftCreated>,
|
||||||
IRecipient<DraftFailed>,
|
IRecipient<DraftFailed>,
|
||||||
@@ -21,6 +23,17 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
protected virtual void OnMailAdded(MailCopy addedMail) { }
|
protected virtual void OnMailAdded(MailCopy addedMail) { }
|
||||||
protected virtual void OnMailRemoved(MailCopy removedMail) { }
|
protected virtual void OnMailRemoved(MailCopy removedMail) { }
|
||||||
protected virtual void OnMailUpdated(MailCopy updatedMail) { }
|
protected virtual void OnMailUpdated(MailCopy updatedMail) { }
|
||||||
|
|
||||||
|
protected virtual void OnMailUpdated(IReadOnlyList<MailCopy> updatedMails)
|
||||||
|
{
|
||||||
|
if (updatedMails == null) return;
|
||||||
|
|
||||||
|
foreach (var mail in updatedMails)
|
||||||
|
{
|
||||||
|
OnMailUpdated(mail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void OnMailDownloaded(MailCopy downloadedMail) { }
|
protected virtual void OnMailDownloaded(MailCopy downloadedMail) { }
|
||||||
protected virtual void OnDraftCreated(MailCopy draftMail, MailAccount account) { }
|
protected virtual void OnDraftCreated(MailCopy draftMail, MailAccount account) { }
|
||||||
protected virtual void OnDraftFailed(MailCopy draftMail, MailAccount account) { }
|
protected virtual void OnDraftFailed(MailCopy draftMail, MailAccount account) { }
|
||||||
@@ -31,6 +44,7 @@ public class MailBaseViewModel : CoreBaseViewModel,
|
|||||||
void IRecipient<MailAddedMessage>.Receive(MailAddedMessage message) => OnMailAdded(message.AddedMail);
|
void IRecipient<MailAddedMessage>.Receive(MailAddedMessage message) => OnMailAdded(message.AddedMail);
|
||||||
void IRecipient<MailRemovedMessage>.Receive(MailRemovedMessage message) => OnMailRemoved(message.RemovedMail);
|
void IRecipient<MailRemovedMessage>.Receive(MailRemovedMessage message) => OnMailRemoved(message.RemovedMail);
|
||||||
void IRecipient<MailUpdatedMessage>.Receive(MailUpdatedMessage message) => OnMailUpdated(message.UpdatedMail);
|
void IRecipient<MailUpdatedMessage>.Receive(MailUpdatedMessage message) => OnMailUpdated(message.UpdatedMail);
|
||||||
|
void IRecipient<BulkMailUpdatedMessage>.Receive(BulkMailUpdatedMessage message) => OnMailUpdated(message.UpdatedMails);
|
||||||
void IRecipient<MailDownloadedMessage>.Receive(MailDownloadedMessage message) => OnMailDownloaded(message.DownloadedMail);
|
void IRecipient<MailDownloadedMessage>.Receive(MailDownloadedMessage message) => OnMailDownloaded(message.DownloadedMail);
|
||||||
|
|
||||||
void IRecipient<DraftMapped>.Receive(DraftMapped message) => OnDraftMapped(message.LocalDraftCopyId, message.RemoteDraftCopyId);
|
void IRecipient<DraftMapped>.Receive(DraftMapped message) => OnDraftMapped(message.LocalDraftCopyId, message.RemoteDraftCopyId);
|
||||||
|
|||||||
@@ -685,6 +685,37 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
await ExecuteUIThread(() => { SetupTopBarActions(); });
|
await ExecuteUIThread(() => { SetupTopBarActions(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnMailUpdated(IReadOnlyList<MailCopy> updatedMails)
|
||||||
|
{
|
||||||
|
base.OnMailUpdated(updatedMails);
|
||||||
|
|
||||||
|
if (updatedMails == null || updatedMails.Count == 0) return;
|
||||||
|
|
||||||
|
// Bulk update: do all changes in a single UI-thread invocation to avoid UI lockups when
|
||||||
|
// thousands of MailUpdatedMessage events would otherwise be processed one by one.
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
foreach (var mail in updatedMails)
|
||||||
|
{
|
||||||
|
if (mail == null) continue;
|
||||||
|
|
||||||
|
// Avoid work for items not in the list.
|
||||||
|
if (!MailCollection.MailCopyIdHashSet.Contains(mail.UniqueId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var container = MailCollection.GetMailItemContainer(mail.UniqueId);
|
||||||
|
|
||||||
|
if (container?.ItemViewModel != null)
|
||||||
|
{
|
||||||
|
container.ItemViewModel.MailCopy = mail;
|
||||||
|
container.ThreadViewModel?.NotifyPropertyChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupTopBarActions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override async void OnMailRemoved(MailCopy removedMail)
|
protected override async void OnMailRemoved(MailCopy removedMail)
|
||||||
{
|
{
|
||||||
base.OnMailRemoved(removedMail);
|
base.OnMailRemoved(removedMail);
|
||||||
|
|||||||
@@ -596,13 +596,22 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
|||||||
// This is done with UniqueId to include FolderId into calculations.
|
// This is done with UniqueId to include FolderId into calculations.
|
||||||
if (initializedMailItemViewModel.UniqueId != updatedMail.UniqueId) return;
|
if (initializedMailItemViewModel.UniqueId != updatedMail.UniqueId) return;
|
||||||
|
|
||||||
// Mail operation might change the mail item like mark read/unread or change flag.
|
|
||||||
// So we need to update the mail item view model when this happens.
|
|
||||||
// Also command bar items must be re-initialized since the items loaded based on the mail item.
|
|
||||||
|
|
||||||
await ExecuteUIThread(() => { InitializeCommandBarItems(); });
|
await ExecuteUIThread(() => { InitializeCommandBarItems(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async void OnMailUpdated(IReadOnlyList<MailCopy> updatedMails)
|
||||||
|
{
|
||||||
|
base.OnMailUpdated(updatedMails);
|
||||||
|
|
||||||
|
if (initializedMailItemViewModel == null || updatedMails == null || updatedMails.Count == 0) return;
|
||||||
|
|
||||||
|
// Only care about the currently rendered item.
|
||||||
|
if (updatedMails.Any(m => m?.UniqueId == initializedMailItemViewModel.UniqueId))
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => { InitializeCommandBarItems(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task OpenAttachmentAsync(MailAttachmentViewModel attachmentViewModel)
|
private async Task OpenAttachmentAsync(MailAttachmentViewModel attachmentViewModel)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
|
||||||
|
namespace Wino.Messaging.UI;
|
||||||
|
|
||||||
|
public record BulkMailUpdatedMessage(IReadOnlyList<MailCopy> UpdatedMails) : UIMessageBase<BulkMailUpdatedMessage>;
|
||||||
Reference in New Issue
Block a user