PM-3350 Fixed iOS Extensions navigation to several pages and improved avoiding duplicate calls to OnNavigatedTo

This commit is contained in:
Federico Maccaroni 2023-11-14 13:43:59 -03:00
parent 5f12bb9747
commit df4d89cd52
No known key found for this signature in database
GPG key ID: 5D233F8F2B034536
13 changed files with 145 additions and 179 deletions

View file

@ -1,10 +1,6 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -19,11 +15,10 @@ namespace Bit.App.Pages
InitializeComponent();
_vm = BindingContext as EnvironmentPageViewModel;
_vm.Page = this;
// 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 (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
}
#if ANDROID
ToolbarItems.RemoveAt(0);
#endif
_webVaultEntry.ReturnType = ReturnType.Next;
_webVaultEntry.ReturnCommand = new Command(() => _apiEntry.Focus());
@ -31,7 +26,7 @@ namespace Bit.App.Pages
_apiEntry.ReturnCommand = new Command(() => _identityEntry.Focus());
_identityEntry.ReturnType = ReturnType.Next;
_identityEntry.ReturnCommand = new Command(() => _iconsEntry.Focus());
_vm.SubmitSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync());
_vm.SubmitSuccessAction = () => MainThread.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync());
_vm.CloseAction = async () =>
{
await Navigation.PopModalAsync();

View file

@ -1,11 +1,7 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -20,16 +16,16 @@ namespace Bit.App.Pages
public HomePage(AppOptions appOptions = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
_vm.Page = this;
_vm.ShowCancelButton = _appOptions?.IosExtension ?? false;
_vm.StartLoginAction = async () => await StartLoginAsync();
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
_vm.StartRegisterAction = () => MainThread.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
_vm.StartSsoLoginAction = () => MainThread.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => MainThread.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
_vm.CloseAction = async () =>
{
await _accountListOverlay.HideAsync();
@ -53,28 +49,28 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(new LoginPage(email, _appOptions)));
}
protected override async void OnAppearing()
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnAppearing();
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
base.OnNavigatedTo(args);
await MainThread.InvokeOnMainThreadAsync(() => _mainContent.Content = _mainLayout);
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(false);
}
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
{
if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
Device.BeginInvokeOnMainThread(() =>
{
UpdateLogo();
});
}
});
try
{
_accountAvatar?.OnAppearing();
if (!_appOptions?.HideAccountSwitcher ?? false)
{
await MainThread.InvokeOnMainThreadAsync(async () => _vm.AvatarImageSource = await GetAvatarImageSourceAsync(false));
}
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
{
if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
MainThread.BeginInvokeOnMainThread(UpdateLogo);
}
});
await _vm.UpdateEnvironment();
}
catch (Exception ex)
@ -83,6 +79,14 @@ namespace Bit.App.Pages
}
}
protected override void OnNavigatingFrom(NavigatingFromEventArgs args)
{
base.OnNavigatingFrom(args);
_broadcasterService?.Unsubscribe(nameof(HomePage));
_accountAvatar?.OnDisappearing();
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
@ -93,13 +97,6 @@ namespace Bit.App.Pages
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(HomePage));
_accountAvatar?.OnDisappearing();
}
private void UpdateLogo()
{
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";

View file

@ -1,18 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
public partial class LoginApproveDevicePage : BaseContentPage
{
private readonly LoginApproveDeviceViewModel _vm;
private readonly AppOptions _appOptions;
@ -28,9 +22,11 @@ namespace Bit.App.Pages
_appOptions = appOptions;
}
protected override void OnAppearing()
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
protected override async Task InitOnNavigatedToAsync()
{
_vm.InitAsync();
await _vm.InitAsync();
}
private async Task ContinueToVaultAsync()
@ -62,4 +58,3 @@ namespace Bit.App.Pages
}
}
}

View file

@ -64,16 +64,12 @@ namespace Bit.App.Pages
}
}
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
public Entry MasterPasswordEntry { get; set; }
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
protected override async Task InitOnNavigatedToAsync()
{
base.OnNavigatedTo(args);
//IsInitialized is used as a workaround to avoid duplicate initialization issues because of OnNavigatedTo being called twice.
if (HasInitialized) { return; }
HasInitialized = true;
_broadcasterService.Subscribe(nameof(LoginPage), message =>
{
if (message.Command == Constants.ClearSensitiveFields)

View file

@ -1,11 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -23,23 +18,25 @@ namespace Bit.App.Pages
_vm.Email = email;
_vm.AuthRequestType = authRequestType;
_vm.AuthingWithSso = authingWithSso;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartTwoFactorAction = () => MainThread.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => MainThread.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.UpdateTempPasswordAction = () => MainThread.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
_vm.CreatePasswordlessLoginCommand.Execute(null);
}
protected override void OnAppearing()
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
protected override Task InitOnNavigatedToAsync()
{
base.OnAppearing();
_vm.StartCheckLoginRequestStatus();
return Task.CompletedTask;
}
protected override void OnDisappearing()
protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
{
base.OnDisappearing();
base.OnNavigatedFrom(args);
_vm.StopCheckLoginRequestStatus();
}
@ -66,4 +63,3 @@ namespace Bit.App.Pages
}
}
}

