mirror of
https://github.com/bitwarden/android.git
synced 2024-12-28 03:48:31 +03:00
Proof of concept for having multiple window in Android for autofill support and navigating with the help of an Extended splash page.
This commit is contained in:
parent
5cbef47fd4
commit
05858bea48
5 changed files with 146 additions and 14 deletions
|
@ -10,6 +10,7 @@ using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
|
using Bit.Core.Pages;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
@ -74,6 +75,8 @@ namespace Bit.App
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Window CurrentWindow { get; private set; }
|
public static Window CurrentWindow { get; private set; }
|
||||||
|
public static Window AutofillWindow { get; private set; }
|
||||||
|
public static Window MainWindow { get; private set; }
|
||||||
|
|
||||||
public void SetOptions(AppOptions appOptions)
|
public void SetOptions(AppOptions appOptions)
|
||||||
{
|
{
|
||||||
|
@ -84,28 +87,28 @@ namespace Bit.App
|
||||||
{
|
{
|
||||||
if (activationState != null
|
if (activationState != null
|
||||||
&& activationState.State.TryGetValue("autofillFramework", out string autofillFramework)
|
&& activationState.State.TryGetValue("autofillFramework", out string autofillFramework)
|
||||||
&& autofillFramework == "true") //TODO: There are likely better ways to filter this. Maybe using Options.
|
&& autofillFramework == "true"
|
||||||
|
&& activationState.State.ContainsKey("autofillFrameworkCipherId")) //AutofillExternalActivity
|
||||||
{
|
{
|
||||||
if (activationState.State.ContainsKey("autofillFrameworkCipherId")) //TODO: There are likely better ways to filter this. Maybe using Options.
|
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
|
||||||
{
|
}
|
||||||
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
|
else if (Options.FromAutofillFramework || Options.Uri != null || Options.OtpData != null || Options.CreateSend != null) //"Internal" Autofill and Uri/Otp/CreateSend
|
||||||
}
|
{
|
||||||
else
|
AutofillWindow = new Window(new NavigationPage(new AndroidExtSplashPage(Options)));
|
||||||
{
|
CurrentWindow = AutofillWindow;
|
||||||
//TODO: IMPORTANT Question: this works if we want to show the CipherSelection, but we are skipping all our ASYNC Navigation workflows where we (for example) check if the user is logged in and/or needs to enter auth again.
|
return CurrentWindow;
|
||||||
var autofillWindow = new Window(new NavigationPage(new CipherSelectionPage(Options)));
|
|
||||||
return autofillWindow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if(CurrentWindow != null)
|
else if(CurrentWindow != null)
|
||||||
{
|
{
|
||||||
//TODO: This likely crashes if we try to have two apps side-by-side on Android
|
//TODO: This likely crashes if we try to have two apps side-by-side on Android
|
||||||
//TODO: Question: In these scenarios should a new Window be created or can the same one be reused?
|
//TODO: Question: In these scenarios should a new Window be created or can the same one be reused?
|
||||||
|
CurrentWindow = MainWindow;
|
||||||
return CurrentWindow;
|
return CurrentWindow;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CurrentWindow = new Window(new NavigationPage(new HomePage(Options)));
|
MainWindow = new Window(new NavigationPage(new HomePage(Options)));
|
||||||
|
CurrentWindow = MainWindow;
|
||||||
return CurrentWindow;
|
return CurrentWindow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,6 @@
|
||||||
<Folder Include="Resources\Fonts\" />
|
<Folder Include="Resources\Fonts\" />
|
||||||
<Folder Include="Effects\" />
|
<Folder Include="Effects\" />
|
||||||
<Folder Include="Resources\Raw\" />
|
<Folder Include="Resources\Raw\" />
|
||||||
<Folder Include="Pages\" />
|
|
||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
<Folder Include="Controls\" />
|
<Folder Include="Controls\" />
|
||||||
<Folder Include="Lists\" />
|
<Folder Include="Lists\" />
|
||||||
|
@ -92,4 +91,9 @@
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<MauiXaml Update="Pages\AndroidExtSplashPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</MauiXaml>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
10
src/Core/Pages/AndroidExtSplashPage.xaml
Normal file
10
src/Core/Pages/AndroidExtSplashPage.xaml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Bit.Core.Pages.AndroidExtSplashPage"
|
||||||
|
Title="AndroidExtSplashPage"
|
||||||
|
Loaded="AndroidExtSplashPage_OnLoaded">
|
||||||
|
<Grid>
|
||||||
|
<ActivityIndicator VerticalOptions="Center" HorizontalOptions="Center" IsRunning="True" />
|
||||||
|
</Grid>
|
||||||
|
</ContentPage>
|
105
src/Core/Pages/AndroidExtSplashPage.xaml.cs
Normal file
105
src/Core/Pages/AndroidExtSplashPage.xaml.cs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.Pages;
|
||||||
|
|
||||||
|
public partial class AndroidExtSplashPage : ContentPage
|
||||||
|
{
|
||||||
|
private IConditionedAwaiterManager _conditionedAwaiterManager;
|
||||||
|
private IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private IStateService _stateService;
|
||||||
|
private AppOptions _appOptions;
|
||||||
|
|
||||||
|
public AndroidExtSplashPage(AppOptions appOptions)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_appOptions = appOptions;
|
||||||
|
_conditionedAwaiterManager = ServiceContainer.Resolve<IConditionedAwaiterManager>();
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetNavigationToExecuteAsync(AppOptions options, Window window)
|
||||||
|
{
|
||||||
|
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
|
|
||||||
|
var authed = await _stateService.IsAuthenticatedAsync();
|
||||||
|
if (authed)
|
||||||
|
{
|
||||||
|
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
||||||
|
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
||||||
|
{
|
||||||
|
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
||||||
|
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
||||||
|
|
||||||
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
||||||
|
var navParams = new LoginNavigationParams(email);
|
||||||
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
{
|
||||||
|
window.Page = new NavigationPage(new LoginPage(loginParams.Email, options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (await _vaultTimeoutService.IsLockedAsync() ||
|
||||||
|
await _vaultTimeoutService.ShouldLockAsync())
|
||||||
|
{
|
||||||
|
/* //TODO: is lockParams needed here?
|
||||||
|
if (navParams is LockNavigationParams lockParams)
|
||||||
|
{
|
||||||
|
return () => new Window(new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{*/
|
||||||
|
window.Page = new NavigationPage(new LockPage(options));
|
||||||
|
/*}*/
|
||||||
|
}
|
||||||
|
else if (options.FromAutofillFramework && options.SaveType.HasValue)
|
||||||
|
{
|
||||||
|
window.Page = new NavigationPage(new CipherAddEditPage(appOptions: options));
|
||||||
|
}
|
||||||
|
else if (options.Uri != null)
|
||||||
|
{
|
||||||
|
window.Page = new NavigationPage(new CipherSelectionPage(options));
|
||||||
|
}
|
||||||
|
else if (options.OtpData != null)
|
||||||
|
{
|
||||||
|
window.Page = new NavigationPage(new CipherSelectionPage(options));
|
||||||
|
}
|
||||||
|
else if (options.CreateSend != null)
|
||||||
|
{
|
||||||
|
window.Page = new NavigationPage(new SendAddEditPage(options));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.Page = new TabsPage(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
||||||
|
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
||||||
|
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
||||||
|
{
|
||||||
|
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
||||||
|
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
||||||
|
|
||||||
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
await _stateService.SetRememberedEmailAsync(email);
|
||||||
|
|
||||||
|
window.Page = new NavigationPage(new HomePage(options));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.Page = new NavigationPage(new HomePage(options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void AndroidExtSplashPage_OnLoaded(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
await GetNavigationToExecuteAsync(_appOptions, this.Window);
|
||||||
|
}
|
||||||
|
}
|
|
@ -144,7 +144,17 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
private void OnUnloaded(object sender, EventArgs e)
|
private void OnUnloaded(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Handler?.DisconnectHandler();
|
try
|
||||||
|
{
|
||||||
|
Handler?.DisconnectHandler();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
//Workaround: Currently the Disconnect Handler needs to be manually called from the App: https://github.com/dotnet/maui/issues/3604
|
||||||
|
// In some specific edges cases the MauiContext can be gone when we call this. (for example filling a field using Accessibility)
|
||||||
|
// In those scenarios the app should just be "closing" anyway, so we just want to avoid the exception.
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetToVaultPage()
|
public void ResetToVaultPage()
|
||||||
|
|
Loading…
Reference in a new issue