PM-3349 Added workaround for Android to avoid issues with setting MainPage when app is in background. They are now kept on a Queue to be executed after the app has resumed.

Updated some things on App.xaml.cs to the new MAUI style
This commit is contained in:
Dinis Vieira 2023-11-21 21:26:14 +00:00
parent df2b0b21d5
commit 06a0195a6d
No known key found for this signature in database
GPG key ID: 9389160FF6C295F3

View file

@ -1,7 +1,4 @@
using System; using Bit.App.Abstractions;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
@ -15,9 +12,6 @@ using Bit.Core.Models.Data;
using Bit.Core.Models.Response; using Bit.Core.Models.Response;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.Controls.Xaml;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)] [assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App namespace Bit.App
@ -45,6 +39,11 @@ namespace Bit.App
private static bool _pendingCheckPasswordlessLoginRequests; private static bool _pendingCheckPasswordlessLoginRequests;
private static object _processingLoginRequestLock = new object(); private static object _processingLoginRequestLock = new object();
// [MAUI-Migration] Workaround to avoid issue on Android where trying to show the LockPage when the app is resuming or in background breaks the app.
// This queue keeps those actions so that when the app has resumed they can still be executed.
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
private readonly Queue<Action> _onResumeActions = new Queue<Action>();
public App() : this(null) public App() : this(null)
{ {
} }
@ -82,32 +81,30 @@ namespace Bit.App
var confirmed = true; var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ? var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText; AppResources.Ok : details.ConfirmText;
Device.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
if (!string.IsNullOrWhiteSpace(details.CancelText)) if (!string.IsNullOrWhiteSpace(details.CancelText))
{ {
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText, confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText); details.CancelText);
} }
else else
{ {
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText); await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
} }
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed)); _messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
}); });
} }
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND) else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{ {
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.iOS)
if (Device.RuntimePlatform == Device.iOS)
{ {
ResumedAsync().FireAndForget(); ResumedAsync().FireAndForget();
} }
} }
else if (message.Command == "slept") else if (message.Command == "slept")
{ {
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.iOS)
if (Device.RuntimePlatform == Device.iOS)
{ {
await SleptAsync(); await SleptAsync();
} }
@ -128,9 +125,9 @@ namespace Bit.App
Options.OtpData = new OtpData((string)message.Data); Options.OtpData = new OtpData((string)message.Data);
} }
Device.InvokeOnMainThreadAsync(async () => MainThread.InvokeOnMainThreadAsync(async () =>
{ {
if (Current.MainPage is TabsPage tabsPage) if (MainPage is TabsPage tabsPage)
{ {
while (tabsPage.Navigation.ModalStack.Count > 0) while (tabsPage.Navigation.ModalStack.Count > 0)
{ {
@ -138,7 +135,7 @@ namespace Bit.App
} }
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE) if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
{ {
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options)); MainPage = new NavigationPage(new CipherSelectionPage(Options));
} }
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE) else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{ {
@ -164,23 +161,23 @@ namespace Bit.App
} }
else if (message.Command == "convertAccountToKeyConnector") else if (message.Command == "convertAccountToKeyConnector")
{ {
Device.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
await Application.Current.MainPage.Navigation.PushModalAsync( await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage())); new NavigationPage(new RemoveMasterPasswordPage()));
}); });
} }
else if (message.Command == Constants.ForceUpdatePassword) else if (message.Command == Constants.ForceUpdatePassword)
{ {
Device.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
await Application.Current.MainPage.Navigation.PushModalAsync( await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage())); new NavigationPage(new UpdateTempPasswordPage()));
}); });
} }
else if (message.Command == Constants.ForceSetPassword) else if (message.Command == Constants.ForceSetPassword)
{ {
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync( await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)))); new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
} }
else if (message.Command == "syncCompleted") else if (message.Command == "syncCompleted")
@ -232,7 +229,7 @@ namespace Bit.App
// Delay to wait for the vault page to appear // Delay to wait for the vault page to appear
await Task.Delay(2000); await Task.Delay(2000);
// if there is a request modal opened ignore all incoming requests // if there is a request modal opened ignore all incoming requests
if (App.Current.MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage)) if (MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage))
{ {
return; return;
} }
@ -252,7 +249,7 @@ namespace Bit.App
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId); _pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
if (!loginRequestData.IsExpired) if (!loginRequestData.IsExpired)
{ {
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page))); await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
} }
} }
@ -265,7 +262,7 @@ namespace Bit.App
} }
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId); var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
Device.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
try try
{ {
@ -286,7 +283,7 @@ namespace Bit.App
public AppOptions Options { get; private set; } public AppOptions Options { get; private set; }
protected async override void OnStart() protected override async void OnStart()
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnStart"); System.Diagnostics.Debug.WriteLine("XF App: OnStart");
_isResumed = true; _isResumed = true;
@ -305,8 +302,7 @@ namespace Bit.App
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); _messagingService.Send(Constants.PasswordlessLoginRequestKey);
} }
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.Android)
if (Device.RuntimePlatform == Device.Android)
{ {
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
// Reset delay on every start // Reset delay on every start
@ -317,12 +313,11 @@ namespace Bit.App
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
} }
protected async override void OnSleep() protected override async void OnSleep()
{ {
System.Diagnostics.Debug.WriteLine("XF App: OnSleep"); System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
_isResumed = false; _isResumed = false;
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.Android)
if (Device.RuntimePlatform == Device.Android)
{ {
var isLocked = await _vaultTimeoutService.IsLockedAsync(); var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked) if (!isLocked)
@ -345,8 +340,7 @@ namespace Bit.App
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); _messagingService.Send(Constants.PasswordlessLoginRequestKey);
} }
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.Android)
if (Device.RuntimePlatform == Device.Android)
{ {
ResumedAsync().FireAndForget(); ResumedAsync().FireAndForget();
} }
@ -369,24 +363,33 @@ namespace Bit.App
await ClearCacheIfNeededAsync(); await ClearCacheIfNeededAsync();
Prime(); Prime();
SyncIfNeeded(); SyncIfNeeded();
if (Current.MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage) if (MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage)
{ {
await lockPage.PromptBiometricAfterResumeAsync(); await lockPage.PromptBiometricAfterResumeAsync();
} }
// [MAUI-Migration] Workaround to avoid issue on Android where trying to show the LockPage when the app is resuming or in background breaks the app.
// Currently we keep those actions in a queue until the app has resumed and execute them here.
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
await Task.Delay(50); //Small delay that is part of the workaround and ensures the app is ready to set "MainPage"
while (_onResumeActions.TryDequeue(out var action))
{
MainThread.BeginInvokeOnMainThread(action);
}
} }
public async Task UpdateThemeAsync() public async Task UpdateThemeAsync()
{ {
await Device.InvokeOnMainThreadAsync(() => await MainThread.InvokeOnMainThreadAsync(() =>
{ {
ThemeManager.SetTheme(Current.Resources); ThemeManager.SetTheme(Resources);
_messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY); _messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY);
}); });
} }
private async Task ClearSensitiveFieldsAsync() private async Task ClearSensitiveFieldsAsync()
{ {
await Device.InvokeOnMainThreadAsync(() => await MainThread.InvokeOnMainThreadAsync(() =>
{ {
_messagingService.Send(Constants.ClearSensitiveFields); _messagingService.Send(Constants.ClearSensitiveFields);
}); });
@ -411,8 +414,7 @@ namespace Bit.App
private void ClearAutofillUri() private void ClearAutofillUri()
{ {
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.Android && !string.IsNullOrWhiteSpace(Options.Uri))
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
{ {
Options.Uri = null; Options.Uri = null;
} }
@ -420,22 +422,21 @@ namespace Bit.App
private bool SetTabsPageFromAutofill(bool isLocked) private bool SetTabsPageFromAutofill(bool isLocked)
{ {
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
!Options.FromAutofillFramework) !Options.FromAutofillFramework)
{ {
Task.Run(() => Task.Run(() =>
{ {
Device.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
Options.Uri = null; Options.Uri = null;
if (isLocked) if (isLocked)
{ {
Current.MainPage = new NavigationPage(new LockPage()); MainPage = new NavigationPage(new LockPage());
} }
else else
{ {
Current.MainPage = new TabsPage(); MainPage = new TabsPage();
} }
}); });
}); });
@ -457,12 +458,12 @@ namespace Bit.App
{ {
InitializeComponent(); InitializeComponent();
SetCulture(); SetCulture();
ThemeManager.SetTheme(Current.Resources); ThemeManager.SetTheme(Resources);
Current.RequestedThemeChanged += (s, a) => RequestedThemeChanged += (s, a) =>
{ {
UpdateThemeAsync(); UpdateThemeAsync();
}; };
Current.MainPage = new NavigationPage(new HomePage(Options)); MainPage = new NavigationPage(new HomePage(Options));
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget(); _accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init(); ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
} }
@ -487,7 +488,7 @@ namespace Bit.App
public async Task SetPreviousPageInfoAsync() public async Task SetPreviousPageInfoAsync()
{ {
PreviousPageInfo lastPageBeforeLock = null; PreviousPageInfo lastPageBeforeLock = null;
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0) if (MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{ {
var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1]; var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1];
if (topPage is NavigationPage navPage) if (topPage is NavigationPage navPage)
@ -514,40 +515,56 @@ namespace Bit.App
} }
public void Navigate(NavigationTarget navTarget, INavigationParams navParams) public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
{
// [MAUI-Migration] Workaround to avoid issue on Android where trying to show the LockPage when the app is resuming or in background breaks the app.
// If we are in background we add the Navigation Actions to a queue to execute when the app resumes.
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
#if ANDROID
if (!_isResumed)
{
_onResumeActions.Enqueue(() => NavigateImpl(navTarget, navParams));
return;
}
#endif
NavigateImpl(navTarget, navParams);
}
public void NavigateImpl(NavigationTarget navTarget, INavigationParams navParams)
{ {
switch (navTarget) switch (navTarget)
{ {
case NavigationTarget.HomeLogin: case NavigationTarget.HomeLogin:
Current.MainPage = new NavigationPage(new HomePage(Options)); MainPage = new NavigationPage(new HomePage(Options));
break; break;
case NavigationTarget.Login: case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams) if (navParams is LoginNavigationParams loginParams)
{ {
Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options)); MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
} }
break; break;
case NavigationTarget.Lock: case NavigationTarget.Lock:
if (navParams is LockNavigationParams lockParams) if (navParams is LockNavigationParams lockParams)
{ {
Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric)); MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
} }
else else
{ {
Current.MainPage = new NavigationPage(new LockPage(Options)); MainPage = new NavigationPage(new LockPage(Options));
} }
break; break;
case NavigationTarget.Home: case NavigationTarget.Home:
Current.MainPage = new TabsPage(Options); MainPage = new TabsPage(Options);
break; break;
case NavigationTarget.AddEditCipher: case NavigationTarget.AddEditCipher:
Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options)); MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
break; break;
case NavigationTarget.AutofillCiphers: case NavigationTarget.AutofillCiphers:
case NavigationTarget.OtpCipherSelection: case NavigationTarget.OtpCipherSelection:
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options)); MainPage = new NavigationPage(new CipherSelectionPage(Options));
break; break;
case NavigationTarget.SendAddEdit: case NavigationTarget.SendAddEdit:
Current.MainPage = new NavigationPage(new SendAddEditPage(Options)); MainPage = new NavigationPage(new SendAddEditPage(Options));
break; break;
} }
} }