ItemsView thing.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Wino.Mail.ViewModels.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for group headers in the flat collection
|
||||
/// </summary>
|
||||
public abstract partial class GroupHeaderBase : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private int itemCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private int unreadCount;
|
||||
|
||||
protected GroupHeaderBase(string key, string displayName)
|
||||
{
|
||||
Key = key;
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The unique key for this group (used for sorting and identification)
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The display name shown in the UI
|
||||
/// </summary>
|
||||
public string DisplayName { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Group header for date-based grouping
|
||||
/// </summary>
|
||||
public partial class DateGroupHeader : GroupHeaderBase
|
||||
{
|
||||
public DateGroupHeader(DateTime date) : base(date.ToString("yyyy-MM-dd"), FormatDisplayName(date))
|
||||
{
|
||||
Date = date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The date this group represents
|
||||
/// </summary>
|
||||
public DateTime Date { get; }
|
||||
|
||||
private static string FormatDisplayName(DateTime date)
|
||||
{
|
||||
var today = DateTime.Today;
|
||||
var yesterday = today.AddDays(-1);
|
||||
|
||||
return date.Date switch
|
||||
{
|
||||
var d when d == today => "Today",
|
||||
var d when d == yesterday => "Yesterday",
|
||||
var d when d >= today.AddDays(-7) => date.ToString("dddd"), // This week
|
||||
var d when d.Year == today.Year => date.ToString("MMMM dd"), // This year
|
||||
_ => date.ToString("MMMM dd, yyyy") // Other years
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Group header for sender name-based grouping
|
||||
/// </summary>
|
||||
public partial class SenderGroupHeader : GroupHeaderBase
|
||||
{
|
||||
public SenderGroupHeader(string senderName) : base(senderName, senderName)
|
||||
{
|
||||
SenderName = senderName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The sender name this group represents
|
||||
/// </summary>
|
||||
public string SenderName { get; }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,514 +0,0 @@
|
||||
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.Core.Domain.Models.Comparers;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
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<IMailItem> MailItemRemoved;
|
||||
|
||||
private ListItemComparer listComparer = new ListItemComparer();
|
||||
|
||||
private readonly ObservableGroupedCollection<object, IMailItem> _mailItemSource = new ObservableGroupedCollection<object, IMailItem>();
|
||||
|
||||
public ReadOnlyObservableGroupedCollection<object, IMailItem> MailItems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Property that defines how the item sorting should be done in the collection.
|
||||
/// </summary>
|
||||
public SortingOptionType SortingType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Threading strategy that will help thread items according to the account type.
|
||||
/// </summary>
|
||||
public IThreadingStrategyProvider ThreadingStrategyProvider { 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; }
|
||||
|
||||
private readonly ThreadingManager _threadingManager;
|
||||
|
||||
public WinoMailCollection(IThreadingStrategyProvider threadingStrategyProvider)
|
||||
{
|
||||
_threadingManager = new ThreadingManager(threadingStrategyProvider);
|
||||
MailItems = new ReadOnlyObservableGroupedCollection<object, IMailItem>(_mailItemSource);
|
||||
}
|
||||
|
||||
public void Clear() => _mailItemSource.Clear();
|
||||
|
||||
private object GetGroupingKey(IMailItem 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, IMailItem mailItem)
|
||||
{
|
||||
UpdateUniqueIdHashes(mailItem, true);
|
||||
|
||||
if (mailItem is MailCopy mailCopy)
|
||||
{
|
||||
_mailItemSource.InsertItem(groupKey, listComparer, new MailItemViewModel(mailCopy), listComparer.GetItemComparer());
|
||||
}
|
||||
else if (mailItem is ThreadMailItem threadMailItem)
|
||||
{
|
||||
_mailItemSource.InsertItem(groupKey, listComparer, new ThreadMailItemViewModel(threadMailItem), listComparer.GetItemComparer());
|
||||
}
|
||||
else
|
||||
{
|
||||
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer.GetItemComparer());
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveItemInternal(ObservableGroup<object, IMailItem> group, IMailItem mailItem)
|
||||
{
|
||||
UpdateUniqueIdHashes(mailItem, false);
|
||||
|
||||
MailItemRemoved?.Invoke(this, mailItem);
|
||||
|
||||
group.Remove(mailItem);
|
||||
|
||||
if (group.Count == 0)
|
||||
{
|
||||
_mailItemSource.RemoveGroup(group.Key);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleThreadingAsync(ObservableGroup<object, IMailItem> 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<object, IMailItem> 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<object, IMailItem> 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<object, IMailItem> currentGroup, ThreadMailItemViewModel threadViewModel, object newGroupKey)
|
||||
{
|
||||
var mailThreadItems = threadViewModel.GetThreadMailItem();
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
RemoveItemInternal(currentGroup, threadViewModel);
|
||||
InsertItemInternal(newGroupKey, new ThreadMailItemViewModel(mailThreadItems));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task CreateNewThreadAsync(ObservableGroup<object, IMailItem> 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)
|
||||
{
|
||||
foreach (var group in _mailItemSource)
|
||||
{
|
||||
foreach (var item in group)
|
||||
{
|
||||
if (_threadingManager.ShouldThread(addedItem, item))
|
||||
{
|
||||
await HandleThreadingAsync(group, item, addedItem);
|
||||
return;
|
||||
}
|
||||
else if (item.Id == addedItem.Id && item is MailItemViewModel itemViewModel)
|
||||
{
|
||||
await UpdateExistingItemAsync(itemViewModel, addedItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
UpdateUniqueIdHashes(itemViewModel, false);
|
||||
UpdateUniqueIdHashes(updatedItem, true);
|
||||
|
||||
await ExecuteUIThread(() => { itemViewModel.MailCopy = updatedItem; });
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<IMailItem> items, bool clearIdCache)
|
||||
{
|
||||
if (clearIdCache)
|
||||
{
|
||||
MailCopyIdHashSet.Clear();
|
||||
}
|
||||
|
||||
var groupedByName = items
|
||||
.GroupBy(a => GetGroupingKey(a))
|
||||
.Select(a => new ObservableGroup<object, IMailItem>(a.Key, a));
|
||||
|
||||
foreach (var group in groupedByName)
|
||||
{
|
||||
// Store all mail copy ids for faster access.
|
||||
foreach (var item in group)
|
||||
{
|
||||
if (item is MailItemViewModel mailCopyItem && !MailCopyIdHashSet.Contains(item.UniqueId))
|
||||
{
|
||||
MailCopyIdHashSet.Add(item.UniqueId);
|
||||
}
|
||||
else if (item is ThreadMailItemViewModel threadMailItem)
|
||||
{
|
||||
foreach (var mailItem in threadMailItem.ThreadItems)
|
||||
{
|
||||
if (!MailCopyIdHashSet.Contains(mailItem.UniqueId))
|
||||
{
|
||||
MailCopyIdHashSet.Add(mailItem.UniqueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.UniqueId == uniqueMailId)
|
||||
return new MailItemContainer(singleMailItemViewModel);
|
||||
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(uniqueMailId))
|
||||
{
|
||||
var singleItemViewModel = threadMailItemViewModel.GetItemById(uniqueMailId) as MailItemViewModel;
|
||||
|
||||
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 &&
|
||||
!string.IsNullOrEmpty(mailItemViewModel.MailCopy?.FromAddress) &&
|
||||
mailItemViewModel.MailCopy.FromAddress.Equals(address, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mailItemViewModel.ThumbnailUpdatedEvent = !mailItemViewModel.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(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.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.GetItemById(mailCopy.UniqueId) as MailItemViewModel;
|
||||
|
||||
if (singleItemViewModel == null) return null;
|
||||
|
||||
var singleItemIndex = threadMailItemViewModel.ThreadItems.IndexOf(singleItemViewModel);
|
||||
|
||||
if (singleItemIndex + 1 < threadMailItemViewModel.ThreadItems.Count)
|
||||
{
|
||||
return threadMailItemViewModel.ThreadItems[singleItemIndex + 1] as MailItemViewModel;
|
||||
}
|
||||
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.GetItemById(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.RemoveCopyItem(removalItem); });
|
||||
|
||||
if (threadMailItemViewModel.ThreadItems.Count == 1)
|
||||
{
|
||||
// Convert to single item.
|
||||
|
||||
var singleViewModel = threadMailItemViewModel.GetSingleItemViewModel();
|
||||
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.ThreadItems.Count == 0)
|
||||
{
|
||||
await ExecuteUIThread(() => { RemoveItemInternal(group, threadMailItemViewModel); });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Item inside the thread is removed.
|
||||
await ExecuteUIThread(() => { threadMailItemViewModel.ThreadItems.Remove(removalItem); });
|
||||
|
||||
UpdateUniqueIdHashes(removalItem, false);
|
||||
}
|
||||
|
||||
shouldExit = true;
|
||||
break;
|
||||
}
|
||||
else if (item.UniqueId == removeItem.UniqueId)
|
||||
{
|
||||
await ExecuteUIThread(() => { RemoveItemInternal(group, item); });
|
||||
|
||||
shouldExit = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteUIThread(Action action) => await CoreDispatcher?.ExecuteOnUIThread(action);
|
||||
}
|
||||
@@ -211,7 +211,7 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
||||
|
||||
isUpdatingMimeBlocked = true;
|
||||
|
||||
var assignedAccount = CurrentMailDraftItem.AssignedAccount;
|
||||
var assignedAccount = CurrentMailDraftItem.MailCopy.AssignedAccount;
|
||||
var sentFolder = await _folderService.GetSpecialFolderByAccountIdAsync(assignedAccount.Id, SpecialFolderType.Sent);
|
||||
|
||||
using MemoryStream memoryStream = new();
|
||||
@@ -223,8 +223,8 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
||||
var draftSendPreparationRequest = new SendDraftPreparationRequest(CurrentMailDraftItem.MailCopy,
|
||||
SelectedAlias,
|
||||
sentFolder,
|
||||
CurrentMailDraftItem.AssignedFolder,
|
||||
CurrentMailDraftItem.AssignedAccount.Preferences,
|
||||
CurrentMailDraftItem.MailCopy.AssignedFolder,
|
||||
CurrentMailDraftItem.MailCopy.AssignedAccount.Preferences,
|
||||
base64EncodedMessage);
|
||||
|
||||
await _worker.ExecuteAsync(draftSendPreparationRequest);
|
||||
@@ -355,7 +355,7 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
||||
|
||||
if (ComposingAccount != null) return true;
|
||||
|
||||
var composingAccount = await _accountService.GetAccountAsync(CurrentMailDraftItem.AssignedAccount.Id).ConfigureAwait(false);
|
||||
var composingAccount = await _accountService.GetAccountAsync(CurrentMailDraftItem.MailCopy.AssignedAccount.Id).ConfigureAwait(false);
|
||||
if (composingAccount == null) return false;
|
||||
|
||||
var aliases = await _accountService.GetAccountAliasesAsync(composingAccount.Id).ConfigureAwait(false);
|
||||
@@ -556,7 +556,7 @@ public partial class ComposePageViewModel : MailBaseViewModel
|
||||
|
||||
if (CurrentMailDraftItem == null) return;
|
||||
|
||||
if (updatedMail.UniqueId == CurrentMailDraftItem.UniqueId)
|
||||
if (updatedMail.UniqueId == CurrentMailDraftItem.MailCopy.UniqueId)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
namespace Wino.Mail.ViewModels.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Single view model for IMailItem representation.
|
||||
/// </summary>
|
||||
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IMailItem
|
||||
public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
||||
|
||||
public Guid UniqueId => ((IMailItem)MailCopy).UniqueId;
|
||||
public string ThreadId => ((IMailItem)MailCopy).ThreadId;
|
||||
public string MessageId => ((IMailItem)MailCopy).MessageId;
|
||||
public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate;
|
||||
public string References => ((IMailItem)MailCopy).References;
|
||||
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ThumbnailUpdatedEvent { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsCustomFocused { get; set; }
|
||||
public partial bool IsSelected { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsSelected { get; set; }
|
||||
public partial bool IsDisplayedInThread { get; set; }
|
||||
|
||||
public bool IsFlagged
|
||||
{
|
||||
@@ -96,14 +85,4 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableObject, IM
|
||||
get => MailCopy.HasAttachments;
|
||||
set => SetProperty(MailCopy.HasAttachments, value, MailCopy, (u, n) => u.HasAttachments = n);
|
||||
}
|
||||
|
||||
public MailItemFolder AssignedFolder => ((IMailItem)MailCopy).AssignedFolder;
|
||||
|
||||
public MailAccount AssignedAccount => ((IMailItem)MailCopy).AssignedAccount;
|
||||
|
||||
public Guid FileId => ((IMailItem)MailCopy).FileId;
|
||||
|
||||
public AccountContact SenderContact => ((IMailItem)MailCopy).SenderContact;
|
||||
|
||||
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
|
||||
}
|
||||
|
||||
@@ -1,126 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
namespace Wino.Mail.ViewModels.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Thread mail item (multiple IMailItem) view model representation.
|
||||
/// </summary>
|
||||
public partial class ThreadMailItemViewModel : ObservableObject, IMailItemThread, IComparable<string>, IComparable<DateTime>
|
||||
public partial class ThreadMailItemViewModel : ObservableRecipient, IDisposable
|
||||
{
|
||||
public ObservableCollection<IMailItem> ThreadItems => (MailItem as IMailItemThread)?.ThreadItems ?? [];
|
||||
public AccountContact SenderContact => ((IMailItemThread)MailItem).SenderContact;
|
||||
private readonly string _threadId;
|
||||
|
||||
private readonly List<MailItemViewModel> _threadEmails = [];
|
||||
private bool _disposed;
|
||||
|
||||
[ObservableProperty]
|
||||
private ThreadMailItem mailItem;
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsThreadExpanded { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isThreadExpanded;
|
||||
public partial bool IsSelected { get; set; }
|
||||
|
||||
public ThreadMailItemViewModel(ThreadMailItem threadMailItem)
|
||||
/// <summary>
|
||||
/// Gets the number of emails in this thread
|
||||
/// </summary>
|
||||
public int EmailCount => _threadEmails.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest email's subject for display
|
||||
/// </summary>
|
||||
public string? Subject => _threadEmails
|
||||
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
||||
.FirstOrDefault()?.MailCopy?.Subject;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest email's sender name for display
|
||||
/// </summary>
|
||||
public string? FromName => _threadEmails
|
||||
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
||||
.FirstOrDefault()?.MailCopy?.SenderContact.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest email's creation date for sorting
|
||||
/// </summary>
|
||||
public DateTime LatestEmailDate => _threadEmails
|
||||
.OrderByDescending(e => e.MailCopy?.CreationDate)
|
||||
.FirstOrDefault()?.MailCopy?.CreationDate ?? DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all emails in this thread (read-only)
|
||||
/// </summary>
|
||||
public IReadOnlyList<MailItemViewModel> ThreadEmails => _threadEmails.AsReadOnly();
|
||||
|
||||
public ThreadMailItemViewModel(string threadId)
|
||||
{
|
||||
MailItem = new ThreadMailItem();
|
||||
_threadId = threadId;
|
||||
}
|
||||
|
||||
// Local copies
|
||||
foreach (var item in threadMailItem.ThreadItems)
|
||||
partial void OnIsSelectedChanged(bool value)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
AddMailItemViewModel(item);
|
||||
_threadEmails.Clear();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public ThreadMailItem GetThreadMailItem() => MailItem;
|
||||
|
||||
public IEnumerable<MailCopy> GetMailCopies()
|
||||
=> ThreadItems.OfType<MailItemViewModel>().Select(a => a.MailCopy);
|
||||
|
||||
public void AddMailItemViewModel(IMailItem mailItem)
|
||||
public void Dispose()
|
||||
{
|
||||
if (mailItem == null) return;
|
||||
|
||||
if (mailItem is MailCopy mailCopy)
|
||||
MailItem.AddThreadItem(new MailItemViewModel(mailCopy));
|
||||
else if (mailItem is MailItemViewModel mailItemViewModel)
|
||||
MailItem.AddThreadItem(mailItemViewModel);
|
||||
else
|
||||
Debugger.Break();
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public bool HasUniqueId(Guid uniqueMailId)
|
||||
=> ThreadItems.Any(a => a.UniqueId == uniqueMailId);
|
||||
|
||||
public IMailItem GetItemById(Guid uniqueMailId)
|
||||
=> ThreadItems.FirstOrDefault(a => a.UniqueId == uniqueMailId);
|
||||
|
||||
public void RemoveCopyItem(IMailItem item)
|
||||
private void NotifyPropertyChanges()
|
||||
{
|
||||
MailCopy copyToRemove = null;
|
||||
OnPropertyChanged(nameof(Subject));
|
||||
OnPropertyChanged(nameof(FromName));
|
||||
}
|
||||
|
||||
if (item is MailItemViewModel mailItemViewModel)
|
||||
copyToRemove = mailItemViewModel.MailCopy;
|
||||
else if (item is MailCopy copyItem)
|
||||
copyToRemove = copyItem;
|
||||
|
||||
var existedItem = ThreadItems.FirstOrDefault(a => a.Id == copyToRemove.Id);
|
||||
|
||||
if (existedItem == null) return;
|
||||
|
||||
ThreadItems.Remove(existedItem);
|
||||
/// <summary>
|
||||
/// Adds an email to this thread
|
||||
/// </summary>
|
||||
public void AddEmail(MailItemViewModel email)
|
||||
{
|
||||
if (email.MailCopy.ThreadId != _threadId)
|
||||
throw new ArgumentException($"Email ThreadId '{email.MailCopy.ThreadId}' does not match expander ThreadId '{_threadId}'");
|
||||
|
||||
_threadEmails.Add(email);
|
||||
NotifyPropertyChanges();
|
||||
}
|
||||
|
||||
public void NotifyPropertyChanges()
|
||||
/// <summary>
|
||||
/// Removes an email from this thread
|
||||
/// </summary>
|
||||
public void RemoveEmail(MailItemViewModel email)
|
||||
{
|
||||
// TODO
|
||||
// Stupid temporary fix for not updating UI.
|
||||
// This view model must be reworked with ThreadMailItem together.
|
||||
|
||||
var current = MailItem;
|
||||
|
||||
MailItem = null;
|
||||
MailItem = current;
|
||||
if (_threadEmails.Remove(email))
|
||||
{
|
||||
NotifyPropertyChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public IMailItem LatestMailItem => ((IMailItemThread)MailItem).LatestMailItem;
|
||||
public IMailItem FirstMailItem => ((IMailItemThread)MailItem).FirstMailItem;
|
||||
|
||||
public string Id => ((IMailItem)MailItem).Id;
|
||||
public string Subject => ((IMailItem)MailItem).Subject;
|
||||
public string ThreadId => ((IMailItem)MailItem).ThreadId;
|
||||
public string MessageId => ((IMailItem)MailItem).MessageId;
|
||||
public string References => ((IMailItem)MailItem).References;
|
||||
public string PreviewText => ((IMailItem)MailItem).PreviewText;
|
||||
public string FromName => ((IMailItem)MailItem).FromName;
|
||||
public DateTime CreationDate => ((IMailItem)MailItem).CreationDate;
|
||||
public string FromAddress => ((IMailItem)MailItem).FromAddress;
|
||||
public bool HasAttachments => ((IMailItem)MailItem).HasAttachments;
|
||||
public bool IsFlagged => ((IMailItem)MailItem).IsFlagged;
|
||||
public bool IsFocused => ((IMailItem)MailItem).IsFocused;
|
||||
public bool IsRead => ((IMailItem)MailItem).IsRead;
|
||||
public bool IsDraft => ((IMailItem)MailItem).IsDraft;
|
||||
public string DraftId => string.Empty;
|
||||
public string InReplyTo => ((IMailItem)MailItem).InReplyTo;
|
||||
|
||||
public MailItemFolder AssignedFolder => ((IMailItem)MailItem).AssignedFolder;
|
||||
|
||||
public MailAccount AssignedAccount => ((IMailItem)MailItem).AssignedAccount;
|
||||
|
||||
public Guid UniqueId => ((IMailItem)MailItem).UniqueId;
|
||||
|
||||
public Guid FileId => ((IMailItem)MailItem).FileId;
|
||||
|
||||
public int CompareTo(DateTime other) => CreationDate.CompareTo(other);
|
||||
public int CompareTo(string other) => FromName.CompareTo(other);
|
||||
|
||||
// Get single mail item view model out of the only item in thread items.
|
||||
public MailItemViewModel GetSingleItemViewModel() => ThreadItems.First() as MailItemViewModel;
|
||||
|
||||
public IEnumerable<Guid> GetContainingIds() => ((IMailItemThread)MailItem).GetContainingIds();
|
||||
}
|
||||
|
||||
@@ -59,8 +59,15 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
private IObservable<System.Reactive.EventPattern<NotifyCollectionChangedEventArgs>> selectionChangedObservable = null;
|
||||
|
||||
public WinoMailCollection MailCollection { get; }
|
||||
public GroupedEmailCollection MailCollection { get; set; } = new GroupedEmailCollection();
|
||||
|
||||
//public IEnumerable<MailItemViewModel> SelectedItems
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
public ObservableCollection<MailItemViewModel> SelectedItems { get; set; } = [];
|
||||
public ObservableCollection<FolderPivotViewModel> PivotFolders { get; set; } = [];
|
||||
public ObservableCollection<MailOperationMenuItem> ActionItems { get; set; } = [];
|
||||
@@ -77,7 +84,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
private readonly IMailDialogService _mailDialogService;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IThreadingStrategyProvider _threadingStrategyProvider;
|
||||
private readonly IContextMenuItemService _contextMenuItemService;
|
||||
private readonly IWinoRequestDelegator _winoRequestDelegator;
|
||||
private readonly IKeyPressService _keyPressService;
|
||||
@@ -154,7 +160,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
IMailService mailService,
|
||||
IStatePersistanceService statePersistenceService,
|
||||
IFolderService folderService,
|
||||
IThreadingStrategyProvider threadingStrategyProvider,
|
||||
IContextMenuItemService contextMenuItemService,
|
||||
IWinoRequestDelegator winoRequestDelegator,
|
||||
IKeyPressService keyPressService,
|
||||
@@ -162,7 +167,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
INewThemeService themeService,
|
||||
IWinoLogger winoLogger)
|
||||
{
|
||||
MailCollection = new WinoMailCollection(threadingStrategyProvider);
|
||||
PreferencesService = preferencesService;
|
||||
ThemeService = themeService;
|
||||
_winoLogger = winoLogger;
|
||||
@@ -172,7 +176,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
_mailDialogService = mailDialogService;
|
||||
_mailService = mailService;
|
||||
_folderService = folderService;
|
||||
_threadingStrategyProvider = threadingStrategyProvider;
|
||||
_contextMenuItemService = contextMenuItemService;
|
||||
_winoRequestDelegator = winoRequestDelegator;
|
||||
_keyPressService = keyPressService;
|
||||
@@ -190,23 +193,23 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
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);
|
||||
}
|
||||
};
|
||||
//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);
|
||||
// }
|
||||
//};
|
||||
}
|
||||
|
||||
private void SetupTopBarActions()
|
||||
@@ -243,10 +246,11 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
{
|
||||
if (SetProperty(ref _selectedSortingOption, value))
|
||||
{
|
||||
if (value != null && MailCollection != null)
|
||||
{
|
||||
MailCollection.SortingType = value.Type;
|
||||
}
|
||||
// TODO: Update sorting in mail collection.
|
||||
//if (value != null && MailCollection != null)
|
||||
//{
|
||||
// MailCollection.SortingType = value.Type;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,16 +322,16 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
if (markAsPreference == MailMarkAsOption.WhenSelected)
|
||||
{
|
||||
var operation = MailOperation.MarkAsRead;
|
||||
var package = new MailOperationPreperationRequest(operation, _activeMailItem.MailCopy);
|
||||
//var operation = MailOperation.MarkAsRead;
|
||||
//var package = new MailOperationPreperationRequest(operation, _activeMailItem.MailCopy);
|
||||
|
||||
if (ActiveFolder?.SpecialFolderType == SpecialFolderType.Unread &&
|
||||
!gmailUnreadFolderMarkedAsReadUniqueIds.Contains(_activeMailItem.UniqueId))
|
||||
{
|
||||
gmailUnreadFolderMarkedAsReadUniqueIds.Add(_activeMailItem.UniqueId);
|
||||
}
|
||||
//if (ActiveFolder?.SpecialFolderType == SpecialFolderType.Unread &&
|
||||
// !gmailUnreadFolderMarkedAsReadUniqueIds.Contains(_activeMailItem.UniqueId))
|
||||
//{
|
||||
// gmailUnreadFolderMarkedAsReadUniqueIds.Add(_activeMailItem.UniqueId);
|
||||
//}
|
||||
|
||||
await ExecuteMailOperationAsync(package);
|
||||
//await ExecuteMailOperationAsync(package);
|
||||
}
|
||||
else if (markAsPreference == MailMarkAsOption.AfterDelay && PreferencesService.MarkAsDelay >= 0)
|
||||
{
|
||||
@@ -353,13 +357,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
OnPropertyChanged(nameof(IsFolderEmpty));
|
||||
}
|
||||
|
||||
protected override void OnDispatcherAssigned()
|
||||
{
|
||||
base.OnDispatcherAssigned();
|
||||
|
||||
MailCollection.CoreDispatcher = Dispatcher;
|
||||
}
|
||||
|
||||
private async void UpdateBarMessage(InfoBarMessageType severity, string title, string message)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
@@ -565,67 +562,35 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
[RelayCommand]
|
||||
private async Task LoadMoreItemsAsync()
|
||||
{
|
||||
if (IsInitializingFolder || IsOnlineSearchEnabled) return;
|
||||
//if (IsInitializingFolder || IsOnlineSearchEnabled) return;
|
||||
|
||||
await ExecuteUIThread(() => { IsInitializingFolder = true; });
|
||||
//await ExecuteUIThread(() => { IsInitializingFolder = true; });
|
||||
|
||||
var initializationOptions = new MailListInitializationOptions(ActiveFolder.HandlingFolders,
|
||||
SelectedFilterOption.Type,
|
||||
SelectedSortingOption.Type,
|
||||
PreferencesService.IsThreadingEnabled,
|
||||
SelectedFolderPivot.IsFocused,
|
||||
IsInSearchMode ? SearchQuery : string.Empty,
|
||||
MailCollection.MailCopyIdHashSet);
|
||||
//var initializationOptions = new MailListInitializationOptions(ActiveFolder.HandlingFolders,
|
||||
// SelectedFilterOption.Type,
|
||||
// SelectedSortingOption.Type,
|
||||
// PreferencesService.IsThreadingEnabled,
|
||||
// SelectedFolderPivot.IsFocused,
|
||||
// IsInSearchMode ? SearchQuery : string.Empty,
|
||||
// MailCollection.MailCopyIdHashSet);
|
||||
|
||||
var items = await _mailService.FetchMailsAsync(initializationOptions).ConfigureAwait(false);
|
||||
//var items = await _mailService.FetchMailsAsync(initializationOptions).ConfigureAwait(false);
|
||||
|
||||
var viewModels = PrepareMailViewModels(items);
|
||||
//var viewModels = PrepareMailViewModels(items);
|
||||
|
||||
await ExecuteUIThread(() => { MailCollection.AddRange(viewModels, clearIdCache: false); });
|
||||
await ExecuteUIThread(() => { IsInitializingFolder = false; });
|
||||
//await ExecuteUIThread(() => { MailCollection.AddRange(viewModels, clearIdCache: false); });
|
||||
//await ExecuteUIThread(() => { IsInitializingFolder = false; });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public Task ExecuteMailOperationAsync(MailOperationPreperationRequest package) => _winoRequestDelegator.ExecuteAsync(package);
|
||||
|
||||
public IEnumerable<MailItemViewModel> GetTargetMailItemViewModels(IMailItem clickedItem)
|
||||
{
|
||||
// Threat threads as a whole and include everything in the group. Except single selections outside of the thread.
|
||||
IEnumerable<MailItemViewModel> contextMailItems = null;
|
||||
public IEnumerable<MailOperationMenuItem> GetAvailableMailActions(IEnumerable<MailItemViewModel> contextMailItems)
|
||||
=> _contextMenuItemService.GetMailItemContextMenuActions(contextMailItems.Select(a => a.MailCopy));
|
||||
|
||||
if (clickedItem is ThreadMailItemViewModel clickedThreadItem)
|
||||
{
|
||||
// Clicked item is a thread.
|
||||
|
||||
clickedThreadItem.IsThreadExpanded = true;
|
||||
contextMailItems = clickedThreadItem.ThreadItems.Cast<MailItemViewModel>();
|
||||
|
||||
// contextMailItems = clickedThreadItem.GetMailCopies();
|
||||
}
|
||||
else if (clickedItem is MailItemViewModel clickedMailItemViewModel)
|
||||
{
|
||||
// If the clicked item is included in SelectedItems, then we need to thing them as whole.
|
||||
// If there are selected items, but clicked item is not one of them, then it's a single context menu.
|
||||
|
||||
bool includedInSelectedItems = SelectedItems.Contains(clickedItem);
|
||||
|
||||
if (includedInSelectedItems)
|
||||
contextMailItems = SelectedItems;
|
||||
else
|
||||
contextMailItems = [clickedMailItemViewModel];
|
||||
}
|
||||
|
||||
return contextMailItems;
|
||||
}
|
||||
|
||||
public IEnumerable<MailOperationMenuItem> GetAvailableMailActions(IEnumerable<IMailItem> contextMailItems)
|
||||
=> _contextMenuItemService.GetMailItemContextMenuActions(contextMailItems);
|
||||
|
||||
public void ChangeCustomFocusedState(IEnumerable<IMailItem> mailItems, bool isFocused)
|
||||
=> mailItems.OfType<MailItemViewModel>().ForEach(a => a.IsCustomFocused = isFocused);
|
||||
|
||||
private bool ShouldPreventItemAdd(IMailItem mailItem)
|
||||
private bool ShouldPreventItemAdd(MailCopy mailItem)
|
||||
{
|
||||
bool condition = mailItem.IsRead
|
||||
&& SelectedFilterOption.Type == FilterOptionType.Unread
|
||||
@@ -660,7 +625,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
await listManipulationSemepahore.WaitAsync();
|
||||
|
||||
await MailCollection.AddAsync(addedMail);
|
||||
// await MailCollection.AddAsync(addedMail);
|
||||
|
||||
await ExecuteUIThread(() => { NotifyItemFoundState(); });
|
||||
}
|
||||
@@ -677,7 +642,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
Debug.WriteLine($"Updating {updatedMail.Id}-> {updatedMail.UniqueId}");
|
||||
|
||||
await MailCollection.UpdateMailCopy(updatedMail);
|
||||
// await MailCollection.UpdateMailCopy(updatedMail);
|
||||
|
||||
await ExecuteUIThread(() => { SetupTopBarActions(); });
|
||||
}
|
||||
@@ -712,12 +677,12 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
nextItem = MailCollection.GetNextItem(removedMail);
|
||||
// nextItem = MailCollection.GetNextItem(removedMail);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the deleted item from the list.
|
||||
await MailCollection.RemoveAsync(removedMail);
|
||||
// await MailCollection.RemoveAsync(removedMail);
|
||||
|
||||
if (nextItem != null)
|
||||
WeakReferenceMessenger.Default.Send(new SelectMailItemContainerEvent(nextItem, ScrollToItem: true));
|
||||
@@ -748,11 +713,11 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
// Otherwise the draft mail item will be duplicated on the next add execution.
|
||||
await listManipulationSemepahore.WaitAsync();
|
||||
|
||||
// Create the item. Draft folder navigation is already done at this point.
|
||||
await MailCollection.AddAsync(draftMail);
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
// Create the item. Draft folder navigation is already done at this point.
|
||||
MailCollection.AddEmail(new MailItemViewModel(draftMail));
|
||||
|
||||
// New draft is created by user. Select the item.
|
||||
Messenger.Send(new MailItemNavigationRequested(draftMail.UniqueId, ScrollToItem: true));
|
||||
|
||||
@@ -765,15 +730,16 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IMailItem> PrepareMailViewModels(IEnumerable<IMailItem> mailItems)
|
||||
private IEnumerable<MailItemViewModel> PrepareMailViewModels(IEnumerable<MailCopy> mailItems)
|
||||
{
|
||||
foreach (var item in mailItems)
|
||||
{
|
||||
if (item is MailCopy singleMailItem)
|
||||
yield return new MailItemViewModel(singleMailItem);
|
||||
else if (item is ThreadMailItem threadMailItem)
|
||||
yield return new ThreadMailItemViewModel(threadMailItem);
|
||||
}
|
||||
return mailItems.Select(a => new MailItemViewModel(a));
|
||||
//foreach (var item in mailItems)
|
||||
//{
|
||||
// if (item is MailCopy singleMailItem)
|
||||
// yield return new MailItemViewModel(singleMailItem);
|
||||
// else if (item is ThreadMailItem threadMailItem)
|
||||
// yield return new ThreadMailItemViewModel(threadMailItem);
|
||||
//}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -793,7 +759,6 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
try
|
||||
{
|
||||
MailCollection.Clear();
|
||||
MailCollection.MailCopyIdHashSet.Clear();
|
||||
|
||||
SelectedItems.Clear();
|
||||
|
||||
@@ -816,17 +781,9 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
await listManipulationSemepahore.WaitAsync(cancellationToken);
|
||||
|
||||
// Setup MailCollection configuration.
|
||||
|
||||
// Don't pass any threading strategy if disabled in settings.
|
||||
MailCollection.ThreadingStrategyProvider = PreferencesService.IsThreadingEnabled ? _threadingStrategyProvider : null;
|
||||
|
||||
// TODO: This should go inside
|
||||
MailCollection.PruneSingleNonDraftItems = ActiveFolder.SpecialFolderType == SpecialFolderType.Draft;
|
||||
|
||||
// Here items are sorted and filtered.
|
||||
|
||||
List<IMailItem> items = null;
|
||||
List<MailCopy> items = null;
|
||||
List<MailCopy> onlineSearchItems = null;
|
||||
|
||||
bool isDoingSearch = !string.IsNullOrEmpty(SearchQuery);
|
||||
@@ -894,7 +851,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
PreferencesService.IsThreadingEnabled,
|
||||
SelectedFolderPivot.IsFocused,
|
||||
SearchQuery,
|
||||
MailCollection.MailCopyIdHashSet,
|
||||
default,
|
||||
onlineSearchItems);
|
||||
|
||||
items = await _mailService.FetchMailsAsync(initializationOptions, cancellationToken).ConfigureAwait(false);
|
||||
@@ -909,7 +866,7 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
MailCollection.AddRange(viewModels, true);
|
||||
MailCollection.AddEmails(viewModels);
|
||||
|
||||
if (isDoingSearch && !isDoingOnlineSearch)
|
||||
{
|
||||
@@ -1055,15 +1012,16 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var mailContainer = MailCollection.GetMailItemContainer(message.UniqueMailId);
|
||||
// TODO: Get container.
|
||||
//var mailContainer = MailCollection.GetMailItemContainer(message.UniqueMailId);
|
||||
|
||||
if (mailContainer != null)
|
||||
{
|
||||
navigatingMailItem = mailContainer.ItemViewModel;
|
||||
threadMailItemViewModel = mailContainer.ThreadViewModel;
|
||||
//if (mailContainer != null)
|
||||
//{
|
||||
// navigatingMailItem = mailContainer.ItemViewModel;
|
||||
// threadMailItemViewModel = mailContainer.ThreadViewModel;
|
||||
|
||||
break;
|
||||
}
|
||||
// break;
|
||||
//}
|
||||
}
|
||||
|
||||
if (threadMailItemViewModel != null)
|
||||
@@ -1138,5 +1096,8 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(ThumbnailAdded message) => MailCollection.UpdateThumbnails(message.Email);
|
||||
public void Receive(ThumbnailAdded message)
|
||||
{
|
||||
// MailCollection.UpdateThumbnails(message.Email);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
|
||||
public bool ShouldDisplayDownloadProgress => IsIndetermineProgress || (CurrentDownloadPercentage > 0 && CurrentDownloadPercentage <= 100);
|
||||
public bool CanUnsubscribe => CurrentRenderModel?.UnsubscribeInfo?.CanUnsubscribe ?? false;
|
||||
public bool IsJunkMail => initializedMailItemViewModel?.AssignedFolder != null && initializedMailItemViewModel.AssignedFolder.SpecialFolderType == SpecialFolderType.Junk;
|
||||
public bool IsJunkMail => initializedMailItemViewModel?.MailCopy.AssignedFolder != null && initializedMailItemViewModel.MailCopy.AssignedFolder.SpecialFolderType == SpecialFolderType.Junk;
|
||||
|
||||
public bool IsImageRenderingDisabled
|
||||
{
|
||||
@@ -283,9 +283,9 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
}
|
||||
};
|
||||
|
||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.MailCopy.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
||||
|
||||
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, initializedMailItemViewModel.MailCopy);
|
||||
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.MailCopy.AssignedAccount, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, initializedMailItemViewModel.MailCopy);
|
||||
|
||||
await _requestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||
|
||||
@@ -375,7 +375,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
private async Task RenderAsync(MailItemViewModel mailItemViewModel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ResetProgress();
|
||||
var isMimeExists = await _mimeFileService.IsMimeExistAsync(mailItemViewModel.AssignedAccount.Id, mailItemViewModel.MailCopy.FileId);
|
||||
var isMimeExists = await _mimeFileService.IsMimeExistAsync(mailItemViewModel.MailCopy.AssignedAccount.Id, mailItemViewModel.MailCopy.FileId);
|
||||
|
||||
if (!isMimeExists)
|
||||
{
|
||||
@@ -384,7 +384,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
|
||||
// Find the MIME for this item and render it.
|
||||
var mimeMessageInformation = await _mimeFileService.GetMimeMessageInformationAsync(mailItemViewModel.MailCopy.FileId,
|
||||
mailItemViewModel.AssignedAccount.Id,
|
||||
mailItemViewModel.MailCopy.AssignedAccount.Id,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (mimeMessageInformation == null)
|
||||
@@ -436,12 +436,12 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
FromAddress = message.From.Mailboxes.FirstOrDefault()?.Address ?? Translator.UnknownAddress;
|
||||
FromName = message.From.Mailboxes.FirstOrDefault()?.Name ?? Translator.UnknownSender;
|
||||
CreationDate = message.Date.DateTime;
|
||||
ContactPicture = initializedMailItemViewModel?.SenderContact?.Base64ContactPicture;
|
||||
ContactPicture = initializedMailItemViewModel?.MailCopy.SenderContact?.Base64ContactPicture;
|
||||
|
||||
// Automatically disable images for Junk folder to prevent pixel tracking.
|
||||
// This can only work for selected mail item rendering, not for EML file rendering.
|
||||
if (initializedMailItemViewModel != null &&
|
||||
initializedMailItemViewModel.AssignedFolder.SpecialFolderType == SpecialFolderType.Junk)
|
||||
initializedMailItemViewModel.MailCopy.AssignedFolder.SpecialFolderType == SpecialFolderType.Junk)
|
||||
{
|
||||
renderingOptions.LoadImages = false;
|
||||
}
|
||||
@@ -480,7 +480,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
var contactViewModel = new AccountContactViewModel(foundContact);
|
||||
|
||||
// Make sure that user account first in the list.
|
||||
if (string.Equals(contactViewModel.Address, initializedMailItemViewModel?.AssignedAccount?.Address, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(contactViewModel.Address, initializedMailItemViewModel?.MailCopy.AssignedAccount?.Address, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
contactViewModel.IsMe = true;
|
||||
accounts.Insert(0, contactViewModel);
|
||||
@@ -563,7 +563,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
}
|
||||
|
||||
// Archive - Unarchive
|
||||
if (initializedMailItemViewModel.AssignedFolder.SpecialFolderType == SpecialFolderType.Archive)
|
||||
if (initializedMailItemViewModel.MailCopy.AssignedFolder.SpecialFolderType == SpecialFolderType.Archive)
|
||||
MenuItems.Add(MailOperationMenuItem.Create(MailOperation.UnArchive));
|
||||
else
|
||||
MenuItems.Add(MailOperationMenuItem.Create(MailOperation.Archive));
|
||||
@@ -594,7 +594,7 @@ public partial class MailRenderingPageViewModel : MailBaseViewModel,
|
||||
|
||||
// Check if the updated mail is the same mail item we are rendering.
|
||||
// This is done with UniqueId to include FolderId into calculations.
|
||||
if (initializedMailItemViewModel.UniqueId != updatedMail.UniqueId) return;
|
||||
if (initializedMailItemViewModel.MailCopy.UniqueId != updatedMail.UniqueId) return;
|
||||
|
||||
// Mail operation might change the mail item like mark read/unread or change flag.
|
||||
// So we need to update the mail item view model when this happens.
|
||||
|
||||
Reference in New Issue
Block a user