Add mail categories support

This commit is contained in:
Burak Kaan Köse
2026-04-15 01:18:07 +02:00
parent feff929333
commit cf8fff8ef1
61 changed files with 2171 additions and 75 deletions
@@ -0,0 +1,6 @@
using Wino.Mail.ViewModels;
using Wino.Mail.WinUI;
namespace Wino.Views.Abstract;
public abstract class MailCategoryManagementPageAbstract : BasePage<MailCategoryManagementPageViewModel> { }
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -253,7 +253,8 @@
BorderThickness="0"
Command="{x:Bind ViewModel.SyncFolderCommand}"
IsEnabled="{x:Bind ViewModel.CanSynchronize, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind domain:Translator.Buttons_Sync}">
ToolTipService.ToolTip="{x:Bind domain:Translator.Buttons_Sync}"
Visibility="{x:Bind ViewModel.IsSyncButtonVisible, Mode=OneWay}">
<coreControls:WinoFontIcon FontSize="14" Icon="Sync" />
</Button>
<ToggleButton
@@ -290,9 +291,11 @@
</StackPanel>
<InfoBar
x:Name="SyncDisabledInfoBar"
Title="{x:Bind domain:Translator.InfoBarTitle_SynchronizationDisabledFolder}"
Grid.Row="0"
Grid.ColumnSpan="3"
x:Load="{x:Bind ViewModel.IsSyncButtonVisible, Mode=OneWay}"
IsClosable="True"
IsOpen="{x:Bind ViewModel.IsFolderSynchronizationEnabled, Converter={StaticResource ReverseBooleanConverter}, Mode=OneWay}"
Message="{x:Bind domain:Translator.InfoBarMessage_SynchronizationDisabledFolder}"
+85 -11
View File
@@ -1,6 +1,6 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Collections;
@@ -18,6 +18,7 @@ using Windows.Foundation;
using Windows.System;
using Wino.Controls;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
@@ -27,7 +28,6 @@ using Wino.Mail.ViewModels.Data;
using Wino.Mail.ViewModels.Messages;
using Wino.Mail.WinUI;
using Wino.Mail.WinUI.Controls.ListView;
using Wino.Mail.WinUI.Extensions;
using Wino.Mail.WinUI.Helpers;
using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models;
@@ -246,14 +246,27 @@ public sealed partial class MailListPage : MailListPageAbstract,
// Default to all selected items.
targetItems = ViewModel.MailCollection.SelectedItems;
var availableActions = ViewModel.GetAvailableMailActions(targetItems);
var (availableCategories, assignedCategoryIds) = await ViewModel.GetAvailableCategoriesAsync(targetItems);
if (availableActions == null || !availableActions.Any()) return;
var clickedOperation = await GetMailOperationFromFlyoutAsync(availableActions, control, p.X, p.Y);
var clickedAction = await GetMailContextActionFromFlyoutAsync(
availableActions,
availableCategories,
assignedCategoryIds,
control,
p.X,
p.Y);
if (clickedOperation == null) return;
if (clickedAction == null) return;
var prepRequest = new MailOperationPreperationRequest(clickedOperation.Operation, targetItems.Select(a => a.MailCopy));
if (clickedAction.Category != null)
{
await ViewModel.ToggleCategoryAssignmentAsync(clickedAction.Category, targetItems, clickedAction.IsCategoryAssignedToAll);
return;
}
var prepRequest = new MailOperationPreperationRequest(clickedAction.Operation.Operation, targetItems.Select(a => a.MailCopy));
await ViewModel.ExecuteMailOperationAsync(prepRequest);
}
@@ -296,14 +309,68 @@ public sealed partial class MailListPage : MailListPageAbstract,
});
}
private async Task<MailOperationMenuItem> GetMailOperationFromFlyoutAsync(IEnumerable<MailOperationMenuItem> availableActions,
UIElement showAtElement,
double x,
double y)
private async Task<MailContextAction?> GetMailContextActionFromFlyoutAsync(
IEnumerable<MailOperationMenuItem> availableActions,
IReadOnlyList<MailCategory> availableCategories,
IReadOnlyCollection<Guid> assignedCategoryIds,
UIElement showAtElement,
double x,
double y)
{
var source = new TaskCompletionSource<MailOperationMenuItem>();
var source = new TaskCompletionSource<MailContextAction?>();
var flyout = new MenuFlyout();
var flyout = new MailOperationFlyout(availableActions, source);
foreach (var action in availableActions)
{
if (action.Operation == MailOperation.Seperator)
{
flyout.Items.Add(new MenuFlyoutSeparator());
continue;
}
var menuFlyoutItem = new MailOperationMenuFlyoutItem(action, clicked =>
{
source.TrySetResult(new MailContextAction(clicked));
flyout.Hide();
});
flyout.Items.Add(menuFlyoutItem);
}
if (availableCategories?.Count > 0)
{
if (flyout.Items.LastOrDefault() is not MenuFlyoutSeparator)
{
flyout.Items.Add(new MenuFlyoutSeparator());
}
var categorySubItem = new MenuFlyoutSubItem
{
Text = Translator.MailCategoryMenuItem
};
foreach (var category in availableCategories)
{
var wasAssignedToAll = assignedCategoryIds.Contains(category.Id);
var categoryItem = new ToggleMenuFlyoutItem
{
Text = category.Name,
IsChecked = wasAssignedToAll
};
categoryItem.Click += (_, _) =>
{
source.TrySetResult(new MailContextAction(category, wasAssignedToAll));
flyout.Hide();
};
categorySubItem.Items.Add(categoryItem);
}
flyout.Items.Add(categorySubItem);
}
flyout.Closing += (_, _) => source.TrySetResult(null);
flyout.ShowAt(showAtElement, new FlyoutShowOptions()
{
@@ -314,6 +381,13 @@ public sealed partial class MailListPage : MailListPageAbstract,
return await source.Task;
}
private sealed record MailContextAction(MailOperationMenuItem Operation, MailCategory Category = null, bool IsCategoryAssignedToAll = false)
{
public MailContextAction(MailCategory category, bool isCategoryAssignedToAll) : this(null, category, isCategoryAssignedToAll)
{
}
}
async void IRecipient<ClearMailSelectionsRequested>.Receive(ClearMailSelectionsRequested message)
{
await ViewModel.MailCollection.UnselectAllAsync();
@@ -0,0 +1,169 @@
<abstract:MailCategoryManagementPageAbstract
x:Class="Wino.Views.Settings.MailCategoryManagementPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Views.Abstract"
xmlns:controls="using:Wino.Controls"
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain"
xmlns:helpers="using:Wino.Helpers"
xmlns:mail="using:Wino.Core.Domain.Entities.Mail"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="root"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="MailCategoryTemplate" x:DataType="mail:MailCategory">
<Grid
Margin="0,0,0,12"
Padding="0,0,0,12"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid
VerticalAlignment="Center"
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="28"
Height="28"
VerticalAlignment="Top"
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}"
BorderBrush="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(TextColorHex), Mode=OneWay}"
BorderThickness="1"
CornerRadius="8" />
<StackPanel
Grid.Column="1"
VerticalAlignment="Center"
Spacing="2">
<TextBlock
FontWeight="SemiBold"
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(TextColorHex), Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}">
<Run Text="{x:Bind BackgroundColorHex}" />
<Run Text=" / " />
<Run Text="{x:Bind TextColorHex}" />
</TextBlock>
</StackPanel>
</Grid>
<ToggleButton
Grid.Column="1"
Checked="FavoriteCategoryChecked"
IsChecked="{x:Bind IsFavorite, Mode=OneWay}"
Tag="{x:Bind}"
Unchecked="FavoriteCategoryUnchecked">
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE208;" />
</ToggleButton>
<Button
Grid.Column="2"
Click="EditCategoryClicked"
Tag="{x:Bind}">
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE70F;" />
</Button>
<Button
Grid.Column="3"
Click="DeleteCategoryClicked"
Tag="{x:Bind}">
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE74D;" />
</Button>
<Rectangle
Grid.ColumnSpan="5"
Height="1"
Margin="0,12,0,0"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
</Grid>
</DataTemplate>
</Page.Resources>
<ListView
ItemTemplate="{StaticResource MailCategoryTemplate}"
ItemsSource="{x:Bind ViewModel.Categories, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemContainerTransitions>
<TransitionCollection>
<NavigationThemeTransition />
</TransitionCollection>
</ListView.ItemContainerTransitions>
<ListView.Header>
<Grid
Padding="16,0,24,20"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Style="{StaticResource InformationAreaGridStyle}">
<TextBlock HorizontalTextAlignment="Center" TextWrapping="WrapWholeWords">
<Run FontWeight="SemiBold" Text="{x:Bind domain:Translator.MailCategoryManagementPage_Title, Mode=OneTime}" />
<LineBreak />
<Run Text="{x:Bind domain:Translator.MailCategoryManagementPage_Description, Mode=OneTime}" />
</TextBlock>
</Grid>
<Button
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Command="{x:Bind ViewModel.RefreshCategoriesCommand}"
Visibility="{x:Bind ViewModel.CanRefresh, Mode=OneWay}">
<StackPanel Spacing="6">
<FontIcon
HorizontalAlignment="Center"
Glyph="&#xE72C;" />
<TextBlock
HorizontalAlignment="Center"
Text="{x:Bind domain:Translator.Buttons_Refresh, Mode=OneTime}" />
</StackPanel>
</Button>
<Button
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Command="{x:Bind ViewModel.AddCategoryCommand}">
<StackPanel Spacing="6">
<FontIcon
HorizontalAlignment="Center"
Glyph="&#xE710;" />
<TextBlock
HorizontalAlignment="Center"
Text="{x:Bind domain:Translator.Buttons_Add, Mode=OneTime}" />
</StackPanel>
</Button>
</Grid>
</ListView.Header>
<ListView.Footer>
<TextBlock
Margin="0,12,0,0"
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind domain:Translator.MailCategoryManagementPage_Empty, Mode=OneTime}"
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.HasCategories), Mode=OneWay}" />
</ListView.Footer>
</ListView>
</abstract:MailCategoryManagementPageAbstract>
@@ -0,0 +1,47 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Wino.Core.Domain.Entities.Mail;
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class MailCategoryManagementPage : MailCategoryManagementPageAbstract
{
public MailCategoryManagementPage()
{
InitializeComponent();
}
private async void FavoriteCategoryChecked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton toggleButton && toggleButton.Tag is MailCategory category)
{
await ViewModel.SetFavoriteAsync(category, true);
}
}
private async void FavoriteCategoryUnchecked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton toggleButton && toggleButton.Tag is MailCategory category)
{
await ViewModel.SetFavoriteAsync(category, false);
}
}
private async void EditCategoryClicked(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is MailCategory category)
{
await ViewModel.EditCategoryAsync(category);
}
}
private async void DeleteCategoryClicked(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is MailCategory category)
{
await ViewModel.DeleteCategoryAsync(category);
}
}
}
+89
View File
@@ -240,6 +240,93 @@
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<DataTemplate x:Key="MailCategoryMenuTemplate" x:DataType="menu:MailCategoryMenuItem">
<coreControls:WinoNavigationViewItem
MinHeight="40"
DataContext="{x:Bind}"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
SelectsOnInvoked="True"
ToolTipService.ToolTip="{x:Bind FolderName, Mode=OneWay}">
<muxc:NavigationViewItem.Icon>
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}"
Glyph="&#xE8EC;" />
</muxc:NavigationViewItem.Icon>
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge
Background="{StaticResource SystemAccentColor}"
Foreground="White"
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
</muxc:NavigationViewItem.InfoBadge>
<muxc:NavigationViewItem.Content>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind FolderName, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
</muxc:NavigationViewItem.Content>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<DataTemplate x:Key="MergedMailCategoryMenuTemplate" x:DataType="menu:MergedMailCategoryMenuItem">
<coreControls:WinoNavigationViewItem
MinHeight="40"
DataContext="{x:Bind}"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
SelectsOnInvoked="True"
ToolTipService.ToolTip="{x:Bind FolderName, Mode=OneWay}">
<muxc:NavigationViewItem.Icon>
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(TextColorHex), Mode=OneWay}"
Glyph="&#xE8EC;" />
</muxc:NavigationViewItem.Icon>
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge
Background="{StaticResource SystemAccentColor}"
Foreground="White"
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
</muxc:NavigationViewItem.InfoBadge>
<muxc:NavigationViewItem.Content>
<Grid
MaxHeight="36"
Padding="2"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="10"
Height="10"
Margin="0,0,8,0"
VerticalAlignment="Center"
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}"
BorderBrush="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(TextColorHex), Mode=OneWay}"
BorderThickness="1"
CornerRadius="3" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(TextColorHex), Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind FolderName, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
</Grid>
</muxc:NavigationViewItem.Content>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<!-- Merged Inbox -->
<DataTemplate x:Key="MergedAccountTemplate" x:DataType="menu:MergedAccountMenuItem">
<controls:AccountNavigationItem
@@ -393,6 +480,7 @@
<coreSelectors:NavigationMenuTemplateSelector
x:Key="NavigationMenuTemplateSelector"
CategoryItemsTemplate="{StaticResource MailCategoryMenuTemplate}"
ClickableAccountMenuTemplate="{StaticResource ClickableAccountMenuTemplate}"
FixAuthenticationIssueTemplate="{StaticResource FixAuthenticationIssueTemplate}"
FixMissingFolderConfigTemplate="{StaticResource FixMissingFolderConfig}"
@@ -400,6 +488,7 @@
MergedAccountFolderTemplate="{StaticResource MergedAccountFolderMenuItemTemplate}"
MergedAccountMoreExpansionItemTemplate="{StaticResource MergedAccountMoreFolderItemTemplate}"
MergedAccountTemplate="{StaticResource MergedAccountTemplate}"
MergedCategoryItemsTemplate="{StaticResource MergedMailCategoryMenuTemplate}"
NewMailTemplate="{StaticResource CreateNewMailTemplate}"
RatingItemTemplate="{StaticResource RatingItemTemplate}"
SeperatorTemplate="{StaticResource SeperatorTemplate}"