New shell experience.

This commit is contained in:
Burak Kaan Köse
2026-03-11 01:39:32 +01:00
parent bf331dfeb3
commit 2b523d64e8
23 changed files with 1901 additions and 68 deletions
+2
View File
@@ -142,6 +142,8 @@ private string searchQuery = string.Empty;
- For dependency properties in WinUI code, always prefer `[GeneratedDependencyProperty]` from CommunityToolkit over manual `DependencyProperty.Register(...)` declarations. - For dependency properties in WinUI code, always prefer `[GeneratedDependencyProperty]` from CommunityToolkit over manual `DependencyProperty.Register(...)` declarations.
- In ViewModels, update all UI-bound properties/collections via `ExecuteUIThread(...)` (especially after awaited calls and any use of `ConfigureAwait(false)`). - In ViewModels, update all UI-bound properties/collections via `ExecuteUIThread(...)` (especially after awaited calls and any use of `ConfigureAwait(false)`).
- In `EventDetailsPageViewModel.LoadAttendeesAsync`, never mutate `CurrentEvent.Attendees` outside `ExecuteUIThread(...)`. - In `EventDetailsPageViewModel.LoadAttendeesAsync`, never mutate `CurrentEvent.Attendees` outside `ExecuteUIThread(...)`.
- Never create pure C# controls or controls that heavily manipulate UI structure from `.cs` files. Define controls in XAML and keep UI composition in XAML.
- For XAML-backed controls and pages, wire framework events like `Loaded`, `Unloaded`, and input events from XAML, not by subscribing in constructors in `.xaml.cs`.
@@ -30,6 +30,7 @@ using Wino.Messaging.UI;
namespace Wino.Calendar.ViewModels; namespace Wino.Calendar.ViewModels;
public partial class CalendarAppShellViewModel : CalendarBaseViewModel, public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
ICalendarShellClient,
IRecipient<VisibleDateRangeChangedMessage>, IRecipient<VisibleDateRangeChangedMessage>,
IRecipient<CalendarEnableStatusChangedMessage>, IRecipient<CalendarEnableStatusChangedMessage>,
IRecipient<NavigateManageAccountsRequested>, IRecipient<NavigateManageAccountsRequested>,
@@ -40,6 +41,17 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
public IStatePersistanceService StatePersistenceService { get; } public IStatePersistanceService StatePersistenceService { get; }
public IAccountCalendarStateService AccountCalendarStateService { get; } public IAccountCalendarStateService AccountCalendarStateService { get; }
public INavigationService NavigationService { get; } public INavigationService NavigationService { get; }
public WinoApplicationMode Mode => WinoApplicationMode.Calendar;
public bool HandlesNavigationSelection => false;
System.Collections.IEnumerable ICalendarShellClient.GroupedAccountCalendars => AccountCalendarStateService.GroupedAccountCalendars;
System.Collections.IEnumerable ICalendarShellClient.DateNavigationHeaderItems => DateNavigationHeaderItems;
object IShellClient.SelectedMenuItem
{
get => null;
set { }
}
System.Windows.Input.ICommand ICalendarShellClient.TodayClickedCommand => TodayClickedCommand;
System.Windows.Input.ICommand ICalendarShellClient.DateClickedCommand => DateClickedCommand;
public MenuItemCollection MenuItems { get; private set; } public MenuItemCollection MenuItems { get; private set; }
public MenuItemCollection FooterItems { get; private set; } public MenuItemCollection FooterItems { get; private set; }
@@ -78,7 +90,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
private readonly ManageAccountsMenuItem _manageAccountsMenuItem = new(); private readonly ManageAccountsMenuItem _manageAccountsMenuItem = new();
private readonly SettingsItem _settingsItem = new(); private readonly SettingsItem _settingsItem = new();
private readonly StoreUpdateMenuItem _storeUpdateMenuItem = new(); private readonly StoreUpdateMenuItem _storeUpdateMenuItem = new();
private readonly NewMailMenuItem _newEventMenuItem = new(); private readonly NewCalendarEventMenuItem _newEventMenuItem = new();
// For updating account calendars asynchronously. // For updating account calendars asynchronously.
private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1); private SemaphoreSlim _accountCalendarUpdateSemaphoreSlim = new(1);
@@ -146,6 +158,10 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
{ {
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
var activationContext = parameters as ShellModeActivationContext;
var isModeResetActivation = activationContext != null;
var shouldRunStartupFlows = activationContext?.IsInitialActivation ?? true;
PreferencesService.PreferenceChanged -= PreferencesServiceChanged; PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
PreferencesService.PreferenceChanged += PreferencesServiceChanged; PreferencesService.PreferenceChanged += PreferencesServiceChanged;
@@ -155,7 +171,7 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
// between Mail and Calendar modes. Back/forward restoration should not // between Mail and Calendar modes. Back/forward restoration should not
// force a new CalendarPage navigation, otherwise pages like // force a new CalendarPage navigation, otherwise pages like
// CalendarEventComposePage get dropped from the inner frame stack. // CalendarEventComposePage get dropped from the inner frame stack.
if (mode != NavigationMode.New) if (mode != NavigationMode.New && !isModeResetActivation)
{ {
UpdateDateNavigationHeaderItems(); UpdateDateNavigationHeaderItems();
await InitializeAccountCalendarsAsync(); await InitializeAccountCalendarsAsync();
@@ -168,7 +184,10 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
await InitializeAccountCalendarsAsync(); await InitializeAccountCalendarsAsync();
ValidateConfiguredNewEventCalendar(); ValidateConfiguredNewEventCalendar();
await ShowWhatIsNewIfNeededAsync(); if (shouldRunStartupFlows)
{
await ShowWhatIsNewIfNeededAsync();
}
TodayClicked(); TodayClicked();
} }
@@ -601,6 +620,18 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
return (startDate, startDate.AddMinutes(30)); return (startDate, startDate.AddMinutes(30));
} }
void IShellClient.Activate(ShellModeActivationContext activationContext)
=> OnNavigatedTo(NavigationMode.New, activationContext);
void IShellClient.Deactivate()
=> OnNavigatedFrom(NavigationMode.New, null!);
Task IShellClient.HandleNavigationItemInvokedAsync(IMenuItem menuItem)
=> menuItem == null ? Task.CompletedTask : HandleNavigationItemInvokedAsync(menuItem);
Task IShellClient.HandleNavigationSelectionChangedAsync(IMenuItem menuItem)
=> Task.CompletedTask;
} }
@@ -0,0 +1,73 @@
#nullable enable
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.MenuItems;
using Wino.Core.Domain.Models;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.Navigation;
namespace Wino.Core.Domain.Interfaces;
public interface IShellClient : INotifyPropertyChanged
{
WinoApplicationMode Mode { get; }
IDispatcher Dispatcher { get; set; }
MenuItemCollection? MenuItems { get; }
object? SelectedMenuItem { get; set; }
bool HandlesNavigationSelection { get; }
void Activate(ShellModeActivationContext activationContext);
void Deactivate();
Task HandleNavigationItemInvokedAsync(IMenuItem? menuItem);
Task HandleNavigationSelectionChangedAsync(IMenuItem? menuItem);
Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args);
}
public interface IMailShellClient : IShellClient
{
IMenuItem CreatePrimaryMenuItem { get; }
IEnumerable<FolderOperationMenuItem> GetFolderContextMenuActions(IBaseFolderMenuItem folder);
Task NavigateFolderAsync(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool>? folderInitAwaitTask = null);
Task ChangeLoadedAccountAsync(IAccountMenuItem clickedBaseAccountMenuItem, bool navigateInbox = true);
Task PerformFolderOperationAsync(FolderOperation operation, IBaseFolderMenuItem folderMenuItem);
Task PerformMoveOperationAsync(IEnumerable<MailCopy> items, IBaseFolderMenuItem targetFolderMenuItem);
Task CreateNewMailForAsync(MailAccount account);
}
public interface ICalendarShellClient : IShellClient
{
IStatePersistanceService StatePersistenceService { get; }
IEnumerable DateNavigationHeaderItems { get; }
int SelectedDateNavigationHeaderIndex { get; }
DateRange? HighlightedDateRange { get; }
ICommand TodayClickedCommand { get; }
ICommand DateClickedCommand { get; }
IEnumerable GroupedAccountCalendars { get; }
}
public interface IShellViewModel
{
WinoApplicationMode CurrentMode { get; }
IShellClient CurrentClient { get; }
MenuItemCollection? CurrentMenuItems { get; }
object? SelectedMenuItem { get; set; }
void SetCurrentMode(WinoApplicationMode mode);
IShellClient GetClient(WinoApplicationMode mode);
}
public interface IShellHost
{
bool HasShellContent { get; }
void ActivateMode(WinoApplicationMode mode, bool isInitialActivation);
}
@@ -0,0 +1,3 @@
namespace Wino.Core.Domain.MenuItems;
public sealed class NewCalendarEventMenuItem : NewMailMenuItem { }
@@ -0,0 +1,6 @@
namespace Wino.Core.Domain.Models.Navigation;
public sealed class ShellModeActivationContext
{
public bool IsInitialActivation { get; init; }
}
@@ -952,7 +952,6 @@
"SystemFolderConfigSetupSuccess_Title": "System Folders Setup", "SystemFolderConfigSetupSuccess_Title": "System Folders Setup",
"SystemTrayMenu_ShowWino": "Open Wino Mail", "SystemTrayMenu_ShowWino": "Open Wino Mail",
"SystemTrayMenu_ShowWinoCalendar": "Open Wino Calendar", "SystemTrayMenu_ShowWinoCalendar": "Open Wino Calendar",
"SystemTrayMenu_ShowWinoContacts": "Open Wino Contacts",
"SystemTrayMenu_ExitWino": "Exit", "SystemTrayMenu_ExitWino": "Exit",
"TestingImapConnectionMessage": "Testing server connection...", "TestingImapConnectionMessage": "Testing server connection...",
"TitleBarServerDisconnectedButton_Description": "Wino is disconnected from the network. Click reconnect to restore connection.", "TitleBarServerDisconnectedButton_Description": "Wino is disconnected from the network. Click reconnect to restore connection.",
+27 -4
View File
@@ -29,6 +29,7 @@ using Wino.Messaging.UI;
namespace Wino.Mail.ViewModels; namespace Wino.Mail.ViewModels;
public partial class MailAppShellViewModel : MailBaseViewModel, public partial class MailAppShellViewModel : MailBaseViewModel,
IMailShellClient,
IRecipient<NavigateManageAccountsRequested>, IRecipient<NavigateManageAccountsRequested>,
IRecipient<MailtoProtocolMessageRequested>, IRecipient<MailtoProtocolMessageRequested>,
IRecipient<RefreshUnreadCountsMessage>, IRecipient<RefreshUnreadCountsMessage>,
@@ -67,6 +68,9 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
public IStatePersistanceService StatePersistenceService { get; } public IStatePersistanceService StatePersistenceService { get; }
public IPreferencesService PreferencesService { get; } public IPreferencesService PreferencesService { get; }
public INavigationService NavigationService { get; } public INavigationService NavigationService { get; }
public WinoApplicationMode Mode => WinoApplicationMode.Mail;
public bool HandlesNavigationSelection => true;
public IMenuItem CreatePrimaryMenuItem => CreateMailMenuItem;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IConfigurationService _configurationService; private readonly IConfigurationService _configurationService;
@@ -227,10 +231,14 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
{ {
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
var activationContext = parameters as ShellModeActivationContext;
var isModeResetActivation = activationContext != null;
var shouldRunStartupFlows = activationContext?.IsInitialActivation ?? true;
PreferencesService.PreferenceChanged -= PreferencesServiceChanged; PreferencesService.PreferenceChanged -= PreferencesServiceChanged;
PreferencesService.PreferenceChanged += PreferencesServiceChanged; PreferencesService.PreferenceChanged += PreferencesServiceChanged;
if (mode == NavigationMode.Back) if (mode == NavigationMode.Back && !isModeResetActivation)
{ {
// Preserve current mail/folder selection and active rendering page when // Preserve current mail/folder selection and active rendering page when
// switching back from Calendar mode. Recreating menu/folder state here // switching back from Calendar mode. Recreating menu/folder state here
@@ -252,13 +260,16 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
await ProcessLaunchOptionsAsync(); await ProcessLaunchOptionsAsync();
await ValidateWebView2RuntimeAsync(); await ValidateWebView2RuntimeAsync();
if (!Debugger.IsAttached) if (shouldRunStartupFlows && !Debugger.IsAttached)
{ {
await ForceAllAccountSynchronizationsAsync(); await ForceAllAccountSynchronizationsAsync();
} }
await ShowWhatIsNewIfNeededAsync(); if (shouldRunStartupFlows)
await MakeSureEnableStartupLaunchAsync(); {
await ShowWhatIsNewIfNeededAsync();
await MakeSureEnableStartupLaunchAsync();
}
} }
private async Task ValidateWebView2RuntimeAsync() private async Task ValidateWebView2RuntimeAsync()
@@ -1258,6 +1269,18 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
} }
}); });
} }
void IShellClient.Activate(ShellModeActivationContext activationContext)
=> OnNavigatedTo(NavigationMode.New, activationContext);
void IShellClient.Deactivate()
=> OnNavigatedFrom(NavigationMode.New, null!);
Task IShellClient.HandleNavigationItemInvokedAsync(IMenuItem menuItem)
=> MenuItemInvokedOrSelectedAsync(menuItem);
Task IShellClient.HandleNavigationSelectionChangedAsync(IMenuItem menuItem)
=> menuItem == null ? Task.CompletedTask : MenuItemInvokedOrSelectedAsync(menuItem);
} }
+97 -1
View File
@@ -3,6 +3,8 @@
x:Class="Wino.Mail.WinUI.App" x:Class="Wino.Mail.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
xmlns:coreStyles="using:Wino.Mail.WinUI.Styles"
xmlns:local="using:Wino.Mail.WinUI" xmlns:local="using:Wino.Mail.WinUI"
xmlns:styles="using:Wino.Styles" xmlns:styles="using:Wino.Styles"
xmlns:uwp="using:Wino.Mail.WinUI"> xmlns:uwp="using:Wino.Mail.WinUI">
@@ -10,7 +12,101 @@
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<uwp:CoreGeneric /> <ResourceDictionary Source="/Styles/Colors.xaml" />
<ResourceDictionary Source="/Styles/ContentPresenters.xaml" />
<ResourceDictionary Source="/Styles/Converters.xaml" />
<ResourceDictionary Source="/Styles/FontIcons.xaml" />
<ResourceDictionary Source="/Styles/WinoInfoBar.xaml" />
<ResourceDictionary Source="/Styles/SharedStyles.xaml" />
<ResourceDictionary Source="/Styles/IconTemplates.xaml" />
<coreStyles:CustomMessageDialogStyles />
<coreStyles:DataTemplates />
<ResourceDictionary>
<Style TargetType="ScrollViewer">
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
</Style>
<SolidColorBrush x:Key="CommandBarBackground" Color="Transparent" />
<SolidColorBrush x:Key="CommandBarBackgroundOpen" Color="Transparent" />
<SolidColorBrush x:Key="CommandBarBorderBrushOpen" Color="Transparent" />
<Thickness x:Key="CommandBarBorderThicknessOpen">0</Thickness>
<StaticResource x:Key="AppBarToggleButtonBackgroundChecked" ResourceKey="SystemAccentColor" />
<StaticResource x:Key="AppBarToggleButtonBackgroundCheckedPointerOver" ResourceKey="SystemAccentColor" />
<StaticResource x:Key="AppBarToggleButtonBackgroundCheckedPressed" ResourceKey="SystemAccentColor" />
<Thickness x:Key="ImapSetupDialogSubPagePadding">24,24,24,24</Thickness>
<Style x:Key="PageRootBorderStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource WinoContentZoneBackgroud}" />
<Setter Property="BorderBrush" Value="{StaticResource CardStrokeColorDefaultBrush}" />
<Setter Property="CornerRadius" Value="7" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style x:Key="InformationAreaGridStyle" TargetType="Grid">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="16" />
</Style>
<Style TargetType="StackPanel">
<Setter Property="ChildrenTransitions">
<Setter.Value>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="WinoDialogStyle"
BasedOn="{StaticResource DefaultContentDialogStyle}"
TargetType="ContentDialog">
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="24" />
</Style>
<Style TargetType="coreControls:WinoNavigationViewItem">
<Setter Property="ContentTransitions">
<Setter.Value>
<TransitionCollection>
<PopupThemeTransition />
</TransitionCollection>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="PageStyle" TargetType="Page">
<Setter Property="Margin" Value="-1,0,0,0" />
<Setter Property="Padding" Value="12" />
<Setter Property="Background" Value="{ThemeResource AppBarBackgroundColor}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Padding="12">
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
<Style
x:Key="SettingsSectionHeaderTextBlockStyle"
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
TargetType="TextBlock">
<Style.Setters>
<Setter Property="Margin" Value="1,30,0,6" />
</Style.Setters>
</Style>
</ResourceDictionary>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" /> <ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
<ResourceDictionary Source="Controls/ListView/WinoListViewStyles.xaml" /> <ResourceDictionary Source="Controls/ListView/WinoListViewStyles.xaml" />
<ResourceDictionary Source="Styles/ItemContainerStyles.xaml" /> <ResourceDictionary Source="Styles/ItemContainerStyles.xaml" />
+8
View File
@@ -24,6 +24,7 @@ using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Synchronization; using Wino.Core.Domain.Models.Synchronization;
using Wino.Mail.Services; using Wino.Mail.Services;
using Wino.Mail.WinUI.ViewModels;
using Wino.Mail.ViewModels; using Wino.Mail.ViewModels;
using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI.Activation; using Wino.Mail.WinUI.Activation;
@@ -142,6 +143,13 @@ public partial class App : WinoApplication,
{ {
services.AddSingleton(typeof(MailAppShellViewModel)); services.AddSingleton(typeof(MailAppShellViewModel));
services.AddSingleton(typeof(CalendarAppShellViewModel)); services.AddSingleton(typeof(CalendarAppShellViewModel));
services.AddSingleton(typeof(ContactsShellClient));
services.AddSingleton(typeof(WinoAppShellViewModel));
services.AddSingleton<IMailShellClient>(serviceProvider => serviceProvider.GetRequiredService<MailAppShellViewModel>());
services.AddSingleton<ICalendarShellClient>(serviceProvider => serviceProvider.GetRequiredService<CalendarAppShellViewModel>());
services.AddSingleton<IShellClient>(serviceProvider => serviceProvider.GetRequiredService<MailAppShellViewModel>());
services.AddSingleton<IShellClient>(serviceProvider => serviceProvider.GetRequiredService<CalendarAppShellViewModel>());
services.AddSingleton<IShellClient>(serviceProvider => serviceProvider.GetRequiredService<ContactsShellClient>());
services.AddTransient(typeof(MailListPageViewModel)); services.AddTransient(typeof(MailListPageViewModel));
services.AddTransient(typeof(MailRenderingPageViewModel)); services.AddTransient(typeof(MailRenderingPageViewModel));
@@ -3,7 +3,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:domain="using:Wino.Core.Domain"> xmlns:domain="using:Wino.Core.Domain"
Loaded="ControlLoaded"
Unloaded="ControlUnloaded">
<Grid> <Grid>
<controls:Segmented <controls:Segmented
@@ -19,9 +19,6 @@ public sealed partial class AppModeFooterSwitcherControl : UserControl
_navigationService = WinoApplication.Current.Services.GetRequiredService<INavigationService>(); _navigationService = WinoApplication.Current.Services.GetRequiredService<INavigationService>();
InitializeComponent(); InitializeComponent();
Loaded += ControlLoaded;
Unloaded += ControlUnloaded;
} }
private void ControlLoaded(object sender, RoutedEventArgs e) private void ControlLoaded(object sender, RoutedEventArgs e)
@@ -34,7 +31,6 @@ public sealed partial class AppModeFooterSwitcherControl : UserControl
{ {
_statePersistenceService.StatePropertyChanged -= StatePropertyChanged; _statePersistenceService.StatePropertyChanged -= StatePropertyChanged;
} }
private void StatePropertyChanged(object? sender, string propertyName) private void StatePropertyChanged(object? sender, string propertyName)
{ {
if (propertyName != nameof(IStatePersistanceService.ApplicationMode)) if (propertyName != nameof(IStatePersistanceService.ApplicationMode))
+7 -7
View File
@@ -9,13 +9,13 @@
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="Styles/Colors.xaml" /> <ResourceDictionary Source="/Styles/Colors.xaml" />
<ResourceDictionary Source="Styles/ContentPresenters.xaml" /> <ResourceDictionary Source="/Styles/ContentPresenters.xaml" />
<ResourceDictionary Source="Styles/Converters.xaml" /> <ResourceDictionary Source="/Styles/Converters.xaml" />
<ResourceDictionary Source="Styles/FontIcons.xaml" /> <ResourceDictionary Source="/Styles/FontIcons.xaml" />
<ResourceDictionary Source="Styles/WinoInfoBar.xaml" /> <ResourceDictionary Source="/Styles/WinoInfoBar.xaml" />
<ResourceDictionary Source="Styles/SharedStyles.xaml" /> <ResourceDictionary Source="/Styles/SharedStyles.xaml" />
<ResourceDictionary Source="Styles/IconTemplates.xaml" /> <ResourceDictionary Source="/Styles/IconTemplates.xaml" />
<styles:CustomMessageDialogStyles /> <styles:CustomMessageDialogStyles />
<styles:DataTemplates /> <styles:DataTemplates />
@@ -2,7 +2,8 @@
"profiles": { "profiles": {
"Wino.Mail.WinUI (Package)": { "Wino.Mail.WinUI (Package)": {
"commandName": "MsixPackage", "commandName": "MsixPackage",
"doNotLaunchApp": false "doNotLaunchApp": false,
"nativeDebugging": true
}, },
"Wino.Mail.WinUI (Unpackaged)": { "Wino.Mail.WinUI (Unpackaged)": {
"commandName": "Project" "commandName": "Project"
@@ -21,13 +21,16 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector
public DataTemplate CreateNewFolderTemplate { get; set; } = null!; public DataTemplate CreateNewFolderTemplate { get; set; } = null!;
public DataTemplate SeperatorTemplate { get; set; } = null!; public DataTemplate SeperatorTemplate { get; set; } = null!;
public DataTemplate NewMailTemplate { get; set; } = null!; public DataTemplate NewMailTemplate { get; set; } = null!;
public DataTemplate CalendarNewEventTemplate { get; set; } = null!;
public DataTemplate CategoryItemsTemplate { get; set; } = null!; public DataTemplate CategoryItemsTemplate { get; set; } = null!;
public DataTemplate FixAuthenticationIssueTemplate { get; set; } = null!; public DataTemplate FixAuthenticationIssueTemplate { get; set; } = null!;
public DataTemplate FixMissingFolderConfigTemplate { get; set; } = null!; public DataTemplate FixMissingFolderConfigTemplate { get; set; } = null!;
protected override DataTemplate SelectTemplateCore(object item) protected override DataTemplate SelectTemplateCore(object item)
{ {
if (item is NewMailMenuItem) if (item is NewCalendarEventMenuItem)
return CalendarNewEventTemplate;
else if (item is NewMailMenuItem)
return NewMailTemplate; return NewMailTemplate;
else if (item is ContactsMenuItem) else if (item is ContactsMenuItem)
return ContactsMenuItemTemplate; return ContactsMenuItemTemplate;
+38 -34
View File
@@ -15,11 +15,11 @@ using Wino.Mail.WinUI;
using Wino.Mail.WinUI.Interfaces; using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Models; using Wino.Mail.WinUI.Models;
using Wino.Mail.WinUI.Services; using Wino.Mail.WinUI.Services;
using Wino.Mail.WinUI.Views;
using Wino.Mail.WinUI.Views.Calendar; using Wino.Mail.WinUI.Views.Calendar;
using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Mails; using Wino.Messaging.Client.Mails;
using Wino.Messaging.Client.Navigation; using Wino.Messaging.Client.Navigation;
using Wino.Mail.WinUI.Views.Contacts;
using Wino.Views; using Wino.Views;
using Wino.Views.Account; using Wino.Views.Account;
using Wino.Views.Mail; using Wino.Views.Mail;
@@ -33,6 +33,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
private readonly IStatePersistanceService _statePersistanceService; private readonly IStatePersistanceService _statePersistanceService;
private readonly IDispatcher _dispatcher; private readonly IDispatcher _dispatcher;
private readonly IWinoWindowManager _windowManager; private readonly IWinoWindowManager _windowManager;
private NavigationTransitionInfo? _pendingInnerShellTransition;
private WinoPage[] _renderingPageTypes = new WinoPage[] private WinoPage[] _renderingPageTypes = new WinoPage[]
{ {
@@ -192,48 +193,33 @@ public class NavigationService : NavigationServiceBase, INavigationService
if (coreFrame == null) return false; if (coreFrame == null) return false;
var currentMode = _statePersistanceService.ApplicationMode; var currentMode = _statePersistanceService.ApplicationMode;
var isInitialShellNavigation = coreFrame.Content is not IShellHost;
// Update the application mode in state persistence service // Update the application mode in state persistence service
_statePersistanceService.ApplicationMode = mode; _statePersistanceService.ApplicationMode = mode;
_statePersistanceService.AppModeTitle = GetApplicationModeTitle(mode); _statePersistanceService.AppModeTitle = GetApplicationModeTitle(mode);
var targetPageType = mode switch if (coreFrame.Content is IShellHost activeShell && activeShell.HasShellContent && currentMode == mode)
{
WinoApplicationMode.Calendar => typeof(CalendarAppShell),
WinoApplicationMode.Contacts => typeof(ContactsAppShell),
_ => typeof(MailAppShell)
};
var currentPageType = coreFrame.Content?.GetType();
var transitionInfo = GetApplicationModeTransitionInfo(currentMode, mode);
// If already on the target page, do nothing
if (currentPageType == targetPageType)
return true; return true;
// Check if we can go back to the target page _pendingInnerShellTransition = isInitialShellNavigation
if (coreFrame.CanGoBack && coreFrame.BackStack.Count > 0) ? null
: GetApplicationModeTransitionInfo(currentMode, mode);
if (coreFrame.Content is not IShellHost)
{ {
var previousPage = coreFrame.BackStack[coreFrame.BackStack.Count - 1]; coreFrame.BackStack.Clear();
if (previousPage.SourcePageType == targetPageType) coreFrame.ForwardStack.Clear();
{ coreFrame.Navigate(typeof(WinoAppShell), null, new SuppressNavigationTransitionInfo());
coreFrame.GoBack(transitionInfo);
return true;
}
} }
// Check if we can go forward to the target page if (coreFrame.Content is IShellHost shell)
if (coreFrame.CanGoForward && coreFrame.ForwardStack.Count > 0)
{ {
var nextPage = coreFrame.ForwardStack[coreFrame.ForwardStack.Count - 1]; shell.ActivateMode(mode, isInitialShellNavigation);
if (nextPage.SourcePageType == targetPageType) return true;
{
coreFrame.GoForward();
return true;
}
} }
// Navigate to the target page only if it's not in the navigation stack _pendingInnerShellTransition = null;
coreFrame.Navigate(targetPageType, null, transitionInfo);
return true; return true;
} }
@@ -300,7 +286,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
} }
} }
return innerShellFrame.Navigate(pageType, parameter); return NavigateInnerShellFrame(innerShellFrame, pageType, parameter, transition);
} }
else else
{ {
@@ -318,14 +304,14 @@ public class NavigationService : NavigationServiceBase, INavigationService
return true; return true;
} }
var transitionInfo = GetNavigationTransitionInfo(transition);
// This page must be opened in the Frame placed in MailListingPage. // This page must be opened in the Frame placed in MailListingPage.
if (isMailListingPageActive && frame == NavigationReferenceFrame.RenderingFrame) if (isMailListingPageActive && frame == NavigationReferenceFrame.RenderingFrame)
{ {
var listingFrame = GetCoreFrameInternal(NavigationReferenceFrame.RenderingFrame); var listingFrame = GetCoreFrameInternal(NavigationReferenceFrame.RenderingFrame);
if (listingFrame == null) return false; if (listingFrame == null) return false;
var transitionInfo = GetNavigationTransitionInfo(transition);
// Active page is mail list page and we are opening a mail item. // Active page is mail list page and we are opening a mail item.
// No navigation needed, just refresh the rendered mail item. // No navigation needed, just refresh the rendered mail item.
if (listingFrame.Content != null if (listingFrame.Content != null
@@ -361,7 +347,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
if ((currentFrameType != null && currentFrameType != pageType) || currentFrameType == null) if ((currentFrameType != null && currentFrameType != pageType) || currentFrameType == null)
{ {
return innerShellFrame.Navigate(pageType, parameter, transitionInfo); return NavigateInnerShellFrame(innerShellFrame, pageType, parameter, transition);
} }
} }
} }
@@ -429,6 +415,24 @@ public class NavigationService : NavigationServiceBase, INavigationService
return new LoadCalendarMessage(targetDate, initiative); return new LoadCalendarMessage(targetDate, initiative);
} }
private bool NavigateInnerShellFrame(Frame frame, Type pageType, object? parameter, NavigationTransitionType transition)
{
var transitionInfo = ConsumeInnerShellTransitionOrDefault(transition);
return frame.Navigate(pageType, parameter, transitionInfo);
}
private NavigationTransitionInfo ConsumeInnerShellTransitionOrDefault(NavigationTransitionType transition)
{
if (_pendingInnerShellTransition != null)
{
var transitionInfo = _pendingInnerShellTransition;
_pendingInnerShellTransition = null;
return transitionInfo;
}
return GetNavigationTransitionInfo(transition);
}
public void GoBack(Core.Domain.Enums.NavigationTransitionEffect slideEffect = Core.Domain.Enums.NavigationTransitionEffect.FromRight) public void GoBack(Core.Domain.Enums.NavigationTransitionEffect slideEffect = Core.Domain.Enums.NavigationTransitionEffect.FromRight)
=> ExecuteOnNavigationThread(() => GoBackInternal(slideEffect)); => ExecuteOnNavigationThread(() => GoBackInternal(slideEffect));
+1 -1
View File
@@ -116,7 +116,7 @@
<MenuFlyout AreOpenCloseAnimationsEnabled="False"> <MenuFlyout AreOpenCloseAnimationsEnabled="False">
<MenuFlyoutItem Command="{x:Bind ShowWinoCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ShowWino}" /> <MenuFlyoutItem Command="{x:Bind ShowWinoCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ShowWino}" />
<MenuFlyoutItem Command="{x:Bind ShowWinoCalendarCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ShowWinoCalendar}" /> <MenuFlyoutItem Command="{x:Bind ShowWinoCalendarCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ShowWinoCalendar}" />
<MenuFlyoutItem Command="{x:Bind ShowWinoContactsCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ShowWinoContacts}" /> <!--<MenuFlyoutItem Command="{x:Bind ShowWinoContactsCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ShowWinoContacts}" />-->
<MenuFlyoutSeparator /> <MenuFlyoutSeparator />
<MenuFlyoutItem Command="{x:Bind ExitWinoCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ExitWino}" /> <MenuFlyoutItem Command="{x:Bind ExitWinoCommand}" Text="{x:Bind domain:Translator.SystemTrayMenu_ExitWino}" />
</MenuFlyout> </MenuFlyout>
+4 -9
View File
@@ -16,9 +16,9 @@ using Wino.Core.Domain.Models.Synchronization;
using Wino.Extensions; using Wino.Extensions;
using Wino.Mail.WinUI.Activation; using Wino.Mail.WinUI.Activation;
using Wino.Mail.WinUI.Interfaces; using Wino.Mail.WinUI.Interfaces;
using Wino.Mail.WinUI.Views;
using Wino.Messaging.Client.Shell; using Wino.Messaging.Client.Shell;
using Wino.Messaging.UI; using Wino.Messaging.UI;
using Wino.Views;
using WinUIEx; using WinUIEx;
namespace Wino.Mail.WinUI; namespace Wino.Mail.WinUI;
@@ -139,12 +139,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
_ = StartCalendarReminderServerAsync(); _ = StartCalendarReminderServerAsync();
} }
// Mail shell has shell content only for mail list page if (e.Content is BasePage basePage)
// Thus, we check if the current content is MailAppShell
if (sender is Frame mainFrame && mainFrame.Content is MailAppShell mailAppShellPage)
ShellTitleBar.Content = mailAppShellPage.TopShellContent;
else if (e.Content is BasePage basePage)
ShellTitleBar.Content = basePage.ShellContent; ShellTitleBar.Content = basePage.ShellContent;
} }
@@ -172,9 +167,9 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
public void Receive(TitleBarShellContentUpdated message) public void Receive(TitleBarShellContentUpdated message)
{ {
if (MainShellFrame.Content is MailAppShell shellPage) if (MainShellFrame.Content is WinoAppShell shellPage)
{ {
ShellTitleBar.Content = shellPage.TopShellContent; ShellTitleBar.Content = shellPage.ShellContent;
} }
} }
@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Wino.Core.Domain;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.MenuItems;
using Wino.Core.Domain.Models;
using Wino.Core.Domain.Models.Navigation;
using Wino.Core.ViewModels;
namespace Wino.Mail.WinUI.ViewModels;
public sealed class ContactsShellClient(INavigationService navigationService) : CoreBaseViewModel, IShellClient
{
public WinoApplicationMode Mode => WinoApplicationMode.Contacts;
public MenuItemCollection? MenuItems { get; private set; }
public object? SelectedMenuItem { get; set; }
public bool HandlesNavigationSelection => false;
protected override void OnDispatcherAssigned()
{
base.OnDispatcherAssigned();
MenuItems ??= new MenuItemCollection(Dispatcher);
}
public void Activate(ShellModeActivationContext activationContext)
{
OnNavigatedTo(NavigationMode.New, activationContext);
navigationService.Navigate(WinoPage.ContactsPage, null, NavigationReferenceFrame.InnerShellFrame);
}
public void Deactivate()
{
OnNavigatedFrom(NavigationMode.New, null!);
}
public Task HandleNavigationItemInvokedAsync(IMenuItem? menuItem) => Task.CompletedTask;
public Task HandleNavigationSelectionChangedAsync(IMenuItem? menuItem) => Task.CompletedTask;
}
@@ -0,0 +1,131 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.MenuItems;
using Wino.Core.ViewModels;
namespace Wino.Mail.WinUI.ViewModels;
public sealed class WinoAppShellViewModel : CoreBaseViewModel, IShellViewModel
{
private readonly Dictionary<WinoApplicationMode, IShellClient> _shellClients;
private WinoApplicationMode _currentMode;
public WinoAppShellViewModel(IMailShellClient mailClient,
ICalendarShellClient calendarClient,
IEnumerable<IShellClient> shellClients,
IPreferencesService preferencesService,
IStatePersistanceService statePersistenceService,
INavigationService navigationService)
{
MailClient = mailClient;
CalendarClient = calendarClient;
PreferencesService = preferencesService;
StatePersistenceService = statePersistenceService;
NavigationService = navigationService;
_shellClients = shellClients.ToDictionary(client => client.Mode);
foreach (var client in _shellClients.Values)
{
client.PropertyChanged += ChildPropertyChanged;
}
StatePersistenceService.StatePropertyChanged += StatePersistenceServiceChanged;
}
public IMailShellClient MailClient { get; }
public ICalendarShellClient CalendarClient { get; }
public IPreferencesService PreferencesService { get; }
public IStatePersistanceService StatePersistenceService { get; }
public INavigationService NavigationService { get; }
public WinoApplicationMode CurrentMode
{
get => _currentMode;
private set
{
if (SetProperty(ref _currentMode, value))
{
OnPropertyChanged(nameof(CurrentClient));
OnPropertyChanged(nameof(CurrentMenuItems));
OnPropertyChanged(nameof(IsMailMode));
OnPropertyChanged(nameof(IsCalendarMode));
OnPropertyChanged(nameof(IsContactsMode));
OnPropertyChanged(nameof(SelectedMenuItem));
}
}
}
public IShellClient CurrentClient => GetClient(CurrentMode);
public bool IsMailMode => CurrentMode == WinoApplicationMode.Mail;
public bool IsCalendarMode => CurrentMode == WinoApplicationMode.Calendar;
public bool IsContactsMode => CurrentMode == WinoApplicationMode.Contacts;
public MenuItemCollection? CurrentMenuItems => CurrentClient.MenuItems;
public object? SelectedMenuItem
{
get => CurrentClient.SelectedMenuItem;
set
{
if (!ReferenceEquals(CurrentClient.SelectedMenuItem, value))
{
CurrentClient.SelectedMenuItem = value;
OnPropertyChanged();
}
}
}
protected override void OnDispatcherAssigned()
{
base.OnDispatcherAssigned();
foreach (var client in _shellClients.Values)
{
client.Dispatcher = Dispatcher;
}
OnPropertyChanged(nameof(CurrentMenuItems));
}
public override void OnNavigatedTo(Core.Domain.Models.Navigation.NavigationMode mode, object parameters)
{
base.OnNavigatedTo(mode, parameters);
CurrentMode = StatePersistenceService.ApplicationMode;
}
public IShellClient GetClient(WinoApplicationMode mode)
=> _shellClients[mode];
public void SetCurrentMode(WinoApplicationMode mode)
{
CurrentMode = mode;
OnPropertyChanged(nameof(CurrentMenuItems));
}
private void ChildPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (ReferenceEquals(sender, CurrentClient))
{
if (e.PropertyName == nameof(IShellClient.SelectedMenuItem))
{
OnPropertyChanged(nameof(SelectedMenuItem));
}
if (e.PropertyName == nameof(IShellClient.MenuItems))
{
OnPropertyChanged(nameof(CurrentMenuItems));
}
}
}
private void StatePersistenceServiceChanged(object? sender, string propertyName)
{
if (propertyName == nameof(IStatePersistanceService.ApplicationMode))
{
SetCurrentMode(StatePersistenceService.ApplicationMode);
}
}
}
@@ -0,0 +1,11 @@
using Wino.Mail.WinUI.ViewModels;
namespace Wino.Mail.WinUI.Views.Abstract;
public abstract class WinoAppShellAbstract : BasePage<WinoAppShellViewModel>
{
protected WinoAppShellAbstract()
{
NavigationCacheMode = Microsoft.UI.Xaml.Navigation.NavigationCacheMode.Enabled;
}
}
@@ -20,7 +20,7 @@
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources> <Page.Resources>
<DataTemplate x:Key="CalendarNewEventTemplate" x:DataType="menu:NewMailMenuItem"> <DataTemplate x:Key="CalendarNewEventTemplate" x:DataType="menu:NewCalendarEventMenuItem">
<coreControls:WinoNavigationViewItem <coreControls:WinoNavigationViewItem
Height="50" Height="50"
DataContext="{x:Bind}" DataContext="{x:Bind}"
@@ -73,6 +73,7 @@
<coreSelectors:NavigationMenuTemplateSelector <coreSelectors:NavigationMenuTemplateSelector
x:Key="NavigationMenuTemplateSelector" x:Key="NavigationMenuTemplateSelector"
CalendarNewEventTemplate="{StaticResource CalendarNewEventTemplate}"
NewMailTemplate="{StaticResource CalendarNewEventTemplate}" NewMailTemplate="{StaticResource CalendarNewEventTemplate}"
RatingItemTemplate="{StaticResource RatingItemTemplate}" RatingItemTemplate="{StaticResource RatingItemTemplate}"
SeperatorTemplate="{StaticResource SeperatorTemplate}" /> SeperatorTemplate="{StaticResource SeperatorTemplate}" />
+730
View File
@@ -0,0 +1,730 @@
<abstract:WinoAppShellAbstract
x:Class="Wino.Mail.WinUI.Views.WinoAppShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract"
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
xmlns:calendarControls="using:Wino.Calendar.Controls"
xmlns:communityControls="using:CommunityToolkit.WinUI.Controls"
xmlns:controls="using:Wino.Controls"
xmlns:controls1="using:CommunityToolkit.WinUI.Controls"
xmlns:coreControls="using:Wino.Mail.WinUI.Controls"
xmlns:coreConverters="using:Wino.Mail.WinUI.Converters"
xmlns:coreSelectors="using:Wino.Mail.WinUI.Selectors"
xmlns:data="using:Wino.Calendar.ViewModels.Data"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain"
xmlns:enums="using:Wino.Core.Domain.Enums"
xmlns:helpers="using:Wino.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:menu="using:Wino.Core.Domain.MenuItems"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
Loaded="OnLoaded"
PreviewKeyDown="OnPreviewKeyDown"
x:Name="Root"
mc:Ignorable="d">
<Page.Resources>
<coreConverters:HexToColorBrushConverter x:Key="HexToColorBrushConverter" />
<!-- Clickable New Style Account Template -->
<DataTemplate x:Key="ClickableAccountMenuTemplate" x:DataType="menu:AccountMenuItem">
<controls:AccountNavigationItem
x:Name="AccountItem"
Height="50"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
BindingData="{x:Bind}"
DataContext="{x:Bind}"
IsActiveAccount="{x:Bind IsSelected, Mode=OneWay}"
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
SelectsOnInvoked="False"
Style="{StaticResource SingleAccountNavigationViewItemTemplate}">
<coreControls:WinoNavigationViewItem.ContentTransitions>
<TransitionCollection>
<EdgeUIThemeTransition Edge="Top" />
</TransitionCollection>
</coreControls:WinoNavigationViewItem.ContentTransitions>
<muxc:NavigationViewItem.Icon>
<coreControls:WinoFontIcon
FontSize="12"
Foreground="{x:Bind AccountColorHex, Converter={StaticResource HexToColorBrushConverter}, Mode=OneWay}"
Icon="{x:Bind helpers:XamlHelpers.GetProviderIcon(Parameter)}" />
</muxc:NavigationViewItem.Icon>
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge
Background="{ThemeResource SystemAccentColor}"
Foreground="White"
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
</muxc:NavigationViewItem.InfoBadge>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center">
<TextBlock
x:Name="AccountNameTextblock"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
MaxLines="1"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind AccountName, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
MaxLines="1"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Parameter.Address, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
Visibility="{x:Bind IsSynchronizationProgressVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}, Mode=OneWay}" />
<TextBlock
x:Name="SyncStatusText"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
MaxLines="1"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind SynchronizationStatus, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
</StackPanel>
<PathIcon
x:Name="AttentionIcon"
Grid.Column="1"
Width="16"
Height="16"
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
x:Load="{x:Bind IsAttentionRequired, Mode=OneWay}"
Data="F1 M 2.021484 18.769531 C 1.767578 18.769531 1.52832 18.720703 1.303711 18.623047 C 1.079102 18.525391 0.880534 18.391928 0.708008 18.222656 C 0.535482 18.053385 0.398763 17.856445 0.297852 17.631836 C 0.19694 17.407227 0.146484 17.167969 0.146484 16.914062 C 0.146484 16.614584 0.211589 16.328125 0.341797 16.054688 L 7.695312 1.347656 C 7.851562 1.035156 8.082682 0.784506 8.388672 0.595703 C 8.694661 0.406902 9.023438 0.3125 9.375 0.3125 C 9.726562 0.3125 10.055338 0.406902 10.361328 0.595703 C 10.667317 0.784506 10.898438 1.035156 11.054688 1.347656 L 18.408203 16.054688 C 18.53841 16.328125 18.603516 16.614584 18.603516 16.914062 C 18.603516 17.167969 18.553059 17.407227 18.452148 17.631836 C 18.351236 17.856445 18.216145 18.053385 18.046875 18.222656 C 17.877604 18.391928 17.679035 18.525391 17.451172 18.623047 C 17.223307 18.720703 16.982422 18.769531 16.728516 18.769531 Z M 16.728516 17.519531 C 16.884766 17.519531 17.027994 17.460938 17.158203 17.34375 C 17.28841 17.226562 17.353516 17.086588 17.353516 16.923828 C 17.353516 16.806641 17.330729 16.702475 17.285156 16.611328 L 9.931641 1.904297 C 9.879557 1.793621 9.80306 1.708984 9.702148 1.650391 C 9.601236 1.591797 9.492188 1.5625 9.375 1.5625 C 9.257812 1.5625 9.148763 1.593426 9.047852 1.655273 C 8.946939 1.717123 8.870442 1.800131 8.818359 1.904297 L 1.464844 16.611328 C 1.419271 16.702475 1.396484 16.803387 1.396484 16.914062 C 1.396484 17.083334 1.459961 17.226562 1.586914 17.34375 C 1.713867 17.460938 1.858724 17.519531 2.021484 17.519531 Z M 8.75 11.875 L 8.75 6.875 C 8.75 6.705729 8.811849 6.559245 8.935547 6.435547 C 9.059244 6.31185 9.205729 6.25 9.375 6.25 C 9.544271 6.25 9.690755 6.31185 9.814453 6.435547 C 9.93815 6.559245 10 6.705729 10 6.875 L 10 11.875 C 10 12.044271 9.93815 12.190756 9.814453 12.314453 C 9.690755 12.438151 9.544271 12.5 9.375 12.5 C 9.205729 12.5 9.059244 12.438151 8.935547 12.314453 C 8.811849 12.190756 8.75 12.044271 8.75 11.875 Z M 8.4375 14.375 C 8.4375 14.114584 8.528646 13.893229 8.710938 13.710938 C 8.893229 13.528646 9.114583 13.4375 9.375 13.4375 C 9.635416 13.4375 9.856771 13.528646 10.039062 13.710938 C 10.221354 13.893229 10.3125 14.114584 10.3125 14.375 C 10.3125 14.635417 10.221354 14.856771 10.039062 15.039062 C 9.856771 15.221354 9.635416 15.3125 9.375 15.3125 C 9.114583 15.3125 8.893229 15.221354 8.710938 15.039062 C 8.528646 14.856771 8.4375 14.635417 8.4375 14.375 Z "
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}" />
<muxc:ProgressRing
x:Name="SynchronizationProgressBar"
Grid.Column="2"
Width="16"
Height="16"
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
</Grid>
</controls:AccountNavigationItem>
</DataTemplate>
<!-- Fix account issues. -->
<!-- Authentication -->
<DataTemplate x:Key="FixAuthenticationIssueTemplate" x:DataType="menu:FixAccountIssuesMenuItem">
<coreControls:WinoNavigationViewItem SelectsOnInvoked="False">
<coreControls:WinoNavigationViewItem.Content>
<TextBlock
Margin="0,10"
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
HorizontalTextAlignment="Center"
TextWrapping="WrapWholeWords">
<Run Text="Account credentials can not be verified." /><LineBreak /><Run Text="Click here to fix it." />
</TextBlock>
</coreControls:WinoNavigationViewItem.Content>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<!-- Missing system folder config. -->
<DataTemplate x:Key="FixMissingFolderConfig" x:DataType="menu:FixAccountIssuesMenuItem">
<coreControls:WinoNavigationViewItem SelectsOnInvoked="False">
<coreControls:WinoNavigationViewItem.Content>
<TextBlock
Margin="0,10"
Foreground="{ThemeResource InfoBarWarningSeverityIconBackground}"
HorizontalTextAlignment="Center"
TextWrapping="WrapWholeWords">
<Run Text="Account is missing system folder configuration." /><LineBreak /><LineBreak /><Run Text="Click here to fix it." />
</TextBlock>
</coreControls:WinoNavigationViewItem.Content>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<!-- New Mail -->
<DataTemplate x:Key="CreateNewMailTemplate" x:DataType="menu:NewMailMenuItem">
<coreControls:WinoNavigationViewItem
Height="50"
DataContext="{x:Bind}"
SelectsOnInvoked="False">
<muxc:NavigationViewItem.Icon>
<coreControls:WinoFontIcon Icon="NewMail" />
</muxc:NavigationViewItem.Icon>
<TextBlock
Margin="0,-2,0,0"
VerticalAlignment="Center"
FontSize="16"
Style="{StaticResource FlyoutPickerTitleTextBlockStyle}"
Text="{x:Bind domain:Translator.MenuNewMail}" />
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<!-- Inbox or any other folders. -->
<DataTemplate x:Key="FolderMenuTemplate" x:DataType="menu:FolderMenuItem">
<coreControls:WinoNavigationViewItem
MinHeight="40"
AllowDrop="True"
ContextRequested="MenuItemContextRequested"
DataContext="{x:Bind}"
DragEnter="ItemDragEnterOnFolder"
DragLeave="ItemDragLeaveFromFolder"
Drop="ItemDroppedOnFolder"
FontSize="50"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
MenuItemsSource="{x:Bind SubMenuItems, Mode=OneWay}"
SelectsOnInvoked="{x:Bind IsMoveTarget, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind FolderName, Mode=OneWay}">
<animations:Implicit.Animations>
<animations:ScaleAnimation Duration="0:0:0.5" />
</animations:Implicit.Animations>
<coreControls:WinoNavigationViewItem.Icon>
<coreControls:WinoFontIcon FontSize="64" Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(Parameter.SpecialFolderType)}" />
</coreControls:WinoNavigationViewItem.Icon>
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge
x:Name="FolderInfoBadge"
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
x:Name="FolderBackgroundGrid"
MaxHeight="36"
Padding="2"
VerticalAlignment="Center">
<Grid
x:Name="BackgroundColorGrid"
x:Load="{x:Bind HasTextColor, Mode=OneWay}"
Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(Parameter.BackgroundColorHex), Mode=OneWay}"
CornerRadius="3">
<TextBlock
x:Name="CustomColorTitle"
Margin="4,0,0,0"
VerticalAlignment="Center"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(Parameter.TextColorHex), Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind FolderName, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</Grid>
<TextBlock
x:Name="NormalTitle"
VerticalAlignment="Center"
x:Load="{x:Bind HasTextColor, Converter={StaticResource ReverseBooleanConverter}}"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind FolderName, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</Grid>
</muxc:NavigationViewItem.Content>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<!-- Merged Inbox -->
<DataTemplate x:Key="MergedAccountTemplate" x:DataType="menu:MergedAccountMenuItem">
<controls:AccountNavigationItem
x:Name="AccountItem"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
BindingData="{x:Bind}"
DataContext="{x:Bind}"
IsActiveAccount="{x:Bind IsSelected, Mode=TwoWay}"
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
MenuItemsSource="{x:Bind SubMenuItems}"
SelectsOnInvoked="False"
Style="{StaticResource SingleAccountNavigationViewItemTemplate}">
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge
x:Name="FolderInfoBadge"
Background="{StaticResource SystemAccentColor}"
Foreground="White"
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
</muxc:NavigationViewItem.InfoBadge>
<coreControls:WinoNavigationViewItem.ContentTransitions>
<TransitionCollection>
<EdgeUIThemeTransition Edge="Top" />
</TransitionCollection>
</coreControls:WinoNavigationViewItem.ContentTransitions>
<coreControls:WinoNavigationViewItem.Icon>
<PathIcon Data="F1 M 8.613281 17.5 C 8.75 17.942709 8.945312 18.359375 9.199219 18.75 L 4.921875 18.75 C 4.433594 18.75 3.966471 18.650717 3.520508 18.452148 C 3.074544 18.25358 2.683919 17.986654 2.348633 17.651367 C 2.013346 17.31608 1.746419 16.925455 1.547852 16.479492 C 1.349284 16.033529 1.25 15.566406 1.25 15.078125 L 1.25 4.921875 C 1.25 4.433594 1.349284 3.966473 1.547852 3.520508 C 1.746419 3.074545 2.013346 2.68392 2.348633 2.348633 C 2.683919 2.013348 3.074544 1.74642 3.520508 1.547852 C 3.966471 1.349285 4.433594 1.25 4.921875 1.25 L 15.078125 1.25 C 15.566406 1.25 16.033527 1.349285 16.479492 1.547852 C 16.925455 1.74642 17.31608 2.013348 17.651367 2.348633 C 17.986652 2.68392 18.25358 3.074545 18.452148 3.520508 C 18.650715 3.966473 18.75 4.433594 18.75 4.921875 L 18.75 6.572266 C 18.580729 6.344402 18.390299 6.132813 18.178711 5.9375 C 17.967121 5.742188 17.740885 5.566407 17.5 5.410156 L 17.5 4.951172 C 17.5 4.625651 17.433268 4.314779 17.299805 4.018555 C 17.16634 3.722332 16.987305 3.461914 16.762695 3.237305 C 16.538086 3.012695 16.277668 2.83366 15.981445 2.700195 C 15.685221 2.566732 15.374349 2.5 15.048828 2.5 L 4.951172 2.5 C 4.619141 2.5 4.303385 2.568359 4.003906 2.705078 C 3.704427 2.841797 3.44401 3.02409 3.222656 3.251953 C 3.001302 3.479818 2.825521 3.745117 2.695312 4.047852 C 2.565104 4.350587 2.5 4.66797 2.5 5 L 13.310547 5 C 12.60091 5.266928 11.998697 5.683594 11.503906 6.25 L 2.5 6.25 L 2.5 15.048828 C 2.5 15.38737 2.568359 15.704753 2.705078 16.000977 C 2.841797 16.297201 3.024088 16.55599 3.251953 16.777344 C 3.479818 16.998697 3.745117 17.174479 4.047852 17.304688 C 4.350586 17.434896 4.667969 17.5 5 17.5 Z" />
</coreControls:WinoNavigationViewItem.Icon>
<Grid Height="50">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center" Spacing="0">
<TextBlock
x:Name="AccountNameTextblock"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsChildSelected), Mode=OneWay}"
MaxLines="1"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind MergedAccountName, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
<TextBlock
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
MaxLines="1"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind SynchronizationStatus, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
<TextBlock
FontSize="12"
MaxLines="1"
Style="{StaticResource CaptionTextBlockStyle}"
TextTrimming="CharacterEllipsis"
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(IsSynchronizationProgressVisible), Mode=OneWay}">
<Run Text="{x:Bind MergedAccountCount, Mode=OneWay}" /><Run Text="{x:Bind domain:Translator.MenuMergedAccountItemAccountsSuffix}" />
</TextBlock>
</StackPanel>
<muxc:ProgressRing
x:Name="SynchronizationProgressBar"
Grid.Column="1"
Width="16"
Height="16"
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsActive="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}"
IsIndeterminate="{x:Bind IsProgressIndeterminate, Mode=OneWay}"
Visibility="{x:Bind IsSynchronizationProgressVisible, Mode=OneWay}" />
</Grid>
</controls:AccountNavigationItem>
</DataTemplate>
<!-- Merged Account Common Folder Item -->
<DataTemplate x:Key="MergedAccountFolderMenuItemTemplate" x:DataType="menu:MergedAccountFolderMenuItem">
<coreControls:WinoNavigationViewItem
MinHeight="30"
AllowDrop="True"
ContextRequested="MenuItemContextRequested"
DataContext="{x:Bind}"
DragEnter="ItemDragEnterOnFolder"
DragLeave="ItemDragLeaveFromFolder"
Drop="ItemDroppedOnFolder"
FontSize="50"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
SelectsOnInvoked="True"
ToolTipService.ToolTip="{x:Bind FolderName, Mode=OneWay}">
<animations:Implicit.Animations>
<animations:ScaleAnimation Duration="0:0:0.5" />
</animations:Implicit.Animations>
<coreControls:WinoNavigationViewItem.Icon>
<coreControls:WinoFontIcon FontSize="64" Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(FolderType)}" />
</coreControls:WinoNavigationViewItem.Icon>
<muxc:NavigationViewItem.InfoBadge>
<muxc:InfoBadge
x:Name="FolderInfoBadge"
Background="{StaticResource SystemAccentColor}"
Foreground="White"
Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(UnreadItemCount), Mode=OneWay}"
Value="{x:Bind UnreadItemCount, Mode=OneWay}" />
</muxc:NavigationViewItem.InfoBadge>
<Grid
x:Name="FolderBackgroundGrid"
Padding="2"
VerticalAlignment="Center">
<TextBlock
x:Name="NormalTitle"
VerticalAlignment="Center"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightBySyncState(IsSelected), Mode=OneWay}"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind FolderName, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</Grid>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<!-- Merged Account More Expansion Item -->
<DataTemplate x:Key="MergedAccountMoreFolderItemTemplate" x:DataType="menu:MergedAccountMoreFolderMenuItem">
<coreControls:WinoNavigationViewItem
MinHeight="30"
Content="{x:Bind domain:Translator.More}"
FontWeight="{x:Bind helpers:XamlHelpers.GetFontWeightByChildSelectedState(IsSelected), Mode=OneWay}"
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}"
IsSelected="{x:Bind IsSelected, Mode=TwoWay}"
MenuItemsSource="{x:Bind SubMenuItems, Mode=OneWay}"
SelectsOnInvoked="True">
<animations:Implicit.Animations>
<animations:ScaleAnimation Duration="0:0:0.5" />
</animations:Implicit.Animations>
<coreControls:WinoNavigationViewItem.Icon>
<coreControls:WinoFontIcon FontSize="64" Icon="{x:Bind helpers:XamlHelpers.GetSpecialFolderPathIconGeometry(enums:SpecialFolderType.More)}" />
</coreControls:WinoNavigationViewItem.Icon>
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<DataTemplate x:Key="CalendarNewEventTemplate" x:DataType="menu:NewCalendarEventMenuItem">
<coreControls:WinoNavigationViewItem
Height="50"
DataContext="{x:Bind}"
SelectsOnInvoked="False">
<muxc:NavigationViewItem.Icon>
<coreControls:WinoFontIcon Icon="NewMail" />
</muxc:NavigationViewItem.Icon>
<TextBlock
Margin="0,-2,0,0"
VerticalAlignment="Center"
FontSize="16"
Style="{StaticResource FlyoutPickerTitleTextBlockStyle}"
Text="{x:Bind domain:Translator.CalendarEventCompose_NewEventButton}" />
</coreControls:WinoNavigationViewItem>
</DataTemplate>
<coreSelectors:NavigationMenuTemplateSelector
x:Key="NavigationMenuTemplateSelector"
CalendarNewEventTemplate="{StaticResource CalendarNewEventTemplate}"
ClickableAccountMenuTemplate="{StaticResource ClickableAccountMenuTemplate}"
FixAuthenticationIssueTemplate="{StaticResource FixAuthenticationIssueTemplate}"
FixMissingFolderConfigTemplate="{StaticResource FixMissingFolderConfig}"
FolderMenuTemplate="{StaticResource FolderMenuTemplate}"
MergedAccountFolderTemplate="{StaticResource MergedAccountFolderMenuItemTemplate}"
MergedAccountMoreExpansionItemTemplate="{StaticResource MergedAccountMoreFolderItemTemplate}"
MergedAccountTemplate="{StaticResource MergedAccountTemplate}"
NewMailTemplate="{StaticResource CreateNewMailTemplate}"
RatingItemTemplate="{StaticResource RatingItemTemplate}"
SeperatorTemplate="{StaticResource SeperatorTemplate}"
StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}" />
<Style
x:Key="CalendarNavigationButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}"
TargetType="Button">
<Setter Property="Margin" Value="0,4,0,0" />
<Setter Property="Padding" Value="8,4,8,6" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Width" Value="40" />
</Style>
</Page.Resources>
<abstract:WinoAppShellAbstract.ShellContent>
<Grid Margin="4,0,0,0" Background="Transparent">
<ContentPresenter x:Name="DynamicPageShellContentPresenter" />
<Grid
x:Name="CalendarShellContentRoot"
Visibility="Collapsed"
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid x:Name="ShellContentArea" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
x:Name="NavigationTitleStack"
Grid.Column="0"
Margin="0,0,12,4"
Orientation="Horizontal"
Spacing="6">
<Button
x:Name="PreviousDateButton"
Click="PreviousDateClicked"
Style="{StaticResource CalendarNavigationButtonStyle}">
<PathIcon x:Name="PreviousDateButtonPathIcon" Data="F1 M 8.72 18.599998 C 8.879999 18.733334 9.059999 18.799999 9.26 18.799999 C 9.459999 18.799999 9.633332 18.719999 9.78 18.559999 C 9.926666 18.4 10 18.219999 10 18.019999 C 10 17.82 9.92 17.653332 9.76 17.52 L 4.52 12.559999 L 17.24 12.559999 C 17.453333 12.559999 17.633331 12.486667 17.779999 12.339999 C 17.926666 12.193334 18 12.013333 18 11.799999 C 18 11.586666 17.926666 11.406667 17.779999 11.259999 C 17.633331 11.113333 17.453333 11.039999 17.24 11.039999 L 4.52 11.039999 L 9.76 6.08 C 9.973333 5.893333 10.046666 5.653332 9.98 5.359999 C 9.913333 5.066666 9.74 4.880001 9.46 4.799999 C 9.179999 4.720001 8.933332 4.786667 8.72 5 L 2.32 11.08 C 2.16 11.24 2.053333 11.426666 2 11.639999 C 1.973333 11.746666 1.973333 11.853333 2 11.959999 C 2.053333 12.173333 2.16 12.360001 2.32 12.52 Z " />
</Button>
<Button
x:Name="NextDateButton"
Click="NextDateClicked"
Style="{StaticResource CalendarNavigationButtonStyle}">
<PathIcon x:Name="NextDateButtonPathIcon" Data="F1 M 11.28 5 C 11.12 4.866667 10.94 4.806667 10.74 4.82 C 10.539999 4.833334 10.366666 4.913334 10.219999 5.059999 C 10.073333 5.206665 10 5.379999 10 5.58 C 10 5.779999 10.08 5.946667 10.24 6.08 L 15.48 11.039999 L 2.76 11.039999 C 2.546667 11.039999 2.366667 11.113333 2.22 11.259999 C 2.073333 11.406667 2 11.586666 2 11.799999 C 2 12.013333 2.073333 12.193334 2.22 12.339999 C 2.366667 12.486667 2.546667 12.559999 2.76 12.559999 L 15.48 12.559999 L 10.24 17.52 C 10.026667 17.706665 9.953333 17.946667 10.02 18.24 C 10.086666 18.533333 10.259999 18.719999 10.54 18.799999 C 10.82 18.879999 11.066667 18.813334 11.28 18.599998 L 17.68 12.52 C 17.84 12.360001 17.946667 12.173333 18 11.959999 C 18 11.853333 18 11.746666 18 11.639999 C 17.946667 11.426666 17.84 11.24 17.68 11.08 Z " />
</Button>
<calendarControls:CustomCalendarFlipView
x:Name="DayHeaderNavigationItemsFlipView"
MaxHeight="30"
Margin="8,4,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
HorizontalContentAlignment="Left"
Background="Transparent"
FontSize="14"
FontWeight="Normal"
IsHitTestVisible="False"
>
<FlipView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock
VerticalAlignment="Center"
FontSize="18"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind}" />
</DataTemplate>
</FlipView.ItemTemplate>
</calendarControls:CustomCalendarFlipView>
</StackPanel>
<AutoSuggestBox
x:Name="SearchBox"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
BorderBrush="Transparent"
PlaceholderText="Search" />
</Grid>
<calendarControls:WinoCalendarTypeSelectorControl
x:Name="CalendarTypeSelector"
Grid.Column="1"
HorizontalAlignment="Right" />
</Grid>
</Grid>
</abstract:WinoAppShellAbstract.ShellContent>
<Grid
x:Name="RootGrid"
Padding="0"
ColumnSpacing="0"
RowSpacing="0">
<muxc:NavigationView
x:Name="navigationView"
Grid.Row="1"
Grid.ColumnSpan="3"
Margin="-1,-1,0,0"
Style="{StaticResource CalendarShellNavigationViewStyle}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
AlwaysShowHeader="True"
DisplayModeChanged="NavigationViewDisplayModeChanged"
IsBackButtonVisible="Collapsed"
IsPaneOpen="{x:Bind ViewModel.PreferencesService.IsNavigationPaneOpened, Mode=TwoWay}"
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
IsTabStop="True"
IsTitleBarAutoPaddingEnabled="False"
ItemInvoked="NavigationViewItemInvoked"
MenuItemTemplateSelector="{StaticResource NavigationMenuTemplateSelector}"
OpenPaneLength="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=TwoWay}"
PaneDisplayMode="Auto"
PaneOpening="NavigationPaneOpening"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
SelectionChanged="MenuSelectionChanged">
<muxc:NavigationView.ContentTransitions>
<TransitionCollection>
<AddDeleteThemeTransition />
</TransitionCollection>
</muxc:NavigationView.ContentTransitions>
<muxc:NavigationView.PaneCustomContent>
<Grid x:Name="PaneCustomContent" Padding="0,0,0,6" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<calendarControls:WinoCalendarView
x:Name="CalendarView"
Grid.Row="0"
Margin="0,12,0,0"
HorizontalAlignment="Center"
TodayBackgroundColor="{ThemeResource SystemAccentColor}" />
<ListView
x:Name="CalendarHostListView"
Grid.Row="1"
SelectionMode="None">
<ListView.Header>
<TextBlock
Margin="20,12,12,12"
FontSize="16"
Text="Calendars" />
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:GroupedAccountCalendarViewModel">
<muxc:Expander
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsExpanded="{x:Bind IsExpanded, Mode=TwoWay}">
<muxc:Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox
Width="26"
MinWidth="0"
IsChecked="{x:Bind IsCheckedState, Mode=TwoWay}"
IsThreeState="True" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
TextWrapping="Wrap">
<Run FontWeight="SemiBold" Text="{x:Bind Account.Name}" />
<Run FontSize="12" Text="(" /><Run FontSize="12" Text="{x:Bind Account.Address}" /><Run FontSize="12" Text=")" />
</TextBlock>
</Grid>
</muxc:Expander.Header>
<muxc:Expander.Content>
<ItemsControl ItemsSource="{x:Bind AccountCalendars}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="data:AccountCalendarViewModel">
<CheckBox
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsChecked="{x:Bind IsChecked, Mode=TwoWay}">
<Grid Margin="0,0,0,5" ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse
Width="20"
Height="20"
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontSize="14"
Text="{x:Bind Name, Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</muxc:Expander.Content>
</muxc:Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</muxc:NavigationView.PaneCustomContent>
<Grid ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls1:PropertySizer
Width="1"
HorizontalAlignment="Left"
Background="Transparent"
Binding="{x:Bind ViewModel.StatePersistenceService.OpenPaneLength, Mode=TwoWay}"
Canvas.ZIndex="20"
Foreground="Transparent"
IsHitTestVisible="{x:Bind navigationView.IsPaneOpen, Mode=OneWay}"
IsTabStop="False"
Maximum="1000"
Minimum="116" />
<!-- Main Content -->
<Frame
x:Name="InnerShellFrame"
Padding="0,0,7,7"
IsNavigationStackEnabled="True"
Navigated="ShellFrameContentNavigated">
<Frame.ContentTransitions>
<TransitionCollection>
<PopupThemeTransition />
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
<!-- InfoBar -->
<coreControls:WinoInfoBar
x:Name="ShellInfoBar"
MaxWidth="700"
Margin="0,60,25,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
IsClosable="False"
IsOpen="False" />
<!-- Teaching Tip -->
<muxc:TeachingTip
x:Name="ShellTip"
IsOpen="False"
PreferredPlacement="Bottom"
Target="{x:Bind ShellInfoBar}" />
</Grid>
</muxc:NavigationView>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LowResolutionStates">
<VisualState x:Name="BigScreen">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="1200" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="navigationView.IsPaneOpen" Value="True" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SmallScreen">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="NavigationTitleStack.Visibility" Value="Collapsed" />
<Setter Target="SearchBox.(Grid.ColumnSpan)" Value="2" />
<Setter Target="navigationView.IsPaneOpen" Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CalendarOrientationStates">
<VisualState x:Name="HorizontalCalendar" />
<VisualState x:Name="VerticalCalendar">
<VisualState.Setters>
<Setter Target="DayHeaderNavigationItemsFlipView.ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Target="PreviousDateButtonPathIcon.Data" Value="F1 M 3.2 10.52 C 2.986666 10.733333 2.92 10.98 3 11.259999 C 3.08 11.54 3.266666 11.713333 3.56 11.78 C 3.853333 11.846666 4.093333 11.773333 4.28 11.559999 L 9.24 6.32 L 9.24 19.039999 C 9.24 19.253332 9.313333 19.433332 9.46 19.58 C 9.606666 19.726665 9.786666 19.799999 10 19.799999 C 10.213333 19.799999 10.393332 19.726665 10.54 19.58 C 10.686666 19.433332 10.76 19.253332 10.76 19.039999 L 10.76 6.32 L 15.719999 11.559999 C 15.906666 11.773333 16.139999 11.846666 16.42 11.78 C 16.700001 11.713333 16.886665 11.54 16.98 11.259999 C 17.073332 10.98 17.013332 10.733333 16.799999 10.52 L 10.719999 4.119999 C 10.559999 3.959999 10.373333 3.853333 10.16 3.799999 C 10.053333 3.799999 9.946667 3.799999 9.84 3.799999 C 9.626666 3.853333 9.439999 3.959999 9.28 4.119999 Z " />
<Setter Target="NextDateButtonPathIcon.Data" Value="F1 M 16.799999 13.079999 C 16.933332 12.92 16.993332 12.74 16.98 12.539999 C 16.966665 12.34 16.886665 12.166667 16.74 12.02 C 16.593334 11.873333 16.42 11.799999 16.219999 11.799999 C 16.02 11.799999 15.853333 11.879999 15.719999 12.039999 L 10.76 17.279999 L 10.76 4.559999 C 10.76 4.346666 10.686666 4.166668 10.54 4.02 C 10.393332 3.873333 10.213333 3.799999 10 3.799999 C 9.786666 3.799999 9.606666 3.873333 9.46 4.02 C 9.313333 4.166668 9.24 4.346666 9.24 4.559999 L 9.24 17.279999 L 4.28 12.039999 C 4.146667 11.879999 3.98 11.799999 3.78 11.799999 C 3.58 11.799999 3.4 11.873333 3.24 12.02 C 3.08 12.166667 3 12.34 3 12.539999 C 3 12.74 3.066667 12.92 3.2 13.079999 L 9.28 19.48 C 9.439999 19.639999 9.626666 19.746666 9.84 19.799999 C 9.946667 19.799999 10.053333 19.799999 10.16 19.799999 C 10.373333 19.746666 10.559999 19.639999 10.719999 19.48 Z " />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ShellStateContentGroup">
<VisualState x:Name="DefaultShellContentState" />
<VisualState x:Name="EventDetailsContentState">
<VisualState.Setters>
<Setter Target="ShellContentArea.Visibility" Value="Collapsed" />
<Setter Target="CalendarTypeSelector.Visibility" Value="Collapsed" />
<Setter Target="CalendarView.IsEnabled" Value="False" />
<Setter Target="CalendarHostListView.IsEnabled" Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</abstract:WinoAppShellAbstract>
+679
View File
@@ -0,0 +1,679 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Wino.Calendar.Controls;
using Wino.Calendar.ViewModels;
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;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Navigation;
using Wino.Mail.ViewModels;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.WinUI.Controls;
using Wino.MenuFlyouts;
using Wino.MenuFlyouts.Context;
using Wino.Messaging.Client.Accounts;
using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Mails;
using Wino.Messaging.Client.Shell;
namespace Wino.Mail.WinUI.Views;
public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
IShellHost,
IRecipient<AccountMenuItemExtended>,
IRecipient<NavigateMailFolderEvent>,
IRecipient<CreateNewMailWithMultipleAccountsRequested>,
IRecipient<CalendarDisplayTypeChangedMessage>
{
private const string StateHorizontalCalendar = "HorizontalCalendar";
private const string StateVerticalCalendar = "VerticalCalendar";
private const string StateDefaultShellContent = "DefaultShellContentState";
private const string StateEventDetailsContent = "EventDetailsContentState";
private WinoApplicationMode? _activeMode;
public WinoAppShell()
{
InitializeComponent();
ViewModel.MailClient.PropertyChanged += MailClientPropertyChanged;
ViewModel.CalendarClient.PropertyChanged += CalendarClientPropertyChanged;
ViewModel.StatePersistenceService.StatePropertyChanged += StatePersistenceServiceChanged;
CalendarTypeSelector.RegisterPropertyChangedCallback(WinoCalendarTypeSelectorControl.SelectedTypeProperty, CalendarTypeSelectorSelectedTypeChanged);
InitializeCalendarControls();
ManageCalendarDisplayType(ViewModel.CalendarClient.StatePersistenceService.CalendarDisplayType);
UpdateEventDetailsVisualState();
ApplyTitleBarContent();
}
public bool HasShellContent => InnerShellFrame.Content != null;
public Frame GetShellFrame() => InnerShellFrame;
public void ActivateMode(WinoApplicationMode mode, bool isInitialActivation)
{
if (_activeMode == mode && InnerShellFrame.Content != null)
return;
DeactivateCurrentMode();
_activeMode = mode;
ViewModel.SetCurrentMode(mode);
RefreshNavigationViewBindings(syncMailSelection: mode != WinoApplicationMode.Mail);
//InnerShellFrame.IsNavigationStackEnabled = mode == WinoApplicationMode.Calendar;
//InnerShellFrame.BackStack.Clear();
//InnerShellFrame.ForwardStack.Clear();
ApplyModeLayout();
UpdateTitleBarSubtitle();
var activationContext = new ShellModeActivationContext
{
IsInitialActivation = isInitialActivation
};
ViewModel.CurrentClient.Activate(activationContext);
ApplyTitleBarContent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
DeactivateCurrentMode();
Bindings.StopTracking();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
UpdateNavigationPaneLayout(navigationView.DisplayMode);
RefreshNavigationViewBindings();
RefreshCalendarControls();
if (_activeMode == null)
{
ActivateMode(ViewModel.StatePersistenceService.ApplicationMode, true);
}
}
private void ApplyModeLayout()
{
var isCalendarMode = ViewModel.IsCalendarMode;
CalendarShellContentRoot.Visibility = isCalendarMode ? Visibility.Visible : Visibility.Collapsed;
DynamicPageShellContentPresenter.Visibility = isCalendarMode ? Visibility.Collapsed : Visibility.Visible;
RefreshCalendarControls();
ManageCalendarDisplayType(ViewModel.CalendarClient.StatePersistenceService.CalendarDisplayType);
UpdateEventDetailsVisualState();
UpdateTitleBarSubtitle();
UpdateNavigationPaneLayout(navigationView.DisplayMode);
ApplyTitleBarContent();
}
private void DeactivateCurrentMode()
{
if (_activeMode == WinoApplicationMode.Mail)
{
ViewModel.MailClient.Deactivate();
}
else if (_activeMode == WinoApplicationMode.Calendar)
{
ViewModel.CalendarClient.Deactivate();
}
else if (_activeMode == WinoApplicationMode.Contacts)
{
ViewModel.CurrentClient.Deactivate();
}
}
private void ApplyTitleBarContent()
{
if (ViewModel.IsCalendarMode)
{
CalendarShellContentRoot.Visibility = Visibility.Visible;
DynamicPageShellContentPresenter.Visibility = Visibility.Collapsed;
return;
}
CalendarShellContentRoot.Visibility = Visibility.Collapsed;
DynamicPageShellContentPresenter.Visibility = Visibility.Visible;
DynamicPageShellContentPresenter.Content = InnerShellFrame.Content is BasePage page ? page.ShellContent : null;
}
private void UpdateTitleBarSubtitle()
{
if (ViewModel.IsContactsMode)
{
ViewModel.StatePersistenceService.CoreWindowTitle = string.Empty;
return;
}
if (ViewModel.IsCalendarMode)
{
ViewModel.StatePersistenceService.CoreWindowTitle = ViewModel.CalendarClient.HighlightedDateRange?.ToString() ?? string.Empty;
return;
}
ViewModel.StatePersistenceService.CoreWindowTitle = string.Empty;
}
private void ManageCalendarDisplayType(Core.Domain.Enums.CalendarDisplayType displayType)
{
DayHeaderNavigationItemsFlipView.DisplayType = displayType;
if (CalendarTypeSelector.SelectedType != displayType)
{
CalendarTypeSelector.SelectedType = displayType;
}
VisualStateManager.GoToState(this, displayType == Core.Domain.Enums.CalendarDisplayType.Month
? StateVerticalCalendar
: StateHorizontalCalendar, false);
}
private void InitializeCalendarControls()
{
CalendarTypeSelector.TodayClickedCommand = ViewModel.CalendarClient.TodayClickedCommand;
CalendarView.DateClickedCommand = ViewModel.CalendarClient.DateClickedCommand;
DayHeaderNavigationItemsFlipView.ItemsSource = ViewModel.CalendarClient.DateNavigationHeaderItems;
CalendarHostListView.ItemsSource = ViewModel.CalendarClient.GroupedAccountCalendars;
RefreshCalendarControls();
}
private void RefreshCalendarControls()
{
DayHeaderNavigationItemsFlipView.ItemsSource = ViewModel.CalendarClient.DateNavigationHeaderItems;
DayHeaderNavigationItemsFlipView.SelectedIndex = ViewModel.CalendarClient.SelectedDateNavigationHeaderIndex;
CalendarTypeSelector.DisplayDayCount = ViewModel.CalendarClient.StatePersistenceService.DayDisplayCount;
CalendarView.HighlightedDateRange = ViewModel.CalendarClient.HighlightedDateRange;
CalendarHostListView.ItemsSource = ViewModel.CalendarClient.GroupedAccountCalendars;
}
private void RefreshNavigationViewBindings(bool syncMailSelection = true)
{
navigationView.MenuItemsSource = ViewModel.CurrentMenuItems;
navigationView.SelectionChanged -= MenuSelectionChanged;
navigationView.SelectedItem = ViewModel.CurrentClient.HandlesNavigationSelection && syncMailSelection
? ViewModel.SelectedMenuItem
: null;
navigationView.SelectionChanged += MenuSelectionChanged;
}
private void UpdateEventDetailsVisualState()
{
VisualStateManager.GoToState(this,
ViewModel.StatePersistenceService.IsEventDetailsVisible ? StateEventDetailsContent : StateDefaultShellContent,
false);
}
private void CalendarTypeSelectorSelectedTypeChanged(DependencyObject sender, DependencyProperty dp)
{
var selectedType = CalendarTypeSelector.SelectedType;
if (ViewModel.CalendarClient.StatePersistenceService.CalendarDisplayType != selectedType)
{
ViewModel.CalendarClient.StatePersistenceService.CalendarDisplayType = selectedType;
}
}
private void PreviousDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoPreviousDateRequestedMessage());
private void NextDateClicked(object sender, RoutedEventArgs e) => WeakReferenceMessenger.Default.Send(new GoNextDateRequestedMessage());
public void Receive(CalendarDisplayTypeChangedMessage message) => ManageCalendarDisplayType(message.NewDisplayType);
private async void NavigationViewItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (ViewModel.IsCalendarMode)
{
if (args.InvokedItemContainer is FrameworkElement { DataContext: IMenuItem menuItem })
{
await ViewModel.CalendarClient.HandleNavigationItemInvokedAsync(menuItem);
}
return;
}
if (args.InvokedItemContainer is WinoNavigationViewItem winoNavigationViewItem)
{
if (winoNavigationViewItem.SelectsOnInvoked)
return;
await ViewModel.CurrentClient.HandleNavigationItemInvokedAsync(winoNavigationViewItem.DataContext as IMenuItem);
}
}
private async void MenuSelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
if (!ViewModel.IsMailMode)
return;
if (args.SelectedItem is IMenuItem invokedMenuItem)
{
await ViewModel.CurrentClient.HandleNavigationSelectionChangedAsync(invokedMenuItem);
}
}
public void Receive(AccountMenuItemExtended message)
{
if (!ViewModel.IsMailMode)
return;
_ = DispatcherQueue.EnqueueAsync(async () =>
{
if (message.FolderId == default)
return;
if (ViewModel.MailClient.MenuItems!.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem foundMenuItem))
{
foundMenuItem.Expand();
await ViewModel.MailClient.NavigateFolderAsync(foundMenuItem);
navigationView.SelectedItem = foundMenuItem;
if (message.NavigateMailItem != null)
{
WeakReferenceMessenger.Default.Send(new MailItemNavigationRequested(message.NavigateMailItem.UniqueId, ScrollToItem: true));
}
return;
}
if (message.NavigateMailItem == null)
return;
if (ViewModel.MailClient.MenuItems!.TryGetAccountMenuItem(message.NavigateMailItem.AssignedAccount.Id, out IAccountMenuItem accountMenuItem))
{
await ViewModel.MailClient.ChangeLoadedAccountAsync(accountMenuItem, navigateInbox: false);
if (ViewModel.MailClient.MenuItems!.TryGetFolderMenuItem(message.FolderId, out IBaseFolderMenuItem accountFolderMenuItem))
{
accountFolderMenuItem.Expand();
await ViewModel.MailClient.NavigateFolderAsync(accountFolderMenuItem);
navigationView.SelectedItem = accountFolderMenuItem;
WeakReferenceMessenger.Default.Send(new MailItemNavigationRequested(message.NavigateMailItem.UniqueId, ScrollToItem: true));
}
}
});
}
public void Receive(NavigateMailFolderEvent message)
{
if (!ViewModel.IsMailMode || message.BaseFolderMenuItem == null)
return;
if (navigationView.SelectedItem != message.BaseFolderMenuItem)
{
var navigateFolderArgs = new NavigateMailFolderEventArgs(message.BaseFolderMenuItem, message.FolderInitLoadAwaitTask);
ViewModel.NavigationService.Navigate(WinoPage.MailListPage, navigateFolderArgs, NavigationReferenceFrame.InnerShellFrame);
navigationView.SelectionChanged -= MenuSelectionChanged;
navigationView.SelectedItem = message.BaseFolderMenuItem;
navigationView.SelectionChanged += MenuSelectionChanged;
}
else
{
message.FolderInitLoadAwaitTask?.TrySetResult(true);
}
}
private void ShellFrameContentNavigated(object sender, NavigationEventArgs e)
{
ApplyTitleBarContent();
if (ViewModel.IsMailMode)
{
RefreshNavigationViewBindings();
}
}
private async void MenuItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
{
if (!ViewModel.IsMailMode)
return;
if (sender is WinoNavigationViewItem menuItem &&
menuItem.DataContext is IBaseFolderMenuItem baseFolderMenuItem &&
baseFolderMenuItem.IsMoveTarget &&
args.TryGetPosition(sender, out Point p))
{
args.Handled = true;
var source = new TaskCompletionSource<FolderOperationMenuItem>();
var actions = ViewModel.MailClient.GetFolderContextMenuActions(baseFolderMenuItem);
var flyout = new FolderOperationFlyout(actions, source);
flyout.ShowAt(menuItem, new FlyoutShowOptions
{
ShowMode = FlyoutShowMode.Standard,
Position = new Point(p.X + 30, p.Y - 20)
});
var operation = await source.Task;
flyout.Dispose();
if (operation != null)
{
await ViewModel.MailClient.PerformFolderOperationAsync(operation.Operation, baseFolderMenuItem);
}
}
}
public void Receive(CreateNewMailWithMultipleAccountsRequested message)
{
if (!ViewModel.IsMailMode)
return;
var container = navigationView.ContainerFromMenuItem(ViewModel.MailClient.CreatePrimaryMenuItem);
var flyout = new AccountSelectorFlyout(message.AllAccounts, ViewModel.MailClient.CreateNewMailForAsync);
flyout.ShowAt(container, new FlyoutShowOptions
{
ShowMode = FlyoutShowMode.Auto,
Placement = FlyoutPlacementMode.Right
});
}
private void NavigationPaneOpening(NavigationView sender, object args)
{
if (!ViewModel.IsMailMode)
return;
if (sender.DisplayMode == NavigationViewDisplayMode.Minimal && sender.SelectedItem is IFolderMenuItem selectedFolderMenuItem)
{
selectedFolderMenuItem.Expand();
}
}
private void NavigationViewDisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
=> UpdateNavigationPaneLayout(args.DisplayMode);
private void MailClientPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(IShellClient.SelectedMenuItem) && ViewModel.IsMailMode)
{
navigationView.SelectionChanged -= MenuSelectionChanged;
navigationView.SelectedItem = ViewModel.MailClient.SelectedMenuItem;
navigationView.SelectionChanged += MenuSelectionChanged;
}
}
private void CalendarClientPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ICalendarShellClient.DateNavigationHeaderItems))
{
DayHeaderNavigationItemsFlipView.ItemsSource = ViewModel.CalendarClient.DateNavigationHeaderItems;
return;
}
if (e.PropertyName == nameof(ICalendarShellClient.SelectedDateNavigationHeaderIndex))
{
DayHeaderNavigationItemsFlipView.SelectedIndex = ViewModel.CalendarClient.SelectedDateNavigationHeaderIndex;
return;
}
if (e.PropertyName == nameof(ICalendarShellClient.HighlightedDateRange))
{
CalendarView.HighlightedDateRange = ViewModel.CalendarClient.HighlightedDateRange;
UpdateTitleBarSubtitle();
}
}
private void StatePersistenceServiceChanged(object? sender, string propertyName)
{
if (propertyName == nameof(IStatePersistanceService.CalendarDisplayType))
{
ManageCalendarDisplayType(ViewModel.CalendarClient.StatePersistenceService.CalendarDisplayType);
return;
}
if (propertyName == nameof(IStatePersistanceService.DayDisplayCount))
{
CalendarTypeSelector.DisplayDayCount = ViewModel.CalendarClient.StatePersistenceService.DayDisplayCount;
return;
}
if (propertyName == nameof(IStatePersistanceService.IsEventDetailsVisible))
{
UpdateEventDetailsVisualState();
}
}
private void UpdateNavigationPaneLayout(NavigationViewDisplayMode displayMode)
{
if (ViewModel.IsCalendarMode)
{
PaneCustomContent.Visibility = displayMode == NavigationViewDisplayMode.Expanded && navigationView.IsPaneOpen
? Visibility.Visible
: Visibility.Collapsed;
InnerShellFrame.Margin = new Thickness(0);
return;
}
PaneCustomContent.Visibility = Visibility.Collapsed;
InnerShellFrame.Margin = displayMode == NavigationViewDisplayMode.Minimal
? new Thickness(7, 0, 0, 0)
: new Thickness(0);
}
private async void OnPreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.KeyStatus.RepeatCount > 1 || ShouldIgnoreShortcut())
return;
var key = NormalizeKey(e.Key);
if (string.IsNullOrEmpty(key))
return;
var mode = ViewModel.CurrentMode;
var shortcutService = WinoApplication.Current.Services.GetRequiredService<IKeyboardShortcutService>();
var shortcut = await shortcutService.GetShortcutForKeyAsync(mode, key, GetCurrentModifierKeys());
if (shortcut == null)
return;
var details = new KeyboardShortcutTriggerDetails
{
ShortcutId = shortcut.Id,
Mode = shortcut.Mode,
Action = shortcut.Action,
Key = shortcut.Key,
ModifierKeys = shortcut.ModifierKeys,
Sender = sender,
Origin = FocusManager.GetFocusedElement(XamlRoot)
};
await ViewModel.CurrentClient.KeyboardShortcutHook(details);
if (InnerShellFrame.Content is BasePage activePage && activePage.AssociatedViewModel != null)
{
await activePage.AssociatedViewModel.KeyboardShortcutHook(details);
}
if (details.Handled)
{
e.Handled = true;
}
}
private bool ShouldIgnoreShortcut()
{
var focusedElement = FocusManager.GetFocusedElement(XamlRoot);
if (focusedElement is TextBox or AutoSuggestBox or PasswordBox or RichEditBox or ComboBox)
return true;
if (focusedElement is FrameworkElement frameworkElement)
{
var typeName = frameworkElement.GetType().Name;
if (typeName.Contains("WebView", StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
private async void ItemDroppedOnFolder(object sender, DragEventArgs e)
{
if (sender is WinoNavigationViewItem droppedContainer)
{
droppedContainer.IsDraggingItemOver = false;
if (CanContinueDragDrop(droppedContainer, e) && droppedContainer.DataContext is IBaseFolderMenuItem draggingFolder)
{
var dragPackage = e.DataView.Properties[nameof(MailDragPackage)] as MailDragPackage;
if (dragPackage == null)
return;
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
var mailCopies = ExtractMailCopies(dragPackage).ToList();
await ViewModel.MailClient.PerformMoveOperationAsync(mailCopies, draggingFolder);
}
}
}
private void ItemDragLeaveFromFolder(object sender, DragEventArgs e)
{
if (sender is WinoNavigationViewItem leavingContainer)
{
leavingContainer.IsDraggingItemOver = false;
}
}
private bool CanContinueDragDrop(WinoNavigationViewItem interactingContainer, DragEventArgs args)
{
if (!ViewModel.IsMailMode || !args.DataView.Properties.ContainsKey(nameof(MailDragPackage)))
return false;
var dragPackage = args.DataView.Properties[nameof(MailDragPackage)] as MailDragPackage;
if (dragPackage == null || !dragPackage.DraggingMails.Any())
return false;
if (interactingContainer.IsSelected)
return false;
if (interactingContainer.DataContext is not IBaseFolderMenuItem folderMenuItem || !folderMenuItem.IsMoveTarget)
return false;
var draggedAccountIds = folderMenuItem.HandlingFolders.Select(a => a.MailAccountId);
var draggedMails = ExtractMailCopies(dragPackage).ToList();
return draggedMails.Any() && draggedMails.Any(a => draggedAccountIds.Contains(a.AssignedAccount.Id));
}
private static IEnumerable<MailCopy> ExtractMailCopies(MailDragPackage dragPackage)
{
foreach (var item in dragPackage.DraggingMails)
{
if (item is MailCopy mailCopy)
{
yield return mailCopy;
}
else if (item is MailItemViewModel singleMailItemViewModel)
{
yield return singleMailItemViewModel.MailCopy;
}
else if (item is ThreadMailItemViewModel threadViewModel)
{
foreach (var threadMail in threadViewModel.ThreadEmails)
{
yield return threadMail.MailCopy;
}
}
}
}
private void ItemDragEnterOnFolder(object sender, DragEventArgs e)
{
if (sender is WinoNavigationViewItem droppedContainer && CanContinueDragDrop(droppedContainer, e))
{
droppedContainer.IsDraggingItemOver = true;
if (droppedContainer.DataContext is IBaseFolderMenuItem draggingFolder)
{
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
e.DragUIOverride.Caption = string.Format(Translator.DragMoveToFolderCaption, draggingFolder.FolderName);
}
}
}
private static ModifierKeys GetCurrentModifierKeys()
{
var modifiers = ModifierKeys.None;
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
modifiers |= ModifierKeys.Control;
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Menu).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
modifiers |= ModifierKeys.Alt;
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
modifiers |= ModifierKeys.Shift;
if (Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.LeftWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down) ||
Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.RightWindows).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
{
modifiers |= ModifierKeys.Windows;
}
return modifiers;
}
private static string NormalizeKey(Windows.System.VirtualKey key)
{
return key switch
{
Windows.System.VirtualKey.Control or
Windows.System.VirtualKey.LeftControl or
Windows.System.VirtualKey.RightControl or
Windows.System.VirtualKey.Menu or
Windows.System.VirtualKey.LeftMenu or
Windows.System.VirtualKey.RightMenu or
Windows.System.VirtualKey.Shift or
Windows.System.VirtualKey.LeftShift or
Windows.System.VirtualKey.RightShift or
Windows.System.VirtualKey.LeftWindows or
Windows.System.VirtualKey.RightWindows => string.Empty,
_ => key.ToString()
};
}
protected override void RegisterRecipients()
{
base.RegisterRecipients();
WeakReferenceMessenger.Default.Register<AccountMenuItemExtended>(this);
WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this);
WeakReferenceMessenger.Default.Register<CalendarDisplayTypeChangedMessage>(this);
}
protected override void UnregisterRecipients()
{
base.UnregisterRecipients();
WeakReferenceMessenger.Default.Unregister<AccountMenuItemExtended>(this);
WeakReferenceMessenger.Default.Unregister<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Unregister<NavigateMailFolderEvent>(this);
WeakReferenceMessenger.Default.Unregister<CalendarDisplayTypeChangedMessage>(this);
}
}