From 9877d85fba3934d1287db598712984bf8eadf8fc Mon Sep 17 00:00:00 2001 From: niimima Date: Fri, 5 Jul 2024 16:12:14 +0900 Subject: [PATCH 01/15] [WIP] Add NavigateFromAsync methods and unittests. --- .../Navigation/INavigationService.cs | 9 +++ .../INavigationServiceExtensions.cs | 9 +++ .../Navigation/PageNavigationService.cs | 41 ++++++++++++++ .../Fixtures/Navigation/NavigationTests.cs | 56 +++++++++++++++++++ 4 files changed, 115 insertions(+) diff --git a/src/Maui/Prism.Maui/Navigation/INavigationService.cs b/src/Maui/Prism.Maui/Navigation/INavigationService.cs index 25e1e12f0..7c20e563d 100644 --- a/src/Maui/Prism.Maui/Navigation/INavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/INavigationService.cs @@ -40,6 +40,15 @@ public interface INavigationService /// Task NavigateAsync(Uri uri, INavigationParameters parameters); + /// + /// Initiates navigation from the using the specified . + /// + /// The name of the View to navigate from + /// The route Uri to navigate from that view + /// The navigation parameters + /// If true a navigate from operation was successful. If false the navigate from operation failed. + Task NavigateFromAsync(string viewName, Uri route, INavigationParameters parameters); + /// /// Selects a Tab of the TabbedPage parent and Navigates to a specified Uri /// diff --git a/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs b/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs index 7e1097aef..4d24fc3f3 100644 --- a/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs +++ b/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs @@ -106,6 +106,15 @@ public static Task NavigateAsync(this INavigationService navi return navigationService.NavigateAsync(name, GetNavigationParameters(parameters)); } + /// + /// Initiates navigation to the target specified by the from the . + /// + /// The name of the View to navigate to + /// The route Uri to navigate to + /// If true a navigate from operation was successful. If false the navigate from operation failed. + public static Task NavigateFromAsync(this INavigationService navigationService, string viewName, Uri route) => + navigationService.NavigateFromAsync(viewName, route, new NavigationParameters()); + /// /// Provides an easy to use way to provide an Error Callback without using await NavigationService /// diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index e79ec21fa..5bebf9301 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -333,6 +333,47 @@ public virtual async Task NavigateAsync(Uri uri, INavigationP } } + /// + public virtual async Task NavigateFromAsync(string viewName, Uri route, INavigationParameters parameters) + { + await WaitForPendingNavigationRequests(); + + try + { + parameters ??= new NavigationParameters(); + + NavigationSource = PageNavigationSource.NavigationService; + + var routeSegments = UriParsingHelper.GetUriSegments(route); + + var page = GetPageFromWindow(); + if (page is not null && ViewModelLocator.GetNavigationName(page) == viewName) + { + await ProcessNavigation(page, routeSegments, parameters, null, null); + } + else + { + var viewNameSegment = new Queue(); + viewNameSegment.Enqueue(viewName); + var navigationSegments = new Queue(viewNameSegment.Concat(routeSegments)); + + await ProcessNavigationForAbsoluteUri(navigationSegments, parameters, null, null); + } + + return Notify(route, parameters); + } + catch (Exception ex) + { + return Notify(route, parameters, ex); + } + finally + { + _lastNavigate = DateTime.Now; + NavigationSource = PageNavigationSource.Device; + _semaphore.Release(); + } + } + /// /// Selects a Tab of the TabbedPage parent. /// diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs index c8823d066..c5235294c 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -558,6 +558,62 @@ public async Task Navigation_Animation_IsFalse() Assert.False(push.Animated); } + [Fact] + public async Task Navigation_FromPageUsingRoot() + { + var mauiApp = CreateBuilder(prism => prism.CreateWindow("MockHome/NavigationPage/MockViewA")) + .Build(); + var window = GetWindow(mauiApp); + + Assert.IsAssignableFrom(window.Page); + var rootPage = (MockHome)window.Page; + Assert.NotNull(rootPage); + + Assert.NotNull(rootPage.Detail); + Assert.IsAssignableFrom(rootPage.Detail); + var navigatingPage = (NavigationPage)rootPage.Detail; + Assert.NotNull(navigatingPage); + Assert.IsType(navigatingPage.CurrentPage); + + var result = await navigatingPage.CurrentPage.GetContainerProvider() + .Resolve() + .NavigateFromAsync("MockHome", UriParsingHelper.Parse("NavigationPage/MockViewB"), null); + + Assert.True(result.Success); + + Assert.NotNull(rootPage.Detail); + var navigatedPage = (NavigationPage)rootPage.Detail; + Assert.IsType(navigatedPage.CurrentPage); + } + + [Fact] + public async Task Navigation_FromNotRootPageUsingRoot() + { + var mauiApp = CreateBuilder(prism => prism.CreateWindow("MockHome/NavigationPage/MockViewA")) + .Build(); + var window = GetWindow(mauiApp); + + Assert.IsAssignableFrom(window.Page); + var rootPage = (MockHome)window.Page; + Assert.NotNull(rootPage); + + Assert.NotNull(rootPage.Detail); + Assert.IsAssignableFrom(rootPage.Detail); + var navigatingPage = (NavigationPage)rootPage.Detail; + Assert.NotNull(navigatingPage); + Assert.IsType(navigatingPage.CurrentPage); + + var result = await navigatingPage.CurrentPage.GetContainerProvider() + .Resolve() + .NavigateFromAsync("NavigationPage", UriParsingHelper.Parse("MockViewB"), null); + + Assert.True(result.Success); + + Assert.NotNull(rootPage.Detail); + var navigatedPage = (NavigationPage)rootPage.Detail; + Assert.IsType(navigatedPage.CurrentPage); + } + [Theory] [InlineData("MockViewA", "MockViewB", null)] [InlineData("NavigationPage/MockViewA", "MockViewB?useModalNavigation=true", true)] From a98996a356a266c16fe759264414116aace14fbf Mon Sep 17 00:00:00 2001 From: niimima Date: Fri, 5 Jul 2024 16:48:47 +0900 Subject: [PATCH 02/15] Modify NavigateFromAsync method and unittests. --- .../Navigation/PageNavigationService.cs | 17 +++---- .../Fixtures/Navigation/NavigationTests.cs | 51 +++++++++---------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index 5bebf9301..a13d3a053 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -346,19 +346,16 @@ public virtual async Task NavigateFromAsync(string viewName, var routeSegments = UriParsingHelper.GetUriSegments(route); - var page = GetPageFromWindow(); - if (page is not null && ViewModelLocator.GetNavigationName(page) == viewName) + // Find a page that matches the viewName. + var page = GetCurrentPage(); + while (page != null) { - await ProcessNavigation(page, routeSegments, parameters, null, null); + if (page is not null && ViewModelLocator.GetNavigationName(page) == viewName) + break; + page = page.GetParentPage(); } - else - { - var viewNameSegment = new Queue(); - viewNameSegment.Enqueue(viewName); - var navigationSegments = new Queue(viewNameSegment.Concat(routeSegments)); - await ProcessNavigationForAbsoluteUri(navigationSegments, parameters, null, null); - } + await ProcessNavigation(page, routeSegments, parameters, null, null); return Notify(route, parameters); } diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs index c5235294c..27e981d22 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -559,59 +559,54 @@ public async Task Navigation_Animation_IsFalse() } [Fact] - public async Task Navigation_FromPageUsingRoot() + public async Task Navigation_FromPageUsingRoute() { var mauiApp = CreateBuilder(prism => prism.CreateWindow("MockHome/NavigationPage/MockViewA")) .Build(); var window = GetWindow(mauiApp); - Assert.IsAssignableFrom(window.Page); - var rootPage = (MockHome)window.Page; - Assert.NotNull(rootPage); - - Assert.NotNull(rootPage.Detail); - Assert.IsAssignableFrom(rootPage.Detail); - var navigatingPage = (NavigationPage)rootPage.Detail; - Assert.NotNull(navigatingPage); - Assert.IsType(navigatingPage.CurrentPage); + var pageNavigatingFrom = (MockHome)window.Page; + var navigationPage = (NavigationPage)pageNavigatingFrom.Detail; + Assert.IsType(navigationPage.CurrentPage); - var result = await navigatingPage.CurrentPage.GetContainerProvider() + var result = await navigationPage.CurrentPage.GetContainerProvider() .Resolve() .NavigateFromAsync("MockHome", UriParsingHelper.Parse("NavigationPage/MockViewB"), null); Assert.True(result.Success); - Assert.NotNull(rootPage.Detail); - var navigatedPage = (NavigationPage)rootPage.Detail; - Assert.IsType(navigatedPage.CurrentPage); + // MockHome(FlyoutPage) has not been replaced. + var pageNavigatedFrom = (MockHome)window.Page; + Assert.Equal(pageNavigatingFrom, pageNavigatedFrom); + + // Navigation should be succeeded. + navigationPage = (NavigationPage)pageNavigatedFrom.Detail; + Assert.IsType(navigationPage.CurrentPage); } [Fact] - public async Task Navigation_FromNotRootPageUsingRoot() + public async Task Navigation_FromIntermediatePageUsingRoute() { var mauiApp = CreateBuilder(prism => prism.CreateWindow("MockHome/NavigationPage/MockViewA")) .Build(); var window = GetWindow(mauiApp); - Assert.IsAssignableFrom(window.Page); - var rootPage = (MockHome)window.Page; - Assert.NotNull(rootPage); - - Assert.NotNull(rootPage.Detail); - Assert.IsAssignableFrom(rootPage.Detail); - var navigatingPage = (NavigationPage)rootPage.Detail; - Assert.NotNull(navigatingPage); - Assert.IsType(navigatingPage.CurrentPage); + var mockHome = (MockHome)window.Page; + var pageNavigatingFrom = (NavigationPage)mockHome.Detail; + Assert.IsType(pageNavigatingFrom.CurrentPage); - var result = await navigatingPage.CurrentPage.GetContainerProvider() + var result = await pageNavigatingFrom.CurrentPage.GetContainerProvider() .Resolve() .NavigateFromAsync("NavigationPage", UriParsingHelper.Parse("MockViewB"), null); Assert.True(result.Success); - Assert.NotNull(rootPage.Detail); - var navigatedPage = (NavigationPage)rootPage.Detail; - Assert.IsType(navigatedPage.CurrentPage); + // NavigationPage has not been replaced. + var pageNavigatedFrom = (NavigationPage)mockHome.Detail; + Assert.Equal(pageNavigatingFrom, pageNavigatedFrom); + + // Navigation should be succeeded. + Assert.IsType(pageNavigatedFrom.CurrentPage); } [Theory] From 67c361c1048169ab34b88b4ee5a88b8b036c8ead Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 29 Jul 2024 11:21:43 +0900 Subject: [PATCH 03/15] Modifies NavigateFromAsync to use navigationStack --- .../Navigation/PageNavigationService.cs | 38 +++++++++++++++---- .../Navigation/NavigationException.cs | 5 +++ .../Fixtures/Navigation/NavigationTests.cs | 25 ++++-------- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index a13d3a053..6f258e08e 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -340,22 +340,29 @@ public virtual async Task NavigateFromAsync(string viewName, try { + if (route.IsAbsoluteUri) throw new NavigationException(NavigationException.UnsupportedAbsoluteUri); + parameters ??= new NavigationParameters(); NavigationSource = PageNavigationSource.NavigationService; - var routeSegments = UriParsingHelper.GetUriSegments(route); + var navigationSegments = UriParsingHelper.GetUriSegments(route); // Find a page that matches the viewName. - var page = GetCurrentPage(); - while (page != null) + var currentPage = GetCurrentPage(); + var navigationPages = currentPage.Navigation.NavigationStack.ToList(); + navigationPages.Reverse(); + var foundPage = navigationPages.FirstOrDefault(page => ViewModelLocator.GetNavigationName(page) == viewName); + var removePageCount = navigationPages.IndexOf(foundPage); + + // Insert RemovePageSegment. + var routeString = route.ToString(); + for (int i = 0; i < removePageCount; i++) { - if (page is not null && ViewModelLocator.GetNavigationName(page) == viewName) - break; - page = page.GetParentPage(); + AddToFront(navigationSegments, RemovePageSegment); } - await ProcessNavigation(page, routeSegments, parameters, null, null); + await ProcessNavigation(currentPage, navigationSegments, parameters, null, null); return Notify(route, parameters); } @@ -371,6 +378,23 @@ public virtual async Task NavigateFromAsync(string viewName, } } + private static void AddToFront(Queue queue, T element) + { + var tempQueue = new Queue(); + + while (queue.Count > 0) + { + tempQueue.Enqueue(queue.Dequeue()); + } + + queue.Enqueue(element); + + while (tempQueue.Count > 0) + { + queue.Enqueue(tempQueue.Dequeue()); + } + } + /// /// Selects a Tab of the TabbedPage parent. /// diff --git a/src/Prism.Core/Navigation/NavigationException.cs b/src/Prism.Core/Navigation/NavigationException.cs index 1ffecac4b..1077f64d0 100644 --- a/src/Prism.Core/Navigation/NavigationException.cs +++ b/src/Prism.Core/Navigation/NavigationException.cs @@ -72,6 +72,11 @@ public class NavigationException : Exception /// public const string UnknownException = "An unknown error occurred. You may need to specify whether to Use Modal Navigation or not."; + /// + /// The Message returned when an absolute path is specified but not supported. + /// + public const string UnsupportedAbsoluteUri = "An unsupported absolute uri. Please use a relative URI."; + /// /// Initializes a new instance of the /// diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs index 27e981d22..981c5171a 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -559,29 +559,20 @@ public async Task Navigation_Animation_IsFalse() } [Fact] - public async Task Navigation_FromPageUsingRoute() + public async Task Navigation_FromPageUsingRoute_OnNavigationPage() { - var mauiApp = CreateBuilder(prism => prism.CreateWindow("MockHome/NavigationPage/MockViewA")) + var mauiApp = CreateBuilder(prism => prism.CreateWindow("NavigationPage/MockViewA/MockViewB/MockViewC")) .Build(); var window = GetWindow(mauiApp); + var navigationService = Prism.Navigation.Xaml.Navigation.GetNavigationService(window.CurrentPage); + var currentNavigationStackUri = string.Join("/", window.CurrentPage.Navigation.NavigationStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewA/MockViewB/MockViewC", currentNavigationStackUri); - var pageNavigatingFrom = (MockHome)window.Page; - var navigationPage = (NavigationPage)pageNavigatingFrom.Detail; - Assert.IsType(navigationPage.CurrentPage); - - var result = await navigationPage.CurrentPage.GetContainerProvider() - .Resolve() - .NavigateFromAsync("MockHome", UriParsingHelper.Parse("NavigationPage/MockViewB"), null); + var result = await navigationService.NavigateFromAsync("MockViewA", UriParsingHelper.Parse("MockViewD/MockViewE"), null); Assert.True(result.Success); - - // MockHome(FlyoutPage) has not been replaced. - var pageNavigatedFrom = (MockHome)window.Page; - Assert.Equal(pageNavigatingFrom, pageNavigatedFrom); - - // Navigation should be succeeded. - navigationPage = (NavigationPage)pageNavigatedFrom.Detail; - Assert.IsType(navigationPage.CurrentPage); + currentNavigationStackUri = string.Join("/", window.CurrentPage.Navigation.NavigationStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewA/MockViewD/MockViewE", currentNavigationStackUri); } [Fact] From faeba296a7d1b7ae21b188828647024dc5f529ab Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 29 Jul 2024 13:59:27 +0900 Subject: [PATCH 04/15] Modify NavigateFromAsync to find parent pages --- .../Navigation/PageNavigationService.cs | 26 ++++++++++++++----- .../Fixtures/Navigation/NavigationTests.cs | 19 +++++++++++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index 6f258e08e..a90a009e5 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -353,13 +353,27 @@ public virtual async Task NavigateFromAsync(string viewName, var navigationPages = currentPage.Navigation.NavigationStack.ToList(); navigationPages.Reverse(); var foundPage = navigationPages.FirstOrDefault(page => ViewModelLocator.GetNavigationName(page) == viewName); - var removePageCount = navigationPages.IndexOf(foundPage); - - // Insert RemovePageSegment. - var routeString = route.ToString(); - for (int i = 0; i < removePageCount; i++) + if (foundPage is null) { - AddToFront(navigationSegments, RemovePageSegment); + var page = currentPage; + while (page != null) + { + if (page is not null && ViewModelLocator.GetNavigationName(page) == viewName) + break; + page = page.GetParentPage(); + } + currentPage = page; + } + else + { + var removePageCount = navigationPages.IndexOf(foundPage); + + // Insert RemovePageSegment. + var routeString = route.ToString(); + for (int i = 0; i < removePageCount; i++) + { + AddToFront(navigationSegments, RemovePageSegment); + } } await ProcessNavigation(currentPage, navigationSegments, parameters, null, null); diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs index 981c5171a..91add2737 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -559,7 +559,7 @@ public async Task Navigation_Animation_IsFalse() } [Fact] - public async Task Navigation_FromPageUsingRoute_OnNavigationPage() + public async Task Navigation_FromPageUsingRoute() { var mauiApp = CreateBuilder(prism => prism.CreateWindow("NavigationPage/MockViewA/MockViewB/MockViewC")) .Build(); @@ -575,6 +575,23 @@ public async Task Navigation_FromPageUsingRoute_OnNavigationPage() Assert.Equal("MockViewA/MockViewD/MockViewE", currentNavigationStackUri); } + [Fact] + public async Task Navigation_FromRootNavigationPageUsingRoute() + { + var mauiApp = CreateBuilder(prism => prism.CreateWindow("NavigationPage/MockViewA/MockViewB/MockViewC")) + .Build(); + var window = GetWindow(mauiApp); + var navigationService = Prism.Navigation.Xaml.Navigation.GetNavigationService(window.CurrentPage); + var currentNavigationStackUri = string.Join("/", window.CurrentPage.Navigation.NavigationStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewA/MockViewB/MockViewC", currentNavigationStackUri); + + var result = await navigationService.NavigateFromAsync("NavigationPage", UriParsingHelper.Parse("MockViewD/MockViewE"), null); + + Assert.True(result.Success); + currentNavigationStackUri = string.Join("/", window.CurrentPage.Navigation.NavigationStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewD/MockViewE", currentNavigationStackUri); + } + [Fact] public async Task Navigation_FromIntermediatePageUsingRoute() { From 97e5312462c91895d1fd94bd1492b8ade9ff7bb0 Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 29 Jul 2024 14:10:34 +0900 Subject: [PATCH 05/15] Modify UnitTest --- .../Fixtures/Navigation/NavigationTests.cs | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs index 91add2737..29ece7302 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -593,28 +593,21 @@ public async Task Navigation_FromRootNavigationPageUsingRoute() } [Fact] - public async Task Navigation_FromIntermediatePageUsingRoute() + public async Task Navigation_FromFlyoutPageUsingRoute() { var mauiApp = CreateBuilder(prism => prism.CreateWindow("MockHome/NavigationPage/MockViewA")) .Build(); var window = GetWindow(mauiApp); - var mockHome = (MockHome)window.Page; - var pageNavigatingFrom = (NavigationPage)mockHome.Detail; - Assert.IsType(pageNavigatingFrom.CurrentPage); + var mockHome = (FlyoutPage)window.Page; + var navigationPage = (NavigationPage)mockHome.Detail; + Assert.IsType(navigationPage.CurrentPage); - var result = await pageNavigatingFrom.CurrentPage.GetContainerProvider() - .Resolve() - .NavigateFromAsync("NavigationPage", UriParsingHelper.Parse("MockViewB"), null); + var navigationService = Prism.Navigation.Xaml.Navigation.GetNavigationService(window.CurrentPage); + var result = await navigationService.NavigateFromAsync("MockHome", UriParsingHelper.Parse("MockViewB"), null); Assert.True(result.Success); - - // NavigationPage has not been replaced. - var pageNavigatedFrom = (NavigationPage)mockHome.Detail; - Assert.Equal(pageNavigatingFrom, pageNavigatedFrom); - - // Navigation should be succeeded. - Assert.IsType(pageNavigatedFrom.CurrentPage); + Assert.IsType(mockHome.Detail); } [Theory] From 1f80c08fcc48070dcf93bafcdc661ae6659e1d50 Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 29 Jul 2024 15:35:31 +0900 Subject: [PATCH 06/15] Modify GetParentPage access modifier --- src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs b/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs index 599049579..fb14bfa7e 100644 --- a/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs +++ b/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs @@ -1,4 +1,4 @@ -namespace Prism.Extensions; +namespace Prism.Extensions; internal static class VisualElementExtensions { @@ -17,7 +17,7 @@ public static Element GetRoot(this Element element) }; } - internal static Page GetParentPage(this Element visualElement) + public static Page GetParentPage(this Element visualElement) { return visualElement.Parent switch { From 193aa59f6c0d427f194905a5c371e356d5e13424 Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 29 Jul 2024 16:50:15 +0900 Subject: [PATCH 07/15] Revert "Modify GetParentPage access modifier" This reverts commit 5af4754cb667df8a3f5d699939fb2bc64a86fa3d. --- src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs b/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs index fb14bfa7e..599049579 100644 --- a/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs +++ b/src/Maui/Prism.Maui/Extensions/VisualElementExtensions.cs @@ -1,4 +1,4 @@ -namespace Prism.Extensions; +namespace Prism.Extensions; internal static class VisualElementExtensions { @@ -17,7 +17,7 @@ public static Element GetRoot(this Element element) }; } - public static Page GetParentPage(this Element visualElement) + internal static Page GetParentPage(this Element visualElement) { return visualElement.Parent switch { From 75ef645412fc3ccb610223ecf43bede01c1596ec Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 29 Jul 2024 16:58:12 +0900 Subject: [PATCH 08/15] Refactor insert RemovePageSegment --- .../Navigation/PageNavigationService.cs | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index a90a009e5..17678146d 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -355,6 +355,7 @@ public virtual async Task NavigateFromAsync(string viewName, var foundPage = navigationPages.FirstOrDefault(page => ViewModelLocator.GetNavigationName(page) == viewName); if (foundPage is null) { + // Find a page from parents. var page = currentPage; while (page != null) { @@ -366,14 +367,19 @@ public virtual async Task NavigateFromAsync(string viewName, } else { + // Insert RemovePageSegment. var removePageCount = navigationPages.IndexOf(foundPage); - // Insert RemovePageSegment. - var routeString = route.ToString(); + var tempQueue = new Queue(); for (int i = 0; i < removePageCount; i++) { - AddToFront(navigationSegments, RemovePageSegment); + tempQueue.Enqueue(RemovePageSegment); + } + while(navigationSegments.Count > 0) + { + tempQueue.Enqueue(navigationSegments.Dequeue()); } + navigationSegments = tempQueue; } await ProcessNavigation(currentPage, navigationSegments, parameters, null, null); @@ -392,23 +398,6 @@ public virtual async Task NavigateFromAsync(string viewName, } } - private static void AddToFront(Queue queue, T element) - { - var tempQueue = new Queue(); - - while (queue.Count > 0) - { - tempQueue.Enqueue(queue.Dequeue()); - } - - queue.Enqueue(element); - - while (tempQueue.Count > 0) - { - queue.Enqueue(tempQueue.Dequeue()); - } - } - /// /// Selects a Tab of the TabbedPage parent. /// From 37fb467d6a08ad146e0ed34bbbfe9bf63c49f470 Mon Sep 17 00:00:00 2001 From: niimima <41614284+niimima@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:54:08 +0900 Subject: [PATCH 09/15] Update src/Maui/Prism.Maui/Navigation/PageNavigationService.cs Co-authored-by: Dan Siegel --- src/Maui/Prism.Maui/Navigation/PageNavigationService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index 17678146d..dfdc486ec 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -340,7 +340,8 @@ public virtual async Task NavigateFromAsync(string viewName, try { - if (route.IsAbsoluteUri) throw new NavigationException(NavigationException.UnsupportedAbsoluteUri); + if (route.IsAbsoluteUri) + throw new NavigationException(NavigationException.UnsupportedAbsoluteUri); parameters ??= new NavigationParameters(); From cf61cc0225d7bfeb30642bd97de0789bd2a7cfdf Mon Sep 17 00:00:00 2001 From: niimima <41614284+niimima@users.noreply.github.com> Date: Sun, 9 Feb 2025 22:54:30 +0900 Subject: [PATCH 10/15] Update src/Prism.Core/Navigation/NavigationException.cs Co-authored-by: Dan Siegel --- src/Prism.Core/Navigation/NavigationException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Prism.Core/Navigation/NavigationException.cs b/src/Prism.Core/Navigation/NavigationException.cs index 1077f64d0..f961f7030 100644 --- a/src/Prism.Core/Navigation/NavigationException.cs +++ b/src/Prism.Core/Navigation/NavigationException.cs @@ -75,7 +75,7 @@ public class NavigationException : Exception /// /// The Message returned when an absolute path is specified but not supported. /// - public const string UnsupportedAbsoluteUri = "An unsupported absolute uri. Please use a relative URI."; + public const string UnsupportedAbsoluteUri = "Absolute Uri's are not supported when Navigated from a named source View. Please use a relative Uri."; /// /// Initializes a new instance of the From 5ce2b813bd0388fe0c22a4a3e3c2cdebd4b0c4d0 Mon Sep 17 00:00:00 2001 From: niimima Date: Sun, 9 Feb 2025 23:05:58 +0900 Subject: [PATCH 11/15] Add overloads that accept a string for the route parameter --- .../INavigationServiceExtensions.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs b/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs index 4d24fc3f3..7db96f27d 100644 --- a/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs +++ b/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs @@ -109,12 +109,34 @@ public static Task NavigateAsync(this INavigationService navi /// /// Initiates navigation to the target specified by the from the . /// + /// Service for handling navigation between views /// The name of the View to navigate to /// The route Uri to navigate to /// If true a navigate from operation was successful. If false the navigate from operation failed. public static Task NavigateFromAsync(this INavigationService navigationService, string viewName, Uri route) => navigationService.NavigateFromAsync(viewName, route, new NavigationParameters()); + /// + /// Initiates navigation to the target specified by the from the . + /// + /// Service for handling navigation between views + /// The name of the View to navigate to + /// The route as a string to navigate to + /// If true a navigate from operation was successful. If false the navigate from operation failed. + public static Task NavigateFromAsync(this INavigationService navigationService, string viewName, string route) => + navigationService.NavigateFromAsync(viewName, new Uri(route, UriKind.RelativeOrAbsolute), new NavigationParameters()); + + /// + /// Initiates navigation to the target specified by the from the . + /// + /// Service for handling navigation between views + /// The name of the View to navigate to + /// The route as a string to navigate to + /// Additional parameters for the navigation. + /// If true a navigate from operation was successful. If false the navigate from operation failed. + public static Task NavigateFromAsync(this INavigationService navigationService, string viewName, string route, NavigationParameters parameters) => + navigationService.NavigateFromAsync(viewName, new Uri(route, UriKind.RelativeOrAbsolute), parameters); + /// /// Provides an easy to use way to provide an Error Callback without using await NavigationService /// From f87e51886cd6843c647325a31fd16db869eca107 Mon Sep 17 00:00:00 2001 From: niimima Date: Sun, 16 Feb 2025 05:14:23 +0900 Subject: [PATCH 12/15] Add FindPageByNameAsync --- src/Maui/Prism.Maui/Common/MvvmHelpers.cs | 52 +++++++++++++++++++ .../Navigation/PageNavigationService.cs | 36 +------------ 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/Maui/Prism.Maui/Common/MvvmHelpers.cs b/src/Maui/Prism.Maui/Common/MvvmHelpers.cs index ebc3d9efa..f049d71a4 100644 --- a/src/Maui/Prism.Maui/Common/MvvmHelpers.cs +++ b/src/Maui/Prism.Maui/Common/MvvmHelpers.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using Prism.Dialogs; +using Prism.Mvvm; using Prism.Navigation; using Prism.Navigation.Regions; using Prism.Navigation.Xaml; @@ -404,4 +405,55 @@ internal static bool IsSameOrSubclassOf(Type potentialDescendant) return potentialDescendant.GetTypeInfo().IsSubclassOf(potentialBase) || potentialDescendant == potentialBase; } + + internal static async Task FindPageByNameAsync(Page? current, string name) + { + if (current == null) return null; + + var modal = current.Navigation.ModalStack.LastOrDefault(); + while (modal != null) + { + var target = await FindPageRecursivelyAsync(modal, name); + if (target != null) return target; + + await current.Navigation.PopModalAsync(); + modal = current.Navigation.ModalStack.LastOrDefault(); + } + + return await FindPageRecursivelyAsync(current, name); + } + + private static async Task FindPageRecursivelyAsync(Page? target, string name) + { + if (target != null && ViewModelLocator.GetNavigationName(target) == name) return target; + + switch (target) + { + case NavigationPage navigation: + var page = navigation.Navigation.NavigationStack.LastOrDefault(); + while (page != null) + { + if (ViewModelLocator.GetNavigationName(page) == name) return page; + if (navigation.Navigation.NavigationStack.Count == 1) break; + + await navigation.Navigation.PopAsync(); + page = navigation.Navigation.NavigationStack.LastOrDefault(); + } + return navigation.Parent switch + { + Page parentPage => await FindPageRecursivelyAsync(parentPage, name), + _ => null, + }; + case TabbedPage: + case FlyoutPage: + case ContentPage: + return target.Parent switch + { + Page parentPage => await FindPageRecursivelyAsync(parentPage, name), + _ => null, + }; + } + + return null; + } } diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index dfdc486ec..fdd40863d 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -349,41 +349,9 @@ public virtual async Task NavigateFromAsync(string viewName, var navigationSegments = UriParsingHelper.GetUriSegments(route); - // Find a page that matches the viewName. - var currentPage = GetCurrentPage(); - var navigationPages = currentPage.Navigation.NavigationStack.ToList(); - navigationPages.Reverse(); - var foundPage = navigationPages.FirstOrDefault(page => ViewModelLocator.GetNavigationName(page) == viewName); - if (foundPage is null) - { - // Find a page from parents. - var page = currentPage; - while (page != null) - { - if (page is not null && ViewModelLocator.GetNavigationName(page) == viewName) - break; - page = page.GetParentPage(); - } - currentPage = page; - } - else - { - // Insert RemovePageSegment. - var removePageCount = navigationPages.IndexOf(foundPage); - - var tempQueue = new Queue(); - for (int i = 0; i < removePageCount; i++) - { - tempQueue.Enqueue(RemovePageSegment); - } - while(navigationSegments.Count > 0) - { - tempQueue.Enqueue(navigationSegments.Dequeue()); - } - navigationSegments = tempQueue; - } + var targetPage = await MvvmHelpers.FindPageByNameAsync(GetCurrentPage(), viewName); - await ProcessNavigation(currentPage, navigationSegments, parameters, null, null); + await ProcessNavigation(targetPage, navigationSegments, parameters, null, null); return Notify(route, parameters); } From 345445b923951974f3c26067aab1a144adb4bc5e Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 24 Feb 2025 00:25:02 +0900 Subject: [PATCH 13/15] Add Test Navigation_FromModal --- .../Fixtures/Navigation/NavigationTests.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs index 29ece7302..ccfb620da 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -610,6 +610,30 @@ public async Task Navigation_FromFlyoutPageUsingRoute() Assert.IsType(mockHome.Detail); } + [Fact] + public async Task Navigation_FromModal() + { + var mauiApp = CreateBuilder(prism => prism.CreateWindow("NavigationPage/MockViewA")) + .Build(); + var window = GetWindow(mauiApp); + var navigationPage = (NavigationPage)window.Page; + var currentNavigationStackUri = string.Join("/", navigationPage.Navigation.NavigationStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewA", currentNavigationStackUri); + + var navigationService = Prism.Navigation.Xaml.Navigation.GetNavigationService(window.CurrentPage); + await navigationService.NavigateAsync("MockViewB/MockViewC", new NavigationParameters { { KnownNavigationParameters.UseModalNavigation, true } }); + var currentModalStackUri = string.Join("/", window.CurrentPage.Navigation.ModalStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewB/MockViewC", currentModalStackUri); + + var result = await navigationService.NavigateFromAsync("MockViewB", UriParsingHelper.Parse("MockViewD"), null); + + Assert.True(result.Success); + currentNavigationStackUri = string.Join("/", navigationPage.Navigation.NavigationStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewA", currentNavigationStackUri); + currentModalStackUri = string.Join("/", navigationPage.Navigation.ModalStack.Select(v => v.GetType().Name)); + Assert.Equal("MockViewB/MockViewD", currentModalStackUri); + } + [Theory] [InlineData("MockViewA", "MockViewB", null)] [InlineData("NavigationPage/MockViewA", "MockViewB?useModalNavigation=true", true)] From 5b7f789c89952bf21f492f1a2fa78dbff7032007 Mon Sep 17 00:00:00 2001 From: niimima Date: Mon, 24 Feb 2025 01:57:17 +0900 Subject: [PATCH 14/15] Add e2e test --- e2e/Maui/MauiModule/ViewModels/ViewModelBase.cs | 9 +++++++++ e2e/Maui/MauiModule/Views/ViewD.xaml | 7 ++++++- .../Navigation/INavigationServiceExtensions.cs | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/e2e/Maui/MauiModule/ViewModels/ViewModelBase.cs b/e2e/Maui/MauiModule/ViewModels/ViewModelBase.cs index 280c43cd5..7af030701 100644 --- a/e2e/Maui/MauiModule/ViewModels/ViewModelBase.cs +++ b/e2e/Maui/MauiModule/ViewModels/ViewModelBase.cs @@ -30,6 +30,7 @@ protected ViewModelBase(BaseServices baseServices) ShowDialog = new DelegateCommand(OnShowDialogCommand, () => !string.IsNullOrEmpty(SelectedDialog)) .ObservesProperty(() => SelectedDialog); GoBack = new DelegateCommand(OnGoToBack); + NavigateFrom = new DelegateCommand(OnNavigateFrom); } public IEnumerable AvailableDialogs { get; } @@ -55,6 +56,8 @@ public string SelectedDialog public DelegateCommand GoBack { get; } + public DelegateCommand NavigateFrom { get; } + private void OnNavigateCommandExecuted(string uri) { Messages.Add($"OnNavigateCommandExecuted: {uri}"); @@ -83,6 +86,12 @@ private void OnGoToBack(string viewName) _navigationService.GoBackToAsync(viewName); } + private void OnNavigateFrom() + { + Messages.Add($"On Navigate From B To C"); + _navigationService.NavigateFromAsync("ViewB", "ViewC"); + } + public void Initialize(INavigationParameters parameters) { Messages.Add("ViewModel Initialized"); diff --git a/e2e/Maui/MauiModule/Views/ViewD.xaml b/e2e/Maui/MauiModule/Views/ViewD.xaml index a2e2ef587..c72e60865 100644 --- a/e2e/Maui/MauiModule/Views/ViewD.xaml +++ b/e2e/Maui/MauiModule/Views/ViewD.xaml @@ -1,4 +1,4 @@ - + +