From 3b3c878d0eb8f2c20f8832acb83f5f408200608c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Tue, 21 Oct 2025 01:57:08 +0200 Subject: [PATCH] Fix resetting selected item on loading more. --- .../Collections/GroupedEmailCollection.cs | 234 +++++++++++++++++- 1 file changed, 231 insertions(+), 3 deletions(-) diff --git a/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs b/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs index b67dba8e..2c6f5a40 100644 --- a/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs +++ b/Wino.Mail.ViewModels/Collections/GroupedEmailCollection.cs @@ -404,14 +404,15 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient + /// Incrementally adds new emails to the collection without clearing existing items + /// + private void IncrementalRefreshGrouping(IList newEmails) + { + _isUpdating = true; + try + { + if (!newEmails.Any()) + return; + + // Update thread expanders with any new emails that should be threaded + UpdateThreadExpandersForNewEmails(newEmails); + + // Process each new email + foreach (var email in newEmails) + { + // Skip if it's already displayed in a thread + if (email.IsDisplayedInThread) + continue; + + // Determine the group key for this email + var groupKey = GetGroupKeyForItem(email); + + // Get or create the group header + var groupHeader = GetOrCreateGroupHeader(groupKey); + + // Find where this email should be inserted in the UI + var insertPosition = FindUIInsertionPosition(email, groupKey); + + // Insert the email at the correct position + Items.Insert(insertPosition, email); + + // Update the group items list + if (!_groupItems.ContainsKey(groupKey)) + { + _groupItems[groupKey] = new List(); + } + + // Insert in the group items list maintaining sort order + var groupItems = _groupItems[groupKey]; + var groupInsertIndex = FindGroupInsertionIndex(email, groupItems); + groupItems.Insert(groupInsertIndex, email); + + // If this is the first item in a new group, we need to add the header + if (groupItems.Count == 1) + { + // Find where to insert the header + var headerInsertPosition = FindHeaderInsertionPosition(groupKey, groupHeader); + Items.Insert(headerInsertPosition, groupHeader); + _groupHeaderIndexCache[groupKey] = headerInsertPosition; + + // Update all subsequent header indices + UpdateSubsequentHeaderIndices(groupKey, 1); + } + } + + // Update group header counts for affected groups + UpdateGroupHeaderCountsForNewEmails(newEmails); + } + finally + { + _isUpdating = false; + } + } + /// /// Rebuilds thread expanders based on current source emails /// @@ -1074,7 +1141,168 @@ public partial class GroupedEmailCollection : ObservableObject, IRecipient + /// Updates thread expanders when new emails are added + /// + private void UpdateThreadExpandersForNewEmails(IList newEmails) + { + // Group new emails by ThreadId + var newThreadGroups = newEmails + .Where(e => !string.IsNullOrEmpty(e.MailCopy?.ThreadId)) + .GroupBy(e => e.MailCopy!.ThreadId!) + .ToList(); + + foreach (var threadGroup in newThreadGroups) + { + var threadId = threadGroup.Key; + + if (_threadExpanders.TryGetValue(threadId, out var existingExpander)) + { + // Add new emails to existing thread + foreach (var email in threadGroup) + { + existingExpander.AddEmail(email); + email.IsDisplayedInThread = true; + } + } + else + { + // Check if we need to create a new thread with existing emails + var existingEmailsInThread = _sourceItems + .Where(e => e.MailCopy?.ThreadId == threadId && !threadGroup.Contains(e)) + .ToList(); + + var allThreadEmails = existingEmailsInThread.Concat(threadGroup).ToList(); + + if (allThreadEmails.Count >= 2) + { + // Create new thread expander + var expander = new ThreadMailItemViewModel(threadId); + _threadExpanders[threadId] = expander; + + // Add all emails to the thread + foreach (var email in allThreadEmails) + { + expander.AddEmail(email); + email.IsDisplayedInThread = true; + } + } + } + } + } + + /// + /// Finds the correct position to insert an email in the UI Items collection + /// + private int FindUIInsertionPosition(MailItemViewModel email, string groupKey) + { + // If group doesn't exist yet, find position for new group + if (!_groupHeaderIndexCache.ContainsKey(groupKey)) + { + return FindGroupInsertionPosition(groupKey); + } + + var headerIndex = _groupHeaderIndexCache[groupKey]; + var groupEndIndex = FindGroupEndIndex(headerIndex); + + return FindItemInsertionIndexInGroup(email, headerIndex, groupEndIndex); + } + + /// + /// Finds the correct position to insert an email within a group's items list + /// + private int FindGroupInsertionIndex(MailItemViewModel email, List groupItems) + { + var emailDate = email.MailCopy?.CreationDate ?? DateTime.MinValue; + + for (int i = 0; i < groupItems.Count; i++) + { + var existingDate = GetEffectiveDate(groupItems[i]); + var comparison = emailDate.CompareTo(existingDate); + + if (SortDirection == EmailSortDirection.Descending) + comparison = -comparison; + + if (comparison < 0) + return i; + } + + return groupItems.Count; + } + + /// + /// Finds the correct position to insert a group header + /// + private int FindHeaderInsertionPosition(string groupKey, GroupHeaderBase groupHeader) + { + if (_groupHeaderIndexCache.Count == 0) + return 0; + + var comparer = GetGroupComparer(); + var insertPosition = 0; + + foreach (var kvp in _groupHeaderIndexCache.OrderBy(k => k.Key, comparer)) + { + var existingGroupKey = kvp.Key; + var comparison = comparer.Compare(groupKey, existingGroupKey); + + if (comparison < 0) + { + insertPosition = kvp.Value; + break; + } + else + { + var groupEndIndex = FindGroupEndIndex(kvp.Value); + insertPosition = groupEndIndex; + } + } + + return insertPosition; + } + + /// + /// Updates header indices after a new group is inserted + /// + private void UpdateSubsequentHeaderIndices(string insertedGroupKey, int itemCount) + { + var insertedHeaderIndex = _groupHeaderIndexCache[insertedGroupKey]; + + var keysToUpdate = _groupHeaderIndexCache + .Where(kvp => kvp.Key != insertedGroupKey && kvp.Value > insertedHeaderIndex) + .Select(kvp => kvp.Key) + .ToList(); + + foreach (var key in keysToUpdate) + { + _groupHeaderIndexCache[key] += itemCount; + } + } + + /// + /// Updates group header counts for affected groups when new emails are added + /// + private void UpdateGroupHeaderCountsForNewEmails(IList newEmails) + { + var affectedGroups = newEmails + .Select(email => GetGroupKey(email)) + .Distinct() + .ToList(); + + foreach (var groupKey in affectedGroups) + { + if (_groupHeaders.TryGetValue(groupKey, out var groupHeader)) + { + UpdateGroupHeaderCounts(groupKey, groupHeader); + } + } + } + + #endregion + + private void OnSourceItemsChanged(object sender, NotifyCollectionChangedEventArgs e) { if (!_isUpdating) {