Render mail categories in list items
This commit is contained in:
@@ -172,6 +172,9 @@ public class MailCopy
|
|||||||
[Ignore]
|
[Ignore]
|
||||||
public Guid? ReadReceiptMessageUniqueId { get; set; }
|
public Guid? ReadReceiptMessageUniqueId { get; set; }
|
||||||
|
|
||||||
|
[Ignore]
|
||||||
|
public List<MailCategory> Categories { get; set; } = [];
|
||||||
|
|
||||||
public IEnumerable<Guid> GetContainingIds() => [UniqueId];
|
public IEnumerable<Guid> GetContainingIds() => [UniqueId];
|
||||||
public override string ToString() => $"{Subject} <-> {Id}";
|
public override string ToString() => $"{Subject} <-> {Id}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public enum MailCopyChangeFlags
|
|||||||
SenderContact = 1 << 23,
|
SenderContact = 1 << 23,
|
||||||
UniqueId = 1 << 24,
|
UniqueId = 1 << 24,
|
||||||
ReadReceiptState = 1 << 25,
|
ReadReceiptState = 1 << 25,
|
||||||
|
Categories = 1 << 26,
|
||||||
All = Id |
|
All = Id |
|
||||||
FolderId |
|
FolderId |
|
||||||
ThreadId |
|
ThreadId |
|
||||||
@@ -57,5 +58,6 @@ public enum MailCopyChangeFlags
|
|||||||
AssignedAccount |
|
AssignedAccount |
|
||||||
SenderContact |
|
SenderContact |
|
||||||
UniqueId |
|
UniqueId |
|
||||||
ReadReceiptState
|
ReadReceiptState |
|
||||||
|
Categories
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public interface IMailCategoryService
|
|||||||
Task AssignCategoryAsync(Guid categoryId, IEnumerable<Guid> mailCopyUniqueIds);
|
Task AssignCategoryAsync(Guid categoryId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||||
Task UnassignCategoryAsync(Guid categoryId, IEnumerable<Guid> mailCopyUniqueIds);
|
Task UnassignCategoryAsync(Guid categoryId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||||
Task<List<MailCategory>> GetCategoriesForMailAsync(Guid accountId, IEnumerable<Guid> mailCopyUniqueIds);
|
Task<List<MailCategory>> GetCategoriesForMailAsync(Guid accountId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||||
|
Task<IReadOnlyDictionary<Guid, IReadOnlyList<MailCategory>>> GetCategoriesByMailAsync(Guid accountId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||||
Task<List<Guid>> GetAssignedCategoryIdsForAllAsync(IEnumerable<Guid> mailCopyUniqueIds);
|
Task<List<Guid>> GetAssignedCategoryIdsForAllAsync(IEnumerable<Guid> mailCopyUniqueIds);
|
||||||
Task<List<string>> GetCategoryNamesForMailAsync(Guid mailCopyUniqueId);
|
Task<List<string>> GetCategoryNamesForMailAsync(Guid mailCopyUniqueId);
|
||||||
Task<List<MailCopy>> GetMailCopiesForCategoryAsync(Guid categoryId);
|
Task<List<MailCopy>> GetMailCopiesForCategoryAsync(Guid categoryId);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Interfaces;
|
namespace Wino.Core.Domain.Interfaces;
|
||||||
@@ -27,4 +29,6 @@ public interface IMailItemDisplayInformation : INotifyPropertyChanged
|
|||||||
bool HasReadReceiptTracking { get; }
|
bool HasReadReceiptTracking { get; }
|
||||||
bool IsReadReceiptAcknowledged { get; }
|
bool IsReadReceiptAcknowledged { get; }
|
||||||
string ReadReceiptDisplayText { get; }
|
string ReadReceiptDisplayText { get; }
|
||||||
|
IReadOnlyList<MailCategory> Categories { get; }
|
||||||
|
bool HasCategories { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,6 +389,24 @@ public partial class PersonalizationPageViewModel : CoreBaseViewModel
|
|||||||
public bool HasReadReceiptTracking { get; } = false;
|
public bool HasReadReceiptTracking { get; } = false;
|
||||||
public bool IsReadReceiptAcknowledged { get; } = false;
|
public bool IsReadReceiptAcknowledged { get; } = false;
|
||||||
public string ReadReceiptDisplayText { get; } = string.Empty;
|
public string ReadReceiptDisplayText { get; } = string.Empty;
|
||||||
|
public IReadOnlyList<MailCategory> Categories { get; } =
|
||||||
|
[
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Name = "Follow Up",
|
||||||
|
BackgroundColorHex = "#DCEBFF",
|
||||||
|
TextColorHex = "#0B5CAD"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Name = "Planning",
|
||||||
|
BackgroundColorHex = "#DDF5D7",
|
||||||
|
TextColorHex = "#236A1E"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
public bool HasCategories => Categories.Count > 0;
|
||||||
public AccountContact SenderContact { get; } = null;
|
public AccountContact SenderContact { get; } = null;
|
||||||
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
|
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
|
||||||
@@ -71,6 +73,8 @@ public partial class AccountContactViewModel : ObservableObject, IMailItemDispla
|
|||||||
public bool HasReadReceiptTracking => false;
|
public bool HasReadReceiptTracking => false;
|
||||||
public bool IsReadReceiptAcknowledged => false;
|
public bool IsReadReceiptAcknowledged => false;
|
||||||
public string ReadReceiptDisplayText => string.Empty;
|
public string ReadReceiptDisplayText => string.Empty;
|
||||||
|
public IReadOnlyList<MailCategory> Categories => [];
|
||||||
|
public bool HasCategories => false;
|
||||||
public AccountContact SenderContact => new()
|
public AccountContact SenderContact => new()
|
||||||
{
|
{
|
||||||
Address = Address,
|
Address = Address,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
@@ -39,6 +40,8 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
||||||
[NotifyPropertyChangedFor(nameof(ContactPictureFileId))]
|
[NotifyPropertyChangedFor(nameof(ContactPictureFileId))]
|
||||||
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(Categories))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasCategories))]
|
||||||
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
public partial MailCopy MailCopy { get; set; } = mailCopy;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -124,6 +127,10 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public IReadOnlyList<MailCategory> Categories => MailCopy.Categories;
|
||||||
|
|
||||||
|
public bool HasCategories => Categories.Count > 0;
|
||||||
|
|
||||||
public string DraftId
|
public string DraftId
|
||||||
{
|
{
|
||||||
get => MailCopy.DraftId;
|
get => MailCopy.DraftId;
|
||||||
@@ -262,6 +269,7 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
nameof(FolderId) => MailCopyChangeFlags.FolderId,
|
nameof(FolderId) => MailCopyChangeFlags.FolderId,
|
||||||
nameof(UniqueId) => MailCopyChangeFlags.UniqueId,
|
nameof(UniqueId) => MailCopyChangeFlags.UniqueId,
|
||||||
nameof(ContactPictureFileId) or nameof(SenderContact) => MailCopyChangeFlags.SenderContact,
|
nameof(ContactPictureFileId) or nameof(SenderContact) => MailCopyChangeFlags.SenderContact,
|
||||||
|
nameof(Categories) or nameof(HasCategories) => MailCopyChangeFlags.Categories,
|
||||||
_ => MailCopyChangeFlags.None
|
_ => MailCopyChangeFlags.None
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -474,9 +482,21 @@ public partial class MailItemViewModel(MailCopy mailCopy) : ObservableRecipient,
|
|||||||
Queue(nameof(SenderContact));
|
Queue(nameof(SenderContact));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((changedFlags & MailCopyChangeFlags.Categories) != 0)
|
||||||
|
{
|
||||||
|
Queue(nameof(Categories));
|
||||||
|
Queue(nameof(HasCategories));
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var changedProperty in changedProperties)
|
foreach (var changedProperty in changedProperties)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(changedProperty);
|
OnPropertyChanged(changedProperty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateCategories(IReadOnlyList<MailCategory> categories)
|
||||||
|
{
|
||||||
|
MailCopy.Categories = categories?.ToList() ?? [];
|
||||||
|
RaisePropertyChanges(MailCopyChangeFlags.Categories);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -111,6 +112,13 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
public bool IsReadReceiptAcknowledged => newestMailViewModel?.IsReadReceiptAcknowledged ?? false;
|
public bool IsReadReceiptAcknowledged => newestMailViewModel?.IsReadReceiptAcknowledged ?? false;
|
||||||
|
|
||||||
public string ReadReceiptDisplayText => newestMailViewModel?.ReadReceiptDisplayText ?? string.Empty;
|
public string ReadReceiptDisplayText => newestMailViewModel?.ReadReceiptDisplayText ?? string.Empty;
|
||||||
|
public IReadOnlyList<MailCategory> Categories => ThreadEmails
|
||||||
|
.SelectMany(a => a.Categories)
|
||||||
|
.GroupBy(a => a.Id)
|
||||||
|
.Select(a => a.First())
|
||||||
|
.OrderBy(a => a.Name)
|
||||||
|
.ToList();
|
||||||
|
public bool HasCategories => ThreadEmails.Any(a => a.HasCategories);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether any email in this thread is a draft
|
/// Gets whether any email in this thread is a draft
|
||||||
@@ -206,6 +214,8 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
[NotifyPropertyChangedFor(nameof(UniqueId))]
|
||||||
[NotifyPropertyChangedFor(nameof(ContactPictureFileId))]
|
[NotifyPropertyChangedFor(nameof(ContactPictureFileId))]
|
||||||
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
[NotifyPropertyChangedFor(nameof(SenderContact))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(Categories))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasCategories))]
|
||||||
public partial ObservableCollection<MailItemViewModel> ThreadEmails { get; set; } = [];
|
public partial ObservableCollection<MailItemViewModel> ThreadEmails { get; set; } = [];
|
||||||
|
|
||||||
private MailItemViewModel newestMailViewModel => _cachedNewestMailViewModel;
|
private MailItemViewModel newestMailViewModel => _cachedNewestMailViewModel;
|
||||||
@@ -467,6 +477,12 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
Queue(nameof(ContactPictureFileId));
|
Queue(nameof(ContactPictureFileId));
|
||||||
Queue(nameof(SenderContact));
|
Queue(nameof(SenderContact));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((changedFlags & MailCopyChangeFlags.Categories) != 0)
|
||||||
|
{
|
||||||
|
Queue(nameof(Categories));
|
||||||
|
Queue(nameof(HasCategories));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,6 +511,12 @@ public partial class ThreadMailItemViewModel : ObservableRecipient, IMailListIte
|
|||||||
if ((changedFlags & MailCopyChangeFlags.IsDraft) != 0 || changedFlags == MailCopyChangeFlags.All)
|
if ((changedFlags & MailCopyChangeFlags.IsDraft) != 0 || changedFlags == MailCopyChangeFlags.All)
|
||||||
Queue(nameof(IsDraft));
|
Queue(nameof(IsDraft));
|
||||||
|
|
||||||
|
if ((changedFlags & MailCopyChangeFlags.Categories) != 0 || changedFlags == MailCopyChangeFlags.All)
|
||||||
|
{
|
||||||
|
Queue(nameof(Categories));
|
||||||
|
Queue(nameof(HasCategories));
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var changedProperty in changedProperties)
|
foreach (var changedProperty in changedProperties)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(changedProperty);
|
OnPropertyChanged(changedProperty);
|
||||||
|
|||||||
@@ -1329,6 +1329,8 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
|
|
||||||
private async Task<List<MailItemViewModel>> PrepareMailViewModelsAsync(IEnumerable<MailCopy> mailItems, CancellationToken cancellationToken = default)
|
private async Task<List<MailItemViewModel>> PrepareMailViewModelsAsync(IEnumerable<MailCopy> mailItems, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
await PopulateMailCategoriesAsync(mailItems, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// Run ViewModel creation on background thread to avoid blocking UI
|
// Run ViewModel creation on background thread to avoid blocking UI
|
||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
@@ -1342,6 +1344,38 @@ public partial class MailListPageViewModel : MailBaseViewModel,
|
|||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task PopulateMailCategoriesAsync(IEnumerable<MailCopy> mailItems, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var mails = mailItems?.Where(a => a != null).ToList() ?? [];
|
||||||
|
if (mails.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var accountIdsByFolderId = ActiveFolder?.HandlingFolders?
|
||||||
|
.GroupBy(a => a.Id)
|
||||||
|
.ToDictionary(a => a.Key, a => a.First().MailAccountId) ?? new Dictionary<Guid, Guid>();
|
||||||
|
|
||||||
|
var mailsByAccount = mails
|
||||||
|
.GroupBy(mail => ResolveMailAccountId(mail, accountIdsByFolderId))
|
||||||
|
.Where(group => group.Key != Guid.Empty)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var groupedMails in mailsByAccount)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var categoriesByMail = await _mailCategoryService
|
||||||
|
.GetCategoriesByMailAsync(groupedMails.Key, groupedMails.Select(a => a.UniqueId))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var mail in groupedMails)
|
||||||
|
{
|
||||||
|
mail.Categories = categoriesByMail.TryGetValue(mail.UniqueId, out var categories)
|
||||||
|
? categories.ToList()
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<HashSet<Guid>> GetPendingOperationUniqueIdsForActiveFolderAccountsAsync(CancellationToken cancellationToken = default)
|
private async Task<HashSet<Guid>> GetPendingOperationUniqueIdsForActiveFolderAccountsAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var pendingOperationUniqueIds = new HashSet<Guid>();
|
var pendingOperationUniqueIds = new HashSet<Guid>();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.ComponentModel;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Wino.Core.Domain;
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -187,6 +188,24 @@ public partial class MessageListPageViewModel : MailBaseViewModel
|
|||||||
public bool HasReadReceiptTracking => true;
|
public bool HasReadReceiptTracking => true;
|
||||||
public bool IsReadReceiptAcknowledged => false;
|
public bool IsReadReceiptAcknowledged => false;
|
||||||
public string ReadReceiptDisplayText => Translator.MailReceiptStatus_Requested;
|
public string ReadReceiptDisplayText => Translator.MailReceiptStatus_Requested;
|
||||||
|
public IReadOnlyList<MailCategory> Categories =>
|
||||||
|
[
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Name = "Urgent",
|
||||||
|
BackgroundColorHex = "#FFE1DE",
|
||||||
|
TextColorHex = "#A1260D"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Name = "Client",
|
||||||
|
BackgroundColorHex = "#E4E8FF",
|
||||||
|
TextColorHex = "#4255C5"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
public bool HasCategories => Categories.Count > 0;
|
||||||
public AccountContact SenderContact => new()
|
public AccountContact SenderContact => new()
|
||||||
{
|
{
|
||||||
Address = "hi@bkaan.dev",
|
Address = "hi@bkaan.dev",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
xmlns:domain="using:Wino.Core.Domain"
|
xmlns:domain="using:Wino.Core.Domain"
|
||||||
xmlns:enums="using:Wino.Core.Domain.Enums"
|
xmlns:enums="using:Wino.Core.Domain.Enums"
|
||||||
xmlns:helpers="using:Wino.Helpers"
|
xmlns:helpers="using:Wino.Helpers"
|
||||||
|
xmlns:mail="using:Wino.Core.Domain.Entities.Mail"
|
||||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
@@ -23,6 +24,28 @@
|
|||||||
Unloaded="OnUnloaded">
|
Unloaded="OnUnloaded">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
<DataTemplate x:Key="DetailedMailCategoryTemplate" x:DataType="mail:MailCategory">
|
||||||
|
<Border
|
||||||
|
Margin="0,0,4,0"
|
||||||
|
Padding="6,2"
|
||||||
|
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}"
|
||||||
|
CornerRadius="8">
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{x:Bind helpers:XamlHelpers.GetCategoryTextBrush(TextColorHex, BackgroundColorHex), Mode=OneWay}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind Name, Mode=OneWay}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="CompactMailCategoryTemplate" x:DataType="mail:MailCategory">
|
||||||
|
<Ellipse
|
||||||
|
Width="8"
|
||||||
|
Height="8"
|
||||||
|
Margin="0,0,4,0"
|
||||||
|
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<Style
|
<Style
|
||||||
x:Key="HoverActionButtonStyle"
|
x:Key="HoverActionButtonStyle"
|
||||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||||
@@ -89,6 +112,7 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Sender + IsDraft + Hover Buttons -->
|
<!-- Sender + IsDraft + Hover Buttons -->
|
||||||
@@ -206,6 +230,21 @@
|
|||||||
Text="{x:Bind helpers:XamlHelpers.GetMailItemDisplaySummaryForListing(MailItemInformation.IsDraft, MailItemInformation.CreationDate, Prefer24HourTimeFormat), Mode=OneWay}" />
|
Text="{x:Bind helpers:XamlHelpers.GetMailItemDisplaySummaryForListing(MailItemInformation.IsDraft, MailItemInformation.CreationDate, Prefer24HourTimeFormat), Mode=OneWay}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<ItemsControl
|
||||||
|
x:Name="DetailedCategoriesContainer"
|
||||||
|
Grid.Row="3"
|
||||||
|
Margin="0,4,0,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
ItemTemplate="{StaticResource DetailedMailCategoryTemplate}"
|
||||||
|
ItemsSource="{x:Bind MailItemInformation.Categories, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(MailItemInformation.Categories.Count), Mode=OneWay}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
<!-- Message -->
|
<!-- Message -->
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="PreviewTextContainerRoot"
|
x:Name="PreviewTextContainerRoot"
|
||||||
@@ -233,6 +272,18 @@
|
|||||||
Margin="4,4,1,4"
|
Margin="4,4,1,4"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="2">
|
Spacing="2">
|
||||||
|
<ItemsControl
|
||||||
|
x:Name="CompactCategoriesContainer"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ItemTemplate="{StaticResource CompactMailCategoryTemplate}"
|
||||||
|
ItemsSource="{x:Bind MailItemInformation.Categories, Mode=OneWay}"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,0,4,0"
|
Margin="0,0,4,0"
|
||||||
@@ -288,8 +339,13 @@
|
|||||||
<VisualStateGroup x:Name="SizingStates">
|
<VisualStateGroup x:Name="SizingStates">
|
||||||
<VisualState x:Name="Compact">
|
<VisualState x:Name="Compact">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Target="RootContainer.Height" Value="60" />
|
<Setter Target="RootContainer.Height" Value="64" />
|
||||||
<Setter Target="ContentGrid.Padding" Value="8,0" />
|
<Setter Target="ContentGrid.Padding" Value="8,6,8,6" />
|
||||||
|
<Setter Target="ContentStackpanel.VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Target="PreviewTextContainerRoot.Margin" Value="0" />
|
||||||
|
<Setter Target="IconsContainer.Margin" Value="4,0,1,0" />
|
||||||
|
<Setter Target="DetailedCategoriesContainer.Visibility" Value="Collapsed" />
|
||||||
|
<Setter Target="CompactCategoriesContainer.Visibility" Value="Visible" />
|
||||||
<Setter Target="PreviewTextContainer.Visibility" Value="Collapsed" />
|
<Setter Target="PreviewTextContainer.Visibility" Value="Collapsed" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
<VisualState.StateTriggers>
|
<VisualState.StateTriggers>
|
||||||
@@ -300,8 +356,13 @@
|
|||||||
<!-- Medium -->
|
<!-- Medium -->
|
||||||
<VisualState x:Name="Medium">
|
<VisualState x:Name="Medium">
|
||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Target="RootContainer.Height" Value="80" />
|
<Setter Target="RootContainer.Height" Value="96" />
|
||||||
<Setter Target="ContentGrid.Padding" Value="6,0" />
|
<Setter Target="ContentGrid.Padding" Value="6,8,6,6" />
|
||||||
|
<Setter Target="ContentStackpanel.VerticalAlignment" Value="Top" />
|
||||||
|
<Setter Target="DetailedCategoriesContainer.Margin" Value="0,2,0,0" />
|
||||||
|
<Setter Target="PreviewTextContainerRoot.Margin" Value="0,2,0,0" />
|
||||||
|
<Setter Target="IconsContainer.Margin" Value="4,2,1,0" />
|
||||||
|
<Setter Target="CompactCategoriesContainer.Visibility" Value="Collapsed" />
|
||||||
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
|
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
<VisualState.StateTriggers>
|
<VisualState.StateTriggers>
|
||||||
@@ -314,6 +375,11 @@
|
|||||||
<VisualState.Setters>
|
<VisualState.Setters>
|
||||||
<Setter Target="RootContainer.Height" Value="Auto" />
|
<Setter Target="RootContainer.Height" Value="Auto" />
|
||||||
<Setter Target="ContentGrid.Padding" Value="12,12,6,12" />
|
<Setter Target="ContentGrid.Padding" Value="12,12,6,12" />
|
||||||
|
<Setter Target="ContentStackpanel.VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Target="DetailedCategoriesContainer.Margin" Value="0,4,0,0" />
|
||||||
|
<Setter Target="PreviewTextContainerRoot.Margin" Value="0" />
|
||||||
|
<Setter Target="IconsContainer.Margin" Value="4,4,1,4" />
|
||||||
|
<Setter Target="CompactCategoriesContainer.Visibility" Value="Collapsed" />
|
||||||
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
|
<Setter Target="PreviewTextContainer.Visibility" Value="Visible" />
|
||||||
</VisualState.Setters>
|
</VisualState.Setters>
|
||||||
<VisualState.StateTriggers>
|
<VisualState.StateTriggers>
|
||||||
|
|||||||
@@ -152,6 +152,12 @@ public static class XamlHelpers
|
|||||||
public static Color GetWindowsColorFromHex(string hex) => hex.ToColor();
|
public static Color GetWindowsColorFromHex(string hex) => hex.ToColor();
|
||||||
|
|
||||||
public static SolidColorBrush GetSolidColorBrushFromHex(string colorHex) => string.IsNullOrEmpty(colorHex) ? new SolidColorBrush(Colors.Transparent) : new SolidColorBrush(colorHex.ToColor());
|
public static SolidColorBrush GetSolidColorBrushFromHex(string colorHex) => string.IsNullOrEmpty(colorHex) ? new SolidColorBrush(Colors.Transparent) : new SolidColorBrush(colorHex.ToColor());
|
||||||
|
public static SolidColorBrush GetCategoryTextBrush(string textColorHex, string backgroundColorHex)
|
||||||
|
=> !string.IsNullOrWhiteSpace(textColorHex)
|
||||||
|
? GetSolidColorBrushFromHex(textColorHex)
|
||||||
|
: string.IsNullOrWhiteSpace(backgroundColorHex)
|
||||||
|
? new SolidColorBrush(Colors.Black)
|
||||||
|
: GetReadableTextColor(backgroundColorHex);
|
||||||
public static FontWeight GetFontWeightBySyncState(bool isSyncing) => isSyncing ? FontWeights.SemiBold : FontWeights.Normal;
|
public static FontWeight GetFontWeightBySyncState(bool isSyncing) => isSyncing ? FontWeights.SemiBold : FontWeights.Normal;
|
||||||
|
|
||||||
public static Brush GetWizardStepBadgeBrush(bool isActive)
|
public static Brush GetWizardStepBadgeBrush(bool isActive)
|
||||||
|
|||||||
@@ -288,6 +288,39 @@ public class MailCategoryService : BaseDatabaseService, IMailCategoryService
|
|||||||
[accountId, .. uniqueIds.Cast<object>()]).ConfigureAwait(false);
|
[accountId, .. uniqueIds.Cast<object>()]).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<Guid, IReadOnlyList<MailCategory>>> GetCategoriesByMailAsync(Guid accountId, IEnumerable<Guid> mailCopyUniqueIds)
|
||||||
|
{
|
||||||
|
var uniqueIds = mailCopyUniqueIds?.Distinct().ToList() ?? [];
|
||||||
|
if (uniqueIds.Count == 0)
|
||||||
|
return new Dictionary<Guid, IReadOnlyList<MailCategory>>();
|
||||||
|
|
||||||
|
var placeholders = string.Join(",", uniqueIds.Select(_ => "?"));
|
||||||
|
var sql =
|
||||||
|
$"SELECT {nameof(MailCategoryAssignment)}.{nameof(MailCategoryAssignment.MailCopyUniqueId)} as {nameof(MailCategoryRow.MailCopyUniqueId)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.Id)} as {nameof(MailCategoryRow.Id)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.MailAccountId)} as {nameof(MailCategoryRow.MailAccountId)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.RemoteId)} as {nameof(MailCategoryRow.RemoteId)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.Name)} as {nameof(MailCategoryRow.Name)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.IsFavorite)} as {nameof(MailCategoryRow.IsFavorite)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.BackgroundColorHex)} as {nameof(MailCategoryRow.BackgroundColorHex)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.TextColorHex)} as {nameof(MailCategoryRow.TextColorHex)}, " +
|
||||||
|
$"{nameof(MailCategory)}.{nameof(MailCategory.Source)} as {nameof(MailCategoryRow.Source)} " +
|
||||||
|
$"FROM {nameof(MailCategory)} " +
|
||||||
|
$"INNER JOIN {nameof(MailCategoryAssignment)} ON {nameof(MailCategory)}.{nameof(MailCategory.Id)} = {nameof(MailCategoryAssignment)}.{nameof(MailCategoryAssignment.MailCategoryId)} " +
|
||||||
|
$"WHERE {nameof(MailCategory)}.{nameof(MailCategory.MailAccountId)} = ? AND {nameof(MailCategoryAssignment)}.{nameof(MailCategoryAssignment.MailCopyUniqueId)} IN ({placeholders}) " +
|
||||||
|
$"ORDER BY {nameof(MailCategoryAssignment)}.{nameof(MailCategoryAssignment.MailCopyUniqueId)}, {nameof(MailCategory)}.{nameof(MailCategory.Name)} COLLATE NOCASE";
|
||||||
|
|
||||||
|
var rows = await Connection.QueryAsync<MailCategoryRow>(
|
||||||
|
sql,
|
||||||
|
[accountId, .. uniqueIds.Cast<object>()]).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return rows
|
||||||
|
.GroupBy(a => a.MailCopyUniqueId)
|
||||||
|
.ToDictionary(
|
||||||
|
a => a.Key,
|
||||||
|
a => (IReadOnlyList<MailCategory>)a.Select(static row => row.ToMailCategory()).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<Guid>> GetAssignedCategoryIdsForAllAsync(IEnumerable<Guid> mailCopyUniqueIds)
|
public async Task<List<Guid>> GetAssignedCategoryIdsForAllAsync(IEnumerable<Guid> mailCopyUniqueIds)
|
||||||
{
|
{
|
||||||
var uniqueIds = mailCopyUniqueIds?.Distinct().ToList() ?? [];
|
var uniqueIds = mailCopyUniqueIds?.Distinct().ToList() ?? [];
|
||||||
@@ -355,4 +388,21 @@ public class MailCategoryService : BaseDatabaseService, IMailCategoryService
|
|||||||
|
|
||||||
private static string NormalizeCategoryName(string name)
|
private static string NormalizeCategoryName(string name)
|
||||||
=> name?.Trim() ?? string.Empty;
|
=> name?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
|
private sealed class MailCategoryRow : MailCategory
|
||||||
|
{
|
||||||
|
public Guid MailCopyUniqueId { get; set; }
|
||||||
|
|
||||||
|
public MailCategory ToMailCategory() => new()
|
||||||
|
{
|
||||||
|
Id = Id,
|
||||||
|
MailAccountId = MailAccountId,
|
||||||
|
RemoteId = RemoteId,
|
||||||
|
Name = Name,
|
||||||
|
IsFavorite = IsFavorite,
|
||||||
|
BackgroundColorHex = BackgroundColorHex,
|
||||||
|
TextColorHex = TextColorHex,
|
||||||
|
Source = Source
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user