From b6ff6e34f6f2868ab0c5c49608f3a1cbfe4d9e3a Mon Sep 17 00:00:00 2001 From: Dinis Vieira Date: Sat, 23 Dec 2023 12:30:21 +0000 Subject: [PATCH] PM-3349 Added custom window so that we can always get the current Active Window. This is used to support the Android Autofil and multi-window scenarios. --- src/Core/App.xaml.cs | 76 +++++++++++----------------------------- src/Core/ResumeWindow.cs | 45 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 src/Core/ResumeWindow.cs diff --git a/src/Core/App.xaml.cs b/src/Core/App.xaml.cs index e550a8989..38709023f 100644 --- a/src/Core/App.xaml.cs +++ b/src/Core/App.xaml.cs @@ -69,12 +69,18 @@ namespace Bit.App CurrentWindow.Page = value; } } - } + } - public static Window CurrentWindow { get; private set; } - - private Window _autofillWindow; - private Window _mainWindow; + /// + /// Find the Current Active Window. There should only be one at any point in Android + /// + public static ResumeWindow CurrentWindow + { + get + { + return Application.Current.Windows.OfType().FirstOrDefault(w => w.IsActive); + } + } //Allows setting Options from MainActivity before base.OnCreate public void SetOptions(AppOptions appOptions) @@ -94,64 +100,22 @@ namespace Bit.App return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally) } - //If there's already an existing autofill window we try to get rid of it, mostly to avoid edge cases scenarios. - if (_autofillWindow != null) - { - CloseWindow(_autofillWindow); - } - //"Internal" Autofill and Uri/Otp/CreateSend. This is where we create the autofill specific Window if (Options != null && (Options.FromAutofillFramework || Options.Uri != null || Options.OtpData != null || Options.CreateSend != null)) { - _autofillWindow = new Window(new NavigationPage(new AndroidNavigationRedirectPage())); - _autofillWindow.Created += WindowOnCreatedOrActivated; - _autofillWindow.Activated += WindowOnCreatedOrActivated; - _autofillWindow.Stopped += WindowOnStopped; - _autofillWindow.Destroying += AutofillWindowOnDestroying; - _isResumed = true; - return _autofillWindow; + _isResumed = true; //Specifically for the Autofill scenario we need to manually set the _isResumed here + return new AutoFillWindow(new NavigationPage(new AndroidNavigationRedirectPage())); } - //If we don't have an existing main window we create it, otherwise we just reuse the one we had. - if(_mainWindow == null) + //If we have an existing MainAppWindow we can use that one + var mainAppWindow = Windows.OfType().FirstOrDefault(); + if (mainAppWindow != null) { - _mainWindow = new Window(new NavigationPage(new HomePage(Options))); - _mainWindow.Created += WindowOnCreatedOrActivated; - _mainWindow.Activated += WindowOnCreatedOrActivated; - _mainWindow.Stopped += WindowOnStopped; - _mainWindow.Destroying += MainWindowOnDestroying; + mainAppWindow.PendingPage = new NavigationPage(new HomePage(Options)); } - return _mainWindow; - } - - private void MainWindowOnDestroying(object sender, EventArgs e) - { - _mainWindow.Created -= WindowOnCreatedOrActivated; - _mainWindow.Activated -= WindowOnCreatedOrActivated; - _mainWindow.Stopped -= WindowOnStopped; - _mainWindow.Destroying -= AutofillWindowOnDestroying; - } - - private void AutofillWindowOnDestroying(object sender, EventArgs e) - { - _autofillWindow.Created -= WindowOnCreatedOrActivated; - _autofillWindow.Activated -= WindowOnCreatedOrActivated; - _autofillWindow.Stopped -= WindowOnStopped; - _autofillWindow.Destroying -= AutofillWindowOnDestroying; - } - - private void WindowOnStopped(object sender, EventArgs e) - { - CurrentWindow = null; - } - - private void WindowOnCreatedOrActivated(object sender, EventArgs e) - { - if (sender is Window window) - { - CurrentWindow = window; - } + //Create new main window + return new MainAppWindow(new NavigationPage(new HomePage(Options))); } #elif IOS //iOS doesn't use the CreateWindow override used in Android so we just set the Application.Current.MainPage directly @@ -589,7 +553,7 @@ namespace Bit.App }; _isResumed = true; #if IOS - //We only set the MainPage here for iOS as Android is using the CreateWindow override for the initial page. + //We only set the MainPage here for iOS. Android is using the CreateWindow override for the initial page. App.MainPage = new NavigationPage(new HomePage(Options)); #endif _accountsManager.NavigateOnAccountChangeAsync().FireAndForget(); diff --git a/src/Core/ResumeWindow.cs b/src/Core/ResumeWindow.cs new file mode 100644 index 000000000..643b7e98c --- /dev/null +++ b/src/Core/ResumeWindow.cs @@ -0,0 +1,45 @@ +namespace Bit.Core +{ + // This ResumeWindow is used as a Workaround for Android to be able to find the current "IsActive" Window + // It also allows setting a "PendingPage" on an existing Window which then navigates when the Window is active. + public class ResumeWindow : Window + { + public Page PendingPage {get;set;} + public bool IsActive { get; set; } + + public ResumeWindow(Page page) : base(page) { } + + /// + /// You need to do this inside OnActivated not OnResumed + /// Androids OnResume maps to OnActivated + /// Androids OnRestart is what Maps to OnResumed + /// I realize this is confusing from the perspective of Android + /// https://github.com/dotnet/maui/issues/1720 explains it a bit better + /// + protected override void OnActivated() + { + base.OnActivated(); + + if (PendingPage is not null) + Page = PendingPage; + + PendingPage = null; + IsActive = true; + } + + protected override void OnDeactivated() + { + IsActive = false; + } + } + + public class MainAppWindow : ResumeWindow + { + public MainAppWindow(Page page) : base(page) { } + } + + public class AutoFillWindow : ResumeWindow + { + public AutoFillWindow(Page page) : base(page){ } + } +}