Back navigation and shell improvements.
This commit is contained in:
@@ -28,11 +28,6 @@ public interface IStatePersistanceService : INotifyPropertyChanged
|
||||
/// </summary>
|
||||
bool IsReaderNarrowed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should display back button on the shell title bar.
|
||||
/// </summary>
|
||||
bool IsBackButtonVisible { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current application mode (Mail or Calendar).
|
||||
/// Not persisted to configuration, only kept in memory.
|
||||
@@ -44,11 +39,6 @@ public interface IStatePersistanceService : INotifyPropertyChanged
|
||||
/// </summary>
|
||||
bool IsEventDetailsVisible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the current application mode has an active backstack that can be navigated.
|
||||
/// </summary>
|
||||
bool HasCurrentModeBackStack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Opened pane length for the navigation view.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,5 +13,6 @@ public interface INavigationService
|
||||
|
||||
Type GetPageType(WinoPage winoPage);
|
||||
bool ChangeApplicationMode(WinoApplicationMode mode);
|
||||
bool CanGoBack();
|
||||
void GoBack(NavigationTransitionEffect slideEffect = NavigationTransitionEffect.FromRight);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if (settingsPage.CanNavigateBack)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new BackBreadcrumNavigationRequested(slideEffect));
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}">
|
||||
<TitleBar.Resources>
|
||||
<HorizontalAlignment x:Key="TitleBarContentHorizontalAlignment">Stretch</HorizontalAlignment>
|
||||
<VerticalAlignment x:Key="TitleBarContentVerticalAlignment">Stretch</VerticalAlignment>
|
||||
</TitleBar.Resources>
|
||||
<TitleBar.RightHeader>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button
|
||||
|
||||
@@ -46,12 +46,14 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
private ICalendarShellClient? _activeCalendarClient;
|
||||
private readonly CalendarTitleBarContent _calendarTitleBarContent = new();
|
||||
private long _calendarTypeSelectorChangedToken;
|
||||
private bool _isBackButtonVisibilityReady;
|
||||
|
||||
public ShellWindow()
|
||||
{
|
||||
RegisterRecipients();
|
||||
|
||||
InitializeComponent();
|
||||
StatePersistanceService.StatePropertyChanged += StatePersistenceServiceChanged;
|
||||
|
||||
MinWidth = 420;
|
||||
MinHeight = 420;
|
||||
@@ -138,7 +140,9 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
_ = StartCalendarReminderServerAsync();
|
||||
}
|
||||
|
||||
_isBackButtonVisibilityReady = true;
|
||||
ApplyTitleBarContent();
|
||||
RefreshBackButtonVisibility();
|
||||
}
|
||||
|
||||
private async Task StartCalendarReminderServerAsync()
|
||||
@@ -166,6 +170,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
public void Receive(TitleBarShellContentUpdated message)
|
||||
{
|
||||
ApplyTitleBarContent();
|
||||
RefreshBackButtonVisibility();
|
||||
}
|
||||
|
||||
public void Receive(ApplicationThemeChanged message)
|
||||
@@ -279,6 +284,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
{
|
||||
AttachCalendarClient(null);
|
||||
ShellTitleBar.Content = MainShellFrame.Content is BasePage basePage ? basePage.ShellContent : null;
|
||||
RefreshBackButtonVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -288,10 +294,43 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
{
|
||||
RefreshCalendarSelector();
|
||||
ShellTitleBar.Content = _calendarTitleBarContent;
|
||||
RefreshBackButtonVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
ShellTitleBar.Content = shellPage.GetShellFrame().Content is BasePage page ? page.ShellContent : null;
|
||||
RefreshBackButtonVisibility();
|
||||
}
|
||||
|
||||
private void StatePersistenceServiceChanged(object? sender, string propertyName)
|
||||
{
|
||||
if (!DispatcherQueue.HasThreadAccess)
|
||||
{
|
||||
var enqueued = DispatcherQueue.TryEnqueue(() => StatePersistenceServiceChanged(sender, propertyName));
|
||||
if (!enqueued)
|
||||
throw new InvalidOperationException("Could not marshal shell state changes onto the UI thread.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyName == nameof(IStatePersistanceService.ApplicationMode) ||
|
||||
propertyName == nameof(IStatePersistanceService.IsReadingMail) ||
|
||||
propertyName == nameof(IStatePersistanceService.IsReaderNarrowed) ||
|
||||
propertyName == nameof(IStatePersistanceService.IsEventDetailsVisible))
|
||||
{
|
||||
RefreshBackButtonVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshBackButtonVisibility()
|
||||
{
|
||||
if (!_isBackButtonVisibilityReady)
|
||||
{
|
||||
ShellTitleBar.IsBackButtonVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ShellTitleBar.IsBackButtonVisible = NavigationService.CanGoBack();
|
||||
}
|
||||
|
||||
private void AttachCalendarClient(ICalendarShellClient? calendarClient)
|
||||
@@ -390,6 +429,7 @@ public sealed partial class ShellWindow : WindowEx, IWinoShellWindow,
|
||||
{
|
||||
AppWindow.Closing -= OnAppWindowClosing;
|
||||
AttachCalendarClient(null);
|
||||
StatePersistanceService.StatePropertyChanged -= StatePersistenceServiceChanged;
|
||||
_calendarTitleBarContent.UnregisterSelectedTypeChanged(_calendarTypeSelectorChangedToken);
|
||||
_calendarTitleBarContent.PreviousDateRequested -= CalendarTitleBarContentPreviousDateRequested;
|
||||
_calendarTitleBarContent.NextDateRequested -= CalendarTitleBarContentNextDateRequested;
|
||||
|
||||
@@ -77,9 +77,6 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
|
||||
// Unregister frame navigation event
|
||||
SettingsFrame.Navigated -= SettingsFrameNavigated;
|
||||
|
||||
// Reset navigation state when leaving SettingsPage
|
||||
ViewModel.StatePersistenceService.HasCurrentModeBackStack = false;
|
||||
|
||||
base.OnNavigatingFrom(e);
|
||||
}
|
||||
|
||||
@@ -225,9 +222,11 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
|
||||
|
||||
private void UpdateBackNavigationState()
|
||||
{
|
||||
ViewModel.StatePersistenceService.HasCurrentModeBackStack = PageHistory.Count > 1 && SettingsFrame.CanGoBack;
|
||||
WeakReferenceMessenger.Default.Send(new TitleBarShellContentUpdated());
|
||||
}
|
||||
|
||||
public bool CanNavigateBack => PageHistory.Count > 1 && SettingsFrame.CanGoBack;
|
||||
|
||||
private async Task RefreshCurrentPageStateAsync()
|
||||
{
|
||||
var activePage = PageHistory.LastOrDefault()?.Request.PageType ?? WinoPage.SettingOptionsPage;
|
||||
|
||||
@@ -157,7 +157,6 @@ public sealed partial class WinoAppShell : Views.Abstract.WinoAppShellAbstract,
|
||||
|
||||
private void ResetShellModeNavigationState()
|
||||
{
|
||||
ViewModel.StatePersistenceService.HasCurrentModeBackStack = false;
|
||||
InnerShellFrame.BackStack.Clear();
|
||||
InnerShellFrame.ForwardStack.Clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user