Add per-account folder customization page (#855)

Introduce a dedicated settings page that lets users reorder, hide,
and pin/unpin folders per account. Folders are organized into Pinned,
Categories (Gmail only), and More sections with drag-to-reorder via
ListView. New Order column on MailItemFolder persists the custom
layout; the default sort falls back to alphabetic when no custom
order is set. A reset action wipes all customization in a single
transaction and restores system-folder stickiness.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Burak Kaan Köse
2026-04-16 14:07:17 +02:00
committed by GitHub
parent 0b136b3d66
commit 98eed39fe6
17 changed files with 599 additions and 10 deletions
+1
View File
@@ -368,6 +368,7 @@ public partial class App : WinoApplication,
services.AddTransient(typeof(ImapCalDavSettingsPageViewModel));
services.AddTransient(typeof(AccountDetailsPageViewModel));
services.AddTransient(typeof(FolderCustomizationPageViewModel));
services.AddTransient(typeof(SignatureManagementPageViewModel));
services.AddTransient(typeof(MessageListPageViewModel));
services.AddTransient(typeof(MailNotificationSettingsPageViewModel));
+4
View File
@@ -274,6 +274,10 @@ public static class XamlHelpers
};
}
// Segoe Fluent icon glyphs for the show/hide toggle on the folder
// customization page. E7B3 = "Hide" (eye with slash), E7B2 = "RedEye".
public static string GetHideGlyph(bool isHidden) => isHidden ? "\uE7B3" : "\uE7B2";
public static WinoIconGlyph GetSpecialFolderPathIconGeometry(SpecialFolderType specialFolderType)
{
return specialFolderType switch
@@ -74,6 +74,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
WinoPage.ManageAccountsPage,
WinoPage.AccountManagementPage,
WinoPage.AccountDetailsPage,
WinoPage.FolderCustomizationPage,
WinoPage.MergedAccountDetailsPage,
WinoPage.SignatureManagementPage,
WinoPage.AboutPage,
@@ -136,6 +137,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
WinoPage.None => null,
WinoPage.IdlePage => typeof(IdlePage),
WinoPage.AccountDetailsPage => typeof(AccountDetailsPage),
WinoPage.FolderCustomizationPage => typeof(FolderCustomizationPage),
WinoPage.MergedAccountDetailsPage => typeof(MergedAccountDetailsPage),
WinoPage.AccountManagementPage => typeof(AccountManagementPage),
WinoPage.ManageAccountsPage => typeof(AccountManagementPage),
@@ -0,0 +1,6 @@
using Wino.Mail.WinUI;
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class FolderCustomizationPageAbstract : BasePage<FolderCustomizationPageViewModel> { }
File diff suppressed because one or more lines are too long
@@ -0,0 +1,188 @@
<abstract:FolderCustomizationPageAbstract
x:Class="Wino.Views.FolderCustomizationPage"
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:CommunityToolkit.WinUI.Controls"
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:data="using:Wino.Mail.ViewModels.Data"
xmlns:domain="using:Wino.Core.Domain"
xmlns:helpers="using:Wino.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="FolderRowTemplate" x:DataType="data:FolderCustomizationItemViewModel">
<Grid
Padding="8"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="6"
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon
Grid.Column="0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE700;" />
<coreControls:WinoFontIcon
Grid.Column="1"
VerticalAlignment="Center"
FontSize="18"
Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(SpecialFolderType)}" />
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Text="{x:Bind FolderName}"
TextTrimming="CharacterEllipsis" />
<Button
Grid.Column="3"
Click="HideToggle_Click"
Style="{StaticResource SubtleButtonStyle}"
ToolTipService.ToolTip="{x:Bind domain:Translator.FolderCustomization_Hide}">
<FontIcon FontSize="16" Glyph="{x:Bind helpers:XamlHelpers.GetHideGlyph(IsHidden), Mode=OneWay}" />
</Button>
<Button
Grid.Column="4"
Click="PinToggle_Click"
Style="{StaticResource SubtleButtonStyle}"
ToolTipService.ToolTip="{x:Bind domain:Translator.FolderCustomization_Pin}">
<FontIcon FontSize="16" Glyph="&#xE718;" />
</Button>
</Grid>
</DataTemplate>
</Page.Resources>
<ScrollViewer>
<Grid Padding="16,12" RowSpacing="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock
FontSize="20"
FontWeight="SemiBold"
Text="{x:Bind domain:Translator.FolderCustomization_Title}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind domain:Translator.FolderCustomization_Description}"
TextWrapping="Wrap" />
</StackPanel>
<Button
Grid.Column="1"
VerticalAlignment="Top"
Command="{x:Bind ViewModel.ResetCommand}"
Content="{x:Bind domain:Translator.FolderCustomization_Reset}" />
</Grid>
<Expander
Grid.Row="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontSize="16" Glyph="&#xE718;" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.FolderCustomization_SectionPinned}" />
</StackPanel>
</Expander.Header>
<ListView
AllowDrop="True"
CanReorderItems="True"
DropCompleted="ListView_DropCompleted"
ItemTemplate="{StaticResource FolderRowTemplate}"
ItemsSource="{x:Bind ViewModel.PinnedFolders}"
SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="4" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Expander>
<Expander
Grid.Row="2"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsExpanded="True"
Visibility="{x:Bind ViewModel.IsGmailAccount, Mode=OneWay}">
<Expander.Header>
<StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontSize="16" Glyph="&#xE8EC;" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.FolderCustomization_SectionCategories}" />
</StackPanel>
</Expander.Header>
<ListView
AllowDrop="True"
CanReorderItems="True"
DropCompleted="ListView_DropCompleted"
ItemTemplate="{StaticResource FolderRowTemplate}"
ItemsSource="{x:Bind ViewModel.CategoryFolders}"
SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="4" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Expander>
<Expander
Grid.Row="3"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontSize="16" Glyph="&#xE712;" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.FolderCustomization_SectionMore}" />
</StackPanel>
</Expander.Header>
<ListView
AllowDrop="True"
CanReorderItems="True"
DropCompleted="ListView_DropCompleted"
ItemTemplate="{StaticResource FolderRowTemplate}"
ItemsSource="{x:Bind ViewModel.MoreFolders}"
SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="4" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Expander>
</Grid>
</ScrollViewer>
</abstract:FolderCustomizationPageAbstract>
@@ -0,0 +1,38 @@
using System.Collections.ObjectModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Wino.Mail.ViewModels.Data;
using Wino.Views.Abstract;
namespace Wino.Views;
public sealed partial class FolderCustomizationPage : FolderCustomizationPageAbstract
{
public FolderCustomizationPage()
{
InitializeComponent();
}
private async void ListView_DropCompleted(UIElement sender, Microsoft.UI.Xaml.Controls.Primitives.DropCompletedEventArgs args)
{
// ListView.CanReorderItems automatically mutates the backing
// ObservableCollection; persist the new order here.
await ViewModel.PersistLayoutAsync();
}
private async void PinToggle_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element && element.DataContext is FolderCustomizationItemViewModel item)
{
await ViewModel.TogglePinAsync(item);
}
}
private async void HideToggle_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element && element.DataContext is FolderCustomizationItemViewModel item)
{
await ViewModel.ToggleHiddenAsync(item);
}
}
}