diff --git a/Wino.Core.Domain/Enums/WinoApplicationMode.cs b/Wino.Core.Domain/Enums/WinoApplicationMode.cs index bee901db..a1b6114d 100644 --- a/Wino.Core.Domain/Enums/WinoApplicationMode.cs +++ b/Wino.Core.Domain/Enums/WinoApplicationMode.cs @@ -4,5 +4,6 @@ public enum WinoApplicationMode { Mail, Calendar, - Contacts + Contacts, + Settings } diff --git a/Wino.Core.Domain/MenuItems/SettingsShellPageMenuItem.cs b/Wino.Core.Domain/MenuItems/SettingsShellPageMenuItem.cs new file mode 100644 index 00000000..f478ab72 --- /dev/null +++ b/Wino.Core.Domain/MenuItems/SettingsShellPageMenuItem.cs @@ -0,0 +1,22 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Wino.Core.Domain.Enums; + +namespace Wino.Core.Domain.MenuItems; + +public partial class SettingsShellPageMenuItem( + WinoPage pageType, + string title, + string description, + string glyph) : MenuItemBase +{ + public WinoPage PageType { get; } = pageType; + + [ObservableProperty] + public partial string Title { get; set; } = title; + + [ObservableProperty] + public partial string Description { get; set; } = description; + + [ObservableProperty] + public partial string Glyph { get; set; } = glyph; +} diff --git a/Wino.Core.Domain/MenuItems/SettingsShellSectionMenuItem.cs b/Wino.Core.Domain/MenuItems/SettingsShellSectionMenuItem.cs new file mode 100644 index 00000000..223d76c2 --- /dev/null +++ b/Wino.Core.Domain/MenuItems/SettingsShellSectionMenuItem.cs @@ -0,0 +1,12 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Wino.Core.Domain.MenuItems; + +public partial class SettingsShellSectionMenuItem(string title, string glyph) : MenuItemBase +{ + [ObservableProperty] + public partial string Title { get; set; } = title; + + [ObservableProperty] + public partial string Glyph { get; set; } = glyph; +} diff --git a/Wino.Core.Domain/Models/Navigation/ShellModeActivationContext.cs b/Wino.Core.Domain/Models/Navigation/ShellModeActivationContext.cs index 5c87ca2c..ccdfaac0 100644 --- a/Wino.Core.Domain/Models/Navigation/ShellModeActivationContext.cs +++ b/Wino.Core.Domain/Models/Navigation/ShellModeActivationContext.cs @@ -3,4 +3,5 @@ namespace Wino.Core.Domain.Models.Navigation; public sealed class ShellModeActivationContext { public bool IsInitialActivation { get; init; } + public object Parameter { get; init; } } diff --git a/Wino.Core.Domain/Models/Settings/SettingsNavigationItemInfo.cs b/Wino.Core.Domain/Models/Settings/SettingsNavigationItemInfo.cs new file mode 100644 index 00000000..91cb2188 --- /dev/null +++ b/Wino.Core.Domain/Models/Settings/SettingsNavigationItemInfo.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; +using Wino.Core.Domain.Enums; + +namespace Wino.Core.Domain.Models.Settings; + +public sealed class SettingsNavigationItemInfo( + WinoPage? pageType, + string title, + string description, + string glyph = "", + bool isSeparator = false) +{ + public WinoPage? PageType { get; } = pageType; + public string Title { get; } = title; + public string Description { get; } = description; + public string Glyph { get; } = glyph; + public bool IsSeparator { get; } = isSeparator; +} + +public static class SettingsNavigationInfoProvider +{ + public static IReadOnlyList GetNavigationItems(string manageAccountsDescription = "") + { + return + [ + new(WinoPage.SettingOptionsPage, + Translator.SettingsHome_Title, + Translator.SettingsOptions_HeroDescription, + "\uE80F"), + new(WinoPage.ManageAccountsPage, + Translator.SettingsManageAccountSettings_Title, + manageAccountsDescription, + "\uE77B"), + new(null, Translator.SettingsOptions_GeneralSection, string.Empty, "\uE713", isSeparator: true), + new(WinoPage.AppPreferencesPage, + Translator.SettingsAppPreferences_Title, + Translator.SettingsAppPreferences_Description, + "\uE770"), + new(WinoPage.LanguageTimePage, + Translator.SettingsLanguageTime_Title, + Translator.SettingsLanguageTime_Description, + "\uE775"), + new(WinoPage.PersonalizationPage, + Translator.SettingsPersonalization_Title, + Translator.SettingsPersonalization_Description, + "\uE771"), + new(WinoPage.AboutPage, + Translator.SettingsAbout_Title, + Translator.SettingsAbout_Description, + "\uE946"), + new(null, Translator.SettingsOptions_MailSection, string.Empty, "\uE715", isSeparator: true), + new(WinoPage.KeyboardShortcutsPage, + Translator.Settings_KeyboardShortcuts_Title, + Translator.Settings_KeyboardShortcuts_Description, + "\uE765"), + new(WinoPage.MessageListPage, + Translator.SettingsMessageList_Title, + Translator.SettingsMessageList_Description, + "\uE8C4"), + new(WinoPage.ReadComposePanePage, + Translator.SettingsReadComposePane_Title, + Translator.SettingsReadComposePane_Description, + "\uE8BD"), + new(WinoPage.SignatureAndEncryptionPage, + Translator.SettingsSignatureAndEncryption_Title, + Translator.SettingsSignatureAndEncryption_Description, + "\uE8D7"), + new(WinoPage.StoragePage, + Translator.SettingsStorage_Title, + Translator.SettingsStorage_Description, + "\uE81C"), + new(null, Translator.SettingsOptions_CalendarSection, string.Empty, "\uE787", isSeparator: true), + new(WinoPage.CalendarSettingsPage, + Translator.SettingsCalendarSettings_Title, + Translator.SettingsCalendarSettings_Description, + "\uE787") + ]; + } + + public static SettingsNavigationItemInfo GetInfo(WinoPage pageType, string manageAccountsDescription = "") + { + var rootPage = GetRootPage(pageType); + return GetNavigationItems(manageAccountsDescription).First(item => item.PageType == rootPage); + } + + public static string GetPageTitle(WinoPage pageType) + => pageType switch + { + WinoPage.SettingOptionsPage => Translator.MenuSettings, + WinoPage.ManageAccountsPage => Translator.SettingsManageAccountSettings_Title, + WinoPage.AccountManagementPage => Translator.SettingsManageAccountSettings_Title, + WinoPage.PersonalizationPage => Translator.SettingsPersonalization_Title, + WinoPage.AboutPage => Translator.SettingsAbout_Title, + WinoPage.MessageListPage => Translator.SettingsMessageList_Title, + WinoPage.ReadComposePanePage => Translator.SettingsReadComposePane_Title, + WinoPage.LanguageTimePage => Translator.SettingsLanguageTime_Title, + WinoPage.AppPreferencesPage => Translator.SettingsAppPreferences_Title, + WinoPage.CalendarSettingsPage => Translator.SettingsCalendarSettings_Title, + WinoPage.SignatureAndEncryptionPage => Translator.SettingsSignatureAndEncryption_Title, + WinoPage.KeyboardShortcutsPage => Translator.Settings_KeyboardShortcuts_Title, + WinoPage.StoragePage => Translator.SettingsStorage_Title, + WinoPage.EmailTemplatesPage => Translator.SettingsEmailTemplates_Title, + WinoPage.CreateEmailTemplatePage => Translator.SettingsEmailTemplates_Title, + _ => GetInfo(pageType).Title + }; + + public static WinoPage GetRootPage(WinoPage pageType) + => pageType switch + { + WinoPage.AccountManagementPage => WinoPage.ManageAccountsPage, + WinoPage.AccountDetailsPage => WinoPage.ManageAccountsPage, + WinoPage.MergedAccountDetailsPage => WinoPage.ManageAccountsPage, + WinoPage.AliasManagementPage => WinoPage.ManageAccountsPage, + WinoPage.SignatureManagementPage => WinoPage.ManageAccountsPage, + WinoPage.ImapCalDavSettingsPage => WinoPage.ManageAccountsPage, + WinoPage.EmailTemplatesPage => WinoPage.ManageAccountsPage, + WinoPage.CreateEmailTemplatePage => WinoPage.ManageAccountsPage, + WinoPage.CalendarAccountSettingsPage => WinoPage.CalendarSettingsPage, + _ => pageType + }; +} diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 97f7e975..34e24fc1 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -815,6 +815,7 @@ "SettingsNotifications_Title": "Notifications", "SettingsNotificationsAndTaskbar_Description": "Change whether notifications should be displayed and taskbar badge for this account.", "SettingsNotificationsAndTaskbar_Title": "Notifications & Taskbar", + "SettingsHome_Title": "Home", "SettingsOptions_Title": "Settings", "SettingsOptions_GeneralSection": "General", "SettingsOptions_MailSection": "Mail", diff --git a/Wino.Core.ViewModels/Data/KeyboardShortcutViewModel.cs b/Wino.Core.ViewModels/Data/KeyboardShortcutViewModel.cs index e664a701..975ac663 100644 --- a/Wino.Core.ViewModels/Data/KeyboardShortcutViewModel.cs +++ b/Wino.Core.ViewModels/Data/KeyboardShortcutViewModel.cs @@ -44,6 +44,7 @@ public partial class KeyboardShortcutViewModel : ObservableObject WinoApplicationMode.Mail => Translator.KeyboardShortcuts_ModeMail, WinoApplicationMode.Calendar => Translator.KeyboardShortcuts_ModeCalendar, WinoApplicationMode.Contacts => Translator.ContactsPage_Title, + WinoApplicationMode.Settings => Translator.MenuSettings, _ => Mode.ToString() }; diff --git a/Wino.Core.ViewModels/SettingOptionsPageViewModel.cs b/Wino.Core.ViewModels/SettingOptionsPageViewModel.cs index 7f33d834..4498e772 100644 --- a/Wino.Core.ViewModels/SettingOptionsPageViewModel.cs +++ b/Wino.Core.ViewModels/SettingOptionsPageViewModel.cs @@ -7,6 +7,7 @@ using Wino.Core.Domain; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Navigation; +using Wino.Core.Domain.Models.Settings; using Wino.Messaging.Client.Navigation; namespace Wino.Core.ViewModels; @@ -63,23 +64,8 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel { if (type is WinoPage pageType) { - string pageTitle = pageType switch - { - WinoPage.ManageAccountsPage => Translator.SettingsManageAccountSettings_Title, - WinoPage.PersonalizationPage => Translator.SettingsPersonalization_Title, - WinoPage.AboutPage => Translator.SettingsAbout_Title, - WinoPage.MessageListPage => Translator.SettingsMessageList_Title, - WinoPage.ReadComposePanePage => Translator.SettingsReadComposePane_Title, - WinoPage.LanguageTimePage => Translator.SettingsLanguageTime_Title, - WinoPage.AppPreferencesPage => Translator.SettingsAppPreferences_Title, - WinoPage.CalendarSettingsPage => Translator.SettingsCalendarSettings_Title, - WinoPage.SignatureAndEncryptionPage => Translator.SettingsSignatureAndEncryption_Title, - WinoPage.KeyboardShortcutsPage => Translator.Settings_KeyboardShortcuts_Title, - WinoPage.StoragePage => Translator.SettingsStorage_Title, - _ => throw new NotImplementedException() - }; - - Messenger.Send(new BreadcrumbNavigationRequested(pageTitle, pageType)); + var pageInfo = SettingsNavigationInfoProvider.GetInfo(pageType, AccountSummaryText); + Messenger.Send(new BreadcrumbNavigationRequested(pageInfo.Title, pageType)); } } diff --git a/Wino.Core.ViewModels/SettingsPageViewModel.cs b/Wino.Core.ViewModels/SettingsPageViewModel.cs index 47c91308..5df81444 100644 --- a/Wino.Core.ViewModels/SettingsPageViewModel.cs +++ b/Wino.Core.ViewModels/SettingsPageViewModel.cs @@ -1,15 +1,54 @@ -using Wino.Core.Domain.Interfaces; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Wino.Core.Domain; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Settings; namespace Wino.Core.ViewModels; -public class SettingsPageViewModel : CoreBaseViewModel +public partial class SettingsPageViewModel : CoreBaseViewModel { - public SettingsPageViewModel(INavigationService navigationService, IStatePersistanceService statePersistenceService) + private readonly IAccountService _accountService; + + public SettingsPageViewModel( + INavigationService navigationService, + IStatePersistanceService statePersistenceService, + IAccountService accountService) { NavigationService = navigationService; StatePersistenceService = statePersistenceService; + _accountService = accountService; } public INavigationService NavigationService { get; } public IStatePersistanceService StatePersistenceService { get; } + + [ObservableProperty] + public partial string CurrentDescription { get; set; } = string.Empty; + + [ObservableProperty] + public partial string ManageAccountsDescription { get; set; } = string.Empty; + + public async Task UpdateActivePageAsync(WinoPage pageType) + { + await EnsureAccountSummaryAsync(); + + var info = SettingsNavigationInfoProvider.GetInfo(pageType, ManageAccountsDescription); + await ExecuteUIThread(() => CurrentDescription = info.Description); + } + + private async Task EnsureAccountSummaryAsync() + { + if (!string.IsNullOrWhiteSpace(ManageAccountsDescription)) + return; + + var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false); + var count = accounts?.Count ?? 0; + + await ExecuteUIThread(() => + { + ManageAccountsDescription = string.Format(Translator.SettingsOptions_AccountsSummary, count); + }); + } } diff --git a/Wino.Mail.ViewModels/AppPreferencesPageViewModel.cs b/Wino.Mail.ViewModels/AppPreferencesPageViewModel.cs index 5c0ab298..2cc0df80 100644 --- a/Wino.Mail.ViewModels/AppPreferencesPageViewModel.cs +++ b/Wino.Mail.ViewModels/AppPreferencesPageViewModel.cs @@ -84,7 +84,8 @@ public partial class AppPreferencesPageViewModel : MailBaseViewModel [ Translator.SettingsAppPreferences_ApplicationMode_Mail, Translator.SettingsAppPreferences_ApplicationMode_Calendar, - Translator.ContactsPage_Title + Translator.ContactsPage_Title, + Translator.MenuSettings ]; SelectedDefaultSearchMode = SearchModes[(int)PreferencesService.DefaultSearchMode]; diff --git a/Wino.Mail.ViewModels/MailAppShellViewModel.cs b/Wino.Mail.ViewModels/MailAppShellViewModel.cs index fc668429..82e32754 100644 --- a/Wino.Mail.ViewModels/MailAppShellViewModel.cs +++ b/Wino.Mail.ViewModels/MailAppShellViewModel.cs @@ -1147,7 +1147,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel, public async void Receive(NavigateAppPreferencesRequested message) { - await MenuItemInvokedOrSelectedAsync(SettingsItem, WinoPage.AppPreferencesPage); + NavigationService.Navigate(WinoPage.SettingsPage, WinoPage.AppPreferencesPage); } protected override void RegisterRecipients() diff --git a/Wino.Mail.WinUI/Activation/AppModeActivationResolver.cs b/Wino.Mail.WinUI/Activation/AppModeActivationResolver.cs index 9a617203..965da52a 100644 --- a/Wino.Mail.WinUI/Activation/AppModeActivationResolver.cs +++ b/Wino.Mail.WinUI/Activation/AppModeActivationResolver.cs @@ -53,6 +53,16 @@ internal static class AppModeActivationResolver return true; } + if (Contains(value, "wino-settings") || + Contains(value, "--mode=settings") || + Contains(value, "mode=settings") || + Contains(value, "settingsapp") || + EqualsToken(value, "settings")) + { + mode = WinoApplicationMode.Settings; + return true; + } + if (Contains(value, "wino-mail") || Contains(value, "--mode=mail") || Contains(value, "mode=mail") || diff --git a/Wino.Mail.WinUI/App.xaml.cs b/Wino.Mail.WinUI/App.xaml.cs index 06bb04cf..d683c653 100644 --- a/Wino.Mail.WinUI/App.xaml.cs +++ b/Wino.Mail.WinUI/App.xaml.cs @@ -144,12 +144,14 @@ public partial class App : WinoApplication, services.AddSingleton(typeof(MailAppShellViewModel)); services.AddSingleton(typeof(CalendarAppShellViewModel)); services.AddSingleton(typeof(ContactsShellClient)); + services.AddSingleton(typeof(SettingsShellClient)); services.AddSingleton(typeof(WinoAppShellViewModel)); services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); + services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); services.AddTransient(typeof(MailListPageViewModel)); services.AddTransient(typeof(MailRenderingPageViewModel)); @@ -966,6 +968,7 @@ public partial class App : WinoApplication, { WinoApplicationMode.Calendar => "--mode=calendar", WinoApplicationMode.Contacts => "--mode=contacts", + WinoApplicationMode.Settings => "--mode=settings", _ => "--mode=mail" }; diff --git a/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml.cs b/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml.cs index cce5742f..c1069ee4 100644 --- a/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml.cs +++ b/Wino.Mail.WinUI/Controls/AppModeFooterSwitcherControl.xaml.cs @@ -64,17 +64,11 @@ public sealed partial class AppModeFooterSwitcherControl : Segmented if (_isUpdatingSelection) return; - if (SelectedIndex == 3) - { - _navigationService.Navigate(WinoPage.SettingsPage); - UpdateSelection(_statePersistenceService.ApplicationMode); - return; - } - var selectedMode = SelectedIndex switch { 1 => WinoApplicationMode.Calendar, 2 => WinoApplicationMode.Contacts, + 3 => WinoApplicationMode.Settings, _ => WinoApplicationMode.Mail }; @@ -91,6 +85,7 @@ public sealed partial class AppModeFooterSwitcherControl : Segmented { WinoApplicationMode.Calendar => 1, WinoApplicationMode.Contacts => 2, + WinoApplicationMode.Settings => 3, _ => 0 }; _isUpdatingSelection = false; diff --git a/Wino.Mail.WinUI/Selectors/NavigationMenuTemplateSelector.cs b/Wino.Mail.WinUI/Selectors/NavigationMenuTemplateSelector.cs index 030026c5..0bfdfe47 100644 --- a/Wino.Mail.WinUI/Selectors/NavigationMenuTemplateSelector.cs +++ b/Wino.Mail.WinUI/Selectors/NavigationMenuTemplateSelector.cs @@ -14,6 +14,8 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector public DataTemplate MergedAccountMoreExpansionItemTemplate { get; set; } = null!; public DataTemplate FolderMenuTemplate { get; set; } = null!; public DataTemplate SettingsItemTemplate { get; set; } = null!; + public DataTemplate SettingsShellPageItemTemplate { get; set; } = null!; + public DataTemplate SettingsShellSectionItemTemplate { get; set; } = null!; public DataTemplate StoreUpdateItemTemplate { get; set; } = null!; public DataTemplate MoreItemsFolderTemplate { get; set; } = null!; public DataTemplate RatingItemTemplate { get; set; } = null!; @@ -38,6 +40,10 @@ public partial class NavigationMenuTemplateSelector : DataTemplateSelector return ContactsMenuItemTemplate; else if (item is SettingsItem) return SettingsItemTemplate; + else if (item is SettingsShellPageMenuItem) + return SettingsShellPageItemTemplate; + else if (item is SettingsShellSectionMenuItem) + return SettingsShellSectionItemTemplate; else if (item is StoreUpdateMenuItem) return StoreUpdateItemTemplate; else if (item is SeperatorItem) diff --git a/Wino.Mail.WinUI/Services/NavigationService.cs b/Wino.Mail.WinUI/Services/NavigationService.cs index 10e63b43..e3bde408 100644 --- a/Wino.Mail.WinUI/Services/NavigationService.cs +++ b/Wino.Mail.WinUI/Services/NavigationService.cs @@ -8,6 +8,7 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Calendar; using Wino.Core.Domain.Models.Navigation; +using Wino.Core.Domain.Models.Settings; using Wino.Helpers; using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Messages; @@ -66,6 +67,32 @@ public class NavigationService : NavigationServiceBase, INavigationService WinoPage.ContactsPage ]; + private static readonly WinoPage[] SettingsOnlyPages = + [ + WinoPage.SettingsPage, + WinoPage.SettingOptionsPage, + WinoPage.ManageAccountsPage, + WinoPage.AccountManagementPage, + WinoPage.AccountDetailsPage, + WinoPage.MergedAccountDetailsPage, + WinoPage.SignatureManagementPage, + WinoPage.AboutPage, + WinoPage.PersonalizationPage, + WinoPage.MessageListPage, + WinoPage.ReadComposePanePage, + WinoPage.LanguageTimePage, + WinoPage.AppPreferencesPage, + WinoPage.AliasManagementPage, + WinoPage.ImapCalDavSettingsPage, + WinoPage.KeyboardShortcutsPage, + WinoPage.SignatureAndEncryptionPage, + WinoPage.EmailTemplatesPage, + WinoPage.CreateEmailTemplatePage, + WinoPage.StoragePage, + WinoPage.CalendarSettingsPage, + WinoPage.CalendarAccountSettingsPage + ]; + public NavigationService(IStatePersistanceService statePersistanceService, IDispatcher dispatcher, IWinoWindowManager windowManager) { _statePersistanceService = statePersistanceService; @@ -186,7 +213,7 @@ public class NavigationService : NavigationServiceBase, INavigationService public bool ChangeApplicationMode(WinoApplicationMode mode) => ExecuteOnNavigationThread(() => ChangeApplicationModeInternal(mode)); - private bool ChangeApplicationModeInternal(WinoApplicationMode mode) + private bool ChangeApplicationModeInternal(WinoApplicationMode mode, object? activationParameter = null) { var coreFrame = GetCoreFrameInternal(NavigationReferenceFrame.ShellFrame); @@ -217,7 +244,8 @@ public class NavigationService : NavigationServiceBase, INavigationService { shell.ActivateMode(mode, new ShellModeActivationContext { - IsInitialActivation = isInitialShellNavigation + IsInitialActivation = isInitialShellNavigation, + Parameter = activationParameter }); return true; } @@ -237,9 +265,15 @@ public class NavigationService : NavigationServiceBase, INavigationService NavigationReferenceFrame frame = NavigationReferenceFrame.InnerShellFrame, NavigationTransitionType transition = NavigationTransitionType.None) { - if (page is WinoPage.ManageAccountsPage or WinoPage.AccountManagementPage) + if (TryGetSettingsActivationTarget(page, parameter, out var settingsTarget)) { - return NavigateInternal(WinoPage.SettingsPage, WinoPage.ManageAccountsPage, frame, transition); + if (_statePersistanceService.ApplicationMode != WinoApplicationMode.Settings) + { + return ChangeApplicationModeInternal(WinoApplicationMode.Settings, settingsTarget); + } + + page = WinoPage.SettingsPage; + parameter = settingsTarget; } var pageType = GetPageType(page); @@ -372,12 +406,16 @@ public class NavigationService : NavigationServiceBase, INavigationService private static bool IsContactsOnlyPage(WinoPage page) => ContactsOnlyPages.Contains(page); + private static bool IsSettingsOnlyPage(WinoPage page) + => SettingsOnlyPages.Contains(page); + private static bool IsPageAllowedInMode(WinoApplicationMode mode, WinoPage page) => mode switch { - WinoApplicationMode.Mail => !IsCalendarOnlyPage(page) && !IsContactsOnlyPage(page), - WinoApplicationMode.Calendar => !IsMailOnlyPage(page) && !IsContactsOnlyPage(page), - WinoApplicationMode.Contacts => !IsMailOnlyPage(page) && !IsCalendarOnlyPage(page), + WinoApplicationMode.Mail => !IsCalendarOnlyPage(page) && !IsContactsOnlyPage(page) && !IsSettingsOnlyPage(page), + WinoApplicationMode.Calendar => !IsMailOnlyPage(page) && !IsContactsOnlyPage(page) && !IsSettingsOnlyPage(page), + WinoApplicationMode.Contacts => !IsMailOnlyPage(page) && !IsCalendarOnlyPage(page) && !IsSettingsOnlyPage(page), + WinoApplicationMode.Settings => IsSettingsOnlyPage(page), _ => true }; @@ -386,6 +424,7 @@ public class NavigationService : NavigationServiceBase, INavigationService { WinoApplicationMode.Calendar => "Wino Calendar", WinoApplicationMode.Contacts => "Wino Contacts", + WinoApplicationMode.Settings => "Wino Settings", _ => "Wino Mail" }; @@ -406,10 +445,28 @@ public class NavigationService : NavigationServiceBase, INavigationService { WinoApplicationMode.Mail => targetMode == WinoApplicationMode.Calendar, WinoApplicationMode.Calendar => targetMode == WinoApplicationMode.Contacts, - WinoApplicationMode.Contacts => targetMode == WinoApplicationMode.Mail, + WinoApplicationMode.Contacts => targetMode == WinoApplicationMode.Settings, + WinoApplicationMode.Settings => targetMode == WinoApplicationMode.Mail, _ => false }; + private static bool TryGetSettingsActivationTarget(WinoPage page, object? parameter, out WinoPage targetPage) + { + targetPage = WinoPage.SettingOptionsPage; + + if (page == WinoPage.SettingsPage) + { + targetPage = parameter as WinoPage? ?? WinoPage.SettingOptionsPage; + return true; + } + + if (!IsSettingsOnlyPage(page)) + return false; + + targetPage = SettingsNavigationInfoProvider.GetRootPage(page); + return true; + } + private static LoadCalendarMessage CreateLoadCalendarMessage(CalendarPageNavigationArgs args) { var targetDate = args.RequestDefaultNavigation diff --git a/Wino.Mail.WinUI/Styles/CalendarShellNavigationViewStyle.xaml b/Wino.Mail.WinUI/Styles/CalendarShellNavigationViewStyle.xaml index d8c64cd3..08579c05 100644 --- a/Wino.Mail.WinUI/Styles/CalendarShellNavigationViewStyle.xaml +++ b/Wino.Mail.WinUI/Styles/CalendarShellNavigationViewStyle.xaml @@ -365,6 +365,7 @@ diff --git a/Wino.Mail.WinUI/Styles/DataTemplates.xaml b/Wino.Mail.WinUI/Styles/DataTemplates.xaml index d914a61a..3abe45d7 100644 --- a/Wino.Mail.WinUI/Styles/DataTemplates.xaml +++ b/Wino.Mail.WinUI/Styles/DataTemplates.xaml @@ -53,6 +53,39 @@ + + + + + + + + + + + + + + + + + + diff --git a/Wino.Mail.WinUI/ViewModels/SettingsShellClient.cs b/Wino.Mail.WinUI/ViewModels/SettingsShellClient.cs new file mode 100644 index 00000000..5b7dbc84 --- /dev/null +++ b/Wino.Mail.WinUI/ViewModels/SettingsShellClient.cs @@ -0,0 +1,149 @@ +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +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.Domain.Models.Settings; +using Wino.Core.ViewModels; +using Wino.Messaging.Client.Navigation; +using Wino.Messaging.Client.Shell; + +namespace Wino.Mail.WinUI.ViewModels; + +public partial class SettingsShellClient(INavigationService navigationService) : + CoreBaseViewModel, + IShellClient, + IRecipient, + IRecipient +{ + public WinoApplicationMode Mode => WinoApplicationMode.Settings; + public MenuItemCollection? MenuItems { get; private set; } + + [ObservableProperty] + public partial object? SelectedMenuItem { get; set; } = null; + + public bool HandlesNavigationSelection => true; + + protected override void OnDispatcherAssigned() + { + base.OnDispatcherAssigned(); + MenuItems ??= new MenuItemCollection(Dispatcher); + RebuildMenuItems(); + } + + public void Activate(ShellModeActivationContext activationContext) + { + RebuildMenuItems(); + + var targetPage = activationContext.Parameter as WinoPage? ?? WinoPage.SettingOptionsPage; + SetSelectedRootPage(SettingsNavigationInfoProvider.GetRootPage(targetPage)); + OnNavigatedTo(NavigationMode.New, activationContext); + + navigationService.Navigate(WinoPage.SettingsPage, targetPage, NavigationReferenceFrame.InnerShellFrame); + } + + public void Deactivate() + { + OnNavigatedFrom(NavigationMode.New, null!); + } + + public Task HandleNavigationItemInvokedAsync(IMenuItem? menuItem) + { + if (menuItem is not SettingsShellPageMenuItem settingsMenuItem) + return Task.CompletedTask; + + var currentPage = (SelectedMenuItem as SettingsShellPageMenuItem)?.PageType; + if (currentPage == settingsMenuItem.PageType && settingsMenuItem.PageType != WinoPage.SettingOptionsPage) + return Task.CompletedTask; + + SetSelectedRootPage(settingsMenuItem.PageType); + Messenger.Send(new SettingsRootNavigationRequested(settingsMenuItem.PageType)); + return Task.CompletedTask; + } + + public Task HandleNavigationSelectionChangedAsync(IMenuItem? menuItem) + { + if (menuItem is not SettingsShellPageMenuItem settingsMenuItem) + return Task.CompletedTask; + + if ((SelectedMenuItem as SettingsShellPageMenuItem)?.PageType == settingsMenuItem.PageType) + return Task.CompletedTask; + + SetSelectedRootPage(settingsMenuItem.PageType); + Messenger.Send(new SettingsRootNavigationRequested(settingsMenuItem.PageType)); + return Task.CompletedTask; + } + + public override Task KeyboardShortcutHook(KeyboardShortcutTriggerDetails args) => Task.CompletedTask; + + public void Receive(ActiveSettingsPageChanged message) + { + SetSelectedRootPage(message.RootPage); + } + + public void Receive(LanguageChanged message) + { + var selectedPage = (SelectedMenuItem as SettingsShellPageMenuItem)?.PageType ?? WinoPage.SettingOptionsPage; + RebuildMenuItems(); + SetSelectedRootPage(selectedPage); + } + + private void RebuildMenuItems() + { + if (MenuItems == null) + return; + + var selectedPage = (SelectedMenuItem as SettingsShellPageMenuItem)?.PageType ?? WinoPage.SettingOptionsPage; + + MenuItems.Clear(); + + foreach (var item in SettingsNavigationInfoProvider.GetNavigationItems()) + { + if (item.IsSeparator) + { + MenuItems.Add(new SettingsShellSectionMenuItem(item.Title, item.Glyph)); + continue; + } + + if (!item.PageType.HasValue) + continue; + + MenuItems.Add(new SettingsShellPageMenuItem(item.PageType.Value, item.Title, item.Description, item.Glyph)); + } + + SetSelectedRootPage(selectedPage); + } + + private void SetSelectedRootPage(WinoPage pageType) + { + if (MenuItems == null) + return; + + var rootPage = SettingsNavigationInfoProvider.GetRootPage(pageType); + var selectedItem = MenuItems.OfType() + .FirstOrDefault(item => item.PageType == rootPage); + + if (ReferenceEquals(SelectedMenuItem, selectedItem)) + return; + + SelectedMenuItem = selectedItem; + } + + protected override void RegisterRecipients() + { + base.RegisterRecipients(); + Messenger.Register(this); + Messenger.Register(this); + } + + protected override void UnregisterRecipients() + { + base.UnregisterRecipients(); + Messenger.Unregister(this); + Messenger.Unregister(this); + } +} diff --git a/Wino.Mail.WinUI/ViewModels/WinoAppShellViewModel.cs b/Wino.Mail.WinUI/ViewModels/WinoAppShellViewModel.cs index d32d41ca..a741a3cc 100644 --- a/Wino.Mail.WinUI/ViewModels/WinoAppShellViewModel.cs +++ b/Wino.Mail.WinUI/ViewModels/WinoAppShellViewModel.cs @@ -54,6 +54,7 @@ public sealed class WinoAppShellViewModel : CoreBaseViewModel, IShellViewModel OnPropertyChanged(nameof(IsMailMode)); OnPropertyChanged(nameof(IsCalendarMode)); OnPropertyChanged(nameof(IsContactsMode)); + OnPropertyChanged(nameof(IsSettingsMode)); OnPropertyChanged(nameof(SelectedMenuItem)); } } @@ -63,6 +64,7 @@ public sealed class WinoAppShellViewModel : CoreBaseViewModel, IShellViewModel public bool IsMailMode => CurrentMode == WinoApplicationMode.Mail; public bool IsCalendarMode => CurrentMode == WinoApplicationMode.Calendar; public bool IsContactsMode => CurrentMode == WinoApplicationMode.Contacts; + public bool IsSettingsMode => CurrentMode == WinoApplicationMode.Settings; public MenuItemCollection? CurrentMenuItems => CurrentClient.MenuItems; public object? SelectedMenuItem diff --git a/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml b/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml index 8c7ba9c8..19cf2602 100644 --- a/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml +++ b/Wino.Mail.WinUI/Views/Account/AccountDetailsPage.xaml @@ -81,7 +81,10 @@ - + diff --git a/Wino.Mail.WinUI/Views/SettingOptionsPage.xaml b/Wino.Mail.WinUI/Views/SettingOptionsPage.xaml index 75f21d54..ca110a67 100644 --- a/Wino.Mail.WinUI/Views/SettingOptionsPage.xaml +++ b/Wino.Mail.WinUI/Views/SettingOptionsPage.xaml @@ -9,18 +9,17 @@ xmlns:enums="using:Wino.Core.Domain.Enums" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Name="root" - Title="{x:Bind domain:Translator.SettingsOptions_Title}" + Title="{x:Bind domain:Translator.SettingsHome_Title}" mc:Ignorable="d"> - + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Wino.Mail.WinUI/Views/SettingsPage.xaml b/Wino.Mail.WinUI/Views/SettingsPage.xaml index ee73fc85..412f3881 100644 --- a/Wino.Mail.WinUI/Views/SettingsPage.xaml +++ b/Wino.Mail.WinUI/Views/SettingsPage.xaml @@ -18,6 +18,7 @@ HorizontalAlignment="Stretch" RowSpacing="20"> + @@ -43,7 +44,15 @@ - + + + diff --git a/Wino.Mail.WinUI/Views/SettingsPage.xaml.cs b/Wino.Mail.WinUI/Views/SettingsPage.xaml.cs index 4bf25b17..fa5fa562 100644 --- a/Wino.Mail.WinUI/Views/SettingsPage.xaml.cs +++ b/Wino.Mail.WinUI/Views/SettingsPage.xaml.cs @@ -1,10 +1,12 @@ using System.Collections.ObjectModel; using System.Linq; +using System.Threading.Tasks; using CommunityToolkit.Mvvm.Messaging; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Navigation; using Wino.Core.Domain; using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Models.Settings; using Wino.Helpers; using Wino.Mail.ViewModels.Data; using Wino.Messaging.Client.Navigation; @@ -17,6 +19,7 @@ namespace Wino.Views; public sealed partial class SettingsPage : SettingsPageAbstract, IRecipient, IRecipient, + IRecipient, IRecipient, IRecipient { @@ -34,39 +37,9 @@ public sealed partial class SettingsPage : SettingsPageAbstract, // Register for frame navigation events to track back button visibility SettingsFrame.Navigated -= SettingsFrameNavigated; SettingsFrame.Navigated += SettingsFrameNavigated; - PageHistory.Clear(); - SettingsFrame.BackStack.Clear(); - SettingsFrame.ForwardStack.Clear(); - SettingsFrame.Navigate(typeof(SettingOptionsPage), null, new SuppressNavigationTransitionInfo()); - - var initialRequest = new BreadcrumbNavigationRequested(Translator.MenuSettings, WinoPage.SettingOptionsPage); - PageHistory.Add(new BreadcrumbNavigationItemViewModel(initialRequest, true, backStackDepth: SettingsFrame.BackStack.Count + 1)); - - if (e.Parameter is WinoPage parameterPage) - { - switch (parameterPage) - { - case WinoPage.AppPreferencesPage: - NavigateBreadcrumb(new BreadcrumbNavigationRequested(Translator.SettingsAppPreferences_Title, WinoPage.AppPreferencesPage)); - break; - case WinoPage.PersonalizationPage: - NavigateBreadcrumb(new BreadcrumbNavigationRequested(Translator.SettingsPersonalization_Title, WinoPage.PersonalizationPage)); - break; - case WinoPage.StoragePage: - NavigateBreadcrumb(new BreadcrumbNavigationRequested(Translator.SettingsStorage_Title, WinoPage.StoragePage)); - break; - case WinoPage.EmailTemplatesPage: - NavigateBreadcrumb(new BreadcrumbNavigationRequested(Translator.SettingsEmailTemplates_Title, WinoPage.EmailTemplatesPage)); - break; - case WinoPage.ManageAccountsPage: - case WinoPage.AccountManagementPage: - NavigateBreadcrumb(new BreadcrumbNavigationRequested(Translator.SettingsManageAccountSettings_Title, WinoPage.ManageAccountsPage)); - break; - } - } - - UpdateWindowTitle(); + var initialPage = e.Parameter as WinoPage? ?? WinoPage.SettingOptionsPage; + NavigateToRootPage(initialPage); } public override void OnLanguageChanged() @@ -88,6 +61,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract, manageAccountsEntry.Title = Translator.SettingsManageAccountSettings_Title; } + _ = RefreshCurrentPageStateAsync(); UpdateWindowTitle(); } @@ -108,6 +82,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract, WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); + WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); } @@ -118,6 +93,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract, WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); + WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); WeakReferenceMessenger.Default.Unregister(this); } @@ -129,8 +105,8 @@ public sealed partial class SettingsPage : SettingsPageAbstract, private void SettingsFrameNavigated(object sender, NavigationEventArgs e) { - // Update back button visibility based on whether we can go back within the frame - ViewModel.StatePersistenceService.IsSettingsNavigating = SettingsFrame.CanGoBack; + UpdateBackNavigationState(); + _ = RefreshCurrentPageStateAsync(); } private void GoBackFrame(Core.Domain.Enums.NavigationTransitionEffect slideEffect) @@ -138,7 +114,8 @@ public sealed partial class SettingsPage : SettingsPageAbstract, if (!BreadcrumbNavigationHelper.GoBack(SettingsFrame, PageHistory, slideEffect)) return; - ViewModel.StatePersistenceService.IsSettingsNavigating = SettingsFrame.CanGoBack; + UpdateBackNavigationState(); + _ = RefreshCurrentPageStateAsync(); UpdateWindowTitle(); } @@ -147,7 +124,8 @@ public sealed partial class SettingsPage : SettingsPageAbstract, if (!BreadcrumbNavigationHelper.NavigateTo(SettingsFrame, PageHistory, args.Index)) return; - ViewModel.StatePersistenceService.IsSettingsNavigating = SettingsFrame.CanGoBack; + UpdateBackNavigationState(); + _ = RefreshCurrentPageStateAsync(); UpdateWindowTitle(); } @@ -156,6 +134,15 @@ public sealed partial class SettingsPage : SettingsPageAbstract, GoBackFrame(message.SlideEffect); } + public void Receive(SettingsRootNavigationRequested message) + { + var currentRootPage = SettingsNavigationInfoProvider.GetRootPage(PageHistory.LastOrDefault()?.Request.PageType ?? WinoPage.SettingOptionsPage); + if (message.PageType != WinoPage.SettingOptionsPage && currentRootPage == message.PageType) + return; + + NavigateToRootPage(message.PageType); + } + public void Receive(AccountUpdatedMessage message) { var activePage = PageHistory.LastOrDefault(a => a.Request.PageType == WinoPage.AccountDetailsPage); @@ -166,6 +153,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract, DispatcherQueue.TryEnqueue(() => { activePage.Title = message.Account.Name; + _ = RefreshCurrentPageStateAsync(); UpdateWindowTitle(); }); } @@ -180,6 +168,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract, DispatcherQueue.TryEnqueue(() => { activePage.Title = message.NewName; + _ = RefreshCurrentPageStateAsync(); UpdateWindowTitle(); }); } @@ -189,9 +178,43 @@ public sealed partial class SettingsPage : SettingsPageAbstract, if (!BreadcrumbNavigationHelper.Navigate(SettingsFrame, PageHistory, message, ViewModel.NavigationService.GetPageType)) return; + UpdateBackNavigationState(); + _ = RefreshCurrentPageStateAsync(); UpdateWindowTitle(); } + private void NavigateToRootPage(WinoPage targetPage) + { + PageHistory.Clear(); + SettingsFrame.BackStack.Clear(); + SettingsFrame.ForwardStack.Clear(); + + NavigateBreadcrumb(new BreadcrumbNavigationRequested(Translator.MenuSettings, WinoPage.SettingOptionsPage)); + + if (targetPage != WinoPage.SettingOptionsPage) + { + NavigateBreadcrumb(new BreadcrumbNavigationRequested( + SettingsNavigationInfoProvider.GetPageTitle(targetPage), + targetPage)); + return; + } + + UpdateWindowTitle(); + } + + private void UpdateBackNavigationState() + { + ViewModel.StatePersistenceService.IsSettingsNavigating = PageHistory.Count > 1 && SettingsFrame.CanGoBack; + } + + private async Task RefreshCurrentPageStateAsync() + { + var activePage = PageHistory.LastOrDefault()?.Request.PageType ?? WinoPage.SettingOptionsPage; + var rootPage = SettingsNavigationInfoProvider.GetRootPage(activePage); + await ViewModel.UpdateActivePageAsync(rootPage); + WeakReferenceMessenger.Default.Send(new ActiveSettingsPageChanged(rootPage)); + } + private void UpdateWindowTitle() { var activeTitle = PageHistory.LastOrDefault()?.Title; diff --git a/Wino.Mail.WinUI/Views/WinoAppShell.xaml b/Wino.Mail.WinUI/Views/WinoAppShell.xaml index 32c5c10d..021ac589 100644 --- a/Wino.Mail.WinUI/Views/WinoAppShell.xaml +++ b/Wino.Mail.WinUI/Views/WinoAppShell.xaml @@ -425,6 +425,8 @@ NewMailTemplate="{StaticResource CreateNewMailTemplate}" RatingItemTemplate="{StaticResource RatingItemTemplate}" SeperatorTemplate="{StaticResource SeperatorTemplate}" + SettingsShellPageItemTemplate="{StaticResource SettingsShellPageItemTemplate}" + SettingsShellSectionItemTemplate="{StaticResource SettingsShellSectionItemTemplate}" StoreUpdateItemTemplate="{StaticResource StoreUpdateItemTemplate}" />