diff --git a/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs b/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs index 305fef65c818..4f69e15c8291 100644 --- a/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs +++ b/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs @@ -419,6 +419,8 @@ void SendWindowAppearing() { if (Navigation.ModalStack.Count == 0) Page?.SendAppearing(); + else if (Page is Shell shell) + shell.SendAppearing(); } void SendWindowDisppearing() diff --git a/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs b/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs index 01b7acd4667a..ba0bce24b432 100644 --- a/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs +++ b/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs @@ -245,13 +245,17 @@ public async Task PushModalAsync(Page modal, bool animated) { if (ModalStack.Count == 0) { - modal.NavigationProxy.Inner = _window.Navigation; + if (modal.NavigationProxy.Inner is null) + modal.NavigationProxy.Inner = _window.Navigation; + await PushModalPlatformAsync(modal, animated); } else { await PushModalPlatformAsync(modal, animated); - modal.NavigationProxy.Inner = _window.Navigation; + + if (modal.NavigationProxy.Inner is null) + modal.NavigationProxy.Inner = _window.Navigation; } } diff --git a/src/Controls/src/Core/Shell/NavigableElement.cs b/src/Controls/src/Core/Shell/NavigableElement.cs index 87187c834a8d..8feff84fc45e 100644 --- a/src/Controls/src/Core/Shell/NavigableElement.cs +++ b/src/Controls/src/Core/Shell/NavigableElement.cs @@ -88,7 +88,21 @@ protected override void OnParentSet() if (navProxy != null) { - NavigationProxy.Inner = navProxy.NavigationProxy; + // Modal navigation needs to all proxy through Shell + // So, when the modal page is parented to the window + // we make sure to set the NavProxy on the modal page to the + // Shell wrapper + if (Parent is Window w && + w.Page is Shell shell && + this is not Shell) + { + if (NavigationProxy.Inner is not Shell.ShellNavigationImplWrapper) + NavigationProxy.Inner = new Shell.ShellNavigationImplWrapper(navProxy.NavigationProxy, shell.Navigation); + } + else + { + NavigationProxy.Inner = navProxy.NavigationProxy; + } } else { diff --git a/src/Controls/src/Core/Shell/Shell.cs b/src/Controls/src/Core/Shell/Shell.cs index 468978f9b701..616bb1e5bed1 100644 --- a/src/Controls/src/Core/Shell/Shell.cs +++ b/src/Controls/src/Core/Shell/Shell.cs @@ -1710,24 +1710,28 @@ protected override async Task OnPushModal(Page modal, bool animated) await base.OnPushModal(modal, animated); - modal.NavigationProxy.Inner = new NavigationImplWrapper(modal.NavigationProxy.Inner, this); + if (modal.NavigationProxy.Inner is not ShellNavigationImplWrapper && + modal.NavigationProxy.Inner is not null) + { + modal.NavigationProxy.Inner = new ShellNavigationImplWrapper(modal.NavigationProxy.Inner, this); + } } + } - class NavigationImplWrapper : NavigationProxy - { - readonly INavigation _shellProxy; + internal class ShellNavigationImplWrapper : NavigationProxy + { + readonly INavigation _shellProxy; - public NavigationImplWrapper(INavigation proxy, INavigation shellProxy) - { - Inner = proxy; - _shellProxy = shellProxy; + public ShellNavigationImplWrapper(INavigation proxy, INavigation shellProxy) + { + Inner = proxy; + _shellProxy = shellProxy; - } + } - protected override Task OnPopModal(bool animated) => _shellProxy.PopModalAsync(animated); + protected override Task OnPopModal(bool animated) => _shellProxy.PopModalAsync(animated); - protected override Task OnPushModal(Page modal, bool animated) => _shellProxy.PushModalAsync(modal, animated); - } + protected override Task OnPushModal(Page modal, bool animated) => _shellProxy.PushModalAsync(modal, animated); } } } diff --git a/src/Controls/src/Core/Shell/ShellSection.cs b/src/Controls/src/Core/Shell/ShellSection.cs index 5a4f86ac4221..59cf750a51c7 100644 --- a/src/Controls/src/Core/Shell/ShellSection.cs +++ b/src/Controls/src/Core/Shell/ShellSection.cs @@ -53,10 +53,12 @@ Page IShellSectionController.PresentedPage { if (Navigation.ModalStack.Count > 0) { - if (Navigation.ModalStack[Navigation.ModalStack.Count - 1] is NavigationPage np) + var currentModalPage = Navigation.ModalStack[Navigation.ModalStack.Count - 1]; + + if (currentModalPage is NavigationPage np) return np.Navigation.NavigationStack[np.Navigation.NavigationStack.Count - 1]; - return Navigation.ModalStack[0]; + return currentModalPage; } if (_navStack.Count > 1) diff --git a/src/Controls/tests/Core.UnitTests/ShellModalTests.cs b/src/Controls/tests/Core.UnitTests/ShellModalTests.cs index 60f3c443abc8..4f834d9ee8a4 100644 --- a/src/Controls/tests/Core.UnitTests/ShellModalTests.cs +++ b/src/Controls/tests/Core.UnitTests/ShellModalTests.cs @@ -12,6 +12,55 @@ namespace Microsoft.Maui.Controls.Core.UnitTests public class ShellModalTests : ShellTestBase { + [Fact] + public async Task AppearingAndDisappearingFireWhenShellCreatedBeforeWindow() + { + var windowPage = new ContentPage(); + var modalPage1 = new ContentPage(); + var modalPage2 = new ContentPage(); + + int modal1Appearing = 0; + int modal1Disappearing = 0; + int modal2Appearing = 0; + int modal2Disappearing = 0; + int windowAppearing = 0; + int windowDisappearing = 0; + int shellAppearing = 0; + + modalPage1.Appearing += (_, _) => modal1Appearing++; + modalPage1.Disappearing += (_, _) => modal1Disappearing++; + + modalPage2.Appearing += (_, _) => modal2Appearing++; + modalPage2.Disappearing += (_, _) => modal2Disappearing++; + + windowPage.Appearing += (_, _) => windowAppearing++; + windowPage.Disappearing += (_, _) => windowDisappearing++; + + var shell = new Shell(); + shell.Appearing += (_, _) => shellAppearing++; + + shell.Items.Add(new ShellContent { Content = windowPage }); + + await windowPage.Navigation.PushModalAsync(modalPage1); + await windowPage.Navigation.PushModalAsync(modalPage2); + + var window = new TestWindow(shell); + + await windowPage.Navigation.PopModalAsync(); + await windowPage.Navigation.PopModalAsync(); + + Assert.Equal(1, shellAppearing); + + Assert.Equal(1, modal1Appearing); + Assert.Equal(1, modal1Disappearing); + + Assert.Equal(1, modal2Appearing); + Assert.Equal(1, modal2Disappearing); + + Assert.Equal(1, windowAppearing); + Assert.Equal(0, windowDisappearing); + } + [Fact] public async Task AppearingAndDisappearingFireOnMultipleModals() { diff --git a/src/Controls/tests/Core.UnitTests/ShellTests.cs b/src/Controls/tests/Core.UnitTests/ShellTests.cs index 05f7267c1fda..fc8cd8526500 100644 --- a/src/Controls/tests/Core.UnitTests/ShellTests.cs +++ b/src/Controls/tests/Core.UnitTests/ShellTests.cs @@ -1237,6 +1237,34 @@ public void GetCurrentPageOnInit() Assert.NotNull(page); } + [Fact] + public async Task CurrentPageReturnsCorrectModalPage() + { + var windowPage = new ContentPage(); + var modalPage1 = new ContentPage(); + var modalPage2 = new ContentPage(); + + var shell = new TestShell(new ShellContent { Content = windowPage }); + + await windowPage.Navigation.PushModalAsync(modalPage1); + await windowPage.Navigation.PushModalAsync(modalPage2); + + Assert.Equal(shell.Window.Navigation.ModalStack[0], modalPage1); + Assert.Equal(shell.Window.Navigation.ModalStack[1], modalPage2); + Assert.Equal(2, shell.Window.Navigation.ModalStack.Count); + Assert.Equal(modalPage2, shell.CurrentPage); + + await windowPage.Navigation.PopModalAsync(); + + Assert.Equal(shell.Window.Navigation.ModalStack[0], modalPage1); + Assert.Equal(1, shell.Window.Navigation.ModalStack.Count); + Assert.Equal(modalPage1, shell.CurrentPage); + + await windowPage.Navigation.PopModalAsync(); + + Assert.Equal(windowPage, shell.CurrentPage); + } + [Fact] public async Task HotReloadStaysOnActiveItem() { diff --git a/src/Controls/tests/DeviceTests/Elements/Modal/ModalTests.cs b/src/Controls/tests/DeviceTests/Elements/Modal/ModalTests.cs index f2efdfc8f52e..ff1a1d142a42 100644 --- a/src/Controls/tests/DeviceTests/Elements/Modal/ModalTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Modal/ModalTests.cs @@ -35,28 +35,70 @@ void SetupBuilder() { builder.ConfigureMauiHandlers(handlers => { - handlers.AddHandler(typeof(Toolbar), typeof(ToolbarHandler)); handlers.AddHandler(typeof(NavigationPage), typeof(NavigationViewHandler)); handlers.AddHandler(typeof(FlyoutPage), typeof(FlyoutViewHandler)); handlers.AddHandler(typeof(TabbedPage), typeof(TabbedViewHandler)); handlers.AddHandler(); handlers.AddHandler(); + SetupShellHandlers(handlers); handlers.AddHandler(typeof(Controls.Shell), typeof(ShellHandler)); - handlers.AddHandler(); - handlers.AddHandler(); - handlers.AddHandler(); - handlers.AddHandler(); - handlers.AddHandler(); -#if WINDOWS - handlers.AddHandler(); - handlers.AddHandler(); - handlers.AddHandler(); -#endif }); }); } + [Fact] + public async Task AppearingAndDisappearingFireWhenShellCreatedBeforeWindow() + { + SetupBuilder(); + var windowPage = new ContentPage(); + var modalPage1 = new ContentPage(); + var modalPage2 = new ContentPage(); + var shell = new Shell(); + + int modal1Appearing = 0; + int modal1Disappearing = 0; + int modal2Appearing = 0; + int modal2Disappearing = 0; + int windowAppearing = 0; + int windowDisappearing = 0; + int shellAppearing = 0; + + shell.Appearing += (_, _) => shellAppearing++; + modalPage1.Appearing += (_, _) => modal1Appearing++; + modalPage1.Disappearing += (_, _) => modal1Disappearing++; + + modalPage2.Appearing += (_, _) => modal2Appearing++; + modalPage2.Disappearing += (_, _) => modal2Disappearing++; + + windowPage.Appearing += (_, _) => windowAppearing++; + windowPage.Disappearing += (_, _) => windowDisappearing++; + + shell.Items.Add(new ShellContent { Content = windowPage }); + + await windowPage.Navigation.PushModalAsync(modalPage1); + await windowPage.Navigation.PushModalAsync(modalPage2); + + await CreateHandlerAndAddToWindow(new Window(shell), + async (_) => + { + await windowPage.Navigation.PopModalAsync(); + await windowPage.Navigation.PopModalAsync(); + }); + + // These values should match up with AppearingAndDisappearingFireWhenShellCreatedBeforeWindow + // in the ShellModalTests on the unit tests + Assert.Equal(1, modal1Appearing); + Assert.Equal(1, modal1Disappearing); + + Assert.Equal(1, modal2Appearing); + Assert.Equal(1, modal2Disappearing); + + Assert.Equal(1, windowAppearing); + Assert.Equal(0, windowDisappearing); + } + + [Theory] [InlineData(true)] [InlineData(false)]