navigation improvements

This commit is contained in:
Burak Kaan Köse
2026-03-14 14:14:58 +01:00
parent 4ba7d5fd07
commit 56b0f79edc
20 changed files with 605 additions and 153 deletions
@@ -416,7 +416,15 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
return; return;
} }
pickedCalendar = await _dialogService.ShowSingleCalendarPickerDialogAsync(availableGroups); var pickingResult = await _dialogService.ShowSingleCalendarPickerDialogAsync(availableGroups);
if (pickingResult.ShouldNavigateToCalendarSettings)
{
NavigationService.Navigate(WinoPage.CalendarSettingsPage);
return;
}
pickedCalendar = pickingResult.PickedCalendar;
} }
if (pickedCalendar == null) if (pickedCalendar == null)
@@ -20,7 +20,7 @@ public interface IMailDialogService : IDialogServiceBase
// Custom dialogs // Custom dialogs
Task<IMailItemFolder> ShowMoveMailFolderDialogAsync(List<IMailItemFolder> availableFolders); Task<IMailItemFolder> ShowMoveMailFolderDialogAsync(List<IMailItemFolder> availableFolders);
Task<MailAccount> ShowAccountPickerDialogAsync(List<MailAccount> availableAccounts); Task<MailAccount> ShowAccountPickerDialogAsync(List<MailAccount> availableAccounts);
Task<AccountCalendar> ShowSingleCalendarPickerDialogAsync(List<CalendarPickerAccountGroup> availableCalendarGroups); Task<AccountCalendarPickingResult> ShowSingleCalendarPickerDialogAsync(List<CalendarPickerAccountGroup> availableCalendarGroups);
/// <summary> /// <summary>
/// Displays a dialog to the user for reordering accounts. /// Displays a dialog to the user for reordering accounts.
@@ -36,6 +36,7 @@ public interface IMailShellClient : IShellClient
IMenuItem CreatePrimaryMenuItem { get; } IMenuItem CreatePrimaryMenuItem { get; }
IEnumerable<FolderOperationMenuItem> GetFolderContextMenuActions(IBaseFolderMenuItem folder); IEnumerable<FolderOperationMenuItem> GetFolderContextMenuActions(IBaseFolderMenuItem folder);
Task HandleAccountCreatedAsync(MailAccount createdAccount);
Task NavigateFolderAsync(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool>? folderInitAwaitTask = null); Task NavigateFolderAsync(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool>? folderInitAwaitTask = null);
Task ChangeLoadedAccountAsync(IAccountMenuItem clickedBaseAccountMenuItem, bool navigateInbox = true); Task ChangeLoadedAccountAsync(IAccountMenuItem clickedBaseAccountMenuItem, bool navigateInbox = true);
Task PerformFolderOperationAsync(FolderOperation operation, IBaseFolderMenuItem folderMenuItem); Task PerformFolderOperationAsync(FolderOperation operation, IBaseFolderMenuItem folderMenuItem);
@@ -45,9 +45,9 @@ public interface IStatePersistanceService : INotifyPropertyChanged
bool IsEventDetailsVisible { get; set; } bool IsEventDetailsVisible { get; set; }
/// <summary> /// <summary>
/// Whether SettingsPage has navigated to a sub-page and can go back. /// Whether the current application mode has an active backstack that can be navigated.
/// </summary> /// </summary>
bool IsSettingsNavigating { get; set; } bool HasCurrentModeBackStack { get; set; }
/// <summary> /// <summary>
/// Setting: Opened pane length for the navigation view. /// Setting: Opened pane length for the navigation view.
@@ -0,0 +1,7 @@
#nullable enable
using Wino.Core.Domain.Entities.Calendar;
namespace Wino.Core.Domain.Models.Calendar;
public sealed record AccountCalendarPickingResult(AccountCalendar? PickedCalendar, bool ShouldNavigateToCalendarSettings);
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
@@ -9,13 +10,15 @@ public sealed class SettingsNavigationItemInfo(
string title, string title,
string description, string description,
string glyph = "", string glyph = "",
bool isSeparator = false) bool isSeparator = false,
string searchKeywords = "")
{ {
public WinoPage? PageType { get; } = pageType; public WinoPage? PageType { get; } = pageType;
public string Title { get; } = title; public string Title { get; } = title;
public string Description { get; } = description; public string Description { get; } = description;
public string Glyph { get; } = glyph; public string Glyph { get; } = glyph;
public bool IsSeparator { get; } = isSeparator; public bool IsSeparator { get; } = isSeparator;
public string SearchKeywords { get; } = searchKeywords;
} }
public static class SettingsNavigationInfoProvider public static class SettingsNavigationInfoProvider
@@ -31,53 +34,90 @@ public static class SettingsNavigationInfoProvider
new(WinoPage.ManageAccountsPage, new(WinoPage.ManageAccountsPage,
Translator.SettingsManageAccountSettings_Title, Translator.SettingsManageAccountSettings_Title,
manageAccountsDescription, manageAccountsDescription,
"\uE77B"), "\uE77B",
searchKeywords: Translator.SettingsSearch_ManageAccounts_Keywords),
new(null, Translator.SettingsOptions_GeneralSection, string.Empty, "\uE713", isSeparator: true), new(null, Translator.SettingsOptions_GeneralSection, string.Empty, "\uE713", isSeparator: true),
new(WinoPage.AppPreferencesPage, new(WinoPage.AppPreferencesPage,
Translator.SettingsAppPreferences_Title, Translator.SettingsAppPreferences_Title,
Translator.SettingsAppPreferences_Description, Translator.SettingsAppPreferences_Description,
"\uE770"), "\uE770",
searchKeywords: Translator.SettingsSearch_AppPreferences_Keywords),
new(WinoPage.LanguageTimePage, new(WinoPage.LanguageTimePage,
Translator.SettingsLanguageTime_Title, Translator.SettingsLanguageTime_Title,
Translator.SettingsLanguageTime_Description, Translator.SettingsLanguageTime_Description,
"\uE775"), "\uE775",
searchKeywords: Translator.SettingsSearch_LanguageTime_Keywords),
new(WinoPage.PersonalizationPage, new(WinoPage.PersonalizationPage,
Translator.SettingsPersonalization_Title, Translator.SettingsPersonalization_Title,
Translator.SettingsPersonalization_Description, Translator.SettingsPersonalization_Description,
"\uE771"), "\uE771",
searchKeywords: Translator.SettingsSearch_Personalization_Keywords),
new(WinoPage.AboutPage, new(WinoPage.AboutPage,
Translator.SettingsAbout_Title, Translator.SettingsAbout_Title,
Translator.SettingsAbout_Description, Translator.SettingsAbout_Description,
"\uE946"), "\uE946",
searchKeywords: Translator.SettingsSearch_About_Keywords),
new(null, Translator.SettingsOptions_MailSection, string.Empty, "\uE715", isSeparator: true), new(null, Translator.SettingsOptions_MailSection, string.Empty, "\uE715", isSeparator: true),
new(WinoPage.KeyboardShortcutsPage, new(WinoPage.KeyboardShortcutsPage,
Translator.Settings_KeyboardShortcuts_Title, Translator.Settings_KeyboardShortcuts_Title,
Translator.Settings_KeyboardShortcuts_Description, Translator.Settings_KeyboardShortcuts_Description,
"\uE765"), "\uE765",
searchKeywords: Translator.SettingsSearch_KeyboardShortcuts_Keywords),
new(WinoPage.MessageListPage, new(WinoPage.MessageListPage,
Translator.SettingsMessageList_Title, Translator.SettingsMessageList_Title,
Translator.SettingsMessageList_Description, Translator.SettingsMessageList_Description,
"\uE8C4"), "\uE8C4",
searchKeywords: Translator.SettingsSearch_MessageList_Keywords),
new(WinoPage.ReadComposePanePage, new(WinoPage.ReadComposePanePage,
Translator.SettingsReadComposePane_Title, Translator.SettingsReadComposePane_Title,
Translator.SettingsReadComposePane_Description, Translator.SettingsReadComposePane_Description,
"\uE8BD"), "\uE8BD",
searchKeywords: Translator.SettingsSearch_ReadComposePane_Keywords),
new(WinoPage.SignatureAndEncryptionPage, new(WinoPage.SignatureAndEncryptionPage,
Translator.SettingsSignatureAndEncryption_Title, Translator.SettingsSignatureAndEncryption_Title,
Translator.SettingsSignatureAndEncryption_Description, Translator.SettingsSignatureAndEncryption_Description,
"\uE8D7"), "\uE8D7",
searchKeywords: Translator.SettingsSearch_SignatureAndEncryption_Keywords),
new(WinoPage.StoragePage, new(WinoPage.StoragePage,
Translator.SettingsStorage_Title, Translator.SettingsStorage_Title,
Translator.SettingsStorage_Description, Translator.SettingsStorage_Description,
"\uE81C"), "\uE81C",
searchKeywords: Translator.SettingsSearch_Storage_Keywords),
new(null, Translator.SettingsOptions_CalendarSection, string.Empty, "\uE787", isSeparator: true), new(null, Translator.SettingsOptions_CalendarSection, string.Empty, "\uE787", isSeparator: true),
new(WinoPage.CalendarSettingsPage, new(WinoPage.CalendarSettingsPage,
Translator.SettingsCalendarSettings_Title, Translator.SettingsCalendarSettings_Title,
Translator.SettingsCalendarSettings_Description, Translator.SettingsCalendarSettings_Description,
"\uE787") "\uE787",
searchKeywords: Translator.SettingsSearch_CalendarSettings_Keywords)
]; ];
} }
public static IReadOnlyList<SettingsNavigationItemInfo> Search(string query, string manageAccountsDescription = "")
{
if (string.IsNullOrWhiteSpace(query))
return [];
var normalizedQuery = NormalizeSearchText(query);
if (string.IsNullOrWhiteSpace(normalizedQuery))
return [];
var queryTerms = normalizedQuery.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return GetNavigationItems(manageAccountsDescription)
.Where(item => item.PageType.HasValue && !item.IsSeparator && item.PageType.Value != WinoPage.SettingOptionsPage)
.Select(item => new
{
Item = item,
Score = CalculateSearchScore(item, normalizedQuery, queryTerms)
})
.Where(x => x.Score > 0)
.OrderByDescending(x => x.Score)
.ThenBy(x => x.Item.Title)
.Select(x => x.Item)
.ToList();
}
public static SettingsNavigationItemInfo GetInfo(WinoPage pageType, string manageAccountsDescription = "") public static SettingsNavigationItemInfo GetInfo(WinoPage pageType, string manageAccountsDescription = "")
{ {
var rootPage = GetRootPage(pageType); var rootPage = GetRootPage(pageType);
@@ -119,4 +159,58 @@ public static class SettingsNavigationInfoProvider
WinoPage.CalendarAccountSettingsPage => WinoPage.CalendarSettingsPage, WinoPage.CalendarAccountSettingsPage => WinoPage.CalendarSettingsPage,
_ => pageType _ => pageType
}; };
private static int CalculateSearchScore(SettingsNavigationItemInfo item, string normalizedQuery, IReadOnlyList<string> queryTerms)
{
var title = NormalizeSearchText(item.Title);
var description = NormalizeSearchText(item.Description);
var keywords = NormalizeSearchText(item.SearchKeywords);
var combinedText = string.Join(' ', new[] { title, description, keywords }.Where(text => !string.IsNullOrWhiteSpace(text)));
if (!combinedText.Contains(normalizedQuery, StringComparison.Ordinal) &&
!queryTerms.All(term => combinedText.Contains(term, StringComparison.Ordinal)))
{
return 0;
}
var score = 0;
if (title.StartsWith(normalizedQuery, StringComparison.Ordinal))
score += 500;
else if (title.Contains(normalizedQuery, StringComparison.Ordinal))
score += 360;
if (keywords.Contains(normalizedQuery, StringComparison.Ordinal))
score += 280;
if (description.Contains(normalizedQuery, StringComparison.Ordinal))
score += 180;
foreach (var term in queryTerms)
{
if (title.Contains(term, StringComparison.Ordinal))
score += 70;
if (keywords.Contains(term, StringComparison.Ordinal))
score += 50;
if (description.Contains(term, StringComparison.Ordinal))
score += 30;
}
return score;
}
private static string NormalizeSearchText(string value)
{
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
var sanitized = value
.ToLowerInvariant()
.Select(character => char.IsLetterOrDigit(character) ? character : ' ')
.ToArray();
return string.Join(' ', new string(sanitized).Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
}
} }
@@ -143,6 +143,8 @@
"CalendarEventCompose_Location": "Location", "CalendarEventCompose_Location": "Location",
"CalendarEventCompose_LocationPlaceholder": "Add a location", "CalendarEventCompose_LocationPlaceholder": "Add a location",
"CalendarEventCompose_NewEventButton": "New Event", "CalendarEventCompose_NewEventButton": "New Event",
"CalendarEventCompose_DefaultCalendarHint": "You can choose a default calendar for new events in Calendar settings.",
"CalendarEventCompose_DefaultCalendarSettingsLink": "Open Calendar settings",
"CalendarEventCompose_NoCalendarsMessage": "There are no calendars available for event creation yet.", "CalendarEventCompose_NoCalendarsMessage": "There are no calendars available for event creation yet.",
"CalendarEventCompose_NoCalendarsTitle": "No calendars available", "CalendarEventCompose_NoCalendarsTitle": "No calendars available",
"CalendarEventCompose_NoEndDate": "No end date", "CalendarEventCompose_NoEndDate": "No end date",
@@ -816,6 +818,22 @@
"SettingsNotificationsAndTaskbar_Description": "Change whether notifications should be displayed and taskbar badge for this account.", "SettingsNotificationsAndTaskbar_Description": "Change whether notifications should be displayed and taskbar badge for this account.",
"SettingsNotificationsAndTaskbar_Title": "Notifications & Taskbar", "SettingsNotificationsAndTaskbar_Title": "Notifications & Taskbar",
"SettingsHome_Title": "Home", "SettingsHome_Title": "Home",
"SettingsHome_SearchTitle": "Find a setting",
"SettingsHome_SearchDescription": "Search by feature, topic, or keyword to jump straight to the right settings page.",
"SettingsHome_SearchPlaceholder": "Search settings",
"SettingsHome_SearchExamples": "Try: theme, storage, language, signature",
"SettingsHome_QuickLinks_Title": "Quick links",
"SettingsHome_QuickLinks_Description": "Jump into the settings people reach for most often.",
"SettingsHome_StorageCard_Description": "See how much local MIME content Wino keeps on this device and clean it up when needed.",
"SettingsHome_StorageEmptySummary": "No cached MIME content detected yet.",
"SettingsHome_StorageLoading": "Checking local MIME usage...",
"SettingsHome_Tips_Title": "Tips & tricks",
"SettingsHome_Tips_Description": "A few small changes can make Wino feel much more personal.",
"SettingsHome_Tip_Theme": "Want dark mode or accent changes? Open Personalization.",
"SettingsHome_Tip_Background": "Use App Preferences to control startup behavior and background sync.",
"SettingsHome_Tip_Shortcuts": "Keyboard Shortcuts helps you move through mail faster.",
"SettingsHome_Resources_Title": "Helpful links",
"SettingsHome_Resources_Description": "Open project resources, support info, and release channels.",
"SettingsOptions_Title": "Settings", "SettingsOptions_Title": "Settings",
"SettingsOptions_GeneralSection": "General", "SettingsOptions_GeneralSection": "General",
"SettingsOptions_MailSection": "Mail", "SettingsOptions_MailSection": "Mail",
@@ -823,6 +841,17 @@
"SettingsOptions_MoreComingSoon": "More options coming soon", "SettingsOptions_MoreComingSoon": "More options coming soon",
"SettingsOptions_HeroDescription": "Customize your Wino Mail experience", "SettingsOptions_HeroDescription": "Customize your Wino Mail experience",
"SettingsOptions_AccountsSummary": "{0} account(s) configured", "SettingsOptions_AccountsSummary": "{0} account(s) configured",
"SettingsSearch_ManageAccounts_Keywords": "account;accounts;mailbox;mailboxes;alias;aliases;profile;address;addresses",
"SettingsSearch_AppPreferences_Keywords": "startup;background;launch;sync;notification;notifications;search;tray;defaults",
"SettingsSearch_LanguageTime_Keywords": "language;time;clock;locale;region;format;24 hour;24h",
"SettingsSearch_Personalization_Keywords": "theme;dark;light;appearance;accent;color;colour;mode;layout;density",
"SettingsSearch_About_Keywords": "about;version;website;privacy;github;donate;store;support",
"SettingsSearch_KeyboardShortcuts_Keywords": "shortcut;shortcuts;hotkey;hotkeys;keyboard;keys",
"SettingsSearch_MessageList_Keywords": "message;messages;list;threading;threads;avatar;preview;sender",
"SettingsSearch_ReadComposePane_Keywords": "reader;compose;composer;font;fonts;external content;display;reading",
"SettingsSearch_SignatureAndEncryption_Keywords": "signature;signatures;encryption;certificate;certificates;s mime;smime;security",
"SettingsSearch_Storage_Keywords": "storage;cache;caching;mime;disk;space;cleanup;clean up;local data",
"SettingsSearch_CalendarSettings_Keywords": "calendar;week;hours;schedule;event;events",
"SettingsPaneLengthReset_Description": "Reset the size of the mail list to original if you have issues with it.", "SettingsPaneLengthReset_Description": "Reset the size of the mail list to original if you have issues with it.",
"SettingsPaneLengthReset_Title": "Reset Mail List Size", "SettingsPaneLengthReset_Title": "Reset Mail List Size",
"SettingsPaypal_Description": "Show much more love ❤️ All donations are appreciated.", "SettingsPaypal_Description": "Show much more love ❤️ All donations are appreciated.",
@@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@@ -8,6 +11,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Models.Settings; using Wino.Core.Domain.Models.Settings;
using Wino.Core.Extensions;
using Wino.Messaging.Client.Navigation; using Wino.Messaging.Client.Navigation;
namespace Wino.Core.ViewModels; namespace Wino.Core.ViewModels;
@@ -16,10 +20,15 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
{ {
private readonly INativeAppService _nativeAppService; private readonly INativeAppService _nativeAppService;
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
private readonly IMimeStorageService _mimeStorageService;
private readonly IStoreRatingService _storeRatingService; private readonly IStoreRatingService _storeRatingService;
public string GitHubUrl => AppUrls.GitHub; public string GitHubUrl => AppUrls.GitHub;
public string PaypalUrl => AppUrls.Paypal; public string PaypalUrl => AppUrls.Paypal;
public string WebsiteUrl => AppUrls.Website;
public string PrivacyPolicyUrl => AppUrls.PrivacyPolicy;
public ObservableCollection<SettingsNavigationItemInfo> SearchSuggestions { get; } = [];
[ObservableProperty] [ObservableProperty]
public partial string VersionText { get; set; } = string.Empty; public partial string VersionText { get; set; } = string.Empty;
@@ -30,12 +39,20 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
[ObservableProperty] [ObservableProperty]
public partial int AccountCount { get; set; } public partial int AccountCount { get; set; }
[ObservableProperty]
public partial string StorageSummaryText { get; set; } = string.Empty;
[ObservableProperty]
public partial string SearchQuery { get; set; } = string.Empty;
public SettingOptionsPageViewModel(INativeAppService nativeAppService, public SettingOptionsPageViewModel(INativeAppService nativeAppService,
IAccountService accountService, IAccountService accountService,
IMimeStorageService mimeStorageService,
IStoreRatingService storeRatingService) IStoreRatingService storeRatingService)
{ {
_nativeAppService = nativeAppService; _nativeAppService = nativeAppService;
_accountService = accountService; _accountService = accountService;
_mimeStorageService = mimeStorageService;
_storeRatingService = storeRatingService; _storeRatingService = storeRatingService;
} }
@@ -44,18 +61,57 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
base.OnNavigatedTo(mode, parameters); base.OnNavigatedTo(mode, parameters);
VersionText = string.Format("{0}{1}", Translator.SettingsAboutVersion, _nativeAppService.GetFullAppVersion()); VersionText = string.Format("{0}{1}", Translator.SettingsAboutVersion, _nativeAppService.GetFullAppVersion());
_ = LoadAccountSummaryAsync(); SearchQuery = string.Empty;
SearchSuggestions.Clear();
StorageSummaryText = Translator.SettingsHome_StorageLoading;
_ = LoadDashboardAsync();
} }
private async Task LoadAccountSummaryAsync() public void UpdateSearchSuggestions(string query)
{ {
var accounts = await _accountService.GetAccountsAsync(); SearchQuery = query;
int count = accounts?.Count ?? 0;
SearchSuggestions.Clear();
foreach (var result in SettingsNavigationInfoProvider.Search(query, AccountSummaryText).Take(6))
{
SearchSuggestions.Add(result);
}
}
public SettingsNavigationItemInfo GetBestSearchSuggestion(string query)
=> SettingsNavigationInfoProvider.Search(query, AccountSummaryText).FirstOrDefault();
public void NavigateToSetting(SettingsNavigationItemInfo item)
{
if (item?.PageType is WinoPage pageType)
{
NavigateSubDetail(pageType);
}
}
private async Task LoadDashboardAsync()
{
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false) ?? [];
var count = accounts.Count;
Dictionary<Guid, long> storageSizeMap = count == 0
? []
: await _mimeStorageService.GetAccountsMimeStorageSizesAsync(accounts.Select(account => account.Id)).ConfigureAwait(false);
var totalStorageBytes = storageSizeMap.Values.Sum();
await ExecuteUIThread(() => await ExecuteUIThread(() =>
{ {
AccountCount = count; AccountCount = count;
AccountSummaryText = string.Format(Translator.SettingsOptions_AccountsSummary, count); AccountSummaryText = string.Format(Translator.SettingsOptions_AccountsSummary, count);
StorageSummaryText = totalStorageBytes == 0
? Translator.SettingsHome_StorageEmptySummary
: string.Format(Translator.SettingsStorage_TotalUsage, totalStorageBytes.GetBytesReadable());
if (!string.IsNullOrWhiteSpace(SearchQuery))
{
UpdateSearchSuggestions(SearchQuery);
}
}); });
} }
@@ -40,8 +40,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
IRecipient<NavigateAppPreferencesRequested>, IRecipient<NavigateAppPreferencesRequested>,
IRecipient<AccountFolderConfigurationUpdated>, IRecipient<AccountFolderConfigurationUpdated>,
IRecipient<AccountRemovedMessage>, IRecipient<AccountRemovedMessage>,
IRecipient<AccountUpdatedMessage>, IRecipient<AccountUpdatedMessage>
IRecipient<AccountCreatedMessage>
{ {
#region Menu Items #region Menu Items
@@ -1154,7 +1153,6 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
{ {
base.RegisterRecipients(); base.RegisterRecipients();
Messenger.Register<AccountCreatedMessage>(this);
Messenger.Register<AccountRemovedMessage>(this); Messenger.Register<AccountRemovedMessage>(this);
Messenger.Register<AccountUpdatedMessage>(this); Messenger.Register<AccountUpdatedMessage>(this);
Messenger.Register<MailtoProtocolMessageRequested>(this); Messenger.Register<MailtoProtocolMessageRequested>(this);
@@ -1172,7 +1170,6 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
{ {
base.UnregisterRecipients(); base.UnregisterRecipients();
Messenger.Unregister<AccountCreatedMessage>(this);
Messenger.Unregister<AccountRemovedMessage>(this); Messenger.Unregister<AccountRemovedMessage>(this);
Messenger.Unregister<AccountUpdatedMessage>(this); Messenger.Unregister<AccountUpdatedMessage>(this);
Messenger.Unregister<MailtoProtocolMessageRequested>(this); Messenger.Unregister<MailtoProtocolMessageRequested>(this);
@@ -1198,14 +1195,17 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
await RestoreSelectedAccountAfterMenuRefreshAsync(false); await RestoreSelectedAccountAfterMenuRefreshAsync(false);
} }
public async void Receive(AccountCreatedMessage message) public async Task HandleAccountCreatedAsync(MailAccount createdAccount)
{ {
var createdAccount = message.Account;
latestSelectedAccountMenuItem = null; latestSelectedAccountMenuItem = null;
await RecreateMenuItemsAsync(); await RecreateMenuItemsAsync();
if (!MenuItems.TryGetAccountMenuItem(createdAccount.Id, out IAccountMenuItem createdMenuItem)) return; if (!MenuItems.TryGetAccountMenuItem(createdAccount.Id, out IAccountMenuItem createdMenuItem))
{
Log.Warning("Created account {AccountId} could not be found in menu items after refresh.", createdAccount.Id);
return;
}
await ChangeLoadedAccountAsync(createdMenuItem); await ChangeLoadedAccountAsync(createdMenuItem);
+2
View File
@@ -722,6 +722,7 @@ public partial class App : WinoApplication,
public void Receive(AccountCreatedMessage message) public void Receive(AccountCreatedMessage message)
{ {
var windowManager = Services.GetRequiredService<IWinoWindowManager>(); var windowManager = Services.GetRequiredService<IWinoWindowManager>();
var navigationService = Services.GetRequiredService<INavigationService>();
// Only transition when the account was created from the WelcomeWindow. // Only transition when the account was created from the WelcomeWindow.
if (windowManager.GetWindow(WinoWindowKind.Welcome) == null) if (windowManager.GetWindow(WinoWindowKind.Welcome) == null)
@@ -732,6 +733,7 @@ public partial class App : WinoApplication,
// Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher. // Create and activate ShellWindow — ActiveWindowChanged fires and rebinds the dispatcher.
CreateWindow(null); CreateWindow(null);
windowManager.HideWindow(WinoWindowKind.Welcome); windowManager.HideWindow(WinoWindowKind.Welcome);
navigationService.ChangeApplicationMode(Core.Domain.Enums.WinoApplicationMode.Mail);
await NewThemeService.ApplyThemeToActiveWindowAsync(); await NewThemeService.ApplyThemeToActiveWindowAsync();
MainWindow?.Activate(); MainWindow?.Activate();
RestartAutoSynchronizationLoop(); RestartAutoSynchronizationLoop();
@@ -25,49 +25,62 @@
</Style> </Style>
</ContentDialog.Resources> </ContentDialog.Resources>
<ScrollViewer Margin="0,8,0,0"> <StackPanel Margin="0,8,0,0" Spacing="12">
<ItemsControl ItemsSource="{x:Bind AvailableGroups}"> <TextBlock
<ItemsControl.ItemTemplate> Foreground="{ThemeResource TextFillColorSecondaryBrush}"
<DataTemplate x:DataType="calendar:CalendarPickerAccountGroup"> Text="{x:Bind domain:Translator.CalendarEventCompose_DefaultCalendarHint, Mode=OneWay}"
<StackPanel Margin="0,0,0,12" Spacing="6"> TextWrapping="WrapWholeWords" />
<TextBlock FontWeight="SemiBold">
<Run Text="{x:Bind Account.Name}" />
<Run Text=" (" />
<Run Text="{x:Bind Account.Address}" />
<Run Text=")" />
</TextBlock>
<ListView <HyperlinkButton
IsItemClickEnabled="True" HorizontalAlignment="Left"
ItemClick="CalendarClicked" Click="OpenCalendarSettingsClicked"
ItemContainerStyle="{StaticResource CalendarPickerListItemStyle}" Content="{x:Bind domain:Translator.CalendarEventCompose_DefaultCalendarSettingsLink, Mode=OneWay}"
ItemsSource="{x:Bind Calendars}" Padding="0" />
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="sharedCalendar:AccountCalendar">
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse <ScrollViewer MaxHeight="400">
Width="14" <ItemsControl ItemsSource="{x:Bind AvailableGroups}">
Height="14" <ItemsControl.ItemTemplate>
VerticalAlignment="Center" <DataTemplate x:DataType="calendar:CalendarPickerAccountGroup">
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" /> <StackPanel Margin="0,0,0,12" Spacing="6">
<TextBlock FontWeight="SemiBold">
<Run Text="{x:Bind Account.Name}" />
<Run Text=" (" />
<Run Text="{x:Bind Account.Address}" />
<Run Text=")" />
</TextBlock>
<TextBlock <ListView
Grid.Column="1" IsItemClickEnabled="True"
VerticalAlignment="Center" ItemClick="CalendarClicked"
Text="{x:Bind Name}" /> ItemContainerStyle="{StaticResource CalendarPickerListItemStyle}"
</Grid> ItemsSource="{x:Bind Calendars}"
</DataTemplate> SelectionMode="None">
</ListView.ItemTemplate> <ListView.ItemTemplate>
</ListView> <DataTemplate x:DataType="sharedCalendar:AccountCalendar">
</StackPanel> <Grid ColumnSpacing="10">
</DataTemplate> <Grid.ColumnDefinitions>
</ItemsControl.ItemTemplate> <ColumnDefinition Width="Auto" />
</ItemsControl> <ColumnDefinition Width="*" />
</ScrollViewer> </Grid.ColumnDefinitions>
<Ellipse
Width="14"
Height="14"
VerticalAlignment="Center"
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</ContentDialog> </ContentDialog>
@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Models.Calendar; using Wino.Core.Domain.Models.Calendar;
@@ -8,6 +9,7 @@ namespace Wino.Dialogs;
public sealed partial class SingleCalendarPickerDialog : ContentDialog public sealed partial class SingleCalendarPickerDialog : ContentDialog
{ {
public AccountCalendar? PickedCalendar { get; private set; } public AccountCalendar? PickedCalendar { get; private set; }
public bool ShouldNavigateToCalendarSettings { get; private set; }
public List<CalendarPickerAccountGroup> AvailableGroups { get; } = []; public List<CalendarPickerAccountGroup> AvailableGroups { get; } = [];
@@ -23,4 +25,10 @@ public sealed partial class SingleCalendarPickerDialog : ContentDialog
PickedCalendar = e.ClickedItem as AccountCalendar; PickedCalendar = e.ClickedItem as AccountCalendar;
Hide(); Hide();
} }
private void OpenCalendarSettingsClicked(object sender, RoutedEventArgs e)
{
ShouldNavigateToCalendarSettings = true;
Hide();
}
} }
+2 -2
View File
@@ -124,7 +124,7 @@ public class DialogService : DialogServiceBase, IMailDialogService
return accountPicker.PickedAccount ?? null!; return accountPicker.PickedAccount ?? null!;
} }
public async Task<AccountCalendar> ShowSingleCalendarPickerDialogAsync(List<CalendarPickerAccountGroup> availableCalendarGroups) public async Task<AccountCalendarPickingResult> ShowSingleCalendarPickerDialogAsync(List<CalendarPickerAccountGroup> availableCalendarGroups)
{ {
var calendarPicker = new SingleCalendarPickerDialog(availableCalendarGroups) var calendarPicker = new SingleCalendarPickerDialog(availableCalendarGroups)
{ {
@@ -133,7 +133,7 @@ public class DialogService : DialogServiceBase, IMailDialogService
await HandleDialogPresentationAsync(calendarPicker); await HandleDialogPresentationAsync(calendarPicker);
return calendarPicker.PickedCalendar ?? null!; return new AccountCalendarPickingResult(calendarPicker.PickedCalendar, calendarPicker.ShouldNavigateToCalendarSettings);
} }
public async Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature? signatureModel = null) public async Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature? signatureModel = null)
+49 -8
View File
@@ -247,6 +247,8 @@ public class NavigationService : NavigationServiceBase, INavigationService
IsInitialActivation = isInitialShellNavigation, IsInitialActivation = isInitialShellNavigation,
Parameter = activationParameter Parameter = activationParameter
}); });
ResetCurrentModeBackStackState();
return true; return true;
} }
@@ -323,6 +325,7 @@ public class NavigationService : NavigationServiceBase, INavigationService
if (innerShellFrame.CanGoBack && lastBackStackEntry?.SourcePageType == pageType) if (innerShellFrame.CanGoBack && lastBackStackEntry?.SourcePageType == pageType)
{ {
innerShellFrame.GoBack(); innerShellFrame.GoBack();
UpdateCurrentModeBackStackState(innerShellFrame);
WeakReferenceMessenger.Default.Send(loadCalendarMessage); WeakReferenceMessenger.Default.Send(loadCalendarMessage);
return true; return true;
} }
@@ -483,7 +486,14 @@ public class NavigationService : NavigationServiceBase, INavigationService
private bool NavigateInnerShellFrame(Frame frame, Type pageType, object? parameter, NavigationTransitionType transition) private bool NavigateInnerShellFrame(Frame frame, Type pageType, object? parameter, NavigationTransitionType transition)
{ {
var transitionInfo = ConsumeInnerShellTransitionOrDefault(transition); var transitionInfo = ConsumeInnerShellTransitionOrDefault(transition);
return frame.Navigate(pageType, parameter, transitionInfo); var navigationResult = frame.Navigate(pageType, parameter, transitionInfo);
if (navigationResult)
{
UpdateCurrentModeBackStackState(frame);
}
return navigationResult;
} }
private NavigationTransitionInfo ConsumeInnerShellTransitionOrDefault(NavigationTransitionType transition) private NavigationTransitionInfo ConsumeInnerShellTransitionOrDefault(NavigationTransitionType transition)
@@ -503,36 +513,67 @@ public class NavigationService : NavigationServiceBase, INavigationService
private void GoBackInternal(Core.Domain.Enums.NavigationTransitionEffect slideEffect = Core.Domain.Enums.NavigationTransitionEffect.FromRight) private void GoBackInternal(Core.Domain.Enums.NavigationTransitionEffect slideEffect = Core.Domain.Enums.NavigationTransitionEffect.FromRight)
{ {
if (_statePersistanceService.IsSettingsNavigating) var innerShellFrame = GetCoreFrameInternal(NavigationReferenceFrame.InnerShellFrame);
if (_statePersistanceService.ApplicationMode == WinoApplicationMode.Settings &&
_statePersistanceService.HasCurrentModeBackStack)
{ {
WeakReferenceMessenger.Default.Send(new BackBreadcrumNavigationRequested(slideEffect)); WeakReferenceMessenger.Default.Send(new BackBreadcrumNavigationRequested(slideEffect));
return; return;
} }
var innerShellFrame = GetCoreFrameInternal(NavigationReferenceFrame.InnerShellFrame); if (_statePersistanceService.ApplicationMode == WinoApplicationMode.Settings &&
innerShellFrame?.Content is SettingsPage)
{
return;
}
if (_statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar && innerShellFrame?.CanGoBack == true) if (_statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar && innerShellFrame?.CanGoBack == true)
{ {
innerShellFrame.GoBack(); innerShellFrame.GoBack();
UpdateCurrentModeBackStackState(innerShellFrame);
// Calendar mode: Navigate back from EventDetailsPage // Calendar mode: Navigate back from EventDetailsPage
_statePersistanceService.IsEventDetailsVisible = false; _statePersistanceService.IsEventDetailsVisible = false;
} }
else else if (_statePersistanceService.ApplicationMode == WinoApplicationMode.Mail)
{ {
if (_statePersistanceService.IsReadingMail && _statePersistanceService.IsReaderNarrowed) if (_statePersistanceService.IsReadingMail && _statePersistanceService.IsReaderNarrowed)
{ {
// Mail mode: Clear selections and dispose rendering frame // Mail mode: Clear selections and dispose rendering frame
_statePersistanceService.IsReadingMail = false; _statePersistanceService.IsReadingMail = false;
_statePersistanceService.HasCurrentModeBackStack = false;
WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested()); WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested());
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested()); WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
} }
else if (innerShellFrame != null && innerShellFrame.CanGoBack)
{
innerShellFrame.GoBack();
}
} }
else
{
UpdateCurrentModeBackStackState(innerShellFrame);
}
}
private void ResetCurrentModeBackStackState()
{
var innerShellFrame = GetCoreFrameInternal(NavigationReferenceFrame.InnerShellFrame);
if (innerShellFrame != null)
{
innerShellFrame.BackStack.Clear();
innerShellFrame.ForwardStack.Clear();
}
_statePersistanceService.HasCurrentModeBackStack = false;
}
private void UpdateCurrentModeBackStackState(Frame? innerShellFrame)
{
if (_statePersistanceService.ApplicationMode == WinoApplicationMode.Settings)
return;
_statePersistanceService.HasCurrentModeBackStack = _statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar &&
innerShellFrame?.CanGoBack == true;
} }
// Standalone EML viewer. // Standalone EML viewer.
@@ -34,8 +34,8 @@ public class StatePersistenceService : ObservableObject, IStatePersistanceServic
public bool IsBackButtonVisible => public bool IsBackButtonVisible =>
ApplicationMode == WinoApplicationMode.Mail ApplicationMode == WinoApplicationMode.Mail
? (IsReadingMail && IsReaderNarrowed) || IsSettingsNavigating ? (IsReadingMail && IsReaderNarrowed) || HasCurrentModeBackStack
: IsEventDetailsVisible || IsSettingsNavigating; : IsEventDetailsVisible || HasCurrentModeBackStack;
private WinoApplicationMode applicationMode = WinoApplicationMode.Mail; private WinoApplicationMode applicationMode = WinoApplicationMode.Mail;
@@ -68,14 +68,14 @@ public class StatePersistenceService : ObservableObject, IStatePersistanceServic
} }
} }
private bool isSettingsNavigating; private bool hasCurrentModeBackStack;
public bool IsSettingsNavigating public bool HasCurrentModeBackStack
{ {
get => isSettingsNavigating; get => hasCurrentModeBackStack;
set set
{ {
if (SetProperty(ref isSettingsNavigating, value)) if (SetProperty(ref hasCurrentModeBackStack, value))
{ {
OnPropertyChanged(nameof(IsBackButtonVisible)); OnPropertyChanged(nameof(IsBackButtonVisible));
} }
+41 -13
View File
@@ -64,23 +64,51 @@
<DataTemplate x:Key="SettingsShellSectionItemTemplate" x:DataType="menu:SettingsShellSectionMenuItem"> <DataTemplate x:Key="SettingsShellSectionItemTemplate" x:DataType="menu:SettingsShellSectionMenuItem">
<coreControls:WinoNavigationViewItem <coreControls:WinoNavigationViewItem
Margin="0,10,0,2" Margin="0,12,0,4"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Stretch"
DataContext="{x:Bind}" DataContext="{x:Bind}"
IsEnabled="False" IsEnabled="False"
SelectsOnInvoked="False"> SelectsOnInvoked="False">
<muxc:NavigationViewItem.Icon> <Grid ColumnSpacing="10">
<FontIcon <Grid.ColumnDefinitions>
FontFamily="{StaticResource SymbolThemeFontFamily}" <ColumnDefinition Width="Auto" />
FontSize="12" <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="24"
Height="24"
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="12">
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="{x:Bind Glyph}" />
</Border>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
CharacterSpacing="40"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="{x:Bind Glyph}" /> Style="{StaticResource CaptionTextBlockStyle}"
</muxc:NavigationViewItem.Icon> Text="{x:Bind Title}"
<TextBlock TextWrapping="NoWrap" />
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}" <Border
Text="{x:Bind Title}" Grid.Column="2"
TextWrapping="NoWrap" /> Height="1"
Margin="2,0,0,0"
VerticalAlignment="Center"
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
</Grid>
</coreControls:WinoNavigationViewItem> </coreControls:WinoNavigationViewItem>
</DataTemplate> </DataTemplate>
+157 -48
View File
@@ -3,43 +3,85 @@
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:abstract="using:Wino.Views.Abstract" xmlns:abstract="using:Wino.Views.Abstract"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain" xmlns:domain="using:Wino.Core.Domain"
xmlns:enums="using:Wino.Core.Domain.Enums" xmlns:enums="using:Wino.Core.Domain.Enums"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:settingsModels="using:Wino.Core.Domain.Models.Settings"
x:Name="root" x:Name="root"
Title="{x:Bind domain:Translator.SettingsHome_Title}" Title="{x:Bind domain:Translator.SettingsHome_Title}"
mc:Ignorable="d"> mc:Ignorable="d">
<ScrollViewer Padding="0,0,16,0" VerticalScrollBarVisibility="Auto"> <ScrollViewer Padding="0,0,16,0" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,8,0,24" Spacing="12"> <StackPanel Margin="0,8,0,24" Spacing="16">
<StackPanel.ChildrenTransitions> <StackPanel.ChildrenTransitions>
<TransitionCollection> <TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True" /> <EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True" />
</TransitionCollection> </TransitionCollection>
</StackPanel.ChildrenTransitions> </StackPanel.ChildrenTransitions>
<Grid <Border
Margin="0,0,0,12" Padding="24"
Padding="24,28"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1" BorderThickness="1"
CornerRadius="8"> CornerRadius="8">
<Grid.ColumnDefinitions> <StackPanel Spacing="8">
<ColumnDefinition Width="Auto" /> <AutoSuggestBox
<ColumnDefinition Width="*" /> x:Name="SettingsSearchBox"
</Grid.ColumnDefinitions> ItemsSource="{x:Bind ViewModel.SearchSuggestions, Mode=OneWay}"
PlaceholderText="{x:Bind domain:Translator.SettingsHome_SearchPlaceholder, Mode=OneTime}"
QueryIcon="Find"
QuerySubmitted="SettingsSearchQuerySubmitted"
SuggestionChosen="SettingsSearchSuggestionChosen"
Text="{x:Bind ViewModel.SearchQuery, Mode=TwoWay}"
TextChanged="SettingsSearchTextChanged">
<AutoSuggestBox.ItemTemplate>
<DataTemplate x:DataType="settingsModels:SettingsNavigationItemInfo">
<Grid Padding="0,4" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid <FontIcon
Grid.Column="1" VerticalAlignment="Top"
Margin="8,0,0,0" FontFamily="{StaticResource SymbolThemeFontFamily}"
VerticalAlignment="Center" Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
ColumnSpacing="12"> Glyph="{x:Bind Glyph}" />
<Grid.RowDefinitions>
<RowDefinition Height="*" /> <StackPanel Grid.Column="1" Spacing="2">
</Grid.RowDefinitions> <TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind Title}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Description}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Grid>
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind domain:Translator.SettingsHome_SearchExamples, Mode=OneTime}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Border>
<Border
Padding="24,20"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8">
<Grid ColumnSpacing="16" RowSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel <StackPanel
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -65,14 +107,11 @@
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.RowSpan="2" Grid.Column="1"
Grid.Column="2"
Margin="0,12,0,0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="12"> Spacing="12">
<Button <Button
Command="{x:Bind ViewModel.NavigateExternalCommand}" Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="{x:Bind ViewModel.PaypalUrl, Mode=OneWay}" CommandParameter="{x:Bind ViewModel.PaypalUrl, Mode=OneWay}"
@@ -87,23 +126,19 @@
</Button> </Button>
<Button <Button
Grid.Column="1"
Command="{x:Bind ViewModel.NavigateExternalCommand}" Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="{x:Bind ViewModel.GitHubUrl, Mode=OneWay}" CommandParameter="{x:Bind ViewModel.GitHubUrl, Mode=OneWay}"
Style="{StaticResource DefaultButtonStyle}" Style="{StaticResource DefaultButtonStyle}"
ToolTipService.ToolTip="{x:Bind domain:Translator.SettingsAboutGithub_Title}"> ToolTipService.ToolTip="{x:Bind domain:Translator.SettingsAboutGithub_Title}">
<StackPanel HorizontalAlignment="Center" Spacing="6"> <Viewbox Width="18" Height="18">
<Viewbox Width="18" Height="18"> <Path
<Path Data="m 12.2135 0 c -6.7538 0 -12.2135 5.5 -12.2135 12.3042 c 0 5.439 3.4983 10.043 8.3513 11.6725 c 0.6067 0.1225 0.829 -0.2647 0.829 -0.5905 c 0 -0.2853 -0.02 -1.263 -0.02 -2.2817 c -3.3975 0.7335 -4.105 -1.4668 -4.105 -1.4668 c -0.546 -1.426 -1.355 -1.7925 -1.355 -1.7925 c -1.112 -0.7538 0.081 -0.7538 0.081 -0.7538 c 1.2335 0.0815 1.8807 1.263 1.8807 1.263 c 1.0918 1.874 2.851 1.3445 3.5587 1.0185 c 0.101 -0.7945 0.4247 -1.3445 0.7685 -1.65 c -2.7097 -0.2853 -5.5607 -1.3445 -5.5607 -6.0708 c 0 -1.3445 0.485 -2.4445 1.2535 -3.3 c -0.1212 -0.3055 -0.546 -1.5687 0.1215 -3.2595 c 0 0 1.0313 -0.326 3.3565 1.263 a 11.7425 11.7425 90 0 1 3.0535 -0.4075 c 1.0313 0 2.0825 0.1428 3.0533 0.4075 c 2.3255 -1.589 3.3567 -1.263 3.3567 -1.263 c 0.6675 1.6908 0.2425 2.954 0.1212 3.2595 c 0.7888 0.8555 1.2538 1.9555 1.2538 3.3 c 0 4.7263 -2.851 5.765 -5.581 6.0708 c 0.445 0.387 0.829 1.1202 0.829 2.2815 c 0 1.65 -0.02 2.9743 -0.02 3.3815 c 0 0.326 0.2225 0.7132 0.829 0.591 c 4.853 -1.63 8.3513 -6.2338 8.3513 -11.6728 c 0.02 -6.8043 -5.4598 -12.3043 -12.1933 -12.3043 z"
Data="m 12.2135 0 c -6.7538 0 -12.2135 5.5 -12.2135 12.3042 c 0 5.439 3.4983 10.043 8.3513 11.6725 c 0.6067 0.1225 0.829 -0.2647 0.829 -0.5905 c 0 -0.2853 -0.02 -1.263 -0.02 -2.2817 c -3.3975 0.7335 -4.105 -1.4668 -4.105 -1.4668 c -0.546 -1.426 -1.355 -1.7925 -1.355 -1.7925 c -1.112 -0.7538 0.081 -0.7538 0.081 -0.7538 c 1.2335 0.0815 1.8807 1.263 1.8807 1.263 c 1.0918 1.874 2.851 1.3445 3.5587 1.0185 c 0.101 -0.7945 0.4247 -1.3445 0.7685 -1.65 c -2.7097 -0.2853 -5.5607 -1.3445 -5.5607 -6.0708 c 0 -1.3445 0.485 -2.4445 1.2535 -3.3 c -0.1212 -0.3055 -0.546 -1.5687 0.1215 -3.2595 c 0 0 1.0313 -0.326 3.3565 1.263 a 11.7425 11.7425 90 0 1 3.0535 -0.4075 c 1.0313 0 2.0825 0.1428 3.0533 0.4075 c 2.3255 -1.589 3.3567 -1.263 3.3567 -1.263 c 0.6675 1.6908 0.2425 2.954 0.1212 3.2595 c 0.7888 0.8555 1.2538 1.9555 1.2538 3.3 c 0 4.7263 -2.851 5.765 -5.581 6.0708 c 0.445 0.387 0.829 1.1202 0.829 2.2815 c 0 1.65 -0.02 2.9743 -0.02 3.3815 c 0 0.326 0.2225 0.7132 0.829 0.591 c 4.853 -1.63 8.3513 -6.2338 8.3513 -11.6728 c 0.02 -6.8043 -5.4598 -12.3043 -12.1933 -12.3043 z" Fill="{ThemeResource TextFillColorPrimaryBrush}"
Fill="{ThemeResource TextFillColorPrimaryBrush}" Stretch="Uniform" />
Stretch="Uniform" /> </Viewbox>
</Viewbox>
</StackPanel>
</Button> </Button>
<Button <Button
Grid.Column="2"
Command="{x:Bind ViewModel.NavigateExternalCommand}" Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="Store" CommandParameter="Store"
ToolTipService.ToolTip="{x:Bind domain:Translator.SettingsStore_Title}"> ToolTipService.ToolTip="{x:Bind domain:Translator.SettingsStore_Title}">
@@ -116,28 +151,102 @@
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Border>
<controls:SettingsCard <StackPanel Spacing="12">
Margin="0,0,0,12"
Click="SettingOptionClicked"
Description="{x:Bind ViewModel.AccountSummaryText, Mode=OneWay}"
Header="{x:Bind domain:Translator.SettingsManageAccountSettings_Title}"
IsClickEnabled="True"
Tag="{x:Bind enums:WinoPage.ManageAccountsPage}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" Glyph="&#xE77B;" />
</controls:SettingsCard.HeaderIcon>
<Button <Button
HorizontalContentAlignment="Stretch"
Click="SettingOptionClicked" Click="SettingOptionClicked"
Style="{StaticResource AccentButtonStyle}"
Tag="{x:Bind enums:WinoPage.ManageAccountsPage}"> Tag="{x:Bind enums:WinoPage.ManageAccountsPage}">
<StackPanel Orientation="Horizontal" Spacing="8"> <Grid ColumnSpacing="12">
<TextBlock Text="{x:Bind domain:Translator.Buttons_Manage}" /> <Grid.ColumnDefinitions>
<FontIcon FontSize="12" Glyph="&#xE76C;" /> <ColumnDefinition Width="Auto" />
</StackPanel> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon
VerticalAlignment="Center"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE77B;" />
<StackPanel Grid.Column="1" Spacing="2">
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsManageAccountSettings_Title, Mode=OneTime}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.AccountSummaryText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</StackPanel>
<FontIcon
Grid.Column="2"
VerticalAlignment="Center"
Glyph="&#xE76C;" />
</Grid>
</Button> </Button>
</controls:SettingsCard>
<Button
HorizontalContentAlignment="Stretch"
Click="SettingOptionClicked"
Tag="{x:Bind enums:WinoPage.StoragePage}">
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<FontIcon
VerticalAlignment="Center"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE74C;" />
<StackPanel Grid.Column="1" Spacing="2">
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsStorage_Title, Mode=OneTime}" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind ViewModel.StorageSummaryText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</StackPanel>
<FontIcon
Grid.Column="2"
VerticalAlignment="Center"
Glyph="&#xE76C;" />
</Grid>
</Button>
</StackPanel>
<Border
Padding="24"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="10">
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsHome_Resources_Title, Mode=OneTime}" />
<StackPanel Spacing="2">
<HyperlinkButton
Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="{x:Bind ViewModel.WebsiteUrl, Mode=OneWay}"
Content="{x:Bind domain:Translator.SettingsWebsite_Title}" />
<HyperlinkButton
Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="{x:Bind ViewModel.PrivacyPolicyUrl, Mode=OneWay}"
Content="{x:Bind domain:Translator.SettingsPrivacyPolicy_Title}" />
<HyperlinkButton
Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="{x:Bind ViewModel.GitHubUrl, Mode=OneWay}"
Content="{x:Bind domain:Translator.SettingsAboutGithub_Title}" />
<HyperlinkButton
Command="{x:Bind ViewModel.NavigateExternalCommand}"
CommandParameter="Store"
Content="{x:Bind domain:Translator.SettingsStore_Title}" />
</StackPanel>
</StackPanel>
</Border>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</abstract:SettingOptionsPageAbstract> </abstract:SettingOptionsPageAbstract>
@@ -1,7 +1,7 @@
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Settings;
using Wino.Views.Abstract; using Wino.Views.Abstract;
namespace Wino.Views.Settings; namespace Wino.Views.Settings;
@@ -18,7 +18,6 @@ public sealed partial class SettingOptionsPage : SettingOptionsPageAbstract
WinoPage? page = sender switch WinoPage? page = sender switch
{ {
Button button when button.Tag is WinoPage p => p, Button button when button.Tag is WinoPage p => p,
SettingsCard card when card.Tag is WinoPage p => p,
_ => null _ => null
}; };
@@ -27,4 +26,28 @@ public sealed partial class SettingOptionsPage : SettingOptionsPageAbstract
ViewModel.NavigateSubDetailCommand.Execute(page.Value); ViewModel.NavigateSubDetailCommand.Execute(page.Value);
} }
} }
private void SettingsSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput || string.IsNullOrWhiteSpace(sender.Text))
{
ViewModel.UpdateSearchSuggestions(sender.Text);
}
}
private void SettingsSearchSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
if (args.SelectedItem is SettingsNavigationItemInfo selectedSetting)
{
ViewModel.SearchQuery = selectedSetting.Title;
}
}
private void SettingsSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
var selectedSetting = args.ChosenSuggestion as SettingsNavigationItemInfo
?? ViewModel.GetBestSearchSuggestion(args.QueryText);
ViewModel.NavigateToSetting(selectedSetting);
}
} }
+16 -2
View File
@@ -71,7 +71,7 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
SettingsFrame.Navigated -= SettingsFrameNavigated; SettingsFrame.Navigated -= SettingsFrameNavigated;
// Reset navigation state when leaving SettingsPage // Reset navigation state when leaving SettingsPage
ViewModel.StatePersistenceService.IsSettingsNavigating = false; ViewModel.StatePersistenceService.HasCurrentModeBackStack = false;
base.OnNavigatingFrom(e); base.OnNavigatingFrom(e);
} }
@@ -202,9 +202,23 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
UpdateWindowTitle(); UpdateWindowTitle();
} }
public void ResetForModeSwitch()
{
while (PageHistory.Count > 1 && SettingsFrame.CanGoBack)
{
if (!BreadcrumbNavigationHelper.GoBack(SettingsFrame, PageHistory, Core.Domain.Enums.NavigationTransitionEffect.FromRight))
break;
}
SettingsFrame.ForwardStack.Clear();
UpdateBackNavigationState();
_ = RefreshCurrentPageStateAsync();
UpdateWindowTitle();
}
private void UpdateBackNavigationState() private void UpdateBackNavigationState()
{ {
ViewModel.StatePersistenceService.IsSettingsNavigating = PageHistory.Count > 1 && SettingsFrame.CanGoBack; ViewModel.StatePersistenceService.HasCurrentModeBackStack = PageHistory.Count > 1 && SettingsFrame.CanGoBack;
} }
private async Task RefreshCurrentPageStateAsync() private async Task RefreshCurrentPageStateAsync()
+21 -2
View File
@@ -33,6 +33,7 @@ using Wino.Messaging.Client.Accounts;
using Wino.Messaging.Client.Calendar; using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Mails; using Wino.Messaging.Client.Mails;
using Wino.Messaging.Client.Shell; using Wino.Messaging.Client.Shell;
using Wino.Messaging.UI;
using Wino.Views.Mail; using Wino.Views.Mail;
using Wino.Views; using Wino.Views;
using Wino.Views.Settings; using Wino.Views.Settings;
@@ -45,7 +46,8 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
IRecipient<AccountMenuItemExtended>, IRecipient<AccountMenuItemExtended>,
IRecipient<NavigateMailFolderEvent>, IRecipient<NavigateMailFolderEvent>,
IRecipient<CreateNewMailWithMultipleAccountsRequested>, IRecipient<CreateNewMailWithMultipleAccountsRequested>,
IRecipient<CalendarDisplayTypeChangedMessage> IRecipient<CalendarDisplayTypeChangedMessage>,
IRecipient<AccountCreatedMessage>
{ {
private const string StateHorizontalCalendar = "HorizontalCalendar"; private const string StateHorizontalCalendar = "HorizontalCalendar";
private const string StateVerticalCalendar = "VerticalCalendar"; private const string StateVerticalCalendar = "VerticalCalendar";
@@ -97,6 +99,7 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
UpdateTitleBarSubtitle(); UpdateTitleBarSubtitle();
ViewModel.CurrentClient.Activate(activationContext); ViewModel.CurrentClient.Activate(activationContext);
ResetShellModeNavigationState();
ApplyTitleBarContent(); ApplyTitleBarContent();
} }
@@ -158,6 +161,11 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
} }
else if (_activeMode == WinoApplicationMode.Settings) else if (_activeMode == WinoApplicationMode.Settings)
{ {
if (InnerShellFrame.Content is SettingsPage settingsPage)
{
settingsPage.ResetForModeSwitch();
}
ViewModel.CurrentClient.Deactivate(); ViewModel.CurrentClient.Deactivate();
} }
@@ -166,7 +174,7 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
private void ResetShellModeNavigationState() private void ResetShellModeNavigationState()
{ {
ViewModel.StatePersistenceService.IsSettingsNavigating = false; ViewModel.StatePersistenceService.HasCurrentModeBackStack = false;
InnerShellFrame.BackStack.Clear(); InnerShellFrame.BackStack.Clear();
InnerShellFrame.ForwardStack.Clear(); InnerShellFrame.ForwardStack.Clear();
} }
@@ -284,6 +292,15 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
public void Receive(CalendarDisplayTypeChangedMessage message) => ManageCalendarDisplayType(message.NewDisplayType); public void Receive(CalendarDisplayTypeChangedMessage message) => ManageCalendarDisplayType(message.NewDisplayType);
public void Receive(AccountCreatedMessage message)
{
_ = DispatcherQueue.EnqueueAsync(async () =>
{
ViewModel.NavigationService.ChangeApplicationMode(WinoApplicationMode.Mail);
await ViewModel.MailClient.HandleAccountCreatedAsync(message.Account);
});
}
private async void NavigationViewItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) private async void NavigationViewItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{ {
if (_isSyncingNavigationViewSelection) if (_isSyncingNavigationViewSelection)
@@ -758,6 +775,7 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this); WeakReferenceMessenger.Default.Register<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this); WeakReferenceMessenger.Default.Register<NavigateMailFolderEvent>(this);
WeakReferenceMessenger.Default.Register<CalendarDisplayTypeChangedMessage>(this); WeakReferenceMessenger.Default.Register<CalendarDisplayTypeChangedMessage>(this);
WeakReferenceMessenger.Default.Register<AccountCreatedMessage>(this);
} }
protected override void UnregisterRecipients() protected override void UnregisterRecipients()
@@ -768,5 +786,6 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
WeakReferenceMessenger.Default.Unregister<CreateNewMailWithMultipleAccountsRequested>(this); WeakReferenceMessenger.Default.Unregister<CreateNewMailWithMultipleAccountsRequested>(this);
WeakReferenceMessenger.Default.Unregister<NavigateMailFolderEvent>(this); WeakReferenceMessenger.Default.Unregister<NavigateMailFolderEvent>(this);
WeakReferenceMessenger.Default.Unregister<CalendarDisplayTypeChangedMessage>(this); WeakReferenceMessenger.Default.Unregister<CalendarDisplayTypeChangedMessage>(this);
WeakReferenceMessenger.Default.Unregister<AccountCreatedMessage>(this);
} }
} }