Skip to content

Commit

Permalink
Merge pull request #2848 from PrismLibrary/active-aware-fix
Browse files Browse the repository at this point in the history
IActiveAware activates inactive views
  • Loading branch information
dansiegel committed Apr 24, 2023
2 parents 3299222 + f44a585 commit 17d20e9
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 27 deletions.
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 @@ protected override void OnDetachingFrom(MultiPage<T> bindable)
/// <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 @@ protected void CurrentPageChangedHandler(object sender, EventArgs e)
/// <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 @@ protected void RootPageAppearingHandler(object sender, EventArgs e)
/// <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,5 +1,6 @@
using System.ComponentModel;
using Prism.Common;
using Prism.Extensions;
using PropertyChangingEventArgs = Microsoft.Maui.Controls.PropertyChangingEventArgs;

namespace Prism.Behaviors;
Expand All @@ -8,14 +9,20 @@ public class NavigationPageActiveAwareBehavior : BehaviorBase<NavigationPage>
{
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 @@ private void NavigationPage_PropertyChanged(object sender, PropertyChangedEventA
{
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

0 comments on commit 17d20e9

Please sign in to comment.