From 27c90d2f8986e4e05951b8e73c0463c5aa2f5ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Tue, 24 Mar 2026 18:05:09 +0100 Subject: [PATCH] Back navigation and shell improvements. --- .../Interfaces/IStatePersistenceService.cs | 10 --- .../Interfaces/IWinoNavigationService.cs | 1 + .../Calendar/CalendarRangeTextFormatter.cs | 8 +++ .../CalendarRangeTextFormatterTests.cs | 2 +- Wino.Mail.WinUI/Services/NavigationService.cs | 63 +++++++++++-------- .../Services/StatePersistenceService.cs | 45 +------------ Wino.Mail.WinUI/ShellWindow.xaml | 5 +- Wino.Mail.WinUI/ShellWindow.xaml.cs | 40 ++++++++++++ Wino.Mail.WinUI/Views/SettingsPage.xaml.cs | 7 +-- Wino.Mail.WinUI/Views/WinoAppShell.xaml.cs | 1 - 10 files changed, 96 insertions(+), 86 deletions(-) diff --git a/Wino.Core.Domain/Interfaces/IStatePersistenceService.cs b/Wino.Core.Domain/Interfaces/IStatePersistenceService.cs index 12e0be84..834415d0 100644 --- a/Wino.Core.Domain/Interfaces/IStatePersistenceService.cs +++ b/Wino.Core.Domain/Interfaces/IStatePersistenceService.cs @@ -28,11 +28,6 @@ public interface IStatePersistanceService : INotifyPropertyChanged /// bool IsReaderNarrowed { get; set; } - /// - /// Should display back button on the shell title bar. - /// - bool IsBackButtonVisible { get; } - /// /// Current application mode (Mail or Calendar). /// Not persisted to configuration, only kept in memory. @@ -44,11 +39,6 @@ public interface IStatePersistanceService : INotifyPropertyChanged /// bool IsEventDetailsVisible { get; set; } - /// - /// Whether the current application mode has an active backstack that can be navigated. - /// - bool HasCurrentModeBackStack { get; set; } - /// /// Setting: Opened pane length for the navigation view. /// diff --git a/Wino.Core.Domain/Interfaces/IWinoNavigationService.cs b/Wino.Core.Domain/Interfaces/IWinoNavigationService.cs index 3a1b2b5a..328f08ac 100644 --- a/Wino.Core.Domain/Interfaces/IWinoNavigationService.cs +++ b/Wino.Core.Domain/Interfaces/IWinoNavigationService.cs @@ -13,5 +13,6 @@ public interface INavigationService Type GetPageType(WinoPage winoPage); bool ChangeApplicationMode(WinoApplicationMode mode); + bool CanGoBack(); void GoBack(NavigationTransitionEffect slideEffect = NavigationTransitionEffect.FromRight); } diff --git a/Wino.Core.Domain/Models/Calendar/CalendarRangeTextFormatter.cs b/Wino.Core.Domain/Models/Calendar/CalendarRangeTextFormatter.cs index 8d0bade7..598ab3e5 100644 --- a/Wino.Core.Domain/Models/Calendar/CalendarRangeTextFormatter.cs +++ b/Wino.Core.Domain/Models/Calendar/CalendarRangeTextFormatter.cs @@ -20,12 +20,20 @@ public sealed class CalendarRangeTextFormatter : ICalendarRangeTextFormatter return FormatDate(range.StartDate, culture); } + if (range.SpansSingleMonth) + { + return $"{FormatDate(range.StartDate, culture)} - {FormatDay(range.EndDate, culture)}"; + } + return $"{FormatDate(range.StartDate, culture)} - {FormatDate(range.EndDate, culture)}"; } private static string FormatDate(DateOnly date, CultureInfo culture) => date.ToString(culture.DateTimeFormat.MonthDayPattern, culture); + private static string FormatDay(DateOnly date, CultureInfo culture) + => date.Day.ToString(culture); + private static string FormatMonth(DateOnly date, CultureInfo culture) => date.ToString(culture.DateTimeFormat.YearMonthPattern, culture); } diff --git a/Wino.Core.Tests/CalendarRangeTextFormatterTests.cs b/Wino.Core.Tests/CalendarRangeTextFormatterTests.cs index d98b9427..8c302e64 100644 --- a/Wino.Core.Tests/CalendarRangeTextFormatterTests.cs +++ b/Wino.Core.Tests/CalendarRangeTextFormatterTests.cs @@ -32,7 +32,7 @@ public class CalendarRangeTextFormatterTests startDate: new DateOnly(2026, 3, 3), endDate: new DateOnly(2026, 3, 10)); - Formatter.Format(range, DateContextProvider).Should().Be("March 3 - March 10"); + Formatter.Format(range, DateContextProvider).Should().Be("March 3 - 10"); } [Fact] diff --git a/Wino.Mail.WinUI/Services/NavigationService.cs b/Wino.Mail.WinUI/Services/NavigationService.cs index cb204daf..994b3e5c 100644 --- a/Wino.Mail.WinUI/Services/NavigationService.cs +++ b/Wino.Mail.WinUI/Services/NavigationService.cs @@ -219,6 +219,9 @@ public class NavigationService : NavigationServiceBase, INavigationService public bool ChangeApplicationMode(WinoApplicationMode mode) => ExecuteOnNavigationThread(() => ChangeApplicationModeInternal(mode)); + public bool CanGoBack() + => ExecuteOnNavigationThread(CanGoBackInternal); + private bool ChangeApplicationModeInternal(WinoApplicationMode mode, object? activationParameter = null) { var coreFrame = GetCoreFrameInternal(NavigationReferenceFrame.ShellFrame); @@ -333,7 +336,6 @@ public class NavigationService : NavigationServiceBase, INavigationService if (innerShellFrame.CanGoBack && lastBackStackEntry?.SourcePageType == pageType) { innerShellFrame.GoBack(); - UpdateCurrentModeBackStackState(innerShellFrame); WeakReferenceMessenger.Default.Send(loadCalendarMessage); return true; } @@ -495,7 +497,7 @@ public class NavigationService : NavigationServiceBase, INavigationService if (navigationResult) { - UpdateCurrentModeBackStackState(frame); + return true; } return navigationResult; @@ -522,15 +524,13 @@ public class NavigationService : NavigationServiceBase, INavigationService var currentApplicationMode = _statePersistanceService.ApplicationMode; if (currentApplicationMode == WinoApplicationMode.Settings && - _statePersistanceService.HasCurrentModeBackStack) + innerShellFrame?.Content is SettingsPage settingsPage) { - WeakReferenceMessenger.Default.Send(new BackBreadcrumNavigationRequested(slideEffect)); - return; - } + if (settingsPage.CanNavigateBack) + { + WeakReferenceMessenger.Default.Send(new BackBreadcrumNavigationRequested(slideEffect)); + } - if (currentApplicationMode == WinoApplicationMode.Settings && - innerShellFrame?.Content is SettingsPage) - { return; } @@ -544,7 +544,6 @@ public class NavigationService : NavigationServiceBase, INavigationService if (innerShellFrame?.CanGoBack == true) { innerShellFrame.GoBack(); - UpdateCurrentModeBackStackState(innerShellFrame); } else if (innerShellFrame != null && innerShellFrame.Content?.GetType() != typeof(CalendarPage)) { @@ -560,16 +559,11 @@ public class NavigationService : NavigationServiceBase, INavigationService { // Mail mode: Clear selections and dispose rendering frame _statePersistanceService.IsReadingMail = false; - _statePersistanceService.HasCurrentModeBackStack = false; WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested()); WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested()); } } - else - { - UpdateCurrentModeBackStackState(innerShellFrame); - } } private void ResetCurrentModeBackStackState() @@ -581,17 +575,6 @@ public class NavigationService : NavigationServiceBase, INavigationService 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; } private void PruneInnerShellBackStackForMode(Frame frame, WinoApplicationMode mode) @@ -636,7 +619,33 @@ public class NavigationService : NavigationServiceBase, INavigationService frame.BackStack.Clear(); frame.ForwardStack.Clear(); - UpdateCurrentModeBackStackState(frame); + } + + private bool CanGoBackInternal() + { + var innerShellFrame = GetCoreFrameInternal(NavigationReferenceFrame.InnerShellFrame); + + return _statePersistanceService.ApplicationMode switch + { + WinoApplicationMode.Mail => _statePersistanceService.IsReadingMail && _statePersistanceService.IsReaderNarrowed, + WinoApplicationMode.Settings => innerShellFrame?.Content is SettingsPage settingsPage && settingsPage.CanNavigateBack, + WinoApplicationMode.Calendar or WinoApplicationMode.Contacts => HasModeScopedBackStack(innerShellFrame, _statePersistanceService.ApplicationMode), + _ => false + }; + } + + private bool HasModeScopedBackStack(Frame? innerShellFrame, WinoApplicationMode mode) + { + if (innerShellFrame == null || innerShellFrame.BackStack.Count == 0) + return false; + + for (int i = innerShellFrame.BackStack.Count - 1; i >= 0; i--) + { + if (IsPageTypeAllowedInMode(mode, innerShellFrame.BackStack[i].SourcePageType)) + return true; + } + + return false; } // Standalone EML viewer. diff --git a/Wino.Mail.WinUI/Services/StatePersistenceService.cs b/Wino.Mail.WinUI/Services/StatePersistenceService.cs index b1edfab7..a14528ff 100644 --- a/Wino.Mail.WinUI/Services/StatePersistenceService.cs +++ b/Wino.Mail.WinUI/Services/StatePersistenceService.cs @@ -32,23 +32,12 @@ public class StatePersistenceService : ObservableObject, IStatePersistanceServic private void ServicePropertyChanged(object? sender, PropertyChangedEventArgs e) => StatePropertyChanged?.Invoke(this, e?.PropertyName ?? string.Empty); - public bool IsBackButtonVisible => - ApplicationMode == WinoApplicationMode.Mail - ? (IsReadingMail && IsReaderNarrowed) || HasCurrentModeBackStack - : HasCurrentModeBackStack; - private WinoApplicationMode applicationMode = WinoApplicationMode.Mail; public WinoApplicationMode ApplicationMode { get => applicationMode; - set - { - if (SetProperty(ref applicationMode, value)) - { - OnPropertyChanged(nameof(IsBackButtonVisible)); - } - } + set => SetProperty(ref applicationMode, value); } private bool isEventDetailsVisible; @@ -60,40 +49,18 @@ public class StatePersistenceService : ObservableObject, IStatePersistanceServic { if (SetProperty(ref isEventDetailsVisible, value)) { - OnPropertyChanged(nameof(IsBackButtonVisible)); - IsReaderNarrowed = value; IsReadingMail = value; } } } - private bool hasCurrentModeBackStack; - - public bool HasCurrentModeBackStack - { - get => hasCurrentModeBackStack; - set - { - if (SetProperty(ref hasCurrentModeBackStack, value)) - { - OnPropertyChanged(nameof(IsBackButtonVisible)); - } - } - } - private bool isReadingMail; public bool IsReadingMail { get => isReadingMail; - set - { - if (SetProperty(ref isReadingMail, value)) - { - OnPropertyChanged(nameof(IsBackButtonVisible)); - } - } + set => SetProperty(ref isReadingMail, value); } private bool shouldShiftMailRenderingDesign; @@ -109,13 +76,7 @@ public class StatePersistenceService : ObservableObject, IStatePersistanceServic public bool IsReaderNarrowed { get => isReaderNarrowed; - set - { - if (SetProperty(ref isReaderNarrowed, value)) - { - OnPropertyChanged(nameof(IsBackButtonVisible)); - } - } + set => SetProperty(ref isReaderNarrowed, value); } private string coreWindowTitle = string.Empty; diff --git a/Wino.Mail.WinUI/ShellWindow.xaml b/Wino.Mail.WinUI/ShellWindow.xaml index 0c4945f0..6952db29 100644 --- a/Wino.Mail.WinUI/ShellWindow.xaml +++ b/Wino.Mail.WinUI/ShellWindow.xaml @@ -31,10 +31,13 @@ VerticalContentAlignment="Stretch" BackRequested="BackButtonClicked" Background="Transparent" - IsBackButtonVisible="{x:Bind StatePersistanceService.IsBackButtonVisible, Mode=OneWay}" IsPaneToggleButtonVisible="True" PaneToggleRequested="PaneButtonClicked" Subtitle="{x:Bind StatePersistanceService.CoreWindowTitle, Mode=OneWay}"> + + Stretch + Stretch +