From b3a076fa52f0023adf733b41aab84007ddd1c82f Mon Sep 17 00:00:00 2001 From: keyldev Date: Sat, 6 Jun 2026 23:21:24 +0300 Subject: [PATCH] fix(nav): disable bottom nav while a mobile modal dialog is open - overlay dialogs live in OverlayLayer; the dim layer doesn't swallow nav taps, so navigation switched CurrentPage under the open dialog - OverlayDialogPresenter now tracks open overlays (IsAnyOpen + ActiveChanged) - gate NavigateCommand on CanNavigate; nav buttons grey out until the dialog closes - desktop is unaffected (real modal ShowDialog windows) --- .../Services/OverlayDialogPresenter.cs | 16 ++++++++++++++++ .../ViewModels/MainWindowViewModel.cs | 12 +++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs b/src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs index ff32a13..bfc0a62 100644 --- a/src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs +++ b/src/OpenIPC.Viewer.App/Services/OverlayDialogPresenter.cs @@ -29,6 +29,18 @@ public static class OverlayDialogPresenter { private static readonly TimeSpan FadeIn = TimeSpan.FromMilliseconds(160); + // Number of overlay dialogs currently on screen. Mobile dialogs live in the + // TopLevel.OverlayLayer; the dim Border does not reliably intercept taps on + // the bottom nav, so the shell gates navigation on this instead. Desktop + // uses real modal Windows (ShowDialog) and never goes through here. + private static int _activeCount; + + /// True while at least one overlay (mobile modal) dialog is open. + public static bool IsAnyOpen => _activeCount > 0; + + /// Raised on the UI thread whenever may have changed. + public static event Action? ActiveChanged; + public static async Task ShowAsync(Control content, Task completion) { var overlay = GetOverlayLayer(); @@ -82,6 +94,8 @@ public static async Task ShowAsync(Control content, Task ShowAsync(Control content, Task(this); WeakReferenceMessenger.Default.Register(this); + + // While a mobile overlay dialog is open the bottom nav must not switch + // pages under it — the dim layer doesn't reliably swallow those taps. + // Re-evaluate CanNavigate whenever an overlay opens/closes; this greys + // out the nav buttons (Avalonia disables a control whose command can't + // execute). This VM is an app-lifetime singleton, so the static + // subscription lives as long as the process — no unsubscribe needed. + OverlayDialogPresenter.ActiveChanged += () => NavigateCommand.NotifyCanExecuteChanged(); } - [RelayCommand] + private static bool CanNavigate() => !OverlayDialogPresenter.IsAnyOpen; + + [RelayCommand(CanExecute = nameof(CanNavigate))] private void Navigate(string target) { if (_activeSingleCamera is not null && target != "library")