Render mail categories in list items

This commit is contained in:
Burak Kaan Köse
2026-04-22 01:27:48 +02:00
parent 09820dda71
commit 59505d6985
13 changed files with 254 additions and 5 deletions
@@ -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>
+6
View File
@@ -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)
+50
View File
@@ -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
};
}
} }