diff --git a/Wino.Mail.ViewModels/Collections/ThreadingManager.cs b/Wino.Mail.ViewModels/Collections/ThreadingManager.cs new file mode 100644 index 00000000..471cf9af --- /dev/null +++ b/Wino.Mail.ViewModels/Collections/ThreadingManager.cs @@ -0,0 +1,31 @@ +using Wino.Core.Domain.Entities.Mail; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.MailItem; + +namespace Wino.Mail.ViewModels.Collections; + +internal class ThreadingManager +{ + private readonly IThreadingStrategyProvider _threadingStrategyProvider; + + public ThreadingManager(IThreadingStrategyProvider threadingStrategyProvider) + { + _threadingStrategyProvider = threadingStrategyProvider; + } + + public bool ShouldThread(MailCopy newItem, IMailItem existingItem) + { + if (_threadingStrategyProvider == null) return false; + + var strategy = _threadingStrategyProvider.GetStrategy(newItem.AssignedAccount.ProviderType); + return strategy?.ShouldThreadWithItem(newItem, existingItem) ?? false; + } + + public ThreadMailItem CreateNewThread(IMailItem existingItem, MailCopy newItem) + { + var thread = new ThreadMailItem(); + thread.AddThreadItem(existingItem); + thread.AddThreadItem(newItem); + return thread; + } +} diff --git a/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs b/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs index dfa4c0ee..9f2a132c 100644 --- a/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs +++ b/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs @@ -49,8 +49,11 @@ public class WinoMailCollection public IDispatcher CoreDispatcher { get; set; } - public WinoMailCollection() + private readonly ThreadingManager _threadingManager; + + public WinoMailCollection(IThreadingStrategyProvider threadingStrategyProvider) { + _threadingManager = new ThreadingManager(threadingStrategyProvider); MailItems = new ReadOnlyObservableGroupedCollection(_mailItemSource); } @@ -111,143 +114,109 @@ public class WinoMailCollection } } + private async Task HandleThreadingAsync(ObservableGroup group, IMailItem item, MailCopy addedItem) + { + if (item is ThreadMailItemViewModel threadViewModel) + { + await HandleExistingThreadAsync(group, threadViewModel, addedItem); + } + else + { + await HandleNewThreadAsync(group, item, addedItem); + } + } + + private async Task HandleExistingThreadAsync(ObservableGroup group, ThreadMailItemViewModel threadViewModel, MailCopy addedItem) + { + var existingGroupKey = GetGroupingKey(threadViewModel); + + await ExecuteUIThread(() => { threadViewModel.AddMailItemViewModel(addedItem); }); + + var newGroupKey = GetGroupingKey(threadViewModel); + + if (!existingGroupKey.Equals(newGroupKey)) + { + await MoveThreadToNewGroupAsync(group, threadViewModel, newGroupKey); + } + else + { + await ExecuteUIThread(() => { threadViewModel.NotifyPropertyChanges(); }); + } + + UpdateUniqueIdHashes(addedItem, true); + } + + private async Task HandleNewThreadAsync(ObservableGroup group, IMailItem item, MailCopy addedItem) + { + if (item.Id == addedItem.Id) + { + await UpdateExistingItemAsync(item, addedItem); + } + else + { + await CreateNewThreadAsync(group, item, addedItem); + } + } + + private async Task MoveThreadToNewGroupAsync(ObservableGroup currentGroup, ThreadMailItemViewModel threadViewModel, object newGroupKey) + { + var mailThreadItems = threadViewModel.GetThreadMailItem(); + + await ExecuteUIThread(() => + { + RemoveItemInternal(currentGroup, threadViewModel); + InsertItemInternal(newGroupKey, new ThreadMailItemViewModel(mailThreadItems)); + }); + } + + private async Task CreateNewThreadAsync(ObservableGroup group, IMailItem item, MailCopy addedItem) + { + var threadMailItem = _threadingManager.CreateNewThread(item, addedItem); + var newGroupKey = GetGroupingKey(threadMailItem); + + await ExecuteUIThread(() => + { + RemoveItemInternal(group, item); + InsertItemInternal(newGroupKey, threadMailItem); + }); + } + public async Task AddAsync(MailCopy addedItem) { - // Check all items for whether this item should be threaded with them. - bool shouldExit = false; - - var groupCount = _mailItemSource.Count; - - var addedAccountProviderType = addedItem.AssignedAccount.ProviderType; - var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType); - - for (int i = 0; i < groupCount; i++) + foreach (var group in _mailItemSource) { - if (shouldExit) break; - - var group = _mailItemSource[i]; - - for (int k = 0; k < group.Count; k++) + foreach (var item in group) { - var item = group[k]; - - if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false) + if (_threadingManager.ShouldThread(addedItem, item)) { - shouldExit = true; - - if (item is ThreadMailItemViewModel threadMailItemViewModel) - { - // Item belongs to existing thread. - - /* Add original item to the thread. - * If new group key is not the same as existing thread: - * -> Remove the whole thread from list - * -> Add the thread to the list again for sorting. - * Update thread properties. - */ - - var existingGroupKey = GetGroupingKey(threadMailItemViewModel); - - await ExecuteUIThread(() => { threadMailItemViewModel.AddMailItemViewModel(addedItem); }); - - var newGroupKey = GetGroupingKey(threadMailItemViewModel); - - if (!existingGroupKey.Equals(newGroupKey)) - { - var mailThreadItems = threadMailItemViewModel.GetThreadMailItem(); - - await ExecuteUIThread(() => - { - // Group must be changed for this thread. - // Remove the thread first. - - RemoveItemInternal(group, threadMailItemViewModel); - - // Insert new view model because the previous one might've been deleted with the group. - InsertItemInternal(newGroupKey, new ThreadMailItemViewModel(mailThreadItems)); - }); - } - else - { - await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); }); - } - - UpdateUniqueIdHashes(addedItem, true); - - break; - } - else - { - // Item belongs to a single mail item that is not threaded yet. - // Same item might've been tried to added as well. - // In that case we must just update the item but not thread it. - - /* Remove target item. - * Create a new thread with both items. - * Add new thread to the list. - */ - - if (item.Id == addedItem.Id) - { - // Item is already added to the list. - // We need to update the copy it holds. - - if (item is MailItemViewModel itemViewModel) - { - await ExecuteUIThread(() => { itemViewModel.MailCopy = addedItem; }); - - UpdateUniqueIdHashes(itemViewModel, false); - UpdateUniqueIdHashes(addedItem, true); - } - } - else - { - // Single item that must be threaded together with added item. - - var threadMailItem = new ThreadMailItem(); - - await ExecuteUIThread(() => - { - threadMailItem.AddThreadItem(item); - threadMailItem.AddThreadItem(addedItem); - - if (threadMailItem.ThreadItems.Count == 1) return; - - var newGroupKey = GetGroupingKey(threadMailItem); - - RemoveItemInternal(group, item); - InsertItemInternal(newGroupKey, threadMailItem); - }); - } - - break; - } + await HandleThreadingAsync(group, item, addedItem); + return; } - else + else if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel) { - // Update properties. - if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel) - { - UpdateUniqueIdHashes(itemViewModel, false); - UpdateUniqueIdHashes(addedItem, true); - - await ExecuteUIThread(() => { itemViewModel.MailCopy = addedItem; }); - - shouldExit = true; - } + await UpdateExistingItemAsync(itemViewModel, addedItem); + return; } } } - if (!shouldExit) + await AddNewItemAsync(addedItem); + } + + private async Task AddNewItemAsync(MailCopy addedItem) + { + var groupKey = GetGroupingKey(addedItem); + await ExecuteUIThread(() => { InsertItemInternal(groupKey, addedItem); }); + } + + private async Task UpdateExistingItemAsync(IMailItem existingItem, MailCopy updatedItem) + { + if (existingItem is MailItemViewModel itemViewModel) { - // At this point all items are already checked and not suitable option was available. - // Item doesn't belong to any thread. - // Just add it to the collection. + UpdateUniqueIdHashes(itemViewModel, false); + UpdateUniqueIdHashes(updatedItem, true); - var groupKey = GetGroupingKey(addedItem); - - await ExecuteUIThread(() => { InsertItemInternal(groupKey, addedItem); }); + await ExecuteUIThread(() => { itemViewModel.MailCopy = updatedItem; }); } } diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs index 056e493f..bc825ea8 100644 --- a/Wino.Mail.ViewModels/MailListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs @@ -58,7 +58,7 @@ public partial class MailListPageViewModel : MailBaseViewModel, private IObservable> selectionChangedObservable = null; - public WinoMailCollection MailCollection { get; } = new WinoMailCollection(); + public WinoMailCollection MailCollection { get; } public ObservableCollection SelectedItems { get; set; } = []; public ObservableCollection PivotFolders { get; set; } = []; @@ -163,6 +163,7 @@ public partial class MailListPageViewModel : MailBaseViewModel, IWinoLogger winoLogger, IWinoServerConnectionManager winoServerConnectionManager) { + MailCollection = new WinoMailCollection(threadingStrategyProvider); PreferencesService = preferencesService; ThemeService = themeService; _winoLogger = winoLogger;