diff --git a/Wino.Mail.ViewModels/Data/MailItemViewModel.cs b/Wino.Mail.ViewModels/Data/MailItemViewModel.cs index bcae3d2d..851d6f79 100644 --- a/Wino.Mail.ViewModels/Data/MailItemViewModel.cs +++ b/Wino.Mail.ViewModels/Data/MailItemViewModel.cs @@ -42,6 +42,15 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient, [NotifyPropertyChangedRecipients] public partial bool IsSelected { get; set; } + /// + /// Direct callback invoked when changes. + /// Used by the ListViewItem container to update its IsCustomSelected DP + /// without subscribing to INotifyPropertyChanged (faster, AOT-safe). + /// + public Action OnSelectionChanged { get; set; } + + partial void OnIsSelectedChanged(bool value) => OnSelectionChanged?.Invoke(value); + /// /// Indicates if this mail item is currently being processed by a network operation. /// Used to show loading state in the UI. diff --git a/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs b/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs index 5f360ee1..e8f9c5f6 100644 --- a/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs +++ b/Wino.Mail.ViewModels/Data/ThreadMailItemViewModel.cs @@ -14,6 +14,8 @@ namespace Wino.Mail.ViewModels.Data; public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListItem { private readonly string _threadId; + private readonly HashSet _uniqueIdSet = []; + private MailItemViewModel _cachedLatestMailViewModel; [ObservableProperty] [NotifyPropertyChangedRecipients] @@ -25,6 +27,15 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte [NotifyPropertyChangedFor(nameof(IsSelectedOrExpanded))] public partial bool IsSelected { get; set; } + /// + /// Direct callback invoked when changes. + /// Used by the ListViewItem container to update its IsCustomSelected DP + /// without subscribing to INotifyPropertyChanged (faster, AOT-safe). + /// + public Action OnSelectionChanged { get; set; } + + partial void OnIsSelectedChanged(bool value) => OnSelectionChanged?.Invoke(value); + [ObservableProperty] public partial bool IsBusy { get; set; } @@ -168,7 +179,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte [NotifyPropertyChangedFor(nameof(Base64ContactPicture))] public partial ObservableCollection ThreadEmails { get; set; } = []; - private MailItemViewModel latestMailViewModel => ThreadEmails.OrderByDescending(e => e.MailCopy?.CreationDate).FirstOrDefault()!; + private MailItemViewModel latestMailViewModel => _cachedLatestMailViewModel; public DateTime SortingDate => CreationDate; @@ -200,6 +211,8 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte } ThreadEmails.Insert(insertIndex, email); + _uniqueIdSet.Add(email.MailCopy.UniqueId); + _cachedLatestMailViewModel = ThreadEmails[0]; // Reassign to trigger property change notifications ThreadEmails = ThreadEmails; } @@ -211,6 +224,8 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte { if (ThreadEmails.Remove(email)) { + _uniqueIdSet.Remove(email.MailCopy.UniqueId); + _cachedLatestMailViewModel = ThreadEmails.Count > 0 ? ThreadEmails[0] : null; // Reassign to trigger property change notifications ThreadEmails = ThreadEmails; } @@ -253,7 +268,7 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte /// /// Checks if this thread contains an email with the specified unique ID /// - public bool HasUniqueId(Guid uniqueId) => ThreadEmails.Any(email => email.MailCopy.UniqueId == uniqueId); + public bool HasUniqueId(Guid uniqueId) => _uniqueIdSet.Contains(uniqueId); public IEnumerable GetContainingIds() => ThreadEmails.Select(a => a.MailCopy.UniqueId); diff --git a/Wino.Mail.WinUI/Controls/ListView/WinoListView.cs b/Wino.Mail.WinUI/Controls/ListView/WinoListView.cs index 49fe2998..2ed82ab2 100644 --- a/Wino.Mail.WinUI/Controls/ListView/WinoListView.cs +++ b/Wino.Mail.WinUI/Controls/ListView/WinoListView.cs @@ -69,14 +69,12 @@ public partial class WinoListView : Microsoft.UI.Xaml.Controls.ListView && container.Item != mailItemViewModel) { container.Item = mailItemViewModel; - container.IsCustomSelected = mailItemViewModel.IsSelected; } else if (item is ThreadMailItemViewModel threadMailItemViewModel && element is WinoThreadMailItemViewModelListViewItem threadContainer && threadContainer.Item != threadMailItemViewModel) { threadContainer.Item = threadMailItemViewModel; - threadContainer.IsSelected = threadMailItemViewModel.IsSelected; threadContainer.IsThreadExpanded = threadMailItemViewModel.IsThreadExpanded; } } @@ -88,12 +86,10 @@ public partial class WinoListView : Microsoft.UI.Xaml.Controls.ListView if (item is MailItemViewModel mailItemViewModel && element is WinoMailItemViewModelListViewItem container) { container.Item = null; - container.IsCustomSelected = false; } else if (item is ThreadMailItemViewModel threadMailItemViewModel && element is WinoThreadMailItemViewModelListViewItem threadContainer) { threadContainer.Item = null; - threadContainer.IsSelected = false; threadContainer.IsThreadExpanded = false; } } diff --git a/Wino.Mail.WinUI/Controls/ListView/WinoMailItemViewModelListViewItem.cs b/Wino.Mail.WinUI/Controls/ListView/WinoMailItemViewModelListViewItem.cs index 81c8eea7..91f6816f 100644 --- a/Wino.Mail.WinUI/Controls/ListView/WinoMailItemViewModelListViewItem.cs +++ b/Wino.Mail.WinUI/Controls/ListView/WinoMailItemViewModelListViewItem.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using CommunityToolkit.WinUI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -23,26 +22,17 @@ public partial class WinoMailItemViewModelListViewItem : ListViewItem partial void OnItemPropertyChanged(DependencyPropertyChangedEventArgs e) { - // TODO: This slows down. Optimize later. - if (e.OldValue is MailItemViewModel oldMailItemViewModel) UnregisterPropertyChanged(oldMailItemViewModel); - if (e.NewValue is MailItemViewModel newMailItemViewModel) RegisterPropertyChanged(newMailItemViewModel); + if (e.OldValue is MailItemViewModel oldItem) + oldItem.OnSelectionChanged = null; - if (e.NewValue is MailItemViewModel mailItemViewModel) - IsCustomSelected = mailItemViewModel.IsSelected; - else - IsCustomSelected = false; - } - - private void RegisterPropertyChanged(MailItemViewModel model) => model.PropertyChanged += ModelPropertyChanged; - private void UnregisterPropertyChanged(MailItemViewModel model) => model.PropertyChanged -= ModelPropertyChanged; - - private void ModelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (sender is not MailItemViewModel mailItemViewModel) return; - - if (e.PropertyName == nameof(MailItemViewModel.IsSelected)) + if (e.NewValue is MailItemViewModel newItem) { - IsCustomSelected = mailItemViewModel.IsSelected; + newItem.OnSelectionChanged = (selected) => IsCustomSelected = selected; + IsCustomSelected = newItem.IsSelected; + } + else + { + IsCustomSelected = false; } } } diff --git a/Wino.Mail.WinUI/Controls/ListView/WinoThreadMailItemViewModelListViewItem.cs b/Wino.Mail.WinUI/Controls/ListView/WinoThreadMailItemViewModelListViewItem.cs index df9a719c..7114eec7 100644 --- a/Wino.Mail.WinUI/Controls/ListView/WinoThreadMailItemViewModelListViewItem.cs +++ b/Wino.Mail.WinUI/Controls/ListView/WinoThreadMailItemViewModelListViewItem.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Linq; using CommunityToolkit.WinUI; using Microsoft.UI.Xaml; @@ -40,20 +39,17 @@ public partial class WinoThreadMailItemViewModelListViewItem : ListViewItem partial void OnItemPropertyChanged(DependencyPropertyChangedEventArgs e) { - if (e.OldValue is ThreadMailItemViewModel oldMailItemViewModel) UnregisterPropertyChanged(oldMailItemViewModel); - if (e.NewValue is ThreadMailItemViewModel newMailItemViewModel) RegisterPropertyChanged(newMailItemViewModel); - } + if (e.OldValue is ThreadMailItemViewModel oldItem) + oldItem.OnSelectionChanged = null; - private void RegisterPropertyChanged(ThreadMailItemViewModel model) => model.PropertyChanged += ModelPropertyChanged; - private void UnregisterPropertyChanged(ThreadMailItemViewModel model) => model.PropertyChanged -= ModelPropertyChanged; - - private void ModelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (sender is not ThreadMailItemViewModel mailItemViewModel) return; - - if (e.PropertyName == nameof(ThreadMailItemViewModel.IsSelected)) + if (e.NewValue is ThreadMailItemViewModel newItem) { - IsCustomSelected = mailItemViewModel.IsSelected; + newItem.OnSelectionChanged = (selected) => IsCustomSelected = selected; + IsCustomSelected = newItem.IsSelected; + } + else + { + IsCustomSelected = false; } } }