diff --git a/src/App/Pages/Accounts/HintPage.xaml b/src/App/Pages/Accounts/HintPage.xaml index 1514acfbc..6eb09e2f3 100644 --- a/src/App/Pages/Accounts/HintPage.xaml +++ b/src/App/Pages/Accounts/HintPage.xaml @@ -14,7 +14,7 @@ - + diff --git a/src/App/Pages/Accounts/HintPage.xaml.cs b/src/App/Pages/Accounts/HintPage.xaml.cs index 178ad5db1..b1f1d762a 100644 --- a/src/App/Pages/Accounts/HintPage.xaml.cs +++ b/src/App/Pages/Accounts/HintPage.xaml.cs @@ -1,5 +1,4 @@ -using System; -using Xamarin.Forms; +using Xamarin.Forms; namespace Bit.App.Pages { @@ -24,14 +23,6 @@ namespace Bit.App.Pages RequestFocus(_email); } - private async void Submit_Clicked(object sender, EventArgs e) - { - if (DoOnce()) - { - await _vm.SubmitAsync(); - } - } - private async void Close_Clicked(object sender, System.EventArgs e) { if (DoOnce()) diff --git a/src/App/Pages/Accounts/HintPageViewModel.cs b/src/App/Pages/Accounts/HintPageViewModel.cs index b074012e8..2e59f98c9 100644 --- a/src/App/Pages/Accounts/HintPageViewModel.cs +++ b/src/App/Pages/Accounts/HintPageViewModel.cs @@ -1,10 +1,11 @@ using System.Threading.Tasks; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; -using Xamarin.Forms; +using Xamarin.CommunityToolkit.ObjectModel; namespace Bit.App.Pages { @@ -13,18 +14,26 @@ namespace Bit.App.Pages private readonly IDeviceActionService _deviceActionService; private readonly IPlatformUtilsService _platformUtilsService; private readonly IApiService _apiService; + private readonly ILogger _logger; public HintPageViewModel() { _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _apiService = ServiceContainer.Resolve("apiService"); + _logger = ServiceContainer.Resolve(); PageTitle = AppResources.PasswordHint; - SubmitCommand = new Command(async () => await SubmitAsync()); + SubmitCommand = new AsyncCommand(SubmitAsync, + onException: ex => + { + _logger.Exception(ex); + _deviceActionService.DisplayAlertAsync(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok).FireAndForget(); + }, + allowsMultipleExecutions: false); } - public Command SubmitCommand { get; } + public ICommand SubmitCommand { get; } public string Email { get; set; } public async Task SubmitAsync() @@ -37,14 +46,14 @@ namespace Bit.App.Pages } if (string.IsNullOrWhiteSpace(Email)) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, + await _deviceActionService.DisplayAlertAsync(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress), AppResources.Ok); return; } if (!Email.Contains("@")) { - await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok); + await _deviceActionService.DisplayAlertAsync(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok); return; } @@ -54,7 +63,7 @@ namespace Bit.App.Pages await _apiService.PostPasswordHintAsync( new Core.Models.Request.PasswordHintRequest { Email = Email }); await _deviceActionService.HideLoadingAsync(); - await Page.DisplayAlert(null, AppResources.PasswordHintAlert, AppResources.Ok); + await _deviceActionService.DisplayAlertAsync(null, AppResources.PasswordHintAlert, AppResources.Ok); await Page.Navigation.PopModalAsync(); } catch (ApiException e) diff --git a/src/App/Pages/Accounts/LoginPage.xaml b/src/App/Pages/Accounts/LoginPage.xaml index 3975a0ccb..e1627cb04 100644 --- a/src/App/Pages/Accounts/LoginPage.xaml +++ b/src/App/Pages/Accounts/LoginPage.xaml @@ -55,6 +55,7 @@ diff --git a/src/App/Pages/Accounts/LoginPage.xaml.cs b/src/App/Pages/Accounts/LoginPage.xaml.cs index 22e3f49e2..b6e74e1c9 100644 --- a/src/App/Pages/Accounts/LoginPage.xaml.cs +++ b/src/App/Pages/Accounts/LoginPage.xaml.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; using Bit.App.Models; -using Bit.App.Resources; using Bit.App.Utilities; +using Bit.Core.Abstractions; using Bit.Core.Utilities; using Xamarin.Forms; @@ -15,6 +15,8 @@ namespace Bit.App.Pages private bool _inputFocused; + readonly LazyResolve _logger = new LazyResolve("logger"); + public LoginPage(string email = null, AppOptions appOptions = null) { _appOptions = appOptions; @@ -30,11 +32,10 @@ namespace Bit.App.Pages await _accountListOverlay.HideAsync(); await Navigation.PopModalAsync(); }; - if (!string.IsNullOrWhiteSpace(email)) - { - _email.IsEnabled = false; - } - else + _vm.IsEmailEnabled = string.IsNullOrWhiteSpace(email); + _vm.IsIosExtension = _appOptions?.IosExtension ?? false; + + if (_vm.IsEmailEnabled) { _vm.ShowCancelButton = true; } @@ -53,7 +54,7 @@ namespace Bit.App.Pages ToolbarItems.Add(_getPasswordHint); } - if (Device.RuntimePlatform == Device.Android && !_email.IsEnabled) + if (Device.RuntimePlatform == Device.Android && !_vm.IsEmailEnabled) { ToolbarItems.Add(_removeAccount); } @@ -110,7 +111,7 @@ namespace Bit.App.Pages { if (DoOnce()) { - await _vm.LogInAsync(true, _email.IsEnabled); + await _vm.LogInAsync(true, _vm.IsEmailEnabled); } } @@ -139,26 +140,16 @@ namespace Bit.App.Pages } } - private async void More_Clicked(object sender, System.EventArgs e) + private async void More_Clicked(object sender, EventArgs e) { - await _accountListOverlay.HideAsync(); - if (!DoOnce()) + try { - return; + await _accountListOverlay.HideAsync(); + _vm.MoreCommand.Execute(null); } - - var buttons = _email.IsEnabled ? new[] { AppResources.GetPasswordHint } - : new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount }; - var selection = await DisplayActionSheet(AppResources.Options, - AppResources.Cancel, null, buttons); - - if (selection == AppResources.GetPasswordHint) + catch (Exception ex) { - await Navigation.PushModalAsync(new NavigationPage(new HintPage())); - } - else if (selection == AppResources.RemoveAccount) - { - await _vm.RemoveAccountAsync(); + _logger.Value.Exception(ex); } } diff --git a/src/App/Pages/Accounts/LoginPageViewModel.cs b/src/App/Pages/Accounts/LoginPageViewModel.cs index 60cc5a247..593b55e91 100644 --- a/src/App/Pages/Accounts/LoginPageViewModel.cs +++ b/src/App/Pages/Accounts/LoginPageViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Controls; using Bit.App.Resources; @@ -8,6 +9,7 @@ using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; namespace Bit.App.Pages @@ -28,6 +30,7 @@ namespace Bit.App.Pages private bool _showCancelButton; private string _email; private string _masterPassword; + private bool _isEmailEnabled; public LoginPageViewModel() { @@ -44,6 +47,7 @@ namespace Bit.App.Pages PageTitle = AppResources.Bitwarden; TogglePasswordCommand = new Command(TogglePassword); LogInCommand = new Command(async () => await LogInAsync()); + MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { @@ -81,10 +85,19 @@ namespace Bit.App.Pages set => SetProperty(ref _masterPassword, value); } + public bool IsEmailEnabled + { + get => _isEmailEnabled; + set => SetProperty(ref _isEmailEnabled, value); + } + + public bool IsIosExtension { get; set; } + public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } public Command LogInCommand { get; } public Command TogglePasswordCommand { get; } + public ICommand MoreCommand { get; internal set; } public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public Action StartTwoFactorAction { get; set; } @@ -201,6 +214,28 @@ namespace Bit.App.Pages } } + private async Task MoreAsync() + { + var buttons = IsEmailEnabled + ? new[] { AppResources.GetPasswordHint } + : new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount }; + var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, buttons); + + if (selection == AppResources.GetPasswordHint) + { + var hintNavigationPage = new NavigationPage(new HintPage()); + if (IsIosExtension) + { + ThemeManager.ApplyResourcesTo(hintNavigationPage); + } + await Page.Navigation.PushModalAsync(hintNavigationPage); + } + else if (selection == AppResources.RemoveAccount) + { + await RemoveAccountAsync(); + } + } + public void TogglePassword() { ShowPassword = !ShowPassword; diff --git a/src/App/Utilities/ThemeManager.cs b/src/App/Utilities/ThemeManager.cs index 9ef032f33..d6a7285f4 100644 --- a/src/App/Utilities/ThemeManager.cs +++ b/src/App/Utilities/ThemeManager.cs @@ -147,11 +147,11 @@ namespace Bit.App.Utilities return Application.Current.RequestedTheme == OSAppTheme.Dark; } - public static void ApplyResourcesToPage(ContentPage page) + public static void ApplyResourcesTo(VisualElement element) { foreach (var resourceDict in Resources().MergedDictionaries) { - page.Resources.Add(resourceDict); + element.Resources.Add(resourceDict); } } diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index 269f4d039..93267ba8d 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -430,7 +430,7 @@ namespace Bit.iOS.Autofill var homePage = new HomePage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(homePage); + ThemeManager.ApplyResourcesTo(homePage); if (homePage.BindingContext is HomeViewModel vm) { vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow()); @@ -453,7 +453,7 @@ namespace Bit.iOS.Autofill var environmentPage = new EnvironmentPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(environmentPage); + ThemeManager.ApplyResourcesTo(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage()); @@ -471,7 +471,7 @@ namespace Bit.iOS.Autofill var registerPage = new RegisterPage(null); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(registerPage); + ThemeManager.ApplyResourcesTo(registerPage); if (registerPage.BindingContext is RegisterPageViewModel vm) { vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email)); @@ -486,10 +486,11 @@ namespace Bit.iOS.Autofill private void LaunchLoginFlow(string email = null) { - var loginPage = new LoginPage(email); - var app = new App.App(new AppOptions { IosExtension = true }); + var appOptions = new AppOptions { IosExtension = true }; + var app = new App.App(appOptions); + var loginPage = new LoginPage(email, appOptions); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(loginPage); + ThemeManager.ApplyResourcesTo(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); @@ -511,7 +512,7 @@ namespace Bit.iOS.Autofill var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(loginPage); + ThemeManager.ApplyResourcesTo(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); @@ -534,7 +535,7 @@ namespace Bit.iOS.Autofill var twoFactorPage = new TwoFactorPage(authingWithSso); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(twoFactorPage); + ThemeManager.ApplyResourcesTo(twoFactorPage); if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm) { vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); @@ -561,7 +562,7 @@ namespace Bit.iOS.Autofill var setPasswordPage = new SetPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(setPasswordPage); + ThemeManager.ApplyResourcesTo(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); @@ -580,7 +581,7 @@ namespace Bit.iOS.Autofill var updateTempPasswordPage = new UpdateTempPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); + ThemeManager.ApplyResourcesTo(updateTempPasswordPage); if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) { vm.UpdateTempPasswordSuccessAction = () => DismissViewController(false, () => LaunchHomePage()); diff --git a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs index a0f16b6a7..3a9f24ce4 100644 --- a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs @@ -339,7 +339,7 @@ namespace Bit.iOS.Core.Controllers var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(loginPage); + ThemeManager.ApplyResourcesTo(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { vm.SsoAuthSuccessAction = () => DoContinue(); diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index fb8d5e0f0..7f879d3bd 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -328,7 +328,7 @@ namespace Bit.iOS.Core.Controllers var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(loginPage); + ThemeManager.ApplyResourcesTo(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { vm.SsoAuthSuccessAction = () => DoContinue(); diff --git a/src/iOS.Core/Services/DeviceActionService.cs b/src/iOS.Core/Services/DeviceActionService.cs index 71546f4ae..ccdaa505a 100644 --- a/src/iOS.Core/Services/DeviceActionService.cs +++ b/src/iOS.Core/Services/DeviceActionService.cs @@ -83,12 +83,11 @@ namespace Bit.iOS.Core.Services { HideLoadingAsync().GetAwaiter().GetResult(); } - var vc = GetPresentedViewController(); if (vc is null) { return Task.CompletedTask; - } + } var result = new TaskCompletionSource(); @@ -360,6 +359,7 @@ namespace Bit.iOS.Core.Services { return null; } + var result = new TaskCompletionSource(); var sheet = UIAlertController.Create(title, null, UIAlertControllerStyle.ActionSheet); if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad) diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs index 8fcf0da27..90cb3f906 100644 --- a/src/iOS.Extension/LoadingViewController.cs +++ b/src/iOS.Extension/LoadingViewController.cs @@ -451,7 +451,7 @@ namespace Bit.iOS.Extension var homePage = new HomePage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(homePage); + ThemeManager.ApplyResourcesTo(homePage); if (homePage.BindingContext is HomeViewModel vm) { vm.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow()); @@ -474,7 +474,7 @@ namespace Bit.iOS.Extension var environmentPage = new EnvironmentPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(environmentPage); + ThemeManager.ApplyResourcesTo(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage()); @@ -492,7 +492,7 @@ namespace Bit.iOS.Extension var registerPage = new RegisterPage(null); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(registerPage); + ThemeManager.ApplyResourcesTo(registerPage); if (registerPage.BindingContext is RegisterPageViewModel vm) { vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email)); @@ -507,10 +507,11 @@ namespace Bit.iOS.Extension private void LaunchLoginFlow(string email = null) { - var loginPage = new LoginPage(email); - var app = new App.App(new AppOptions { IosExtension = true }); + var appOptions = new AppOptions { IosExtension = true }; + var app = new App.App(appOptions); + var loginPage = new LoginPage(email, appOptions); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(loginPage); + ThemeManager.ApplyResourcesTo(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); @@ -532,7 +533,7 @@ namespace Bit.iOS.Extension var loginPage = new LoginSsoPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(loginPage); + ThemeManager.ApplyResourcesTo(loginPage); if (loginPage.BindingContext is LoginSsoPageViewModel vm) { vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); @@ -555,7 +556,7 @@ namespace Bit.iOS.Extension var twoFactorPage = new TwoFactorPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(twoFactorPage); + ThemeManager.ApplyResourcesTo(twoFactorPage); if (twoFactorPage.BindingContext is TwoFactorPageViewModel vm) { vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); @@ -582,7 +583,7 @@ namespace Bit.iOS.Extension var setPasswordPage = new SetPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(setPasswordPage); + ThemeManager.ApplyResourcesTo(setPasswordPage); if (setPasswordPage.BindingContext is SetPasswordPageViewModel vm) { vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); @@ -601,7 +602,7 @@ namespace Bit.iOS.Extension var updateTempPasswordPage = new UpdateTempPasswordPage(); var app = new App.App(new AppOptions { IosExtension = true }); ThemeManager.SetTheme(app.Resources); - ThemeManager.ApplyResourcesToPage(updateTempPasswordPage); + ThemeManager.ApplyResourcesTo(updateTempPasswordPage); if (updateTempPasswordPage.BindingContext is UpdateTempPasswordPageViewModel vm) { vm.UpdateTempPasswordSuccessAction = () => DismissViewController(false, () => LaunchHomePage()); diff --git a/src/iOS.ShareExtension/LoadingViewController.cs b/src/iOS.ShareExtension/LoadingViewController.cs index 8463c6795..74f983ba7 100644 --- a/src/iOS.ShareExtension/LoadingViewController.cs +++ b/src/iOS.ShareExtension/LoadingViewController.cs @@ -38,6 +38,7 @@ namespace Bit.iOS.ShareExtension Lazy _storyboard = new Lazy(() => UIStoryboard.FromName(STORYBOARD_NAME, null)); + private readonly Lazy _appOptions = new Lazy(() => new AppOptions { IosExtension = true }); private App.App _app = null; private UIViewController _currentModalController; private bool _presentingOnNavigationPage; @@ -279,10 +280,10 @@ namespace Bit.iOS.ShareExtension { if (_app is null) { - var app = new App.App(new AppOptions { IosExtension = true }); + var app = new App.App(_appOptions.Value); ThemeManager.SetTheme(app.Resources); } - ThemeManager.ApplyResourcesToPage(page); + ThemeManager.ApplyResourcesTo(page); return _app; } @@ -307,7 +308,7 @@ namespace Bit.iOS.ShareExtension { var environmentPage = new EnvironmentPage(); SetupAppAndApplyResources(environmentPage); - ThemeManager.ApplyResourcesToPage(environmentPage); + ThemeManager.ApplyResourcesTo(environmentPage); if (environmentPage.BindingContext is EnvironmentPageViewModel vm) { vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage()); @@ -331,7 +332,7 @@ namespace Bit.iOS.ShareExtension private void LaunchLoginFlow(string email = null) { - var loginPage = new LoginPage(email); + var loginPage = new LoginPage(email, _appOptions.Value); SetupAppAndApplyResources(loginPage); if (loginPage.BindingContext is LoginPageViewModel vm) {