Skip to content

Commit

Permalink
Merge pull request #3039 from PrismLibrary/dev/ds/navigation-tabs
Browse files Browse the repository at this point in the history
Use RootPage Title/IconImageSource when parent is TabbedPage
  • Loading branch information
dansiegel authored Jan 6, 2024
2 parents 4e69c5f + 8524916 commit 9e17d1b
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 9 deletions.
7 changes: 4 additions & 3 deletions e2e/Maui/MauiModule/Views/ViewA.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiModule.Views.ViewA"
Title="{Binding Title}"
IconImageSource="home.png"
BackgroundColor="White">
<Grid RowDefinitions="*,Auto"
ColumnDefinitions="*,*">
Expand Down Expand Up @@ -31,18 +32,18 @@
</CollectionView.ItemTemplate>
</CollectionView>

<Button Text="ViewB"
<Button Text="ViewB"
Command="{prism:NavigateTo 'ViewB'}"
Margin="10"
Grid.Row="1">
<Button.CommandParameter>
<prism:Parameter Key="Message" Value="Hi from View A!"/>
</Button.CommandParameter>
</Button>
<Button Text="Show Alert Dialog"
<Button Text="Show Alert Dialog"
Command="{Binding ShowPageDialog}"
Margin="10"
Grid.Row="1"
Grid.Column="1"/>
</Grid>
</ContentPage>
</ContentPage>
3 changes: 2 additions & 1 deletion e2e/Maui/MauiModule/Views/ViewB.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiModule.Views.ViewB"
Title="{Binding Title}"
IconImageSource="profile.png"
BackgroundColor="White">
<Grid RowDefinitions="*,Auto"
ColumnDefinitions="*,*">
Expand Down Expand Up @@ -41,4 +42,4 @@
Grid.Row="1"
Grid.Column="1"/>
</Grid>
</ContentPage>
</ContentPage>
3 changes: 2 additions & 1 deletion e2e/Maui/MauiModule/Views/ViewC.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiModule.Views.ViewC"
Title="{Binding Title}"
IconImageSource="setting.png"
BackgroundColor="White">
<Grid RowDefinitions="*,Auto"
ColumnDefinitions="*,*">
Expand Down Expand Up @@ -41,4 +42,4 @@
Grid.Row="1"
Grid.Column="1"/>
</Grid>
</ContentPage>
</ContentPage>
Binary file added e2e/Maui/PrismMauiDemo/Resources/Images/home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace Prism.Behaviors;

/// <summary>
/// Adds a behavior to use the RootPage Title and IconImageSource if they are not set on the NavigaitonPage
/// when the NavigationPage has a TabbedPage parent.
/// </summary>
public sealed class NavigationPageTabbedParentBehavior : BehaviorBase<NavigationPage>
{
private static readonly BindableProperty NavigationPageRootPageMonitorTitleProperty =
BindableProperty.CreateAttached("NavigationPageRootPageMonitorTitle", typeof(bool), typeof(NavigationPageTabbedParentBehavior), false);

private static readonly BindableProperty NavigationPageRootPageMonitorIconImageSourceProperty =
BindableProperty.CreateAttached("NavigationPageRootPageMonitorIconImageSource", typeof(bool), typeof(NavigationPageTabbedParentBehavior), false);

private static bool GetNavigationPageRootPageMonitorTitle(BindableObject bindable) =>
(bool)bindable.GetValue(NavigationPageRootPageMonitorTitleProperty);

private static void SetNavigationPageRootPageMonitorTitle(BindableObject bindable, bool monitorTitle) =>
bindable.SetValue(NavigationPageRootPageMonitorTitleProperty, monitorTitle);

private static bool GetNavigationPageRootPageMonitorIconImageSource(BindableObject bindable) =>
(bool)bindable.GetValue(NavigationPageRootPageMonitorIconImageSourceProperty);

private static void SetNavigationPageRootPageMonitorIconImageSource(BindableObject bindable, bool monitorTitle) =>
bindable.SetValue(NavigationPageRootPageMonitorIconImageSourceProperty, monitorTitle);

/// <inheritdoc />
protected override void OnAttachedTo(NavigationPage bindable)
{
base.OnAttachedTo(bindable);
SetNavigationPageRootPageMonitorTitle(bindable, !bindable.IsSet(NavigationPage.TitleProperty));
SetNavigationPageRootPageMonitorIconImageSource(bindable, !bindable.IsSet(NavigationPage.IconImageSourceProperty));
bindable.ParentChanged += OnParentChanged;
if (bindable.Parent is TabbedPage)
OnParentChanged(bindable, EventArgs.Empty);
}

/// <inheritdoc />
protected override void OnDetachingFrom(NavigationPage bindable)
{
base.OnDetachingFrom(bindable);
bindable.ParentChanged -= OnParentChanged;
}

private void OnParentChanged(object sender, EventArgs e)
{
if (sender is not NavigationPage navigationPage || navigationPage.Parent is not TabbedPage)
return;

if (GetNavigationPageRootPageMonitorTitle(navigationPage))
navigationPage.SetBinding(NavigationPage.TitleProperty, new Binding("RootPage.Title", BindingMode.OneWay, source: navigationPage));

if (GetNavigationPageRootPageMonitorIconImageSource(navigationPage))
navigationPage.SetBinding(NavigationPage.IconImageSourceProperty, new Binding("RootPage.IconImageSource", BindingMode.OneWay, source: navigationPage));
}
}
40 changes: 37 additions & 3 deletions src/Maui/Prism.Maui/Navigation/NavigationRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Prism.Behaviors;
using Prism.Common;
using Prism.Ioc;
using Prism.Mvvm;
using Prism.Navigation.Xaml;
using TabbedPage = Microsoft.Maui.Controls.TabbedPage;
Expand Down Expand Up @@ -33,6 +32,18 @@ private static void ConfigurePage(IContainerProvider container, Page page)
{
var scope = container.CreateScope();
ConfigurePage(scope, navPage.RootPage);

if (navPage.RootPage.GetType().Equals(typeof(Page)))
{
if (navPage.RootPage == navPage.CurrentPage)
{
navPage.Pushed += PreventDefaultRootPage;
}
else
{
navPage.Navigation.RemovePage(navPage.RootPage);
}
}
}

