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

Fix page swap from background #19532

Merged
merged 4 commits into from
Feb 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
183 changes: 183 additions & 0 deletions src/Controls/samples/Controls.Sample.UITests/Issues/Issue11501.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 11501, "Making Fragment Changes While App is Backgrounded Fails", PlatformAffected.Android)]
public class Issue11501 : TestContentPage
{
Func<Task> _currentTest;
Page _mainPage;
List<Page> _modalStack;
Window _window;
public Issue11501()
{
Loaded += OnLoaded;
}

private void OnLoaded(object sender, EventArgs e)
{
_window = Window;
_mainPage = Application.Current.MainPage;
_modalStack = Navigation.ModalStack.ToList();
}

private async void OnWindowActivated(object sender, EventArgs e)
{
DisconnectFromWindow();
if (_currentTest is not null)
{
await Task.Yield();
await _currentTest();
_currentTest = null;
}
}

void ConnectToWindow()
{
_window.Stopped -= OnWindowActivated;
_window.Stopped += OnWindowActivated;
}

void DisconnectFromWindow()
{
_window.Stopped -= OnWindowActivated;
}

protected override void Init()
{
Content = new VerticalStackLayout()
{
new Button()
{
Text = "Swap Main Page",
AutomationId = "SwapMainPage",
Command = new Command( () =>
{
Application.Current.MainPage =
new ContentPage() { Title = "Test", Content = new Label() { AutomationId = "BackgroundMe", Text = "Background/Minimize the app" } };

ConnectToWindow();
_currentTest = () =>
{
Application.Current.MainPage = CreateDestinationPage();
return Task.CompletedTask;
};
})
},
new Button()
{
Text = "Changing Details/Flyout on FlyoutPage in Background",
AutomationId = "SwapFlyoutPage",
Command = new Command(()=>
{
var flyoutPage = new FlyoutPage()
{
Flyout = new ContentPage() { Title = "Test", Content = new Label(){Text = "Background/Minimize the app" } },
Detail = new NavigationPage(new ContentPage(){ Title = "Test", Content = new Label() { AutomationId = "BackgroundMe", Text = "Background/Minimize the app" } })
{
Title = "Test"
},
};

Application.Current.MainPage = flyoutPage;
ConnectToWindow();

_currentTest = () =>
{
flyoutPage.Flyout = CreateDestinationPage();
flyoutPage.Detail = new NavigationPage(CreateDestinationPage());
return Task.CompletedTask;
};
})
},

new Button()
{
Text = "Swap Tabbed Page",
AutomationId = "SwapTabbedPage",
Command = new Command( () =>
{
Application.Current.MainPage = new ContentPage() { Title = "Test", Content = new Label() {AutomationId = "BackgroundMe", Text = "Background/Minimize the app" } };
ConnectToWindow();
_currentTest = () =>
{
Application.Current.MainPage = new TabbedPage()
{
Children =
{
new NavigationPage(CreateDestinationPage())
{
Title = "Test"
},
new ContentPage() { Title = "Test", Content = new Label(){Text = "Second Page" } },
}
};
return Task.CompletedTask;
};
})
},
new Button()
{
Text = "Removing and Changing Tabs",
AutomationId = "RemoveAddTabs",
Command = new Command(() =>
{
var tabbedPage = new TabbedPage()
{
Children =
{
new ContentPage() { Title = "Test", Content = new Label() { AutomationId = "BackgroundMe", Text = "Background/Minimize the app" } },
new NavigationPage(CreateDestinationPage())
{
Title = "Test"
}
}
};

Application.Current.MainPage = tabbedPage;
ConnectToWindow();

_currentTest = () =>
{
tabbedPage.Children.RemoveAt(0);
tabbedPage.Children.Add(CreateDestinationPage());
return Task.CompletedTask;
};
})
},
};
}

ContentPage CreateDestinationPage()
{
return new ContentPage()
{
Title = "Test",
Content = new VerticalStackLayout()
{
new Button()
{
AutomationId = "Restore",
Text = "Restore",
Command = new Command(async ()=>
{
Application.Current.MainPage = _mainPage;

await Task.Yield();

foreach(var page in _modalStack)
{
await _mainPage.Navigation.PushModalAsync(page);
}
})
}
}
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,9 @@ public static void UpdateBackButton(this AToolbar nativeToolbar, Toolbar toolbar
nativeToolbar.Context ??
toolbar.Handler?.MauiContext?.Context;

nativeToolbar.NavigationIcon ??= new DrawerArrowDrawable(context!)
{
Progress = 1
};
nativeToolbar.NavigationIcon ??= new DrawerArrowDrawable(context!);
if (nativeToolbar.NavigationIcon is DrawerArrowDrawable iconDrawable)
iconDrawable.Progress = 1;

var backButtonTitle = toolbar.BackButtonTitle;
ImageSource image = toolbar.TitleIcon;
Expand All @@ -110,6 +109,9 @@ public static void UpdateBackButton(this AToolbar nativeToolbar, Toolbar toolbar
}
else
{
if (nativeToolbar.NavigationIcon is DrawerArrowDrawable iconDrawable)
iconDrawable.Progress = 0;

nativeToolbar.SetNavigationContentDescription(Resource.String.nav_app_bar_open_drawer_description);
}
}
Expand Down
67 changes: 45 additions & 22 deletions src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using Android.App.Roles;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Views;
using AndroidX.CoordinatorLayout.Widget;
using AndroidX.Fragment.App;
using AndroidX.ViewPager.Widget;
using AndroidX.ViewPager2.Widget;
using Google.Android.Material.AppBar;
using Google.Android.Material.BottomNavigation;
Expand Down Expand Up @@ -60,13 +58,11 @@ internal class TabbedPageManager
ColorStateList _currentBarTextColorStateList;
bool _tabItemStyleLoaded;
TabLayoutMediator _tabLayoutMediator;

NavigationRootManager NavigationRootManager { get; }
IDisposable _pendingFragment;

public TabbedPageManager(IMauiContext context)
{
_context = context;
NavigationRootManager = _context.GetNavigationRootManager();
_listeners = new Listeners(this);
_viewPager = new ViewPager2(context.Context)
{
Expand Down Expand Up @@ -175,6 +171,9 @@ void OnLayoutChanged(object sender, AView.LayoutChangeEventArgs e)

void RemoveTabs()
{
_pendingFragment?.Dispose();
_pendingFragment = null;

if (_tabLayoutFragment != null)
{
var fragment = _tabLayoutFragment;
Expand All @@ -189,13 +188,19 @@ void RemoveTabs()
{
SetContentBottomMargin(0);

_ = _context
.GetNavigationRootManager()
.FragmentManager
.BeginTransaction()
.Remove(fragment)
.SetReorderingAllowed(true)
.Commit();
if (_context?.Context is Context c)
{
_pendingFragment =
fragmentManager
.RunOrWaitForResume(c, fm =>
{
fm
.BeginTransaction()
.Remove(fragment)
.SetReorderingAllowed(true)
.Commit();
});
}
}

_tabplacementId = 0;
Expand Down Expand Up @@ -223,6 +228,9 @@ void RootViewChanged(object sender, EventArgs e)

internal void SetTabLayout()
{
_pendingFragment?.Dispose();
_pendingFragment = null;

int id;
var rootManager =
_context.GetNavigationRootManager();
Expand All @@ -231,7 +239,6 @@ internal void SetTabLayout()
if (rootManager.RootView == null)
{
rootManager.RootViewChanged += RootViewChanged;

return;
}

Expand All @@ -241,7 +248,6 @@ internal void SetTabLayout()
if (_tabplacementId == id)
return;

_tabLayoutFragment = new ViewFragment(BottomNavigationView);
SetContentBottomMargin(_context.Context.Resources.GetDimensionPixelSize(Resource.Dimension.design_bottom_navigation_height));
}
else
Expand All @@ -250,17 +256,34 @@ internal void SetTabLayout()
if (_tabplacementId == id)
return;

_tabLayoutFragment = new ViewFragment(TabLayout);
SetContentBottomMargin(0);
}

_tabplacementId = id;
_ = rootManager
.FragmentManager
.BeginTransaction()
.Replace(id, _tabLayoutFragment)
.SetReorderingAllowed(true)
.Commit();
if (_context?.Context is Context c)
{
_pendingFragment =
rootManager
.FragmentManager
.RunOrWaitForResume(c, fm =>
{
if (IsBottomTabPlacement)
{
_tabLayoutFragment = new ViewFragment(BottomNavigationView);
}
else
{
_tabLayoutFragment = new ViewFragment(TabLayout);
}

_tabplacementId = id;

fm
.BeginTransactionEx()
.ReplaceEx(id, _tabLayoutFragment)
.SetReorderingAllowed(true)
.Commit();
});
}
}

void SetContentBottomMargin(int bottomMargin)
Expand Down
45 changes: 45 additions & 0 deletions src/Controls/tests/UITests/Tests/Issues/Issue11501.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.AppiumTests.Issues
{
public class Issue11501 : _IssuesUITest
{
public Issue11501(TestDevice device) : base(device)
{
}

public override string Issue => "Making Fragment Changes While App is Backgrounded Fails";

[TestCase("SwapMainPage")]
[TestCase("SwapFlyoutPage")]
[TestCase("SwapTabbedPage")]
[TestCase("RemoveAddTabs")]
public async Task MakingFragmentRelatedChangesWhileAppIsBackgroundedFails(string scenario)
{
this.IgnoreIfPlatforms(new TestDevice[] { TestDevice.Mac, TestDevice.Windows });

try
{
App.WaitForElement(scenario);
App.Click(scenario);
App.BackgroundApp();

// Wait for app to finish backgrounding
await Task.Yield();
App.WaitForNoElement("BackgroundMe");
App.ForegroundApp();
App.WaitForElement("Restore");
App.Click("Restore");
}
catch
{
// Just in case these tests leave the app in an unreliable state
App.ResetApp();
FixtureSetup();
throw;
}
}
}
}
Loading
Loading