From 9b03e57266010f5205a6972c93127a1bca19d369 Mon Sep 17 00:00:00 2001 From: Dan Siegel Date: Sun, 19 Nov 2023 10:30:38 -0600 Subject: [PATCH 1/3] feat: adding select tab API to INavigationService --- .../Navigation/INavigationService.cs | 11 +++- .../Navigation/PageNavigationService.cs | 57 ++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/Maui/Prism.Maui/Navigation/INavigationService.cs b/src/Maui/Prism.Maui/Navigation/INavigationService.cs index d6f1325939..88f726d3de 100644 --- a/src/Maui/Prism.Maui/Navigation/INavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/INavigationService.cs @@ -33,9 +33,18 @@ public interface INavigationService /// /// The Uri to navigate to /// The navigation parameters + /// indicating whether the request was successful or if there was an encountered . /// Navigation parameters can be provided in the Uri and by using the . /// - /// NavigateAsync(new Uri("MainPage?id=3&name=brian", UriKind.RelativeSource), parameters); + /// NavigateAsync(new Uri("MainPage?id=3&name=Brian", UriKind.RelativeSource), parameters); /// Task NavigateAsync(Uri uri, INavigationParameters parameters); + + /// + /// Selects a Tab of the TabbedPage parent. + /// + /// The name of the tab to select + /// The navigation parameters + /// indicating whether the request was successful or if there was an encountered . + Task SelectTabAsync(string name, INavigationParameters parameters); } diff --git a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs index 1ad5afbd2c..5113365005 100644 --- a/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Maui/Prism.Maui/Navigation/PageNavigationService.cs @@ -35,6 +35,10 @@ protected Window Window { _window = _pageAccessor.Page.GetParentWindow(); } + else + { + _window = _windowManager.Windows.OfType().FirstOrDefault(); + } return _window; } @@ -49,6 +53,7 @@ protected Window Window /// The that will be used to resolve pages for navigation. /// The that will let the NavigationService retrieve, open or close the app Windows. /// The that will raise . + /// The that will let the NavigationService retrieve the for the current scope.q public PageNavigationService(IContainerProvider container, IWindowManager windowManager, IEventAggregator eventAggregator, @@ -319,6 +324,56 @@ public virtual async Task NavigateAsync(Uri uri, INavigationP } } + /// + /// Selects a Tab of the TabbedPage parent. + /// + /// The name of the tab to select + /// The navigation parameters + /// indicating whether the request was successful or if there was an encountered . + public virtual async Task SelectTabAsync(string tabName, INavigationParameters parameters) + { + try + { + var tabbedPage = GetTabbedPage(_pageAccessor.Page); + TabbedPage GetTabbedPage(Element page) => + page switch + { + TabbedPage tabbedPage => tabbedPage, + null => null, + _ => GetTabbedPage(page.Parent) + }; + + if (tabbedPage is null) + throw new NullReferenceException("The Page is null."); + + var parts = tabName.Split('|'); + Page selectedChild = null; + if (parts.Length == 1) + selectedChild = tabbedPage.Children.FirstOrDefault(x => ViewModelLocator.GetNavigationName(x) == tabName || (x is NavigationPage navPage && ViewModelLocator.GetNavigationName(navPage.RootPage) == tabName)); + else if (parts.Length == 2) + selectedChild = tabbedPage.Children.FirstOrDefault(x => x is NavigationPage navPage && ViewModelLocator.GetNavigationName(navPage) == parts[0] && ViewModelLocator.GetNavigationName(navPage.RootPage) == parts[1]); + else + throw new NavigationException($"Invalid Tab Name: {tabName}"); + + if (selectedChild is null) + throw new NavigationException($"No Tab found with the Name: {tabName}"); + + var navigatedFromPage = _pageAccessor.Page; + if (!await MvvmHelpers.CanNavigateAsync(navigatedFromPage, parameters)) + throw new NavigationException(NavigationException.IConfirmNavigationReturnedFalse, navigatedFromPage); + + tabbedPage.CurrentPage = selectedChild; + MvvmHelpers.OnNavigatedFrom(navigatedFromPage, parameters); + MvvmHelpers.OnNavigatedTo(selectedChild, parameters); + + return new NavigationResult(); + } + catch (Exception ex) + { + return new NavigationResult(ex); + } + } + /// /// Processes the Navigation for the Queued navigation segments /// @@ -605,7 +660,7 @@ await DoNavigateAction(currentPage, nextSegment, nextPage, parameters, async () var nextSegmentType = Registry.GetViewType(UriParsingHelper.GetSegmentName(nextSegment)); - //we must recreate the NavigationPage everytime or the transitions on iOS will not work properly, unless we meet the two scenarios below + //we must recreate the NavigationPage every time or the transitions on iOS will not work properly, unless we meet the two scenarios below bool detailIsNavPage = false; bool reuseNavPage = false; if (detail is NavigationPage navPage) From 7a2327204d40f7d2def4c44b081bf459309acc43 Mon Sep 17 00:00:00 2001 From: Dan Siegel Date: Sat, 6 Jan 2024 09:42:38 -0600 Subject: [PATCH 2/3] test: adding Tabbed Navigation Tests --- .../INavigationServiceExtensions.cs | 9 +++ .../Fixtures/Navigation/NavigationTests.cs | 60 ++++++++++++++++++- .../Mocks/ViewModels/MockViewModelBase.cs | 26 +++++++- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs b/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs index 19bd384e48..f853818303 100644 --- a/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs +++ b/src/Maui/Prism.Maui/Navigation/INavigationServiceExtensions.cs @@ -117,6 +117,15 @@ public static void OnNavigationError(this Task navigationTask }); } + /// + /// Selects a tab programatically + /// + /// + /// The name of the tab to select + /// The . + public static Task SelectTabAsync(this INavigationService navigationService, string tabName) => + navigationService.SelectTabAsync(tabName, new NavigationParameters()); + private static INavigationParameters GetNavigationParameters((string Key, object Value)[] parameters) { var navParams = new NavigationParameters(); 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 9521731740..92a8e4521a 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -249,7 +249,7 @@ public async Task GoBack_Issue2232() } [Fact] - public async Task TabbedPageSelectTabSetsCurrentTab() + public async Task TabbedPage_SelectTabSets_CurrentTab() { var mauiApp = CreateBuilder(prism => prism.CreateWindow("TabbedPage?createTab=MockViewA&createTab=MockViewB&selectedTab=MockViewB")) .Build(); @@ -262,7 +262,7 @@ public async Task TabbedPageSelectTabSetsCurrentTab() } [Fact] - public async Task TabbedPageSelectTabSetsCurrentTabWithNavigationPageTab() + public async Task TabbedPage_SelectTab_SetsCurrentTab_WithNavigationPageTab() { var mauiApp = CreateBuilder(prism => prism.CreateWindow("TabbedPage?createTab=NavigationPage%2FMockViewA&createTab=NavigationPage%2FMockViewB&selectedTab=NavigationPage|MockViewB")) .Build(); @@ -276,6 +276,62 @@ public async Task TabbedPageSelectTabSetsCurrentTabWithNavigationPageTab() Assert.IsType(navPage.CurrentPage); } + [Fact] + public async Task TabbedPage_SelectsNewTab() + { + var mauiApp = CreateBuilder(prism => prism + .OnAppStart(nav => nav.CreateBuilder() + .AddTabbedSegment(s => s.CreateTab("MockViewA") + .CreateTab("MockViewB") + .CreateTab("MockViewC")) + .NavigateAsync())) + .Build(); + var window = GetWindow(mauiApp); + Assert.IsAssignableFrom(window.Page); + var tabbed = window.Page as TabbedPage; + + Assert.NotNull(tabbed); + + Assert.IsType(tabbed.CurrentPage); + var mockViewA = tabbed.CurrentPage; + var mockViewANav = Prism.Navigation.Xaml.Navigation.GetNavigationService(mockViewA); + + await mockViewANav.SelectTabAsync("MockViewB"); + + Assert.IsNotType(tabbed.CurrentPage); + Assert.IsType(tabbed.CurrentPage); + } + + [Fact] + public async Task TabbedPage_SelectsNewTab_WithNavigationParameters() + { + var mauiApp = CreateBuilder(prism => prism + .OnAppStart(nav => nav.CreateBuilder() + .AddTabbedSegment(s => s.CreateTab("MockViewA") + .CreateTab("MockViewB") + .CreateTab("MockViewC")) + .NavigateAsync())) + .Build(); + var window = GetWindow(mauiApp); + Assert.IsAssignableFrom(window.Page); + var tabbed = window.Page as TabbedPage; + + Assert.NotNull(tabbed); + + Assert.IsType(tabbed.CurrentPage); + var mockViewA = tabbed.CurrentPage; + var mockViewANav = Prism.Navigation.Xaml.Navigation.GetNavigationService(mockViewA); + + var expectedMessage = nameof(TabbedPage_SelectsNewTab_WithNavigationParameters); + await mockViewANav.SelectTabAsync("MockViewB", new NavigationParameters { { "Message", expectedMessage } }); + + Assert.IsNotType(tabbed.CurrentPage); + Assert.IsType(tabbed.CurrentPage); + + var viewModel = tabbed.CurrentPage.BindingContext as MockViewBViewModel; + Assert.Equal(expectedMessage, viewModel?.Message); + } + [Fact] public async Task NavigationPage_DoesNotHave_MauiPage_AsRootPage() { diff --git a/tests/Maui/Prism.DryIoc.Maui.Tests/Mocks/ViewModels/MockViewModelBase.cs b/tests/Maui/Prism.DryIoc.Maui.Tests/Mocks/ViewModels/MockViewModelBase.cs index f4affb67f6..f5c8a69f5f 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Mocks/ViewModels/MockViewModelBase.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Mocks/ViewModels/MockViewModelBase.cs @@ -2,7 +2,7 @@ namespace Prism.DryIoc.Maui.Tests.Mocks.ViewModels; -public abstract class MockViewModelBase : IActiveAware, IConfirmNavigation +public abstract class MockViewModelBase : IActiveAware, INavigationAware, IConfirmNavigation { private readonly IPageAccessor _pageAccessor; @@ -14,6 +14,11 @@ protected MockViewModelBase(IPageAccessor pageAccessor, INavigationService navig NavigationService = navigationService; } + public string Message { get; private set; } + + private List _actions = []; + public IEnumerable Actions => _actions; + public INavigationService NavigationService { get; } public Task GoBack() => NavigationService.GoBackAsync(); @@ -26,6 +31,21 @@ protected MockViewModelBase(IPageAccessor pageAccessor, INavigationService navig public bool IsActive { get; set; } - public bool CanNavigate(INavigationParameters parameters) => - !StopNavigation; + public bool CanNavigate(INavigationParameters parameters) + { + _actions.Add(nameof(CanNavigate)); + return !StopNavigation; + } + + public void OnNavigatedFrom(INavigationParameters parameters) + { + _actions.Add(nameof(OnNavigatedFrom)); + } + + public void OnNavigatedTo(INavigationParameters parameters) + { + _actions.Add(nameof(OnNavigatedTo)); + if (parameters.TryGetValue("Message", out var message)) + Message = message; + } } From 1d0beeacd4e81df7be280bdaffb04f81e86c6425 Mon Sep 17 00:00:00 2001 From: Dan Siegel Date: Sat, 6 Jan 2024 12:26:21 -0600 Subject: [PATCH 3/3] test: fix updated api from rebase --- .../Fixtures/Navigation/NavigationTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 92a8e4521a..52273ef6a1 100644 --- a/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs +++ b/tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -280,7 +280,7 @@ public async Task TabbedPage_SelectTab_SetsCurrentTab_WithNavigationPageTab() public async Task TabbedPage_SelectsNewTab() { var mauiApp = CreateBuilder(prism => prism - .OnAppStart(nav => nav.CreateBuilder() + .CreateWindow(nav => nav.CreateBuilder() .AddTabbedSegment(s => s.CreateTab("MockViewA") .CreateTab("MockViewB") .CreateTab("MockViewC")) @@ -306,7 +306,7 @@ public async Task TabbedPage_SelectsNewTab() public async Task TabbedPage_SelectsNewTab_WithNavigationParameters() { var mauiApp = CreateBuilder(prism => prism - .OnAppStart(nav => nav.CreateBuilder() + .CreateWindow(nav => nav.CreateBuilder() .AddTabbedSegment(s => s.CreateTab("MockViewA") .CreateTab("MockViewB") .CreateTab("MockViewC")) @@ -336,7 +336,7 @@ public async Task TabbedPage_SelectsNewTab_WithNavigationParameters() public async Task NavigationPage_DoesNotHave_MauiPage_AsRootPage() { var mauiApp = CreateBuilder(prism => prism - .OnAppStart("NavigationPage/MockViewA")) + .CreateWindow("NavigationPage/MockViewA")) .Build(); var window = GetWindow(mauiApp); @@ -354,7 +354,7 @@ public async Task NavigationPage_DoesNotHave_MauiPage_AsRootPage() public async Task NavigationPage_UsesRootPageTitle_WithTabbedParent() { var mauiApp = CreateBuilder(prism => prism - .OnAppStart(n => n.CreateBuilder() + .CreateWindow(n => n.CreateBuilder() .AddTabbedSegment(s => s .CreateTab(t => t.AddNavigationPage() .AddSegment("MockViewA")))