View file

@ -1,26 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Services;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
public class LoginPasswordlessRequestViewModel : CaptchaProtectedViewModel
@ -72,7 +60,7 @@ namespace Bit.App.Pages
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(() => Device.InvokeOnMainThreadAsync(CloseAction),
CloseCommand = new AsyncCommand(() => MainThread.InvokeOnMainThreadAsync(CloseAction),
onException: _logger.Exception,
allowsMultipleExecutions: false);
}

View file

@ -39,25 +39,10 @@ namespace Bit.App.Pages
}
}
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
protected override async Task InitOnNavigatedToAsync()
{
base.OnNavigatedTo(args);
//IsInitialized is used as a workaround to avoid duplicate initialization issues because of OnNavigatedTo being called twice.
if (HasInitialized) { return; }
HasInitialized = true;
await _vm.InitAsync();
if (string.IsNullOrWhiteSpace(_vm.OrgIdentifier))
{
RequestFocus(_orgIdentifier);
}
}
protected override async void OnNavigatedFrom(NavigatedFromEventArgs args)
{
base.OnNavigatedFrom(args);
await _vm.InitAsync();
if (string.IsNullOrWhiteSpace(_vm.OrgIdentifier))
{

View file

@ -1,9 +1,4 @@
using System;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
namespace Bit.App.Pages
{
public partial class RegisterPage : BaseContentPage
{
@ -16,18 +11,17 @@ namespace Bit.App.Pages
InitializeComponent();
_vm = BindingContext as RegisterPageViewModel;
_vm.Page = this;
_vm.RegistrationSuccess = () => Device.BeginInvokeOnMainThread(async () => await RegistrationSuccessAsync(homePage));
_vm.RegistrationSuccess = () => MainThread.BeginInvokeOnMainThread(async () => await RegistrationSuccessAsync(homePage));
_vm.CloseAction = async () =>
{
await Navigation.PopModalAsync();
};
MasterPasswordEntry = _masterPassword;
ConfirmMasterPasswordEntry = _confirmMasterPassword;
// 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 (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
}
#if ANDROID
ToolbarItems.RemoveAt(0);
#endif
_email.ReturnType = ReturnType.Next;
_email.ReturnCommand = new Command(() => _masterPassword.Focus());
@ -40,14 +34,17 @@ namespace Bit.App.Pages
public Entry MasterPasswordEntry { get; set; }
public Entry ConfirmMasterPasswordEntry { get; set; }
protected override void OnAppearing()
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
protected override Task InitOnNavigatedToAsync()
{
base.OnAppearing();
if (!_inputFocused)
{
RequestFocus(_email);
_inputFocused = true;
}
return Task.CompletedTask;
}
private async void Submit_Clicked(object sender, EventArgs e)

View file

@ -1,9 +1,5 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Models;
using Bit.App.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -19,17 +15,16 @@ namespace Bit.App.Pages
_vm = BindingContext as SetPasswordPageViewModel;
_vm.Page = this;
_vm.SetPasswordSuccessAction =
() => Device.BeginInvokeOnMainThread(async () => await SetPasswordSuccessAsync());
() => MainThread.BeginInvokeOnMainThread(async () => await SetPasswordSuccessAsync());
_vm.CloseAction = async () =>
{
await Navigation.PopModalAsync();
};
_vm.OrgIdentifier = orgIdentifier;
// 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 (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
}
#if ANDROID
ToolbarItems.RemoveAt(0);
#endif
MasterPasswordEntry = _masterPassword;
ConfirmMasterPasswordEntry = _confirmMasterPassword;
@ -43,9 +38,10 @@ namespace Bit.App.Pages
public Entry MasterPasswordEntry { get; set; }
public Entry ConfirmMasterPasswordEntry { get; set; }
protected override async void OnAppearing()
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
protected override async Task InitOnNavigatedToAsync()
{
base.OnAppearing();
await _vm.InitAsync();
RequestFocus(_masterPassword);
}
@ -58,7 +54,7 @@ namespace Bit.App.Pages
}
}
private async void Close_Clicked(object sender, EventArgs e)
private void Close_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{

View file

@ -22,8 +22,8 @@ namespace Bit.App.Pages
SetActivityIndicator();
_appOptions = appOptions;
_orgIdentifier = orgIdentifier;
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_messagingService = ServiceContainer.Resolve<IMessagingService>();
_vm = BindingContext as TwoFactorPageViewModel;
_vm.Page = this;
_vm.AuthingWithSso = authingWithSso ?? false;
@ -40,25 +40,21 @@ namespace Bit.App.Pages
_vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView;
if (DeviceInfo.Platform == DevicePlatform.Android)
{
ToolbarItems.Remove(_cancelItem);
}
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
ToolbarItems.Add(_moreItem);
}
else
{
ToolbarItems.Add(_useAnotherTwoStepMethod);
}
#if ANDROID
ToolbarItems.Remove(_cancelItem);
ToolbarItems.Add(_useAnotherTwoStepMethod);
#else
ToolbarItems.Add(_moreItem);
#endif
}
public HybridWebView DuoWebView { get; set; }
protected override async void OnAppearing()
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
protected override async Task InitOnNavigatedToAsync()
{
base.OnAppearing();
_broadcasterService.Subscribe(nameof(TwoFactorPage), (message) =>
{
if (message.Command == "gotYubiKeyOTP")
@ -101,10 +97,11 @@ namespace Bit.App.Pages
return Task.FromResult(0);
});
}
protected override void OnDisappearing()
protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
{
base.OnDisappearing();
base.OnNavigatedFrom(args);
if (!_vm.YubikeyMethod)
{
_messagingService.Send("listenYubiKeyOTP", false);

View file

@ -1,9 +1,6 @@
using System;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -17,8 +14,8 @@ namespace Bit.App.Pages
public UpdateTempPasswordPage()
{
// Service Init
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>();
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
// Binding
InitializeComponent();
@ -30,9 +27,9 @@ namespace Bit.App.Pages
// Actions Declaration
_vm.LogOutAction = () =>
{
_messagingService.Send("logout");
_messagingService.Send(AccountsManagerMessageCommands.LOGOUT);
};
_vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess);
_vm.UpdateTempPasswordSuccessAction = () => MainThread.BeginInvokeOnMainThread(UpdateTempPasswordSuccess);
// Link fields that will be referenced in codebehind
MasterPasswordEntry = _masterPassword;
@ -48,9 +45,10 @@ namespace Bit.App.Pages
public Entry MasterPasswordEntry { get; set; }
public Entry ConfirmMasterPasswordEntry { get; set; }
protected override async void OnAppearing()
protected override bool ShouldCheckToPreventOnNavigatedToCalledTwice => true;
protected override async Task InitOnNavigatedToAsync()
{
base.OnAppearing();
await LoadOnAppearedAsync(_mainLayout, true, async () =>
{
await _vm.InitAsync(true);
@ -81,7 +79,7 @@ namespace Bit.App.Pages
private void UpdateTempPasswordSuccess()
{
_messagingService.Send("logout");
_messagingService.Send(AccountsManagerMessageCommands.LOGOUT);
}
}
}

View file

@ -3,8 +3,10 @@ using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
#if IOS
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
#endif
namespace Bit.App.Pages
{
@ -16,22 +18,34 @@ namespace Bit.App.Pages
protected int ShowModalAnimationDelay = 400;
protected int ShowPageAnimationDelay = 100;
/// <summary>
/// Used as a workaround to avoid duplicate initialization issues for some pages where OnNavigatedTo is called twice.
/// </summary>
private bool _hasInitedOnNavigatedTo;
public BaseContentPage()
{
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
On<iOS>().SetUseSafeArea(true);
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
}
#if IOS
On<iOS>().SetUseSafeArea(true);
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
#endif
}
//IsInitialized is used as a workaround to avoid duplicate initialization issues for some pages where OnNavigatedTo is called twice.
protected bool HasInitialized { get; set; }
public DateTime? LastPageAction { get; set; }
public bool IsThemeDirty { get; set; }
/// <summary>
/// This flag is used to see if check is needed to avoid duplicate calls of <see cref="OnNavigatedTo(NavigatedToEventArgs)"/>
/// Usually on modal navigation to the current page this flag should be <c>true</c>
/// Also this flag is added instead of directly checking for all pages to avoid potential issues on the app
/// and focusing only on the places where it's actually needed.
/// </summary>
/// <remarks>
/// This should be removed once MAUI fixes the issue of duplicate call to the method.
/// </remarks>
protected virtual bool ShouldCheckToPreventOnNavigatedToCalledTwice => false;
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
@ -42,6 +56,22 @@ namespace Bit.App.Pages
}
await SaveActivityAsync();
if (ShouldCheckToPreventOnNavigatedToCalledTwice && _hasInitedOnNavigatedTo)
{
return;
}
_hasInitedOnNavigatedTo = true;
await InitOnNavigatedToAsync();
}
protected virtual Task InitOnNavigatedToAsync() => Task.CompletedTask;
protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
{
base.OnNavigatedFrom(args);
_hasInitedOnNavigatedTo = false;
}
public bool DoOnce(Action action = null, int milliseconds = 1000)

View file

@ -73,10 +73,6 @@
</Compile>
<BundleResource Include="Resources\MaterialIcons_Regular.ttf" />
<BundleResource Include="Resources\bwi-font.ttf" />
<Compile Include="TestViewController.cs" />
<Compile Include="TestViewController.designer.cs">
<DependentUpon>TestViewController.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />