Attempt to bring back ListView.
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
public interface IMailHashContainer
|
||||||
|
{
|
||||||
|
IEnumerable<Guid> GetContainingIds();
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Comparers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used to insert date grouping into proper place in Reader page.
|
|
||||||
/// </summary>
|
|
||||||
public class DateTimeComparer : IComparer<DateTime>
|
|
||||||
{
|
|
||||||
public int Compare(DateTime x, DateTime y)
|
|
||||||
{
|
|
||||||
return DateTime.Compare(y, x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Comparers;
|
|
||||||
|
|
||||||
public class FolderNameComparer : IComparer<MailItemFolder>
|
|
||||||
{
|
|
||||||
public int Compare(MailItemFolder x, MailItemFolder y)
|
|
||||||
{
|
|
||||||
return x.FolderName.CompareTo(y.FolderName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
|
||||||
|
public class ListItemComparer : IComparer<object>
|
||||||
|
{
|
||||||
|
public bool SortByName { get; set; }
|
||||||
|
|
||||||
|
public int Compare(object x, object y)
|
||||||
|
{
|
||||||
|
if (x is MailCopy xMail && y is MailCopy yMail)
|
||||||
|
return SortByName ? string.Compare(xMail.FromName, yMail.FromName, StringComparison.OrdinalIgnoreCase) : DateTime.Compare(yMail.CreationDate, xMail.CreationDate);
|
||||||
|
else if (x is DateTime dateX && y is DateTime dateY)
|
||||||
|
return DateTime.Compare(dateY, dateX);
|
||||||
|
else if (x is string stringX && y is string stringY)
|
||||||
|
return stringY.CompareTo(stringX);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,516 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.Collections;
|
||||||
|
using Serilog;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Mail.ViewModels.Data;
|
||||||
|
|
||||||
|
namespace Wino.Mail.ViewModels.Collections;
|
||||||
|
|
||||||
|
public class WinoMailCollection
|
||||||
|
{
|
||||||
|
// We cache each mail copy id for faster access on updates.
|
||||||
|
// If the item provider here for update or removal doesn't exist here
|
||||||
|
// we can ignore the operation.
|
||||||
|
|
||||||
|
public HashSet<Guid> MailCopyIdHashSet = [];
|
||||||
|
|
||||||
|
public event EventHandler<MailItemViewModel> MailItemRemoved;
|
||||||
|
|
||||||
|
private ListItemComparer listComparer = new();
|
||||||
|
|
||||||
|
private readonly ObservableGroupedCollection<object, IMailListItem> _mailItemSource = new ObservableGroupedCollection<object, IMailListItem>();
|
||||||
|
|
||||||
|
public ReadOnlyObservableGroupedCollection<object, IMailListItem> MailItems { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Property that defines how the item sorting should be done in the collection.
|
||||||
|
/// </summary>
|
||||||
|
public SortingOptionType SortingType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Automatically deletes single mail items after the delete operation or thread->single transition.
|
||||||
|
/// This is useful when reply draft is discarded in the thread. Only enabled for Draft folder for now.
|
||||||
|
/// </summary>
|
||||||
|
public bool PruneSingleNonDraftItems { get; set; }
|
||||||
|
|
||||||
|
public int Count => _mailItemSource.Count;
|
||||||
|
|
||||||
|
public IDispatcher CoreDispatcher { get; set; }
|
||||||
|
|
||||||
|
public WinoMailCollection()
|
||||||
|
{
|
||||||
|
MailItems = new ReadOnlyObservableGroupedCollection<object, IMailListItem>(_mailItemSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => _mailItemSource.Clear();
|
||||||
|
|
||||||
|
private object GetGroupingKey(IMailListItem mailItem)
|
||||||
|
{
|
||||||
|
if (SortingType == SortingOptionType.ReceiveDate)
|
||||||
|
return mailItem.CreationDate.ToLocalTime().Date;
|
||||||
|
else
|
||||||
|
return mailItem.FromName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUniqueIdHashes(IMailHashContainer itemContainer, bool isAdd)
|
||||||
|
{
|
||||||
|
foreach (var item in itemContainer.GetContainingIds())
|
||||||
|
{
|
||||||
|
if (isAdd)
|
||||||
|
{
|
||||||
|
MailCopyIdHashSet.Add(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MailCopyIdHashSet.Remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertItemInternal(object groupKey, IMailListItem mailItem)
|
||||||
|
{
|
||||||
|
UpdateUniqueIdHashes(mailItem, true);
|
||||||
|
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveItemInternal(ObservableGroup<object, IMailListItem> group, IMailListItem mailItem)
|
||||||
|
{
|
||||||
|
UpdateUniqueIdHashes(mailItem, false);
|
||||||
|
|
||||||
|
if (mailItem is MailItemViewModel singleMailItem)
|
||||||
|
{
|
||||||
|
MailItemRemoved?.Invoke(this, singleMailItem);
|
||||||
|
}
|
||||||
|
else if (mailItem is ThreadMailItemViewModel threadViewModel)
|
||||||
|
{
|
||||||
|
foreach (var threadMailItem in threadViewModel.ThreadEmails)
|
||||||
|
{
|
||||||
|
MailItemRemoved?.Invoke(this, threadMailItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Remove(mailItem);
|
||||||
|
|
||||||
|
if (group.Count == 0)
|
||||||
|
{
|
||||||
|
_mailItemSource.RemoveGroup(group.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleThreadingAsync(ObservableGroup<object, IMailListItem> group, IMailListItem item, MailCopy addedItem)
|
||||||
|
{
|
||||||
|
if (item is ThreadMailItemViewModel threadViewModel)
|
||||||
|
{
|
||||||
|
await HandleExistingThreadAsync(group, threadViewModel, addedItem);
|
||||||
|
}
|
||||||
|
else if (item is MailItemViewModel mailViewModel)
|
||||||
|
{
|
||||||
|
await HandleNewThreadAsync(group, mailViewModel, addedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleExistingThreadAsync(ObservableGroup<object, IMailListItem> group, ThreadMailItemViewModel threadViewModel, MailCopy addedItem)
|
||||||
|
{
|
||||||
|
var existingGroupKey = GetGroupingKey(threadViewModel);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
var newMailItem = new MailItemViewModel(addedItem);
|
||||||
|
threadViewModel.AddEmail(newMailItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
var newGroupKey = GetGroupingKey(threadViewModel);
|
||||||
|
|
||||||
|
if (!existingGroupKey.Equals(newGroupKey))
|
||||||
|
{
|
||||||
|
await MoveThreadToNewGroupAsync(group, threadViewModel, newGroupKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => { threadViewModel.NotifyPropertyChanges(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUniqueIdHashes(new MailItemViewModel(addedItem), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleNewThreadAsync(ObservableGroup<object, IMailListItem> group, MailItemViewModel item, MailCopy addedItem)
|
||||||
|
{
|
||||||
|
if (item.MailCopy.UniqueId == addedItem.UniqueId)
|
||||||
|
{
|
||||||
|
await UpdateExistingItemAsync(item, addedItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await CreateNewThreadAsync(group, item, addedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MoveThreadToNewGroupAsync(ObservableGroup<object, IMailListItem> currentGroup, ThreadMailItemViewModel threadViewModel, object newGroupKey)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
RemoveItemInternal(currentGroup, threadViewModel);
|
||||||
|
InsertItemInternal(newGroupKey, threadViewModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateNewThreadAsync(ObservableGroup<object, IMailListItem> group, MailItemViewModel item, MailCopy addedItem)
|
||||||
|
{
|
||||||
|
var threadViewModel = new ThreadMailItemViewModel(item.MailCopy.ThreadId);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
threadViewModel.AddEmail(item);
|
||||||
|
threadViewModel.AddEmail(new MailItemViewModel(addedItem));
|
||||||
|
});
|
||||||
|
|
||||||
|
var newGroupKey = GetGroupingKey(threadViewModel);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
RemoveItemInternal(group, item);
|
||||||
|
InsertItemInternal(newGroupKey, threadViewModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddAsync(MailCopy addedItem)
|
||||||
|
{
|
||||||
|
foreach (var group in _mailItemSource)
|
||||||
|
{
|
||||||
|
foreach (var item in group)
|
||||||
|
{
|
||||||
|
// Compare ThreadIds - if they match and both have ThreadIds, thread them together
|
||||||
|
bool shouldThread = !string.IsNullOrEmpty(addedItem.ThreadId) &&
|
||||||
|
item is MailItemViewModel mailItem &&
|
||||||
|
!string.IsNullOrEmpty(mailItem.MailCopy.ThreadId) &&
|
||||||
|
string.Equals(addedItem.ThreadId, mailItem.MailCopy.ThreadId, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (!shouldThread && item is ThreadMailItemViewModel threadViewModel)
|
||||||
|
{
|
||||||
|
// Check if any email in the thread has matching ThreadId
|
||||||
|
shouldThread = !string.IsNullOrEmpty(addedItem.ThreadId) &&
|
||||||
|
threadViewModel.ThreadEmails.Any(e =>
|
||||||
|
!string.IsNullOrEmpty(e.MailCopy.ThreadId) &&
|
||||||
|
string.Equals(addedItem.ThreadId, e.MailCopy.ThreadId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldThread)
|
||||||
|
{
|
||||||
|
await HandleThreadingAsync(group, item, addedItem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (item is MailItemViewModel itemViewModel && itemViewModel.MailCopy.UniqueId == addedItem.UniqueId)
|
||||||
|
{
|
||||||
|
await UpdateExistingItemAsync(itemViewModel, addedItem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await AddNewItemAsync(addedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddNewItemAsync(MailCopy addedItem)
|
||||||
|
{
|
||||||
|
var newMailItem = new MailItemViewModel(addedItem);
|
||||||
|
var groupKey = GetGroupingKey(newMailItem);
|
||||||
|
await ExecuteUIThread(() => { InsertItemInternal(groupKey, newMailItem); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateExistingItemAsync(MailItemViewModel existingItem, MailCopy updatedItem)
|
||||||
|
{
|
||||||
|
UpdateUniqueIdHashes(existingItem, false);
|
||||||
|
UpdateUniqueIdHashes(new MailItemViewModel(updatedItem), true);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => { existingItem.MailCopy = updatedItem; });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<IMailListItem> items, bool clearIdCache)
|
||||||
|
{
|
||||||
|
if (clearIdCache)
|
||||||
|
{
|
||||||
|
MailCopyIdHashSet.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupedByName = items
|
||||||
|
.GroupBy(a => GetGroupingKey(a))
|
||||||
|
.Select(a => new ObservableGroup<object, IMailListItem>(a.Key, a));
|
||||||
|
|
||||||
|
foreach (var group in groupedByName)
|
||||||
|
{
|
||||||
|
// Store all mail copy ids for faster access.
|
||||||
|
foreach (var item in group)
|
||||||
|
{
|
||||||
|
UpdateUniqueIdHashes(item, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingGroup = _mailItemSource.FirstGroupByKeyOrDefault(group.Key);
|
||||||
|
|
||||||
|
if (existingGroup == null)
|
||||||
|
{
|
||||||
|
_mailItemSource.AddGroup(group.Key, group);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var item in group)
|
||||||
|
{
|
||||||
|
existingGroup.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailItemContainer GetMailItemContainer(Guid uniqueMailId)
|
||||||
|
{
|
||||||
|
var groupCount = _mailItemSource.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < groupCount; i++)
|
||||||
|
{
|
||||||
|
var group = _mailItemSource[i];
|
||||||
|
|
||||||
|
for (int k = 0; k < group.Count; k++)
|
||||||
|
{
|
||||||
|
var item = group[k];
|
||||||
|
|
||||||
|
if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.MailCopy.UniqueId == uniqueMailId)
|
||||||
|
return new MailItemContainer(singleMailItemViewModel);
|
||||||
|
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(uniqueMailId))
|
||||||
|
{
|
||||||
|
var singleItemViewModel = threadMailItemViewModel.ThreadEmails.FirstOrDefault(e => e.MailCopy.UniqueId == uniqueMailId);
|
||||||
|
|
||||||
|
return new MailItemContainer(singleItemViewModel, threadMailItemViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateThumbnails(string address)
|
||||||
|
{
|
||||||
|
if (CoreDispatcher == null) return;
|
||||||
|
|
||||||
|
CoreDispatcher.ExecuteOnUIThread(() =>
|
||||||
|
{
|
||||||
|
foreach (var group in _mailItemSource)
|
||||||
|
{
|
||||||
|
foreach (var item in group)
|
||||||
|
{
|
||||||
|
if (item is MailItemViewModel mailItemViewModel && mailItemViewModel.MailCopy.FromAddress.Equals(address, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mailItemViewModel.ThumbnailUpdatedEvent = !mailItemViewModel.ThumbnailUpdatedEvent;
|
||||||
|
}
|
||||||
|
else if (item is ThreadMailItemViewModel threadViewModel)
|
||||||
|
{
|
||||||
|
foreach (var threadMailItem in threadViewModel.ThreadEmails)
|
||||||
|
{
|
||||||
|
if (threadMailItem.MailCopy.FromAddress.Equals(address, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
threadMailItem.ThumbnailUpdatedEvent = !threadMailItem.ThumbnailUpdatedEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fins the item container that updated mail copy belongs to and updates it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="updatedMailCopy">Updated mail copy.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task UpdateMailCopy(MailCopy updatedMailCopy)
|
||||||
|
{
|
||||||
|
// This item doesn't exist in the list.
|
||||||
|
if (!MailCopyIdHashSet.Contains(updatedMailCopy.UniqueId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
var itemContainer = GetMailItemContainer(updatedMailCopy.UniqueId);
|
||||||
|
|
||||||
|
if (itemContainer == null) return;
|
||||||
|
|
||||||
|
if (itemContainer.ItemViewModel != null)
|
||||||
|
{
|
||||||
|
UpdateUniqueIdHashes(itemContainer.ItemViewModel, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemContainer.ItemViewModel != null)
|
||||||
|
{
|
||||||
|
itemContainer.ItemViewModel.MailCopy = updatedMailCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUniqueIdHashes(new MailItemViewModel(updatedMailCopy), true);
|
||||||
|
|
||||||
|
// Call thread notifications if possible.
|
||||||
|
itemContainer.ThreadViewModel?.NotifyPropertyChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailItemViewModel GetNextItem(MailCopy mailCopy)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var groupCount = _mailItemSource.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < groupCount; i++)
|
||||||
|
{
|
||||||
|
var group = _mailItemSource[i];
|
||||||
|
|
||||||
|
for (int k = 0; k < group.Count; k++)
|
||||||
|
{
|
||||||
|
var item = group[k];
|
||||||
|
|
||||||
|
if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.MailCopy.UniqueId == mailCopy.UniqueId)
|
||||||
|
{
|
||||||
|
if (k + 1 < group.Count)
|
||||||
|
{
|
||||||
|
return group[k + 1] as MailItemViewModel;
|
||||||
|
}
|
||||||
|
else if (i + 1 < groupCount)
|
||||||
|
{
|
||||||
|
return _mailItemSource[i + 1][0] as MailItemViewModel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(mailCopy.UniqueId))
|
||||||
|
{
|
||||||
|
var singleItemViewModel = threadMailItemViewModel.ThreadEmails.FirstOrDefault(e => e.MailCopy.UniqueId == mailCopy.UniqueId);
|
||||||
|
|
||||||
|
if (singleItemViewModel == null) return null;
|
||||||
|
|
||||||
|
var singleItemIndex = threadMailItemViewModel.ThreadEmails.ToList().IndexOf(singleItemViewModel);
|
||||||
|
|
||||||
|
if (singleItemIndex + 1 < threadMailItemViewModel.ThreadEmails.Count)
|
||||||
|
{
|
||||||
|
return threadMailItemViewModel.ThreadEmails[singleItemIndex + 1];
|
||||||
|
}
|
||||||
|
else if (i + 1 < groupCount)
|
||||||
|
{
|
||||||
|
return _mailItemSource[i + 1][0] as MailItemViewModel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Failed to find the next item to select.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveAsync(MailCopy removeItem)
|
||||||
|
{
|
||||||
|
// This item doesn't exist in the list.
|
||||||
|
if (!MailCopyIdHashSet.Contains(removeItem.UniqueId)) return;
|
||||||
|
|
||||||
|
// Check all items for whether this item should be threaded with them.
|
||||||
|
bool shouldExit = false;
|
||||||
|
|
||||||
|
var groupCount = _mailItemSource.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < groupCount; i++)
|
||||||
|
{
|
||||||
|
if (shouldExit) break;
|
||||||
|
|
||||||
|
var group = _mailItemSource[i];
|
||||||
|
|
||||||
|
for (int k = 0; k < group.Count; k++)
|
||||||
|
{
|
||||||
|
var item = group[k];
|
||||||
|
|
||||||
|
if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(removeItem.UniqueId))
|
||||||
|
{
|
||||||
|
var removalItem = threadMailItemViewModel.ThreadEmails.FirstOrDefault(e => e.MailCopy.UniqueId == removeItem.UniqueId);
|
||||||
|
|
||||||
|
if (removalItem == null) return;
|
||||||
|
|
||||||
|
// Threads' Id is equal to the last item they hold.
|
||||||
|
// We can't do Id check here because that'd remove the whole thread.
|
||||||
|
|
||||||
|
/* Remove item from the thread.
|
||||||
|
* If thread had 1 item inside:
|
||||||
|
* -> Remove the thread and insert item as single item.
|
||||||
|
* If thread had 0 item inside:
|
||||||
|
* -> Remove the thread.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var oldGroupKey = GetGroupingKey(threadMailItemViewModel);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => { threadMailItemViewModel.RemoveEmail(removalItem); });
|
||||||
|
|
||||||
|
if (threadMailItemViewModel.EmailCount == 1)
|
||||||
|
{
|
||||||
|
// Convert to single item.
|
||||||
|
|
||||||
|
var singleViewModel = threadMailItemViewModel.ThreadEmails.First();
|
||||||
|
var groupKey = GetGroupingKey(singleViewModel);
|
||||||
|
|
||||||
|
await ExecuteUIThread(() =>
|
||||||
|
{
|
||||||
|
RemoveItemInternal(group, threadMailItemViewModel);
|
||||||
|
InsertItemInternal(groupKey, singleViewModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
if (PruneSingleNonDraftItems && !singleViewModel.IsDraft)
|
||||||
|
{
|
||||||
|
// This item should not be here anymore.
|
||||||
|
// It's basically a reply mail in Draft folder.
|
||||||
|
var newGroup = _mailItemSource.FirstGroupByKeyOrDefault(groupKey);
|
||||||
|
|
||||||
|
if (newGroup != null)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => { RemoveItemInternal(newGroup, singleViewModel); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (threadMailItemViewModel.EmailCount == 0)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Item inside the thread is removed - update hash
|
||||||
|
UpdateUniqueIdHashes(removalItem, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldExit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (item is MailItemViewModel mailItemViewModel && mailItemViewModel.MailCopy.UniqueId == removeItem.UniqueId)
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => { RemoveItemInternal(group, item); });
|
||||||
|
|
||||||
|
shouldExit = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteUIThread(Action action) => await CoreDispatcher?.ExecuteOnUIThread(action);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace Wino.Mail.ViewModels.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for mail items that can be displayed in a mail list.
|
||||||
|
/// Implemented by both MailItemViewModel and ThreadMailItemViewModel.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMailListItem : IMailHashContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the latest creation date for sorting purposes.
|
||||||
|
/// For MailItemViewModel: the mail's creation date
|
||||||
|
/// For ThreadMailItemViewModel: the latest email's creation date
|
||||||
|
/// </summary>
|
||||||
|
DateTime CreationDate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the sender's name for grouping purposes.
|
||||||
|
/// For MailItemViewModel: the mail's from name
|
||||||
|
/// For ThreadMailItemViewModel: the latest email's from name
|
||||||
|
/// </summary>
|
||||||
|
string FromName { get; }
|
||||||
|
}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels.Data;
|
namespace Wino.Mail.ViewModels.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Single view model for IMailItem representation.
|
/// Single view model for IMailItem representation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject
|
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IMailListItem
|
||||||
{
|
{
|
||||||
|
public DateTime CreationDate => MailCopy.CreationDate;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
||||||
|
|
||||||
@@ -85,4 +89,6 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject
|
|||||||
get => MailCopy.HasAttachments;
|
get => MailCopy.HasAttachments;
|
||||||
set => SetProperty(MailCopy.HasAttachments, value, MailCopy, (u, n) => u.HasAttachments = n);
|
set => SetProperty(MailCopy.HasAttachments, value, MailCopy, (u, n) => u.HasAttachments = n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Guid> GetContainingIds() => [MailCopy.UniqueId];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels.Data;
|
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 partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable
|
public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable, IMailListItem
|
||||||
{
|
{
|
||||||
private readonly string _threadId;
|
private readonly string _threadId;
|
||||||
|
|
||||||
@@ -30,21 +31,21 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the latest email's subject for display
|
/// Gets the latest email's subject for display
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Subject => _threadEmails
|
public string Subject => _threadEmails
|
||||||
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
||||||
.FirstOrDefault()?.MailCopy?.Subject;
|
.FirstOrDefault()?.MailCopy?.Subject;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the latest email's sender name for display
|
/// Gets the latest email's sender name for display
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? FromName => _threadEmails
|
public string FromName => _threadEmails
|
||||||
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
||||||
.FirstOrDefault()?.MailCopy?.SenderContact.Name;
|
.FirstOrDefault()?.MailCopy?.SenderContact.Name;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the latest email's creation date for sorting
|
/// Gets the latest email's creation date for sorting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime LatestEmailDate => _threadEmails
|
public DateTime CreationDate => _threadEmails
|
||||||
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
||||||
.FirstOrDefault()?.MailCopy?.CreationDate ?? DateTime.MinValue;
|
.FirstOrDefault()?.MailCopy?.CreationDate ?? DateTime.MinValue;
|
||||||
|
|
||||||
@@ -79,11 +80,11 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NotifyPropertyChanges()
|
public void NotifyPropertyChanges()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(Subject));
|
OnPropertyChanged(nameof(Subject));
|
||||||
OnPropertyChanged(nameof(FromName));
|
OnPropertyChanged(nameof(FromName));
|
||||||
OnPropertyChanged(nameof(LatestEmailDate));
|
OnPropertyChanged(nameof(CreationDate));
|
||||||
OnPropertyChanged(nameof(LatestMailViewModel));
|
OnPropertyChanged(nameof(LatestMailViewModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,4 +116,6 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable
|
|||||||
/// Checks if this thread contains an email with the specified unique ID
|
/// Checks if this thread contains an email with the specified unique ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasUniqueId(Guid uniqueId) => _threadEmails.Any(email => email.MailCopy.UniqueId == uniqueId);
|
public bool HasUniqueId(Guid uniqueId) => _threadEmails.Any(email => email.MailCopy.UniqueId == uniqueId);
|
||||||
|
|
||||||
|
public IEnumerable<Guid> GetContainingIds() => ThreadEmails.Select(a => a.MailCopy.UniqueId);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user