Fixing an issue with thread creation and selected items notifications.

This commit is contained in:
Burak Kaan Köse
2024-07-15 00:00:38 +02:00
parent e0c01343a8
commit 7de89ffe57
8 changed files with 160 additions and 105 deletions

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using SQLite; using SQLite;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
@@ -140,7 +141,7 @@ namespace Wino.Core.Domain.Entities
/// </summary> /// </summary>
[Ignore] [Ignore]
public MailAccount AssignedAccount { get; set; } public MailAccount AssignedAccount { get; set; }
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
public override string ToString() => $"{Subject} <-> {Id}"; public override string ToString() => $"{Subject} <-> {Id}";
} }
} }

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// An interface that returns the UniqueId store for IMailItem.
/// For threads, it may be multiple items.
/// For single mails, it'll always be one item.
/// </summary>
public interface IMailHashContainer
{
IEnumerable<Guid> GetContainingIds();
}
}

View File

@@ -6,7 +6,7 @@ namespace Wino.Core.Domain.Models.MailItem
/// <summary> /// <summary>
/// Interface of simplest representation of a MailCopy. /// Interface of simplest representation of a MailCopy.
/// </summary> /// </summary>
public interface IMailItem public interface IMailItem : IMailHashContainer
{ {
Guid UniqueId { get; } Guid UniqueId { get; }
string Id { get; } string Id { get; }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
@@ -40,6 +41,8 @@ namespace Wino.Core.Domain.Models.MailItem
} }
} }
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;
#region IMailItem #region IMailItem
public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty; public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty;

View File

