Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding select tab API to INavigationService #3014

Merged
merged 3 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Loading