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

IActiveAware activates inactive views #2848

Merged
merged 4 commits into from
Apr 24, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions e2e/Maui/PrismMauiDemo.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Prism.DryIoc.Maui", "..\..\
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Prism.DryIoc.Shared", "..\..\src\Containers\Prism.DryIoc.Shared\Prism.DryIoc.Shared.shproj", "{6E7EC81D-DA39-4C4F-A898-0148558C34F4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E91F80AA-3D61-4C28-B876-3EDFB5921E7D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Prism.DryIoc.Maui.Tests", "..\..\tests\Maui\Prism.DryIoc.Maui.Tests\Prism.DryIoc.Maui.Tests.csproj", "{EE6F0C99-61D1-4E2E-8185-FBA0D246D5C7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Prism.Maui.Tests", "..\..\tests\Maui\Prism.Maui.Tests\Prism.Maui.Tests.csproj", "{F3D2DFDB-95FB-4CBB-A624-35EB6550854D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Prism.Core.Tests", "..\..\tests\Prism.Core.Tests\Prism.Core.Tests.csproj", "{E0F13AA9-8083-47CA-B10D-93C5285D1505}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -56,6 +64,18 @@ Global
{2CE09D7A-B67C-4586-8432-64EA2D1A3CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CE09D7A-B67C-4586-8432-64EA2D1A3CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CE09D7A-B67C-4586-8432-64EA2D1A3CE4}.Release|Any CPU.Build.0 = Release|Any CPU
{EE6F0C99-61D1-4E2E-8185-FBA0D246D5C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE6F0C99-61D1-4E2E-8185-FBA0D246D5C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE6F0C99-61D1-4E2E-8185-FBA0D246D5C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE6F0C99-61D1-4E2E-8185-FBA0D246D5C7}.Release|Any CPU.Build.0 = Release|Any CPU
{F3D2DFDB-95FB-4CBB-A624-35EB6550854D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3D2DFDB-95FB-4CBB-A624-35EB6550854D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3D2DFDB-95FB-4CBB-A624-35EB6550854D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3D2DFDB-95FB-4CBB-A624-35EB6550854D}.Release|Any CPU.Build.0 = Release|Any CPU
{E0F13AA9-8083-47CA-B10D-93C5285D1505}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0F13AA9-8083-47CA-B10D-93C5285D1505}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0F13AA9-8083-47CA-B10D-93C5285D1505}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0F13AA9-8083-47CA-B10D-93C5285D1505}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -66,6 +86,9 @@ Global
{CC05A6F0-D8FF-498C-80F0-59DC9AAEB08E} = {8202B92A-A573-4365-8A15-E246504A7CBD}
{2CE09D7A-B67C-4586-8432-64EA2D1A3CE4} = {8202B92A-A573-4365-8A15-E246504A7CBD}
{6E7EC81D-DA39-4C4F-A898-0148558C34F4} = {8202B92A-A573-4365-8A15-E246504A7CBD}
{EE6F0C99-61D1-4E2E-8185-FBA0D246D5C7} = {E91F80AA-3D61-4C28-B876-3EDFB5921E7D}
{F3D2DFDB-95FB-4CBB-A624-35EB6550854D} = {E91F80AA-3D61-4C28-B876-3EDFB5921E7D}
{E0F13AA9-8083-47CA-B10D-93C5285D1505} = {E91F80AA-3D61-4C28-B876-3EDFB5921E7D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {50B0D1F3-D832-4C6C-858E-24F5F3B33632}
Expand Down
41 changes: 21 additions & 20 deletions src/Maui/Prism.Maui/Behaviors/MultiPageActiveAwareBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using Prism.Common;
using Prism.Extensions;

namespace Prism.Behaviors;

public class MultiPageActiveAwareBehavior<T> : BehaviorBase<MultiPage<T>> where T : Page

Check warning on line 6 in src/Maui/Prism.Maui/Behaviors/MultiPageActiveAwareBehavior.cs

View workflow job for this annotation

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

Missing XML comment for publicly visible type or member 'MultiPageActiveAwareBehavior<T>'
{
protected T _lastSelectedPage;

/// <inheritDoc/>
protected override void OnAttachedTo(MultiPage<T> bindable)
{
Expand All @@ -31,16 +30,7 @@
/// <param name="e">Event Args</param>
protected void CurrentPageChangedHandler(object sender, EventArgs e)
{
if (_lastSelectedPage == null)
_lastSelectedPage = AssociatedObject.CurrentPage;

//inactive
SetIsActive(_lastSelectedPage, false);

_lastSelectedPage = AssociatedObject.CurrentPage;

//active
SetIsActive(_lastSelectedPage, true);
SetActiveAware();
}

/// <summary>
Expand All @@ -50,10 +40,7 @@
/// <param name="e">Event Args</param>
protected void RootPageAppearingHandler(object sender, EventArgs e)
{
if (_lastSelectedPage == null)
_lastSelectedPage = AssociatedObject.CurrentPage;

SetIsActive(_lastSelectedPage, true);
SetActiveAware();
}

/// <summary>
Expand All @@ -63,13 +50,27 @@
/// <param name="e">Event Args</param>
protected void RootPageDisappearingHandler(object sender, EventArgs e)
{
SetIsActive(_lastSelectedPage, false);
SetActiveAware();
}

private static void SetIsActive(object view, bool isActive)
private void SetActiveAware()
{
var pageToSetIsActive = view is NavigationPage page ? page.CurrentPage : view;
AssociatedObject.Children.ForEach(page => SetPageIsActive(page, AssociatedObject.CurrentPage == page));
}

MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(pageToSetIsActive, activeAware => activeAware.IsActive = isActive);
private void SetPageIsActive(Page page, bool isActive)
{
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(page, aa => SetIsActive(aa, isActive));

if (page is NavigationPage navPage)
{
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(navPage.CurrentPage, aa => SetIsActive(aa, isActive));
}
}

private static void SetIsActive(IActiveAware activeAware, bool isActive)
{
if (activeAware?.IsActive != isActive)
activeAware.IsActive = isActive;
}
}
42 changes: 36 additions & 6 deletions src/Maui/Prism.Maui/Behaviors/NavigationPageActiveAwareBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
using System.ComponentModel;
using Prism.Common;
using Prism.Extensions;
using PropertyChangingEventArgs = Microsoft.Maui.Controls.PropertyChangingEventArgs;

namespace Prism.Behaviors;

public class NavigationPageActiveAwareBehavior : BehaviorBase<NavigationPage>

Check warning on line 8 in src/Maui/Prism.Maui/Behaviors/NavigationPageActiveAwareBehavior.cs

View workflow job for this annotation

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

Missing XML comment for publicly visible type or member 'NavigationPageActiveAwareBehavior'
{
protected override void OnAttachedTo(NavigationPage bindable)

Check warning on line 10 in src/Maui/Prism.Maui/Behaviors/NavigationPageActiveAwareBehavior.cs

View workflow job for this annotation

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

Missing XML comment for publicly visible type or member 'NavigationPageActiveAwareBehavior.OnAttachedTo(NavigationPage)'
{
bindable.PropertyChanging += NavigationPage_PropertyChanging;
bindable.PropertyChanged += NavigationPage_PropertyChanged;
if (bindable.Parent is null)
bindable.ParentChanged += OnParentChanged;
base.OnAttachedTo(bindable);
}

private void OnParentChanged(object sender, EventArgs e)
{
AssociatedObject.ParentChanged -= OnParentChanged;
SetActiveAware();
}

protected override void OnDetachingFrom(NavigationPage bindable)

Check warning on line 24 in src/Maui/Prism.Maui/Behaviors/NavigationPageActiveAwareBehavior.cs

View workflow job for this annotation

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

Missing XML comment for publicly visible type or member 'NavigationPageActiveAwareBehavior.OnDetachingFrom(NavigationPage)'
{
bindable.PropertyChanging -= NavigationPage_PropertyChanging;
bindable.PropertyChanged -= NavigationPage_PropertyChanged;
base.OnDetachingFrom(bindable);
}
Expand All @@ -24,15 +31,38 @@
{
if (e.PropertyName == "CurrentPage")
{
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(AssociatedObject.CurrentPage, (obj) => obj.IsActive = true);
SetActiveAware();
}
}

private void NavigationPage_PropertyChanging(object sender, PropertyChangingEventArgs e)
private void SetActiveAware()
{
if (e.PropertyName == "CurrentPage")
if(AssociatedObject.Parent is TabbedPage tabbed && tabbed.CurrentPage != AssociatedObject)
{
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(AssociatedObject, SetNotActive);
AssociatedObject.Navigation.NavigationStack.ForEach(page => MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(page, SetNotActive));
}
else
{
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(AssociatedObject.CurrentPage, (obj) => obj.IsActive = false);
AssociatedObject.Navigation.NavigationStack.ForEach(page =>
{
if (page != AssociatedObject.CurrentPage)
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(page, SetNotActive);
else
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(page, SetIsActive);
});
}
}

private void SetNotActive(IActiveAware activeAware)
{
if (activeAware.IsActive)
activeAware.IsActive = false;
}

private void SetIsActive(IActiveAware activeAware)
{
if(!activeAware.IsActive)
activeAware.IsActive = true;
}
}
16 changes: 16 additions & 0 deletions src/Maui/Prism.Maui/Extensions/CollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Prism.Extensions;

internal static class CollectionExtensions
{
public static void ForEach<T>(this IReadOnlyList<T> list, Action<T> action)
{
foreach (var item in list)
action(item);
}

public static void ForEach<T>(this IList<T> list, Action<T> action)
{
foreach (var item in list)
action(item);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Microsoft.Maui.Controls;
using Prism.Controls;
using Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
using Prism.DryIoc.Maui.Tests.Mocks.Views;
using Prism.Navigation.Builder;

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

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

[Fact]
public void RootPageIsNotActive()
{
var rootPage = StartAndGetRootPage("NavigationPage/MockViewA/MockViewB");

Assert.IsType<PrismNavigationPage>(rootPage);
var navPage = (NavigationPage)rootPage;

var viewA = navPage.Navigation.NavigationStack[0];
var viewB = navPage.Navigation.NavigationStack[1];

Assert.IsType<MockViewA>(viewA);
Assert.IsType<MockViewB>(viewB);

AssertIsActive(viewA, false);
AssertIsActive(viewB, true);
}

[Fact]
public void TabPageSetsFirstTabIsActive()
{
var rootPage = StartAndGetRootPage(b => b.AddTabbedSegment(t => t.CreateTab("MockViewA").CreateTab("MockViewB")));

Assert.IsType<TabbedPage>(rootPage);

var tabbed = (TabbedPage)rootPage;
AssertIsActive(tabbed.Children[0], true);
AssertIsActive(tabbed.Children[1], false);
}

[Fact]
public void TabPageSetsSecondTabIsActive()
{
var rootPage = StartAndGetRootPage(b => b.AddTabbedSegment(t => t.CreateTab("MockViewA").CreateTab("MockViewB").SelectedTab("MockViewB")));

Assert.IsType<TabbedPage>(rootPage);

var tabbed = (TabbedPage)rootPage;
AssertIsActive(tabbed.Children[0], false);
AssertIsActive(tabbed.Children[1], true);
}

[Fact]
public void TabPageSetsFirstTabIsActiveWithNavigationPage()
{
var rootPage = StartAndGetRootPage(b => b.AddTabbedSegment(t => t.CreateTab(tb => tb.AddNavigationPage().AddSegment("MockViewA")).CreateTab(tb => tb.AddNavigationPage().AddSegment("MockViewB"))));

Assert.IsType<TabbedPage>(rootPage);

var tabbed = (TabbedPage)rootPage;

AssertIsActive(GetTabChild(tabbed, 0), true);
AssertIsActive(GetTabChild(tabbed, 1), false);
}

[Fact]
public void TabPageSetsSecondTabIsActiveWithNavigationPage()
{
var rootPage = StartAndGetRootPage(b =>
b.AddTabbedSegment(t =>
t.CreateTab(tb => tb.AddNavigationPage().AddSegment("MockViewA"))
.CreateTab(tb => tb.AddNavigationPage().AddSegment("MockViewB"))
.SelectedTab("NavigationPage|MockViewB")));

Assert.IsType<TabbedPage>(rootPage);

var tabbed = (TabbedPage)rootPage;
AssertIsActive(GetTabChild(tabbed, 0), false);
AssertIsActive(GetTabChild(tabbed, 1), true);
}

private void AssertIsActive(Page page, bool expected)
{
var viewModel = (MockViewModelBase)page.BindingContext;
Assert.Equal(expected, viewModel.IsActive);
}

private Page GetTabChild(TabbedPage tabbed, int index)
{
var child = tabbed.Children[index];
Assert.IsType<PrismNavigationPage>(child);
var navPage = (NavigationPage)child;
return navPage.CurrentPage;
}

private Page StartAndGetRootPage(Action<INavigationBuilder> initialNav)
{
var mauiApp = CreateBuilder(prism => prism.OnAppStart((_, nav) =>
{
var navBuilder = nav.CreateBuilder();
initialNav(navBuilder);
return navBuilder.NavigateAsync();
}))
.Build();
var window = GetWindow(mauiApp);

var rootPage = window.Page;

Assert.NotNull(rootPage);
return rootPage;
}

private Page StartAndGetRootPage(string uri)
{
var mauiApp = CreateBuilder(prism => prism.OnAppStart(uri))
.Build();
var window = GetWindow(mauiApp);

var rootPage = window.Page;

Assert.NotNull(rootPage);
return rootPage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
using Prism.DryIoc.Maui.Tests.Mocks.Views;
using Prism.Navigation.Xaml;
using TabbedPage = Microsoft.Maui.Controls.TabbedPage;

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

Expand Down Expand Up @@ -247,6 +248,34 @@ public async Task GoBack_Issue2232()
Assert.IsType<MockViewA>(navigationPage.CurrentPage);
}

[Fact]
public async Task TabbedPageSelectTabSetsCurrentTab()
{
var mauiApp = CreateBuilder(prism => prism.OnAppStart("TabbedPage?createTab=MockViewA&createTab=MockViewB&selectedTab=MockViewB"))
.Build();
var window = GetWindow(mauiApp);

Assert.IsAssignableFrom<TabbedPage>(window.Page);
var tabbedPage = (TabbedPage)window.Page;
Assert.NotNull(tabbedPage);
Assert.IsType<MockViewB>(tabbedPage.CurrentPage);
}

[Fact]
public async Task TabbedPageSelectTabSetsCurrentTabWithNavigationPageTab()
{
var mauiApp = CreateBuilder(prism => prism.OnAppStart("TabbedPage?createTab=NavigationPage%2FMockViewA&createTab=NavigationPage%2FMockViewB&selectedTab=NavigationPage|MockViewB"))
.Build();
var window = GetWindow(mauiApp);

Assert.IsAssignableFrom<TabbedPage>(window.Page);
var tabbedPage = (TabbedPage)window.Page;
Assert.NotNull(tabbedPage);
var navPage = tabbedPage.CurrentPage as NavigationPage;
Assert.NotNull(navPage);
Assert.IsType<MockViewB>(navPage.CurrentPage);
}

private void TestPage(Page page)
{
Assert.NotNull(page.BindingContext);
Expand Down