@@ -18,7 +18,9 @@ namespace Wino.Mail.ViewModels.Collections
// If the item provider here for update or removal doesn't exist here // If the item provider here for update or removal doesn't exist here
// we can ignore the operation. // we can ignore the operation.
public HashSet<Guid> MailCopyIdHashSet = new HashSet<Guid>(); public HashSet<Guid> MailCopyIdHashSet = [];
public event EventHandler<IMailItem> MailItemRemoved;
private ListItemComparer listComparer = new ListItemComparer(); private ListItemComparer listComparer = new ListItemComparer();
@@ -61,45 +63,51 @@ namespace Wino.Mail.ViewModels.Collections
return mailItem.FromName; return mailItem.FromName;
} }
private async Task InsertItemInternalAsync(object groupKey, IMailItem mailItem) private void UpdateUniqueIdHashes(IMailHashContainer itemContainer, bool isAdd)
=> await ExecuteUIThread(() =>
{ {
foreach (var item in itemContainer.GetContainingIds())
{
if (isAdd)
{
MailCopyIdHashSet.Add(item);
}
else
{
MailCopyIdHashSet.Remove(item);
}
}
}
private void InsertItemInternal(object groupKey, IMailItem mailItem)
{
UpdateUniqueIdHashes(mailItem, true);
if (mailItem is MailCopy mailCopy) if (mailItem is MailCopy mailCopy)
{ {
MailCopyIdHashSet.Add(mailCopy.UniqueId);
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer()); _mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
} }
else if (mailItem is ThreadMailItem threadMailItem) else if (mailItem is ThreadMailItem threadMailItem)
{ {
foreach (var item in threadMailItem.ThreadItems)
{
MailCopyIdHashSet.Add(item.UniqueId);
}
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer()); _mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
} }
else if (mailItem is MailItemViewModel) else
{ {
MailCopyIdHashSet.Add(mailItem.UniqueId);
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer()); _mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
} }
}); }
private async Task RemoveItemInternalAsync(ObservableGroup<object, IMailItem> group, IMailItem mailItem) private void RemoveItemInternal(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
{ {
MailCopyIdHashSet.Remove(mailItem.UniqueId); UpdateUniqueIdHashes(mailItem, false);
MailItemRemoved?.Invoke(this, mailItem);
await ExecuteUIThread(() =>
{
group.Remove(mailItem); group.Remove(mailItem);
if (group.Count == 0) if (group.Count == 0)
{ {
_mailItemSource.RemoveGroup(group.Key); _mailItemSource.RemoveGroup(group.Key);
} }
});
} }
public async Task AddAsync(MailCopy addedItem) public async Task AddAsync(MailCopy addedItem)
@@ -109,6 +117,9 @@ namespace Wino.Mail.ViewModels.Collections
var groupCount = _mailItemSource.Count; var groupCount = _mailItemSource.Count;
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
for (int i = 0; i < groupCount; i++) for (int i = 0; i < groupCount; i++)
{ {
if (shouldExit) break; if (shouldExit) break;
@@ -119,10 +130,6 @@ namespace Wino.Mail.ViewModels.Collections
{ {
var item = group[k]; var item = group[k];
var addedAccountProviderType = addedItem.AssignedAccount.ProviderType;
var threadingStrategy = ThreadingStrategyProvider?.GetStrategy(addedAccountProviderType);
if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false) if (threadingStrategy?.ShouldThreadWithItem(addedItem, item) ?? false)
{ {
shouldExit = true; shouldExit = true;
@@ -140,22 +147,31 @@ namespace Wino.Mail.ViewModels.Collections
var existingGroupKey = GetGroupingKey(threadMailItemViewModel); var existingGroupKey = GetGroupingKey(threadMailItemViewModel);
threadMailItemViewModel.AddMailItemViewModel(addedItem); await ExecuteUIThread(() => { threadMailItemViewModel.AddMailItemViewModel(addedItem); });
var newGroupKey = GetGroupingKey(threadMailItemViewModel); var newGroupKey = GetGroupingKey(threadMailItemViewModel);
if (!existingGroupKey.Equals(newGroupKey)) if (!existingGroupKey.Equals(newGroupKey))
{ {
await RemoveItemInternalAsync(group, threadMailItemViewModel); var mailThreadItems = threadMailItemViewModel.GetThreadMailItem();
await InsertItemInternalAsync(newGroupKey, threadMailItemViewModel);
}
await ExecuteUIThread(() => { threadMailItemViewModel.NotifyPropertyChanges(); }); await ExecuteUIThread(() =>
if (!MailCopyIdHashSet.Contains(addedItem.UniqueId))
{ {
MailCopyIdHashSet.Add(addedItem.UniqueId); // 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; break;
} }
@@ -177,10 +193,10 @@ namespace Wino.Mail.ViewModels.Collections
if (item is MailItemViewModel itemViewModel) if (item is MailItemViewModel itemViewModel)
{ {
itemViewModel.Update(addedItem); await ExecuteUIThread(() => { itemViewModel.Update(addedItem); });
MailCopyIdHashSet.Remove(itemViewModel.UniqueId); UpdateUniqueIdHashes(itemViewModel, false);
MailCopyIdHashSet.Add(addedItem.UniqueId); UpdateUniqueIdHashes(addedItem, true);
} }
} }
else else
@@ -189,6 +205,8 @@ namespace Wino.Mail.ViewModels.Collections
var threadMailItem = new ThreadMailItem(); var threadMailItem = new ThreadMailItem();
await ExecuteUIThread(() =>
{
threadMailItem.AddThreadItem(item); threadMailItem.AddThreadItem(item);
threadMailItem.AddThreadItem(addedItem); threadMailItem.AddThreadItem(addedItem);
@@ -196,8 +214,9 @@ namespace Wino.Mail.ViewModels.Collections
var newGroupKey = GetGroupingKey(threadMailItem); var newGroupKey = GetGroupingKey(threadMailItem);
await RemoveItemInternalAsync(group, item); RemoveItemInternal(group, item);
await InsertItemInternalAsync(newGroupKey, threadMailItem); InsertItemInternal(newGroupKey, threadMailItem);
});
} }
break; break;
@@ -208,6 +227,9 @@ namespace Wino.Mail.ViewModels.Collections
// Update properties. // Update properties.
if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel) if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel)
{ {
UpdateUniqueIdHashes(itemViewModel, false);
UpdateUniqueIdHashes(addedItem, true);
await ExecuteUIThread(() => { itemViewModel.Update(addedItem); }); await ExecuteUIThread(() => { itemViewModel.Update(addedItem); });
shouldExit = true; shouldExit = true;
@@ -224,7 +246,7 @@ namespace Wino.Mail.ViewModels.Collections
var groupKey = GetGroupingKey(addedItem); var groupKey = GetGroupingKey(addedItem);
await InsertItemInternalAsync(groupKey, addedItem); await ExecuteUIThread(() => { InsertItemInternal(groupKey, addedItem); });
} }
} }
@@ -268,12 +290,9 @@ namespace Wino.Mail.ViewModels.Collections
} }
else else
{ {
foreach (var item in group) foreach (var item in group)
{ {
existingGroup.Add(item); existingGroup.Add(item);
// _mailItemSource.InsertItem(existingGroup, item);
} }
} }
} }
@@ -325,11 +344,14 @@ namespace Wino.Mail.ViewModels.Collections
if (itemContainer == null) return; if (itemContainer == null) return;
// mailCopyIdHashSet.Remove(itemContainer.ItemViewModel.UniqueId); if (itemContainer.ItemViewModel != null)
{
UpdateUniqueIdHashes(itemContainer.ItemViewModel, false);
}
itemContainer.ItemViewModel?.Update(updatedMailCopy); itemContainer.ItemViewModel?.Update(updatedMailCopy);
// mailCopyIdHashSet.Add(updatedMailCopy.UniqueId); UpdateUniqueIdHashes(updatedMailCopy, true);
// Call thread notifications if possible. // Call thread notifications if possible.
itemContainer.ThreadViewModel?.NotifyPropertyChanges(); itemContainer.ThreadViewModel?.NotifyPropertyChanges();
@@ -426,6 +448,8 @@ namespace Wino.Mail.ViewModels.Collections
* -> Remove the thread. * -> Remove the thread.
*/ */
var oldGroupKey = GetGroupingKey(threadMailItemViewModel);
await ExecuteUIThread(() => { threadMailItemViewModel.RemoveCopyItem(removalItem); }); await ExecuteUIThread(() => { threadMailItemViewModel.RemoveCopyItem(removalItem); });
if (threadMailItemViewModel.ThreadItems.Count == 1) if (threadMailItemViewModel.ThreadItems.Count == 1)
@@ -435,25 +459,38 @@ namespace Wino.Mail.ViewModels.Collections
var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel(); var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel();
var groupKey = GetGroupingKey(singleViewModel); var groupKey = GetGroupingKey(singleViewModel);
await RemoveItemInternalAsync(group, threadMailItemViewModel); await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
// If thread->single conversion is being done, we should ignore it for non-draft items. // If thread->single conversion is being done, we should ignore it for non-draft items.
// eg. Deleting a reply message from draft folder. Single non-draft item should not be re-added. // eg. Deleting a reply message from draft folder. Single non-draft item should not be re-added.
if (!PruneSingleNonDraftItems || singleViewModel.IsDraft) if (!PruneSingleNonDraftItems || singleViewModel.IsDraft)
{ {
await InsertItemInternalAsync(groupKey, singleViewModel); await ExecuteUIThread(() => { InsertItemInternal(groupKey, singleViewModel); });
} }
} }
else if (threadMailItemViewModel.ThreadItems.Count == 0) else if (threadMailItemViewModel.ThreadItems.Count == 0)
{ {
await RemoveItemInternalAsync(group, threadMailItemViewModel); await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
} }
else else
{ {
// Item inside the thread is removed. // Item inside the thread is removed.
await ExecuteUIThread(() => { threadMailItemViewModel.ThreadItems.Remove(removalItem); });
threadMailItemViewModel.ThreadItems.Remove(removalItem); UpdateUniqueIdHashes(removalItem, false);
}
var newGroupKey = GetGroupingKey(threadMailItemViewModel);
// Make sure to update group key after the thread is updated.
if (!oldGroupKey.Equals(newGroupKey))
{
await ExecuteUIThread(() =>
{
RemoveItemInternal(group, threadMailItemViewModel);
InsertItemInternal(newGroupKey, threadMailItemViewModel);
});
} }
shouldExit = true; shouldExit = true;
@@ -461,7 +498,8 @@ namespace Wino.Mail.ViewModels.Collections
} }
else if (item.UniqueId == removeItem.UniqueId) else if (item.UniqueId == removeItem.UniqueId)
{ {
await RemoveItemInternalAsync(group, item); await ExecuteUIThread(() => { RemoveItemInternal(group, item); });
shouldExit = true; shouldExit = true;
break; break;

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain.Entities; using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Models.MailItem; using Wino.Core.Domain.Models.MailItem;
@@ -94,5 +95,7 @@ namespace Wino.Mail.ViewModels.Data
OnPropertyChanged(nameof(Subject)); OnPropertyChanged(nameof(Subject));
OnPropertyChanged(nameof(PreviewText)); OnPropertyChanged(nameof(PreviewText));
} }
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
} }
} }

