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

Fixing IsRootPage #3110

Merged
merged 5 commits into from
Mar 27, 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
213 changes: 128 additions & 85 deletions src/Maui/Prism.Maui/Navigation/PrismWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,112 +6,155 @@
using Prism.Navigation.Xaml;
using TabbedPage = Microsoft.Maui.Controls.TabbedPage;

namespace Prism.Navigation;

internal class PrismWindow : Window
namespace Prism.Navigation
{
public const string DefaultWindowName = "__PrismRootWindow";

public PrismWindow(string name = DefaultWindowName)
/// <summary>
/// Represents a window used for Prism navigation in a Maui application.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public class PrismWindow : Window
{
Name = name;
ModalPopping += PrismWindow_ModalPopping;
}
/// <summary>
/// The default name for the Prism window.
/// </summary>
public const string DefaultWindowName = "__PrismRootWindow";

/// <summary>
/// Initializes a new instance of the <see cref="PrismWindow"/> class with the specified name.
/// </summary>
/// <param name="name">The name of the window.</param>
public PrismWindow(string name = DefaultWindowName)
{
Name = name;
ModalPopping += PrismWindow_ModalPopping;
}

public string Name { get; }
/// <summary>
/// Gets the name of the window.
/// </summary>
public string Name { get; }

public bool IsActive { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether the window is active.
/// </summary>
public bool IsActive { get; internal set; }

internal Page CurrentPage => Page is null ? null : MvvmHelpers.GetCurrentPage(Page);
/// <summary>
/// Gets the current page displayed in the window.
/// </summary>
internal Page CurrentPage => Page is null ? null : MvvmHelpers.GetCurrentPage(Page);

internal bool IsRootPage => Page switch
{
TabbedPage tabbed => tabbed.CurrentPage,
NavigationPage nav => nav.RootPage,
_ => Page
} == CurrentPage;
/// <summary>
/// Gets a value indicating whether the current page is the root page.
/// </summary>
internal bool IsRootPage => GetRootPage(Page) == CurrentPage;

[EditorBrowsable(EditorBrowsableState.Never)]
public void OnSystemBack()
{
var currentPage = CurrentPage;
if(currentPage?.Parent is NavigationPage navPage)
private Page GetRootPage(Page page) =>
page switch
{
TabbedPage tabbed => GetRootPage(tabbed.CurrentPage),
NavigationPage nav => GetRootPage(nav.RootPage),
FlyoutPage flyout => GetRootPage(flyout.Detail),
_ => page
};

/// <summary>
/// Handles the system back button press.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void OnSystemBack()
{
// The NavigationPage has already taken care of the GoBack
return;
}
var currentPage = CurrentPage;
if (currentPage?.Parent is NavigationPage navPage)
{
// The NavigationPage has already taken care of the GoBack
return;
}

var container = currentPage.GetContainerProvider();
var container = currentPage.GetContainerProvider();

if (IsRoot(currentPage))
{
var app = container.Resolve<IApplication>() as Application;
app.Quit();
return;
}
else if (currentPage is IDialogContainer dialogContainer)
{
if (dialogContainer.Dismiss.CanExecute(null))
dialogContainer.Dismiss.Execute(null);
}
else
{
var navigation = container.Resolve<INavigationService>();
navigation.GoBackAsync();
if (IsRoot(currentPage))
{
var app = container.Resolve<IApplication>() as Application;
app.Quit();
return;
}
else if (currentPage is IDialogContainer dialogContainer)
{
if (dialogContainer.Dismiss.CanExecute(null))
dialogContainer.Dismiss.Execute(null);
}
else
{
var navigation = container.Resolve<INavigationService>();
navigation.GoBackAsync();
}
}
}

private bool IsRoot(Page page)
{
if (page == Page) return true;

return page.Parent switch
private bool IsRoot(Page page)
{
FlyoutPage flyout => IsRoot(flyout),
TabbedPage tabbed => IsRoot(tabbed),
NavigationPage navigation => IsRoot(navigation),
_ => false
};
}
if (page == Page) return true;

private async void PrismWindow_ModalPopping(object sender, ModalPoppingEventArgs e)
{
if (PageNavigationService.NavigationSource == PageNavigationSource.Device)
{
e.Cancel = true;
var dialogModal = IDialogContainer.DialogStack.LastOrDefault();
if (dialogModal is not null)
return page.Parent switch
{
if (dialogModal.Dismiss.CanExecute(null))
dialogModal.Dismiss.Execute(null);
}
else
FlyoutPage flyout => IsRoot(flyout),
TabbedPage tabbed => IsRoot(tabbed),
NavigationPage navigation => IsRoot(navigation),
_ => false
};
}

private async void PrismWindow_ModalPopping(object sender, ModalPoppingEventArgs e)
{
if (PageNavigationService.NavigationSource == PageNavigationSource.Device)
{
var navService = Xaml.Navigation.GetNavigationService(e.Modal);
await navService.GoBackAsync();
e.Cancel = true;
var dialogModal = IDialogContainer.DialogStack.LastOrDefault();
if (dialogModal is not null)
{
if (dialogModal.Dismiss.CanExecute(null))
dialogModal.Dismiss.Execute(null);
}
else
{
var navService = Xaml.Navigation.GetNavigationService(e.Modal);
await navService.GoBackAsync();
}
}
}
}

protected override void OnActivated()
{
IsActive = true;
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(CurrentPage, x => x.IsActive = true);
}
/// <summary>
/// Called when the window is activated.
/// </summary>
protected override void OnActivated()
{
IsActive = true;
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(CurrentPage, x => x.IsActive = true);
}

protected override void OnDeactivated()
{
IsActive = false;
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(CurrentPage, x => x.IsActive = true);
}
/// <summary>
/// Called when the window is deactivated.
/// </summary>
protected override void OnDeactivated()
{
IsActive = false;
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(CurrentPage, x => x.IsActive = true);
}

protected override void OnResumed()
{
MvvmHelpers.InvokeViewAndViewModelAction<IApplicationLifecycleAware>(CurrentPage, x => x.OnResume());
}
/// <summary>
/// Called when the window is resumed.
/// </summary>
protected override void OnResumed()
{
MvvmHelpers.InvokeViewAndViewModelAction<IApplicationLifecycleAware>(CurrentPage, x => x.OnResume());
}

protected override void OnStopped()
{
MvvmHelpers.InvokeViewAndViewModelAction<IApplicationLifecycleAware>(CurrentPage, x => x.OnSleep());
/// <summary>
/// Called when the window is stopped.
/// </summary>
protected override void OnStopped()
{
MvvmHelpers.InvokeViewAndViewModelAction<IApplicationLifecycleAware>(CurrentPage, x => x.OnSleep());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
using System.Collections.Generic;
using System.Linq;
using Moq;
using NuGet.Frameworks;
using Prism.Ioc;
using Prism.Navigation;
using Prism.Navigation.Regions;
using Prism.Navigation.Regions.Navigation;
using Prism.Navigation.Regions.Navigation;
using Xamarin.Forms;
using Xunit;
using Region = Prism.Navigation.Regions.Region;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Prism.Controls;
using Prism.DryIoc.Maui.Tests.Mocks.Views;

namespace Prism.DryIoc.Maui.Tests.Fixtures.Navigation;

public class PrismWindowTests : TestBase
{
public PrismWindowTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}

[Fact]
public void CurrentPageEqualsRootPage()
{
var mauiApp = CreateBuilder(prism => prism.CreateWindow("MockViewA"))
.Build();
var window = GetWindow(mauiApp);

Assert.IsType<MockViewA>(window.CurrentPage);
Assert.IsType<MockViewA>(window.Page);
Assert.True(window.IsRootPage);
}

[Fact]
public void CurrentPage_FromNavigationPage_EqualsRootPage()
{
var mauiApp = CreateBuilder(prism => prism.CreateWindow("NavigationPage/MockViewA"))
.Build();
var window = GetWindow(mauiApp);

Assert.IsType<MockViewA>(window.CurrentPage);
Assert.IsType<PrismNavigationPage>(window.Page);
Assert.True(window.IsRootPage);
}

[Fact]
public void CurrentPage_FromNavigationPage_IsNotRootPage()
{
var mauiApp = CreateBuilder(prism => prism.CreateWindow("NavigationPage/MockViewA/MockViewB"))
.Build();
var window = GetWindow(mauiApp);

Assert.IsType<MockViewB>(window.CurrentPage);
Assert.IsType<PrismNavigationPage>(window.Page);
Assert.False(window.IsRootPage);
}

[Fact]
public void CurrentPage_IsRoot_FromTabbedPage_WithNavigationPageTab()
{
var mauiApp = CreateBuilder(prism => prism.CreateWindow(n =>
n.CreateBuilder()
.AddTabbedSegment(b => b.CreateTab(t => t.AddNavigationPage().AddSegment("MockViewA"))
.CreateTab("MockViewB")).NavigateAsync()))
.Build();
var window = GetWindow(mauiApp);

Assert.IsType<MockViewA>(window.CurrentPage);
Assert.IsType<TabbedPage>(window.Page);
Assert.True(window.IsRootPage);
}

[Fact]
public void CurrentPage_IsNotRoot_FromTabbedPage_WithDeepLinkedNavigationPageTab()
{
var mauiApp = CreateBuilder(prism => prism.CreateWindow(n =>
n.CreateBuilder()
.AddTabbedSegment(b => b.CreateTab(t =>
t.AddNavigationPage()
.AddSegment("MockViewA")
.AddSegment("MockViewC"))
.CreateTab("MockViewB")).NavigateAsync()))
.Build();
var window = GetWindow(mauiApp);

Assert.IsType<MockViewC>(window.CurrentPage);
Assert.IsType<TabbedPage>(window.Page);
Assert.False(window.IsRootPage);
}

[Fact]
public void CurrentPage_IsRoot_WithFlyoutPage()
{
var mauiApp = CreateBuilder(prism => prism.CreateWindow(n =>
n.CreateBuilder()
.AddSegment("MockHome")
.AddNavigationPage()
.AddSegment("MockViewA")
.NavigateAsync()))
.Build();

var window = GetWindow(mauiApp);

Assert.IsType<MockViewA>(window.CurrentPage);
Assert.IsType<MockHome>(window.Page);
Assert.True(window.IsRootPage);
}
}
2 changes: 1 addition & 1 deletion tests/Maui/Prism.DryIoc.Maui.Tests/Fixtures/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected MauiAppBuilder CreateBuilder(Action<PrismAppBuilder> configurePrism)
});
}

protected static Window GetWindow(MauiApp mauiApp)
protected static PrismWindow GetWindow(MauiApp mauiApp)
{
var app = mauiApp.Services.GetService<IApplication>();
Assert.NotNull(app);
Expand Down
2 changes: 1 addition & 1 deletion tests/Prism.Core.Tests/Prism.Core.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down
Loading