Skip to content

Commit

Permalink
Merge pull request #3014 from PrismLibrary/dev/ds/maui-select-tab
Browse files Browse the repository at this point in the history
Adding select tab API to INavigationService
  • Loading branch information
dansiegel authored Jan 6, 2024
2 parents 9e17d1b + 1d0beea commit e7058ca
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 9 deletions.
11 changes: 10 additions & 1 deletion src/Maui/Prism.Maui/Navigation/INavigationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,18 @@ public interface INavigationService
/// </summary>
/// <param name="uri">The Uri to navigate to</param>
/// <param name="parameters">The navigation parameters</param>
/// <returns><see cref="INavigationResult"/> indicating whether the request was successful or if there was an encountered <see cref="Exception"/>.</returns>
/// <remarks>Navigation parameters can be provided in the Uri and by using the <paramref name="parameters"/>.</remarks>
/// <example>
/// NavigateAsync(new Uri("MainPage?id=3&amp;name=brian", UriKind.RelativeSource), parameters);
/// NavigateAsync(new Uri("MainPage?id=3&amp;name=Brian", UriKind.RelativeSource), parameters);
/// </example>
Task<INavigationResult> NavigateAsync(Uri uri, INavigationParameters parameters);

/// <summary>
/// Selects a Tab of the TabbedPage parent.
/// </summary>
/// <param name="name">The name of the tab to select</param>
/// <param name="parameters">The navigation parameters</param>
/// <returns><see cref="INavigationResult"/> indicating whether the request was successful or if there was an encountered <see cref="Exception"/>.</returns>
Task<INavigationResult> SelectTabAsync(string name, INavigationParameters parameters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ public static void OnNavigationError(this Task<INavigationResult> navigationTask
});
}

/// <summary>
/// Selects a tab programatically
/// </summary>
/// <param name="navigationService"></param>
/// <param name="tabName">The name of the tab to select</param>
/// <returns>The <see cref="INavigationResult"/>.</returns>
public static Task<INavigationResult> SelectTabAsync(this INavigationService navigationService, string tabName) =>
navigationService.SelectTabAsync(tabName, new NavigationParameters());

private static INavigationParameters GetNavigationParameters((string Key, object Value)[] parameters)
{
var navParams = new NavigationParameters();
Expand Down
57 changes: 56 additions & 1 deletion src/Maui/Prism.Maui/Navigation/PageNavigationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ protected Window Window
{
_window = _pageAccessor.Page.GetParentWindow();
}
else
{
_window = _windowManager.Windows.OfType<PrismWindow>().FirstOrDefault();
}

return _window;
}
Expand All @@ -49,6 +53,7 @@ protected Window Window
/// <param name="container">The <see cref="IContainerProvider"/> that will be used to resolve pages for navigation.</param>
/// <param name="windowManager">The <see cref="IWindowManager"/> that will let the NavigationService retrieve, open or close the app Windows.</param>
/// <param name="eventAggregator">The <see cref="IEventAggregator"/> that will raise <see cref="NavigationRequestEvent"/>.</param>
/// <param name="pageAccessor">The <see cref="IPageAccessor"/> that will let the NavigationService retrieve the <see cref="Page"/> for the current scope.</param>q
public PageNavigationService(IContainerProvider container,
IWindowManager windowManager,
IEventAggregator eventAggregator,
Expand Down Expand Up @@ -319,6 +324,56 @@ public virtual async Task<INavigationResult> NavigateAsync(Uri uri, INavigationP
}
}

/// <summary>
/// Selects a Tab of the TabbedPage parent.
/// </summary>
/// <param name="tabName">The name of the tab to select</param>
/// <param name="parameters">The navigation parameters</param>
/// <returns><see cref="INavigationResult"/> indicating whether the request was successful or if there was an encountered <see cref="Exception"/>.</returns>
public virtual async Task<INavigationResult> 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);
}
}

/// <summary>
/// Processes the Navigation for the Queued navigation segments
/// </summary>
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -276,11 +276,67 @@ public async Task TabbedPageSelectTabSetsCurrentTabWithNavigationPageTab()
Assert.IsType<MockViewB>(navPage.CurrentPage);
}

[Fact]
public async Task TabbedPage_SelectsNewTab()
{
var mauiApp = CreateBuilder(prism => prism
.CreateWindow(nav => nav.CreateBuilder()
.AddTabbedSegment(s => s.CreateTab("MockViewA")
.CreateTab("MockViewB")
.CreateTab("MockViewC"))
.NavigateAsync()))
.Build();
var window = GetWindow(mauiApp);
Assert.IsAssignableFrom<TabbedPage>(window.Page);
var tabbed = window.Page as TabbedPage;

Assert.NotNull(tabbed);

Assert.IsType<MockViewA>(tabbed.CurrentPage);
var mockViewA = tabbed.CurrentPage;
var mockViewANav = Prism.Navigation.Xaml.Navigation.GetNavigationService(mockViewA);

await mockViewANav.SelectTabAsync("MockViewB");

Assert.IsNotType<MockViewA>(tabbed.CurrentPage);
Assert.IsType<MockViewB>(tabbed.CurrentPage);
}

[Fact]
public async Task TabbedPage_SelectsNewTab_WithNavigationParameters()
{
var mauiApp = CreateBuilder(prism => prism
.CreateWindow(nav => nav.CreateBuilder()
.AddTabbedSegment(s => s.CreateTab("MockViewA")
.CreateTab("MockViewB")
.CreateTab("MockViewC"))
.NavigateAsync()))
.Build();
var window = GetWindow(mauiApp);
Assert.IsAssignableFrom<TabbedPage>(window.Page);
var tabbed = window.Page as TabbedPage;

Assert.NotNull(tabbed);

Assert.IsType<MockViewA>(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<MockViewA>(tabbed.CurrentPage);
Assert.IsType<MockViewB>(tabbed.CurrentPage);

var viewModel = tabbed.CurrentPage.BindingContext as MockViewBViewModel;
Assert.Equal(expectedMessage, viewModel?.Message);
}

[Fact]
public async Task NavigationPage_DoesNotHave_MauiPage_AsRootPage()
{
var mauiApp = CreateBuilder(prism => prism
.OnAppStart("NavigationPage/MockViewA"))
.CreateWindow("NavigationPage/MockViewA"))
.Build();
var window = GetWindow(mauiApp);

Expand All @@ -298,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")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -14,6 +14,11 @@ protected MockViewModelBase(IPageAccessor pageAccessor, INavigationService navig
NavigationService = navigationService;
}

public string Message { get; private set; }

private List<string> _actions = [];
public IEnumerable<string> Actions => _actions;

public INavigationService NavigationService { get; }

public Task<INavigationResult> GoBack() => NavigationService.GoBackAsync();
Expand All @@ -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<string>("Message", out var message))
Message = message;
}
}

0 comments on commit e7058ca

Please sign in to comment.