View File

@@ -12,18 +12,14 @@ namespace Wino.Mail.ViewModels.Data
/// <summary> /// <summary>
/// Thread mail item (multiple IMailItem) view model representation. /// Thread mail item (multiple IMailItem) view model representation.
/// </summary> /// </summary>
public class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime> public partial class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime>
{ {
public ObservableCollection<IMailItem> ThreadItems => ((IMailItemThread)_threadMailItem).ThreadItems; public ObservableCollection<IMailItem> ThreadItems => ((IMailItemThread)_threadMailItem).ThreadItems;
private readonly ThreadMailItem _threadMailItem; private readonly ThreadMailItem _threadMailItem;
[ObservableProperty]
private bool isThreadExpanded; private bool isThreadExpanded;
public bool IsThreadExpanded
{
get => isThreadExpanded;
set => SetProperty(ref isThreadExpanded, value);
}
public ThreadMailItemViewModel(ThreadMailItem threadMailItem) public ThreadMailItemViewModel(ThreadMailItem threadMailItem)
{ {
@@ -36,6 +32,8 @@ namespace Wino.Mail.ViewModels.Data
} }
} }
public ThreadMailItem GetThreadMailItem() => _threadMailItem;
public IEnumerable<MailCopy> GetMailCopies() public IEnumerable<MailCopy> GetMailCopies()
=> ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy); => ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy);
@@ -123,5 +121,7 @@ namespace Wino.Mail.ViewModels.Data
// Get single mail item view model out of the only item in thread items. // Get single mail item view model out of the only item in thread items.
public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel; public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel;
public IEnumerable<Guid> GetContainingIds() => ((IMailItemThread)_threadMailItem).GetContainingIds();
} }
} }