if (page.GetContainerProvider() is null)
Expand All @@ -47,11 +58,34 @@ private static void ConfigurePage(IContainerProvider container, Page page)
#endif
throw new NavigationException($"Invalid Scope provided. The current scope Page Accessor contains '{accessor.Page.GetType().FullName}', expected '{page.GetType().FullName}'.", page);
}
else if (accessor.Page is null)
accessor.Page = page;

accessor.Page ??= page;

var behaviorFactories = container.Resolve<IEnumerable<IPageBehaviorFactory>>();
foreach (var factory in behaviorFactories)
factory.ApplyPageBehaviors(page);
}

private static void PreventDefaultRootPage(object sender, NavigationEventArgs e)
{
if (sender is not NavigationPage navigationPage)
{
return;
}

if (!navigationPage.RootPage.GetType().Equals(typeof(Page)))
{
navigationPage.Pushed -= PreventDefaultRootPage;
return;
}

if (navigationPage.RootPage == navigationPage.CurrentPage)
{
return;
}

navigationPage.Pushed -= PreventDefaultRootPage;

navigationPage.Navigation.RemovePage(navigationPage.RootPage);
}
}
1 change: 1 addition & 0 deletions src/Maui/Prism.Maui/PrismAppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ private void RegisterDefaultRequiredTypes(IContainerRegistry containerRegistry)
containerRegistry.RegisterManySingleton<PrismWindowManager>();
containerRegistry.RegisterPageBehavior<NavigationPage, NavigationPageSystemGoBackBehavior>();
containerRegistry.RegisterPageBehavior<NavigationPage, NavigationPageActiveAwareBehavior>();
containerRegistry.RegisterPageBehavior<NavigationPage, NavigationPageTabbedParentBehavior>();
containerRegistry.RegisterPageBehavior<TabbedPage, TabbedPageActiveAwareBehavior>();
containerRegistry.RegisterPageBehavior<PageLifeCycleAwareBehavior>();
containerRegistry.RegisterPageBehavior<PageScopeBehavior>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,49 @@ public async Task TabbedPageSelectTabSetsCurrentTabWithNavigationPageTab()
Assert.IsType<MockViewB>(navPage.CurrentPage);
}

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

Check failure on line 283 in tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

'PrismAppBuilder' does not contain a definition for 'OnAppStart' and no accessible extension method 'OnAppStart' accepting a first argument of type 'PrismAppBuilder' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 283 in tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

'PrismAppBuilder' does not contain a definition for 'OnAppStart' and no accessible extension method 'OnAppStart' accepting a first argument of type 'PrismAppBuilder' could be found (are you missing a using directive or an assembly reference?)
.Build();
var window = GetWindow(mauiApp);

Assert.IsAssignableFrom<NavigationPage>(window.Page);
var navPage = window.Page as NavigationPage;

Assert.NotNull(navPage);
Assert.IsNotType<Page>(navPage.RootPage);
Assert.IsType<MockViewA>(navPage.RootPage);

Assert.Same(navPage.RootPage, navPage.CurrentPage);
}

[Fact]
public async Task NavigationPage_UsesRootPageTitle_WithTabbedParent()
{
var mauiApp = CreateBuilder(prism => prism
.OnAppStart(n => n.CreateBuilder()

Check failure on line 301 in tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

'PrismAppBuilder' does not contain a definition for 'OnAppStart' and no accessible extension method 'OnAppStart' accepting a first argument of type 'PrismAppBuilder' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 301 in tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

'PrismAppBuilder' does not contain a definition for 'OnAppStart' and no accessible extension method 'OnAppStart' accepting a first argument of type 'PrismAppBuilder' could be found (are you missing a using directive or an assembly reference?)
.AddTabbedSegment(s => s
.CreateTab(t => t.AddNavigationPage()
.AddSegment("MockViewA")))
.NavigateAsync()))
.Build();
var window = GetWindow(mauiApp);
Assert.IsAssignableFrom<TabbedPage>(window.Page);
var tabbed = window.Page as TabbedPage;

Assert.NotNull(tabbed);

Assert.Single(tabbed.Children);
var child = tabbed.Children[0];
Assert.IsAssignableFrom<NavigationPage>(child);
var navPage = child as NavigationPage;

Assert.Equal(navPage.Title, navPage.RootPage.Title);
Assert.Equal(MockViewA.ExpectedTitle, navPage.Title);
}

private void TestPage(Page page)
{
Assert.NotNull(page.BindingContext);
Expand Down
11 changes: 10 additions & 1 deletion tests/Maui/Prism.DryIoc.Maui.Tests/Mocks/Views/MockViewA.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
namespace Prism.DryIoc.Maui.Tests.Mocks.Views;

public class MockViewA : ContentPage { }
public class MockViewA : ContentPage
{
public const string ExpectedTitle = "Mock View A";
public static readonly ImageSource ExpectedIconImageSource = "home.png";
public MockViewA()
{
Title = ExpectedTitle;
IconImageSource = ExpectedIconImageSource;
}
}

0 comments on commit 9e17d1b

Please sign in to comment.