View File

@@ -172,6 +172,24 @@ namespace Wino.Mail.ViewModels
{ {
await ExecuteUIThread(() => { SelectedItemCollectionUpdated(a.EventArgs); }); await ExecuteUIThread(() => { SelectedItemCollectionUpdated(a.EventArgs); });
}); });
MailCollection.MailItemRemoved += (c, removedItem) =>
{
if (removedItem is ThreadMailItemViewModel removedThreadViewModelItem)
{
foreach (var viewModel in removedThreadViewModelItem.ThreadItems.Cast<MailItemViewModel>())
{
if (SelectedItems.Contains(viewModel))
{
SelectedItems.Remove(viewModel);
}
}
}
else if (removedItem is MailItemViewModel removedMailItemViewModel && SelectedItems.Contains(removedMailItemViewModel))
{
SelectedItems.Remove(removedMailItemViewModel);
}
};
} }
#region Properties #region Properties
@@ -310,25 +328,6 @@ namespace Wino.Mail.ViewModels
MailCollection.CoreDispatcher = Dispatcher; MailCollection.CoreDispatcher = Dispatcher;
} }
//protected override async void OnFolderUpdated(MailItemFolder updatedFolder, MailAccount account)
//{
// base.OnFolderUpdated(updatedFolder, account);
// // Don't need to update if the folder update does not belong to the current folder menu item.
// if (ActiveFolder == null || updatedFolder == null || !ActiveFolder.HandlingFolders.Any(a => a.Id == updatedFolder.Id)) return;
// await ExecuteUIThread(() =>
// {
// ActiveFolder.UpdateFolder(updatedFolder);
// OnPropertyChanged(nameof(CanSynchronize));
// OnPropertyChanged(nameof(IsFolderSynchronizationEnabled));
// });
// // Force synchronization after enabling the folder.
// SyncFolder();
//}
private async void UpdateBarMessage(InfoBarMessageType severity, string title, string message) private async void UpdateBarMessage(InfoBarMessageType severity, string title, string message)
{ {
await ExecuteUIThread(() => await ExecuteUIThread(() =>
@@ -618,12 +617,9 @@ namespace Wino.Mail.ViewModels
if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return; if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return;
await ExecuteUIThread(async () =>
{
await MailCollection.AddAsync(addedMail); await MailCollection.AddAsync(addedMail);
NotifyItemFoundState(); await ExecuteUIThread(() => { NotifyItemFoundState(); });
});
} }
catch { } catch { }
finally finally
@@ -693,6 +689,7 @@ namespace Wino.Mail.ViewModels
gmailUnreadFolderMarkedAsReadUniqueIds.Remove(removedMail.UniqueId); gmailUnreadFolderMarkedAsReadUniqueIds.Remove(removedMail.UniqueId);
} }
} }
protected override async void OnDraftCreated(MailCopy draftMail, MailAccount account) protected override async void OnDraftCreated(MailCopy draftMail, MailAccount account)
{ {
base.OnDraftCreated(draftMail, account); base.OnDraftCreated(draftMail, account);
@@ -704,10 +701,10 @@ namespace Wino.Mail.ViewModels
await listManipulationSemepahore.WaitAsync(); await listManipulationSemepahore.WaitAsync();
// Create the item. Draft folder navigation is already done at this point. // Create the item. Draft folder navigation is already done at this point.
await ExecuteUIThread(async () =>
{
await MailCollection.AddAsync(draftMail); await MailCollection.AddAsync(draftMail);
await ExecuteUIThread(() =>
{
// New draft is created by user. Select the item. // New draft is created by user. Select the item.
Messenger.Send(new MailItemNavigationRequested(draftMail.UniqueId, ScrollToItem: true)); Messenger.Send(new MailItemNavigationRequested(draftMail.UniqueId, ScrollToItem: true));
@@ -940,8 +937,6 @@ namespace Wino.Mail.ViewModels
if (navigatingMailItem != null) if (navigatingMailItem != null)
WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(navigatingMailItem, message.ScrollToItem)); WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(navigatingMailItem, message.ScrollToItem));
else
Debugger.Break();
} }
#endregion #endregion