From 2c7870d6602af62a6464e360ac627e7b760bd002 Mon Sep 17 00:00:00 2001 From: Dinis Vieira Date: Thu, 16 Nov 2023 22:31:01 +0000 Subject: [PATCH] PM-3349 PM-3350 Removed AsyncCommand "wrapper" and added AsyncRelayCommand directly in all ViewModels that were using the other one. --- .../AccountSwitchingOverlayView.xaml.cs | 41 +++++---- .../AccountSwitchingOverlayViewModel.cs | 4 +- .../HiddenCustomFieldItemViewModel.cs | 2 +- .../Accounts/EnvironmentPageViewModel.cs | 7 +- src/Core/Pages/Accounts/HintPageViewModel.cs | 6 +- src/Core/Pages/Accounts/HomePageViewModel.cs | 17 ++-- .../Accounts/LoginApproveDeviceViewModel.cs | 8 +- src/Core/Pages/Accounts/LoginPageViewModel.cs | 4 +- .../LoginPasswordlessRequestViewModel.cs | 4 +- .../Accounts/LoginPasswordlessViewModel.cs | 4 +- .../Pages/Accounts/LoginSsoPageViewModel.cs | 2 +- .../Pages/Accounts/TwoFactorPageViewModel.cs | 2 +- .../UpdateTempPasswordPageViewModel.cs | 5 +- .../Accounts/VerificationCodeViewModel.cs | 4 +- src/Core/Pages/BaseViewModel.cs | 39 +------- .../Pages/Generator/GeneratorPageViewModel.cs | 8 +- src/Core/Pages/PickerViewModel.cs | 7 +- .../Settings/AboutSettingsPageViewModel.cs | 35 ++++---- .../AppearanceSettingsPageViewModel.cs | 22 ++--- .../AutofillSettingsPageViewModel.android.cs | 36 ++++---- .../Settings/AutofillSettingsPageViewModel.cs | 21 ++--- .../AutofillSettingsPageViewModel.ios.cs | 9 +- .../BlockAutofillUrisPageViewModel.cs | 4 +- .../LoginPasswordlessRequestsListViewModel.cs | 9 +- .../Settings/OtherSettingsPageViewModel.cs | 38 ++++---- .../Settings/SecuritySettingsPageViewModel.cs | 58 +++++------- .../SettingsPage/SettingsPageViewModel.cs | 5 +- .../Settings/VaultSettingsPageViewModel.cs | 6 +- .../Pages/Vault/AttachmentsPageViewModel.cs | 2 +- src/Core/Pages/Vault/BaseCipherViewModel.cs | 5 +- .../Pages/Vault/CipherAddEditPageViewModel.cs | 9 +- .../Pages/Vault/CipherDetailsPageViewModel.cs | 14 +-- .../Vault/CipherSelectionPageViewModel.cs | 6 +- src/Core/Pages/Vault/CiphersPageViewModel.cs | 4 +- .../GroupingsPageTOTPListItem.cs | 6 +- .../GroupingsPage/GroupingsPageViewModel.cs | 4 +- src/Core/Pages/Vault/ScanPageViewModel.cs | 2 +- src/Core/Pages/Vault/SharePageViewModel.cs | 2 +- src/Core/Pages/VaultFilterViewModel.cs | 2 +- src/Core/Utilities/AsyncCommand.cs | 80 ----------------- src/Core/Utilities/ExtendedViewModel.cs | 88 ++++++++++++++++++- 41 files changed, 282 insertions(+), 349 deletions(-) delete mode 100644 src/Core/Utilities/AsyncCommand.cs diff --git a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs index 5a71e90b7..c9fae9e6d 100644 --- a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs +++ b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs @@ -1,7 +1,7 @@ using System.Windows.Input; -using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Controls { @@ -37,17 +37,14 @@ namespace Bit.App.Controls { InitializeComponent(); - ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync, - onException: ex => _logger.Value.Exception(ex), - allowsMultipleExecutions: false); + ToggleVisibililtyCommand = new AsyncRelayCommand(ToggleVisibilityAsync, + AsyncRelayCommandOptions.None); - SelectAccountCommand = new AsyncCommand(SelectAccountAsync, - onException: ex => _logger.Value.Exception(ex), - allowsMultipleExecutions: false); + SelectAccountCommand = new AsyncRelayCommand(SelectAccountAsync, + AsyncRelayCommandOptions.None); - LongPressAccountCommand = new AsyncCommand(LongPressAccountAsync, - onException: ex => _logger.Value.Exception(ex), - allowsMultipleExecutions: false); + LongPressAccountCommand = new AsyncRelayCommand(LongPressAccountAsync, + AsyncRelayCommandOptions.None); } public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel; @@ -70,13 +67,20 @@ namespace Bit.App.Controls public async Task ToggleVisibilityAsync() { - if (IsVisible) + try { - await HideAsync(); + if (IsVisible) + { + await HideAsync(); + } + else + { + await ShowAsync(); + } } - else + catch (Exception ex) { - await ShowAsync(); + _logger.Value.Exception(ex); } } @@ -172,12 +176,13 @@ namespace Bit.App.Controls private async Task LongPressAccountAsync(AccountViewCellViewModel item) { - if (!LongPressAccountEnabled || item == null || !item.IsAccount) - { - return; - } try { + if (!LongPressAccountEnabled || item == null || !item.IsAccount) + { + return; + } + await Task.Delay(100); await HideAsync(); diff --git a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs index fde0cdea0..9a3a44805 100644 --- a/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs +++ b/src/Core/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs @@ -17,11 +17,11 @@ namespace Bit.App.Controls _stateService = stateService; _messagingService = messagingService; - SelectAccountCommand = new AsyncCommand(SelectAccountAsync, + SelectAccountCommand = CreateDefaultAsyncRelayCommand(SelectAccountAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); - LongPressAccountCommand = new AsyncCommand>(LongPressAccountAsync, + LongPressAccountCommand = CreateDefaultAsyncRelayCommand>(LongPressAccountAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs b/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs index ad8ad96f4..526e0dc96 100644 --- a/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs +++ b/src/Core/Lists/ItemViewModels/CustomFields/HiddenCustomFieldItemViewModel.cs @@ -30,7 +30,7 @@ namespace Bit.App.Lists.ItemViewModels.CustomFields _eventService = eventService; CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field)); - ToggleHiddenValueCommand = new AsyncCommand(ToggleHiddenValueAsync, null, ex => + ToggleHiddenValueCommand = CreateDefaultAsyncRelayCommand(ToggleHiddenValueAsync, null, ex => { //#if !FDROID // Microsoft.AppCenter.Crashes.Crashes.TrackError(ex); diff --git a/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs b/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs index 76175ac34..3a7062c1f 100644 --- a/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs +++ b/src/Core/Pages/Accounts/EnvironmentPageViewModel.cs @@ -1,11 +1,8 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Utilities; -using Bit.App.Utilities; namespace Bit.App.Pages { @@ -26,7 +23,7 @@ namespace Bit.App.Pages IdentityUrl = _environmentService.IdentityUrl; IconsUrl = _environmentService.IconsUrl; NotificationsUrls = _environmentService.NotificationsUrl; - SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); + SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, onException: OnSubmitException, allowsMultipleExecutions: false); } public ICommand SubmitCommand { get; } diff --git a/src/Core/Pages/Accounts/HintPageViewModel.cs b/src/Core/Pages/Accounts/HintPageViewModel.cs index 443c35fc1..1640be1a5 100644 --- a/src/Core/Pages/Accounts/HintPageViewModel.cs +++ b/src/Core/Pages/Accounts/HintPageViewModel.cs @@ -1,11 +1,9 @@ -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Exceptions; using Bit.Core.Utilities; -using Bit.App.Utilities; namespace Bit.App.Pages { @@ -25,7 +23,7 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve(); PageTitle = AppResources.PasswordHint; - SubmitCommand = new AsyncCommand(SubmitAsync, + SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, onException: ex => { _logger.Exception(ex); diff --git a/src/Core/Pages/Accounts/HomePageViewModel.cs b/src/Core/Pages/Accounts/HomePageViewModel.cs index 1c4751649..34aeb0166 100644 --- a/src/Core/Pages/Accounts/HomePageViewModel.cs +++ b/src/Core/Pages/Accounts/HomePageViewModel.cs @@ -11,6 +11,7 @@ using Bit.Core.Utilities; using Microsoft.Maui.Controls; using Microsoft.Maui; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -50,12 +51,12 @@ namespace Bit.App.Pages AllowActiveAccountSelection = true }; RememberEmailCommand = new Command(() => RememberEmail = !RememberEmail); - ContinueCommand = new AsyncCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false); - CreateAccountCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(StartRegisterAction), + ContinueCommand = CreateDefaultAsyncRelayCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false); + CreateAccountCommand = CreateDefaultAsyncRelayCommand(async () => Device.InvokeOnMainThreadAsync(StartRegisterAction), onException: _logger.Exception, allowsMultipleExecutions: false); - CloseCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(CloseAction), + CloseCommand = CreateDefaultAsyncRelayCommand(async () => Device.InvokeOnMainThreadAsync(CloseAction), onException: _logger.Exception, allowsMultipleExecutions: false); - ShowEnvironmentPickerCommand = new AsyncCommand(ShowEnvironmentPickerAsync, + ShowEnvironmentPickerCommand = CreateDefaultAsyncRelayCommand(ShowEnvironmentPickerAsync, onException: _logger.Exception, allowsMultipleExecutions: false); InitAsync().FireAndForget(); } @@ -113,10 +114,10 @@ namespace Bit.App.Pages public Action StartEnvironmentAction { get; set; } public Action CloseAction { get; set; } public Command RememberEmailCommand { get; set; } - public AsyncCommand ContinueCommand { get; } - public AsyncCommand CloseCommand { get; } - public AsyncCommand CreateAccountCommand { get; } - public AsyncCommand ShowEnvironmentPickerCommand { get; } + public AsyncRelayCommand ContinueCommand { get; } + public AsyncRelayCommand CloseCommand { get; } + public AsyncRelayCommand CreateAccountCommand { get; } + public AsyncRelayCommand ShowEnvironmentPickerCommand { get; } public async Task InitAsync() { diff --git a/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs b/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs index ad2f56e2c..050cb80bc 100644 --- a/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs +++ b/src/Core/Pages/Accounts/LoginApproveDeviceViewModel.cs @@ -55,19 +55,19 @@ namespace Bit.App.Pages PageTitle = AppResources.LogInInitiated; RememberThisDevice = true; - ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction), + ApproveWithMyOtherDeviceCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction), + RequestAdminApprovalCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction), + ApproveWithMasterPasswordCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync, + ContinueCommand = CreateDefaultAsyncRelayCommand(CreateNewSsoUserAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); diff --git a/src/Core/Pages/Accounts/LoginPageViewModel.cs b/src/Core/Pages/Accounts/LoginPageViewModel.cs index 214f3d96a..81a1120e9 100644 --- a/src/Core/Pages/Accounts/LoginPageViewModel.cs +++ b/src/Core/Pages/Accounts/LoginPageViewModel.cs @@ -62,8 +62,8 @@ 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); - LogInWithDeviceCommand = new AsyncCommand(() => Device.InvokeOnMainThreadAsync(LogInWithDeviceAction), onException: _logger.Exception, allowsMultipleExecutions: false); + MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); + LogInWithDeviceCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(LogInWithDeviceAction), onException: _logger.Exception, allowsMultipleExecutions: false); AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) { diff --git a/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs b/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs index ab96ebde1..0a28f76f8 100644 --- a/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs +++ b/src/Core/Pages/Accounts/LoginPasswordlessRequestViewModel.cs @@ -56,11 +56,11 @@ namespace Bit.App.Pages _cryptoFunctionService = ServiceContainer.Resolve(); _cryptoService = ServiceContainer.Resolve(); - CreatePasswordlessLoginCommand = new AsyncCommand(CreatePasswordlessLoginAsync, + CreatePasswordlessLoginCommand = CreateDefaultAsyncRelayCommand(CreatePasswordlessLoginAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - CloseCommand = new AsyncCommand(() => MainThread.InvokeOnMainThreadAsync(CloseAction), + CloseCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(CloseAction), onException: _logger.Exception, allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs b/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs index 3ba5b87fb..a5809659c 100644 --- a/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs +++ b/src/Core/Pages/Accounts/LoginPasswordlessViewModel.cs @@ -39,10 +39,10 @@ namespace Bit.App.Pages PageTitle = AppResources.LogInRequested; - AcceptRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(true), + AcceptRequestCommand = CreateDefaultAsyncRelayCommand(() => PasswordlessLoginAsync(true), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - RejectRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(false), + RejectRequestCommand = CreateDefaultAsyncRelayCommand(() => PasswordlessLoginAsync(false), onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs b/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs index 975784b7c..c150d5bcd 100644 --- a/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs +++ b/src/Core/Pages/Accounts/LoginSsoPageViewModel.cs @@ -54,7 +54,7 @@ namespace Bit.App.Pages _cryptoService = ServiceContainer.Resolve(); PageTitle = AppResources.Bitwarden; - LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false); + LogInCommand = CreateDefaultAsyncRelayCommand(LogInAsync, allowsMultipleExecutions: false); } public string OrgIdentifier diff --git a/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs b/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs index 5ac680d48..7fcefd6f0 100644 --- a/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/Core/Pages/Accounts/TwoFactorPageViewModel.cs @@ -62,7 +62,7 @@ namespace Bit.App.Pages PageTitle = AppResources.TwoStepLogin; SubmitCommand = new Command(async () => await SubmitAsync()); - MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); + MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); } public string TotpInstruction diff --git a/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs b/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs index 78ddfb10c..df095c655 100644 --- a/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs +++ b/src/Core/Pages/Accounts/UpdateTempPasswordPageViewModel.cs @@ -11,6 +11,7 @@ using Bit.Core.Utilities; using Microsoft.Maui.Controls; using Microsoft.Maui; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -25,14 +26,14 @@ namespace Bit.App.Pages PageTitle = AppResources.UpdateMasterPassword; TogglePasswordCommand = new Command(TogglePassword); ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); - SubmitCommand = new AsyncCommand(SubmitAsync, + SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); _userVerificationService = ServiceContainer.Resolve(); } - public AsyncCommand SubmitCommand { get; } + public AsyncRelayCommand SubmitCommand { get; } public Command TogglePasswordCommand { get; } public Command ToggleConfirmPasswordCommand { get; } public Action UpdateTempPasswordSuccessAction { get; set; } diff --git a/src/Core/Pages/Accounts/VerificationCodeViewModel.cs b/src/Core/Pages/Accounts/VerificationCodeViewModel.cs index eb6d63b72..bbd388f7f 100644 --- a/src/Core/Pages/Accounts/VerificationCodeViewModel.cs +++ b/src/Core/Pages/Accounts/VerificationCodeViewModel.cs @@ -39,8 +39,8 @@ namespace Bit.App.Pages PageTitle = AppResources.VerificationCode; TogglePasswordCommand = new Command(TogglePassword); - MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false); - RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false); + MainActionCommand = CreateDefaultAsyncRelayCommand(MainActionAsync, allowsMultipleExecutions: false); + RequestOTPCommand = CreateDefaultAsyncRelayCommand(RequestOTPAsync, allowsMultipleExecutions: false); } public bool ShowPassword diff --git a/src/Core/Pages/BaseViewModel.cs b/src/Core/Pages/BaseViewModel.cs index e0c95ab82..a19107e11 100644 --- a/src/Core/Pages/BaseViewModel.cs +++ b/src/Core/Pages/BaseViewModel.cs @@ -1,27 +1,13 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; -using Bit.App.Abstractions; -using Bit.App.Controls; +using Bit.App.Controls; using Bit.Core.Resources.Localization; -using Bit.Core.Abstractions; -using Bit.Core.Exceptions; using Bit.Core.Utilities; -using Microsoft.Maui.Networking; -using Microsoft.Maui.Controls; -using Microsoft.Maui; -using Bit.App.Utilities; - namespace Bit.App.Pages { public abstract class BaseViewModel : ExtendedViewModel { private string _pageTitle = string.Empty; private AvatarImageSource _avatar; - private LazyResolve _deviceActionService = new LazyResolve(); - private LazyResolve _platformUtilsService = new LazyResolve(); - private LazyResolve _logger = new LazyResolve(); public string PageTitle { @@ -37,29 +23,6 @@ namespace Bit.App.Pages public ContentPage Page { get; set; } - protected void HandleException(Exception ex, string message = null) - { - if (ex is ApiException apiException && apiException.Error != null) - { - message = apiException.Error.GetSingleMessage(); - } - - Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () => - { - await _deviceActionService.Value.HideLoadingAsync(); - await _platformUtilsService.Value.ShowDialogAsync(message ?? AppResources.GenericErrorMessage); - }).FireAndForget(); - _logger.Value.Exception(ex); - } - - protected AsyncCommand CreateDefaultAsyncCommnad(Func execute, Func canExecute = null) - { - return new AsyncCommand(execute, - canExecute, - ex => HandleException(ex), - allowsMultipleExecutions: false); - } - protected async Task HasConnectivityAsync() { if (Connectivity.NetworkAccess == NetworkAccess.None) diff --git a/src/Core/Pages/Generator/GeneratorPageViewModel.cs b/src/Core/Pages/Generator/GeneratorPageViewModel.cs index d6226c207..50e9d2f30 100644 --- a/src/Core/Pages/Generator/GeneratorPageViewModel.cs +++ b/src/Core/Pages/Generator/GeneratorPageViewModel.cs @@ -89,11 +89,11 @@ namespace Bit.App.Pages }; UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp); - RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); - RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); + RegenerateCommand = CreateDefaultAsyncRelayCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); + RegenerateUsernameCommand = CreateDefaultAsyncRelayCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret); - CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); - CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); + CopyCommand = CreateDefaultAsyncRelayCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); + CloseCommand = CreateDefaultAsyncRelayCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); } public List GeneratorTypeOptions { get; set; } diff --git a/src/Core/Pages/PickerViewModel.cs b/src/Core/Pages/PickerViewModel.cs index 43168ecdc..d94643b08 100644 --- a/src/Core/Pages/PickerViewModel.cs +++ b/src/Core/Pages/PickerViewModel.cs @@ -2,8 +2,7 @@ using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Utilities; - -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -33,10 +32,10 @@ namespace Bit.App.Pages _onSelectionChangingAsync = onSelectionChangingAsync; _title = title; - SelectOptionCommand = new AsyncCommand(SelectOptionAsync, canExecuteSelectOptionCommand, onSelectOptionCommandException, allowsMultipleExecutions: false); + SelectOptionCommand = CreateDefaultAsyncRelayCommand(SelectOptionAsync, canExecuteSelectOptionCommand, onSelectOptionCommandException, allowsMultipleExecutions: false); } - public AsyncCommand SelectOptionCommand { get; } + public AsyncRelayCommand SelectOptionCommand { get; } public TKey SelectedKey => _selectedKey; diff --git a/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs b/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs index 7b1e69a08..8a69ae295 100644 --- a/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/AboutSettingsPageViewModel.cs @@ -1,13 +1,10 @@ -using System; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using Microsoft.Maui.ApplicationModel; -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -29,32 +26,32 @@ namespace Bit.App.Pages var environmentService = ServiceContainer.Resolve(); var clipboardService = ServiceContainer.Resolve(); - ToggleSubmitCrashLogsCommand = CreateDefaultAsyncCommnad(ToggleSubmitCrashLogsAsync); + ToggleSubmitCrashLogsCommand = CreateDefaultAsyncRelayCommand(ToggleSubmitCrashLogsAsync, allowsMultipleExecutions: false); - GoToHelpCenterCommand = CreateDefaultAsyncCommnad( + GoToHelpCenterCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.LearnMoreAboutHowToUseBitwardenOnTheHelpCenter, AppResources.ContinueToHelpCenter, - ExternalLinksConstants.HELP_CENTER)); + ExternalLinksConstants.HELP_CENTER), allowsMultipleExecutions: false); - ContactBitwardenSupportCommand = CreateDefaultAsyncCommnad( + ContactBitwardenSupportCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.ContactSupportDescriptionLong, AppResources.ContinueToContactSupport, - ExternalLinksConstants.CONTACT_SUPPORT)); + ExternalLinksConstants.CONTACT_SUPPORT), allowsMultipleExecutions: false); - GoToWebVaultCommand = CreateDefaultAsyncCommnad( + GoToWebVaultCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.ExploreMoreFeaturesOfYourBitwardenAccountOnTheWebApp, AppResources.ContinueToWebApp, - environmentService.GetWebVaultUrl())); + environmentService.GetWebVaultUrl()), allowsMultipleExecutions: false); - GoToLearnAboutOrgsCommand = CreateDefaultAsyncCommnad( + GoToLearnAboutOrgsCommand = CreateDefaultAsyncRelayCommand( () => LaunchUriAsync(AppResources.LearnAboutOrganizationsDescriptionLong, string.Format(AppResources.ContinueToX, ExternalLinksConstants.BITWARDEN_WEBSITE), - ExternalLinksConstants.HELP_ABOUT_ORGANIZATIONS)); + ExternalLinksConstants.HELP_ABOUT_ORGANIZATIONS), allowsMultipleExecutions: false); - RateTheAppCommand = CreateDefaultAsyncCommnad(RateAppAsync); + RateTheAppCommand = CreateDefaultAsyncRelayCommand(RateAppAsync, allowsMultipleExecutions: false); - CopyAppInfoCommand = CreateDefaultAsyncCommnad( - () => clipboardService.CopyTextAsync(AppInfo)); + CopyAppInfoCommand = CreateDefaultAsyncRelayCommand( + () => clipboardService.CopyTextAsync(AppInfo), allowsMultipleExecutions: false); } public bool ShouldSubmitCrashLogs @@ -80,7 +77,7 @@ namespace Bit.App.Pages } } - public AsyncCommand ToggleSubmitCrashLogsCommand { get; } + public AsyncRelayCommand ToggleSubmitCrashLogsCommand { get; } public ICommand GoToHelpCenterCommand { get; } public ICommand ContactBitwardenSupportCommand { get; } public ICommand GoToWebVaultCommand { get; } @@ -97,7 +94,7 @@ namespace Bit.App.Pages MainThread.BeginInvokeOnMainThread(() => { TriggerPropertyChanged(nameof(ShouldSubmitCrashLogs)); - ToggleSubmitCrashLogsCommand.RaiseCanExecuteChanged(); + ToggleSubmitCrashLogsCommand.NotifyCanExecuteChanged(); }); } diff --git a/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs b/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs index ef4792e40..6f91c56e4 100644 --- a/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/AppearanceSettingsPageViewModel.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -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.Abstractions; using Bit.Core.Utilities; -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -64,7 +58,7 @@ namespace Bit.App.Pages () => _inited, ex => HandleException(ex)); - ToggleShowWebsiteIconsCommand = CreateDefaultAsyncCommnad(ToggleShowWebsiteIconsAsync, () => _inited); + ToggleShowWebsiteIconsCommand = CreateDefaultAsyncRelayCommand(ToggleShowWebsiteIconsAsync, () => _inited, allowsMultipleExecutions: false); } public PickerViewModel LanguagePickerViewModel { get; } @@ -87,7 +81,7 @@ namespace Bit.App.Pages public bool IsShowWebsiteIconsEnabled => ToggleShowWebsiteIconsCommand.CanExecute(null); - public AsyncCommand ToggleShowWebsiteIconsCommand { get; } + public AsyncRelayCommand ToggleShowWebsiteIconsCommand { get; } public async Task InitAsync() { @@ -102,10 +96,10 @@ namespace Bit.App.Pages MainThread.BeginInvokeOnMainThread(() => { - ToggleShowWebsiteIconsCommand.RaiseCanExecuteChanged(); - LanguagePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - ThemePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - DefaultDarkThemePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); + ToggleShowWebsiteIconsCommand.NotifyCanExecuteChanged(); + LanguagePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + ThemePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + DefaultDarkThemePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); }); } diff --git a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs index 45d499a24..90e3b42c3 100644 --- a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs +++ b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.android.cs @@ -1,10 +1,6 @@ -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.Core.Resources.Localization; -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -16,8 +12,7 @@ namespace Bit.App.Pages private bool _useDrawOver; private bool _askToAddLogin; - public bool SupportsAndroidAutofillServices => // 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 -Device.RuntimePlatform == Device.Android && _deviceActionService.SupportsAutofillServices(); + public bool SupportsAndroidAutofillServices => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsAutofillServices(); public bool UseAutofillServices { @@ -45,8 +40,7 @@ Device.RuntimePlatform == Device.Android && _deviceActionService.SupportsAutofil } } - public bool ShowUseAccessibilityToggle => // 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 -Device.RuntimePlatform == Device.Android; + public bool ShowUseAccessibilityToggle => DeviceInfo.Platform == DevicePlatform.Android; public string UseAccessibilityDescription => _deviceActionService.GetAutofillAccessibilityDescription(); @@ -90,21 +84,21 @@ Device.RuntimePlatform == Device.Android; } } - public AsyncCommand ToggleUseAutofillServicesCommand { get; private set; } - public AsyncCommand ToggleUseInlineAutofillCommand { get; private set; } - public AsyncCommand ToggleUseAccessibilityCommand { get; private set; } - public AsyncCommand ToggleUseDrawOverCommand { get; private set; } - public AsyncCommand ToggleAskToAddLoginCommand { get; private set; } + public AsyncRelayCommand ToggleUseAutofillServicesCommand { get; private set; } + public AsyncRelayCommand ToggleUseInlineAutofillCommand { get; private set; } + public AsyncRelayCommand ToggleUseAccessibilityCommand { get; private set; } + public AsyncRelayCommand ToggleUseDrawOverCommand { get; private set; } + public AsyncRelayCommand ToggleAskToAddLoginCommand { get; private set; } public ICommand GoToBlockAutofillUrisCommand { get; private set; } private void InitAndroidCommands() { - ToggleUseAutofillServicesCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited); - ToggleUseInlineAutofillCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited); - ToggleUseAccessibilityCommand = CreateDefaultAsyncCommnad(ToggleUseAccessibilityAsync, () => _inited); - ToggleUseDrawOverCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleDrawOver()), () => _inited); - ToggleAskToAddLoginCommand = CreateDefaultAsyncCommnad(ToggleAskToAddLoginAsync, () => _inited); - GoToBlockAutofillUrisCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage())); + ToggleUseAutofillServicesCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited, allowsMultipleExecutions: false); + ToggleUseInlineAutofillCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited, allowsMultipleExecutions: false); + ToggleUseAccessibilityCommand = CreateDefaultAsyncRelayCommand(ToggleUseAccessibilityAsync, () => _inited, allowsMultipleExecutions: false); + ToggleUseDrawOverCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleDrawOver()), () => _inited, allowsMultipleExecutions: false); + ToggleAskToAddLoginCommand = CreateDefaultAsyncRelayCommand(ToggleAskToAddLoginAsync, () => _inited, allowsMultipleExecutions: false); + GoToBlockAutofillUrisCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()), allowsMultipleExecutions: false); } private async Task InitAndroidAutofillSettingsAsync() diff --git a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs index 58e7ee292..8c247b0fe 100644 --- a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Utilities; -using Microsoft.Maui.ApplicationModel; -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -36,7 +33,7 @@ namespace Bit.App.Pages () => _inited, ex => HandleException(ex)); - ToggleCopyTotpAutomaticallyCommand = CreateDefaultAsyncCommnad(ToggleCopyTotpAutomaticallyAsync, () => _inited); + ToggleCopyTotpAutomaticallyCommand = CreateDefaultAsyncRelayCommand(ToggleCopyTotpAutomaticallyAsync, () => _inited, allowsMultipleExecutions: false); InitAndroidCommands(); InitIOSCommands(); @@ -56,7 +53,7 @@ namespace Bit.App.Pages public PickerViewModel DefaultUriMatchDetectionPickerViewModel { get; } - public AsyncCommand ToggleCopyTotpAutomaticallyCommand { get; private set; } + public AsyncRelayCommand ToggleCopyTotpAutomaticallyCommand { get; private set; } public async Task InitAsync() { @@ -72,11 +69,11 @@ namespace Bit.App.Pages { TriggerPropertyChanged(nameof(CopyTotpAutomatically)); - ToggleUseAutofillServicesCommand.RaiseCanExecuteChanged(); - ToggleUseInlineAutofillCommand.RaiseCanExecuteChanged(); - ToggleUseAccessibilityCommand.RaiseCanExecuteChanged(); - ToggleUseDrawOverCommand.RaiseCanExecuteChanged(); - DefaultUriMatchDetectionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); + ToggleUseAutofillServicesCommand.NotifyCanExecuteChanged(); + ToggleUseInlineAutofillCommand.NotifyCanExecuteChanged(); + ToggleUseAccessibilityCommand.NotifyCanExecuteChanged(); + ToggleUseDrawOverCommand.NotifyCanExecuteChanged(); + DefaultUriMatchDetectionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); }); } diff --git a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs index 954bf3b2d..a2f2d1101 100644 --- a/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs +++ b/src/Core/Pages/Settings/AutofillSettingsPageViewModel.ios.cs @@ -1,21 +1,18 @@ using System.Windows.Input; -using Microsoft.Maui.Controls; -using Microsoft.Maui; namespace Bit.App.Pages { public partial class AutofillSettingsPageViewModel { - public bool SupportsiOSAutofill => // 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 -Device.RuntimePlatform == Device.iOS && _deviceActionService.SupportsAutofillServices(); + public bool SupportsiOSAutofill => DeviceInfo.Platform == DevicePlatform.iOS && _deviceActionService.SupportsAutofillServices(); public ICommand GoToPasswordAutofillCommand { get; private set; } public ICommand GoToAppExtensionCommand { get; private set; } private void InitIOSCommands() { - GoToPasswordAutofillCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage()))); - GoToAppExtensionCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()))); + GoToPasswordAutofillCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage())), allowsMultipleExecutions: false); + GoToAppExtensionCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage())), allowsMultipleExecutions: false); } } } diff --git a/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs b/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs index 510eb8629..05b85a794 100644 --- a/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs +++ b/src/Core/Pages/Settings/BlockAutofillUrisPageViewModel.cs @@ -28,11 +28,11 @@ namespace Bit.App.Pages _stateService = ServiceContainer.Resolve(); _deviceActionService = ServiceContainer.Resolve(); - AddUriCommand = new AsyncCommand(AddUriAsync, + AddUriCommand = CreateDefaultAsyncRelayCommand(AddUriAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - EditUriCommand = new AsyncCommand(EditUriAsync, + EditUriCommand = CreateDefaultAsyncRelayCommand(EditUriAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs b/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs index 64d299faa..083f8c4e6 100644 --- a/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs +++ b/src/Core/Pages/Settings/LoginPasswordlessRequestsListViewModel.cs @@ -13,6 +13,7 @@ using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Controls; using Microsoft.Maui; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -34,11 +35,11 @@ namespace Bit.App.Pages PageTitle = AppResources.PendingLogInRequests; LoginRequests = new ObservableRangeCollection(); - AnswerRequestCommand = new AsyncCommand(PasswordlessLoginAsync, + AnswerRequestCommand = CreateDefaultAsyncRelayCommand(PasswordlessLoginAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - DeclineAllRequestsCommand = new AsyncCommand(DeclineAllRequestsAsync, + DeclineAllRequestsCommand = CreateDefaultAsyncRelayCommand(DeclineAllRequestsAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); @@ -47,9 +48,9 @@ namespace Bit.App.Pages public ICommand RefreshCommand { get; } - public AsyncCommand AnswerRequestCommand { get; } + public AsyncRelayCommand AnswerRequestCommand { get; } - public AsyncCommand DeclineAllRequestsCommand { get; } + public AsyncRelayCommand DeclineAllRequestsCommand { get; } public ObservableRangeCollection LoginRequests { get; } diff --git a/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs b/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs index f11a0ddde..bef7d1801 100644 --- a/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/OtherSettingsPageViewModel.cs @@ -1,16 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Utilities; - -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; -using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -42,19 +35,21 @@ namespace Bit.App.Pages _watchDeviceService = ServiceContainer.Resolve(); _logger = ServiceContainer.Resolve(); - SyncCommand = CreateDefaultAsyncCommnad(SyncAsync, () => _inited); - ToggleIsScreenCaptureAllowedCommand = CreateDefaultAsyncCommnad(ToggleIsScreenCaptureAllowedAsync, () => _inited); - ToggleShouldConnectToWatchCommand = CreateDefaultAsyncCommnad(ToggleShouldConnectToWatchAsync, () => _inited); + SyncCommand = CreateDefaultAsyncRelayCommand(SyncAsync, CanExecuteIfInited, allowsMultipleExecutions: false); + ToggleIsScreenCaptureAllowedCommand = CreateDefaultAsyncRelayCommand(ToggleIsScreenCaptureAllowedAsync, CanExecuteIfInited, allowsMultipleExecutions: false); + ToggleShouldConnectToWatchCommand = CreateDefaultAsyncRelayCommand(ToggleShouldConnectToWatchAsync, CanExecuteIfInited, allowsMultipleExecutions: false); ClearClipboardPickerViewModel = new PickerViewModel( _deviceActionService, _logger, OnClearClipboardChangingAsync, AppResources.ClearClipboard, - () => _inited, + CanExecuteIfInited, ex => HandleException(ex)); } + private bool CanExecuteIfInited() => _inited; + public bool EnableSyncOnRefresh { get => _syncOnRefresh; @@ -103,9 +98,9 @@ namespace Bit.App.Pages public bool CanToggleShouldConnectToWatch => ToggleShouldConnectToWatchCommand.CanExecute(null); - public AsyncCommand SyncCommand { get; } - public AsyncCommand ToggleIsScreenCaptureAllowedCommand { get; } - public AsyncCommand ToggleShouldConnectToWatchCommand { get; } + public AsyncRelayCommand SyncCommand { get; } + public AsyncRelayCommand ToggleIsScreenCaptureAllowedCommand { get; } + public AsyncRelayCommand ToggleShouldConnectToWatchCommand { get; } public async Task InitAsync() { @@ -124,10 +119,10 @@ namespace Bit.App.Pages { TriggerPropertyChanged(nameof(IsScreenCaptureAllowed)); TriggerPropertyChanged(nameof(ShouldConnectToWatch)); - SyncCommand.RaiseCanExecuteChanged(); - ClearClipboardPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - ToggleIsScreenCaptureAllowedCommand.RaiseCanExecuteChanged(); - ToggleShouldConnectToWatchCommand.RaiseCanExecuteChanged(); + SyncCommand.NotifyCanExecuteChanged(); + ClearClipboardPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + ToggleIsScreenCaptureAllowedCommand.NotifyCanExecuteChanged(); + ToggleShouldConnectToWatchCommand.NotifyCanExecuteChanged(); }); } @@ -141,8 +136,7 @@ namespace Bit.App.Pages [30] = AppResources.ThirtySeconds, [60] = AppResources.OneMinute }; - // 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.iOS) + if (DeviceInfo.Platform != DevicePlatform.iOS) { clearClipboardOptions.Add(120, AppResources.TwoMinutes); clearClipboardOptions.Add(300, AppResources.FiveMinutes); diff --git a/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs b/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs index af81910fb..4ed324bed 100644 --- a/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/SecuritySettingsPageViewModel.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Input; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Pages.Accounts; using Bit.Core.Resources.Localization; @@ -12,10 +8,7 @@ using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Utilities; - -using Microsoft.Maui.ApplicationModel; -using Microsoft.Maui.Controls; -using Microsoft.Maui; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -80,16 +73,16 @@ namespace Bit.App.Pages () => _inited && !HasVaultTimeoutActionPolicy, ex => HandleException(ex)); - ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncCommnad(ToggleUseThisDeviceToApproveLoginRequestsAsync, () => _inited); - GoToPendingLogInRequestsCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage()))); - ToggleCanUnlockWithBiometricsCommand = CreateDefaultAsyncCommnad(ToggleCanUnlockWithBiometricsAsync, () => _inited); - ToggleCanUnlockWithPinCommand = CreateDefaultAsyncCommnad(ToggleCanUnlockWithPinAsync, () => _inited); - ShowAccountFingerprintPhraseCommand = CreateDefaultAsyncCommnad(ShowAccountFingerprintPhraseAsync); - GoToTwoStepLoginCommand = CreateDefaultAsyncCommnad(() => GoToWebVaultSettingsAsync(AppResources.TwoStepLoginDescriptionLong, AppResources.ContinueToWebApp)); - GoToChangeMasterPasswordCommand = CreateDefaultAsyncCommnad(() => GoToWebVaultSettingsAsync(AppResources.ChangeMasterPasswordDescriptionLong, AppResources.ContinueToWebApp)); - LockCommand = CreateDefaultAsyncCommnad(() => _vaultTimeoutService.LockAsync(true, true)); - LogOutCommand = CreateDefaultAsyncCommnad(LogOutAsync); - DeleteAccountCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()))); + ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncRelayCommand(ToggleUseThisDeviceToApproveLoginRequestsAsync, () => _inited, allowsMultipleExecutions: false); + GoToPendingLogInRequestsCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage())), allowsMultipleExecutions: false); + ToggleCanUnlockWithBiometricsCommand = CreateDefaultAsyncRelayCommand(ToggleCanUnlockWithBiometricsAsync, () => _inited, allowsMultipleExecutions: false); + ToggleCanUnlockWithPinCommand = CreateDefaultAsyncRelayCommand(ToggleCanUnlockWithPinAsync, () => _inited, allowsMultipleExecutions: false); + ShowAccountFingerprintPhraseCommand = CreateDefaultAsyncRelayCommand(ShowAccountFingerprintPhraseAsync, allowsMultipleExecutions: false); + GoToTwoStepLoginCommand = CreateDefaultAsyncRelayCommand(() => GoToWebVaultSettingsAsync(AppResources.TwoStepLoginDescriptionLong, AppResources.ContinueToWebApp), allowsMultipleExecutions: false); + GoToChangeMasterPasswordCommand = CreateDefaultAsyncRelayCommand(() => GoToWebVaultSettingsAsync(AppResources.ChangeMasterPasswordDescriptionLong, AppResources.ContinueToWebApp), allowsMultipleExecutions: false); + LockCommand = CreateDefaultAsyncRelayCommand(() => _vaultTimeoutService.LockAsync(true, true), allowsMultipleExecutions: false); + LogOutCommand = CreateDefaultAsyncRelayCommand(LogOutAsync, allowsMultipleExecutions: false); + DeleteAccountCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage())), allowsMultipleExecutions: false); } public bool UseThisDeviceToApproveLoginRequests @@ -114,8 +107,7 @@ namespace Bit.App.Pages } var biometricName = AppResources.Biometrics; - // 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.iOS) + if (DeviceInfo.Platform == DevicePlatform.iOS) { biometricName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID @@ -209,18 +201,17 @@ namespace Bit.App.Pages private int? CurrentVaultTimeout => GetRawVaultTimeoutFrom(VaultTimeoutPickerViewModel.SelectedKey); - private bool IncludeLinksWithSubscriptionInfo => // 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 -Device.RuntimePlatform != Device.iOS; + private bool IncludeLinksWithSubscriptionInfo => DeviceInfo.Platform != DevicePlatform.iOS; private bool HasVaultTimeoutActionPolicy => !string.IsNullOrEmpty(_vaultTimeoutActionPolicy); public PickerViewModel VaultTimeoutPickerViewModel { get; } public PickerViewModel VaultTimeoutActionPickerViewModel { get; } - public AsyncCommand ToggleUseThisDeviceToApproveLoginRequestsCommand { get; } + public AsyncRelayCommand ToggleUseThisDeviceToApproveLoginRequestsCommand { get; } public ICommand GoToPendingLogInRequestsCommand { get; } - public AsyncCommand ToggleCanUnlockWithBiometricsCommand { get; } - public AsyncCommand ToggleCanUnlockWithPinCommand { get; } + public AsyncRelayCommand ToggleCanUnlockWithBiometricsCommand { get; } + public AsyncRelayCommand ToggleCanUnlockWithPinCommand { get; } public ICommand ShowAccountFingerprintPhraseCommand { get; } public ICommand GoToTwoStepLoginCommand { get; } public ICommand GoToChangeMasterPasswordCommand { get; } @@ -256,11 +247,11 @@ Device.RuntimePlatform != Device.iOS; TriggerPropertyChanged(nameof(VaultTimeoutPolicyDescription)); TriggerPropertyChanged(nameof(ShowChangeMasterPassword)); TriggerUpdateCustomVaultTimeoutPicker(); - ToggleUseThisDeviceToApproveLoginRequestsCommand.RaiseCanExecuteChanged(); - ToggleCanUnlockWithBiometricsCommand.RaiseCanExecuteChanged(); - ToggleCanUnlockWithPinCommand.RaiseCanExecuteChanged(); - VaultTimeoutPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); - VaultTimeoutActionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); + ToggleUseThisDeviceToApproveLoginRequestsCommand.NotifyCanExecuteChanged(); + ToggleCanUnlockWithBiometricsCommand.NotifyCanExecuteChanged(); + ToggleCanUnlockWithPinCommand.NotifyCanExecuteChanged(); + VaultTimeoutPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); + VaultTimeoutActionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged(); }); } @@ -275,7 +266,7 @@ Device.RuntimePlatform != Device.iOS; _maximumVaultTimeoutPolicy = maximumVaultTimeoutPolicy?.GetInt(Policy.MINUTES_KEY); _vaultTimeoutActionPolicy = maximumVaultTimeoutPolicy?.GetString(Policy.ACTION_KEY); - MainThread.BeginInvokeOnMainThread(VaultTimeoutActionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged); + MainThread.BeginInvokeOnMainThread(VaultTimeoutActionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged); } private async Task InitVaultTimeoutPickerAsync() @@ -368,10 +359,9 @@ Device.RuntimePlatform != Device.iOS; return; } - // 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 (!_supportsBiometric || - !await _platformUtilsService.AuthenticateBiometricAsync(null, Device.RuntimePlatform == Device.Android ? "." : null)) + !await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null)) { _canUnlockWithBiometrics = false; MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics))); diff --git a/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index d20c6ab24..21f50f468 100644 --- a/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -1,5 +1,6 @@ using Bit.App.Utilities; using Bit.Core.Resources.Localization; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -7,7 +8,7 @@ namespace Bit.App.Pages { public SettingsPageViewModel() { - ExecuteSettingItemCommand = new AsyncCommand(item => item.ExecuteAsync(), + ExecuteSettingItemCommand = CreateDefaultAsyncRelayCommand(item => item.ExecuteAsync(), onException: ex => HandleException(ex), allowsMultipleExecutions: false); @@ -24,7 +25,7 @@ namespace Bit.App.Pages public List SettingsItems { get; } - public AsyncCommand ExecuteSettingItemCommand { get; } + public AsyncRelayCommand ExecuteSettingItemCommand { get; } private async Task NavigateToAsync(Page page) { diff --git a/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs b/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs index 3911fd6dd..ae676ba0b 100644 --- a/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs +++ b/src/Core/Pages/Settings/VaultSettingsPageViewModel.cs @@ -22,15 +22,15 @@ namespace Bit.App.Pages _platformUtilsService = ServiceContainer.Resolve(); _environmentService = ServiceContainer.Resolve(); - GoToFoldersCommand = new AsyncCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage())), + GoToFoldersCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage())), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - GoToExportVaultCommand = new AsyncCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage())), + GoToExportVaultCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage())), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - GoToImportItemsCommand = new AsyncCommand(GoToImportItemsAsync, + GoToImportItemsCommand = CreateDefaultAsyncRelayCommand(GoToImportItemsAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Vault/AttachmentsPageViewModel.cs b/src/Core/Pages/Vault/AttachmentsPageViewModel.cs index 6e66b2f76..9446c757d 100644 --- a/src/Core/Pages/Vault/AttachmentsPageViewModel.cs +++ b/src/Core/Pages/Vault/AttachmentsPageViewModel.cs @@ -45,7 +45,7 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve(); Attachments = new ExtendedObservableCollection(); DeleteAttachmentCommand = new Command(DeleteAsync); - SubmitAsyncCommand = new AsyncCommand(SubmitAsync, allowsMultipleExecutions: false); + SubmitAsyncCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, allowsMultipleExecutions: false); PageTitle = AppResources.Attachments; } diff --git a/src/Core/Pages/Vault/BaseCipherViewModel.cs b/src/Core/Pages/Vault/BaseCipherViewModel.cs index 671aca2a5..8f7e084a4 100644 --- a/src/Core/Pages/Vault/BaseCipherViewModel.cs +++ b/src/Core/Pages/Vault/BaseCipherViewModel.cs @@ -7,6 +7,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; namespace Bit.App.Pages { @@ -28,7 +29,7 @@ namespace Bit.App.Pages _auditService = ServiceContainer.Resolve("auditService"); _logger = ServiceContainer.Resolve("logger"); - CheckPasswordCommand = new AsyncCommand(CheckPasswordAsync, allowsMultipleExecutions: false); + CheckPasswordCommand = CreateDefaultAsyncRelayCommand(CheckPasswordAsync, allowsMultipleExecutions: false); } public CipherView Cipher @@ -39,7 +40,7 @@ namespace Bit.App.Pages public string CreationDate => string.Format(AppResources.CreatedXY, Cipher?.CreationDate.ToShortDateString(), Cipher?.CreationDate.ToShortTimeString()); - public AsyncCommand CheckPasswordCommand { get; } + public AsyncRelayCommand CheckPasswordCommand { get; } protected async Task CheckPasswordAsync() { diff --git a/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs b/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs index b44a26e15..abd2a6c49 100644 --- a/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs +++ b/src/Core/Pages/Vault/CipherAddEditPageViewModel.cs @@ -16,6 +16,7 @@ using Bit.Core.Utilities; using Microsoft.Maui.Controls; using Microsoft.Maui; using Bit.App.Utilities; +using CommunityToolkit.Mvvm.Input; #nullable enable @@ -99,8 +100,8 @@ namespace Bit.App.Pages UriOptionsCommand = new Command(UriOptions); FieldOptionsCommand = new Command(FieldOptions); PasswordPromptHelpCommand = new Command(PasswordPromptHelp); - CopyCommand = new AsyncCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - GenerateUsernameCommand = new AsyncCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false); + CopyCommand = CreateDefaultAsyncRelayCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + GenerateUsernameCommand = CreateDefaultAsyncRelayCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false); Uris = new ExtendedObservableCollection(); Fields = new ExtendedObservableCollection(); Collections = new ExtendedObservableCollection(); @@ -163,8 +164,8 @@ namespace Bit.App.Pages public Command UriOptionsCommand { get; set; } public Command FieldOptionsCommand { get; set; } public Command PasswordPromptHelpCommand { get; set; } - public AsyncCommand CopyCommand { get; set; } - public AsyncCommand GenerateUsernameCommand { get; set; } + public AsyncRelayCommand CopyCommand { get; set; } + public AsyncRelayCommand GenerateUsernameCommand { get; set; } public string CipherId { get; set; } public string OrganizationId { get; set; } public string FolderId { get; set; } diff --git a/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs b/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs index fb56ee526..2ccb788ff 100644 --- a/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs +++ b/src/Core/Pages/Vault/CipherDetailsPageViewModel.cs @@ -14,7 +14,7 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; - +using CommunityToolkit.Mvvm.Input; using Microsoft.Maui.Controls; using Microsoft.Maui; @@ -66,15 +66,15 @@ namespace Bit.App.Pages _clipboardService = ServiceContainer.Resolve("clipboardService"); _watchDeviceService = ServiceContainer.Resolve(); - CopyCommand = new AsyncCommand((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - CopyUriCommand = new AsyncCommand(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - CopyFieldCommand = new AsyncCommand(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + CopyCommand = CreateDefaultAsyncRelayCommand((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + CopyUriCommand = CreateDefaultAsyncRelayCommand(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); + CopyFieldCommand = CreateDefaultAsyncRelayCommand(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); LaunchUriCommand = new Command(LaunchUri); - CloneCommand = new AsyncCommand(CloneAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); + CloneCommand = CreateDefaultAsyncRelayCommand(CloneAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); TogglePasswordCommand = new Command(TogglePassword); ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardCodeCommand = new Command(ToggleCardCode); - DownloadAttachmentCommand = new AsyncCommand(DownloadAttachmentAsync, allowsMultipleExecutions: false); + DownloadAttachmentCommand = CreateDefaultAsyncRelayCommand(DownloadAttachmentAsync, allowsMultipleExecutions: false); PageTitle = AppResources.ViewItem; } @@ -87,7 +87,7 @@ namespace Bit.App.Pages public Command TogglePasswordCommand { get; set; } public Command ToggleCardNumberCommand { get; set; } public Command ToggleCardCodeCommand { get; set; } - public AsyncCommand DownloadAttachmentCommand { get; set; } + public AsyncRelayCommand DownloadAttachmentCommand { get; set; } public string CipherId { get; set; } protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[] { diff --git a/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs b/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs index 7f9e9b79c..c98c467e4 100644 --- a/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs +++ b/src/Core/Pages/Vault/CipherSelectionPageViewModel.cs @@ -45,13 +45,13 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve(); GroupedItems = new ObservableRangeCollection(); - CipherOptionsCommand = new AsyncCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), + CipherOptionsCommand = CreateDefaultAsyncRelayCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - SelectCipherCommand = new AsyncCommand(SelectCipherAsync, + SelectCipherCommand = CreateDefaultAsyncRelayCommand(SelectCipherAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); - AddCipherCommand = new AsyncCommand(AddCipherAsync, + AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); diff --git a/src/Core/Pages/Vault/CiphersPageViewModel.cs b/src/Core/Pages/Vault/CiphersPageViewModel.cs index 7eb9e7a05..d4747e2a9 100644 --- a/src/Core/Pages/Vault/CiphersPageViewModel.cs +++ b/src/Core/Pages/Vault/CiphersPageViewModel.cs @@ -52,10 +52,10 @@ namespace Bit.App.Pages _logger = ServiceContainer.Resolve("logger"); Ciphers = new ExtendedObservableCollection(); - CipherOptionsCommand = new AsyncCommand(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), + CipherOptionsCommand = CreateDefaultAsyncRelayCommand(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), onException: ex => HandleException(ex), allowsMultipleExecutions: false); - AddCipherCommand = new AsyncCommand(AddCipherAsync, + AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs index 89513cd91..9d39b6fb8 100644 --- a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs +++ b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageTOTPListItem.cs @@ -5,7 +5,7 @@ using Bit.App.Utilities; using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Utilities; - +using CommunityToolkit.Mvvm.Input; using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.Controls; using Microsoft.Maui; @@ -37,13 +37,13 @@ namespace Bit.App.Pages Cipher = cipherView; WebsiteIconsEnabled = websiteIconsEnabled; - CopyCommand = new AsyncCommand(CopyToClipboardAsync, + CopyCommand = CreateDefaultAsyncRelayCommand(CopyToClipboardAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); _totpTickHelper = new TotpHelper(cipherView); } - public AsyncCommand CopyCommand { get; set; } + public AsyncRelayCommand CopyCommand { get; set; } public CipherView Cipher { diff --git a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index bf0e3304a..8be572f95 100644 --- a/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/Core/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -71,10 +71,10 @@ namespace Bit.App.Pages Refreshing = true; await LoadAsync(); }); - CipherOptionsCommand = new AsyncCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), + CipherOptionsCommand = CreateDefaultAsyncRelayCommand(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); - VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, + VaultFilterCommand = CreateDefaultAsyncRelayCommand(VaultFilterOptionsAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); diff --git a/src/Core/Pages/Vault/ScanPageViewModel.cs b/src/Core/Pages/Vault/ScanPageViewModel.cs index 04f761866..c9c2495a3 100644 --- a/src/Core/Pages/Vault/ScanPageViewModel.cs +++ b/src/Core/Pages/Vault/ScanPageViewModel.cs @@ -20,7 +20,7 @@ namespace Bit.App.Pages public ScanPageViewModel() { - ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException); + ToggleScanModeCommand = CreateDefaultAsyncRelayCommand(ToggleScanMode, onException: HandleException); StartCameraCommand = new Command(StartCamera); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); diff --git a/src/Core/Pages/Vault/SharePageViewModel.cs b/src/Core/Pages/Vault/SharePageViewModel.cs index d870ad530..e0c73d049 100644 --- a/src/Core/Pages/Vault/SharePageViewModel.cs +++ b/src/Core/Pages/Vault/SharePageViewModel.cs @@ -37,7 +37,7 @@ namespace Bit.App.Pages OrganizationOptions = new List>(); PageTitle = AppResources.MoveToOrganization; - MoveCommand = new AsyncCommand(MoveAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); + MoveCommand = CreateDefaultAsyncRelayCommand(MoveAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false); } public string CipherId { get; set; } diff --git a/src/Core/Pages/VaultFilterViewModel.cs b/src/Core/Pages/VaultFilterViewModel.cs index 5aa82194b..9791f7f95 100644 --- a/src/Core/Pages/VaultFilterViewModel.cs +++ b/src/Core/Pages/VaultFilterViewModel.cs @@ -25,7 +25,7 @@ namespace Bit.App.Pages public VaultFilterViewModel() { - VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, + VaultFilterCommand = CreateDefaultAsyncRelayCommand(VaultFilterOptionsAsync, onException: ex => logger.Exception(ex), allowsMultipleExecutions: false); } diff --git a/src/Core/Utilities/AsyncCommand.cs b/src/Core/Utilities/AsyncCommand.cs deleted file mode 100644 index cc81ed0b2..000000000 --- a/src/Core/Utilities/AsyncCommand.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Windows.Input; -using CommunityToolkit.Mvvm.Input; - -namespace Bit.App.Utilities -{ - // TODO: [MAUI-Migration] DELETE WHEN MIGRATION IS DONE - /// - - /// Wrapper of just to ease with the MAUI migration process. - /// After the process is done, remove this and use AsyncRelayCommand directly - /// - public class AsyncCommand : ICommand - { - readonly AsyncRelayCommand _relayCommand; - - public AsyncCommand(Func execute, Func canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) - { - async Task doAsync() - { - try - { - await execute?.Invoke(); - } - catch (Exception ex) - { - onException?.Invoke(ex); - } - } - - var safeCanExecute = canExecute; - if (canExecute is null) - { - safeCanExecute = () => true; - } - _relayCommand = new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); - } - - public event EventHandler CanExecuteChanged; - - public bool CanExecute(object parameter) => _relayCommand.CanExecute(parameter); - public void Execute(object parameter) => _relayCommand.Execute(parameter); - public void RaiseCanExecuteChanged() => _relayCommand.NotifyCanExecuteChanged(); - } - - /// Wrapper of just to ease with the MAUI migration process. - /// After the process is done, remove this and use AsyncRelayCommand directly - /// - public class AsyncCommand : ICommand - { - readonly AsyncRelayCommand _relayCommand; - - public AsyncCommand(Func execute, Predicate canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) - { - async Task doAsync(T foo) - { - try - { - await execute?.Invoke(foo); - } - catch (Exception ex) - { - onException?.Invoke(ex); - } - } - - var safeCanExecute = canExecute; - if (canExecute is null) - { - safeCanExecute = _ => true; - } - _relayCommand = new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); - } - - public event EventHandler CanExecuteChanged; - - public bool CanExecute(object parameter) => _relayCommand.CanExecute(parameter); - public void Execute(object parameter) => _relayCommand.Execute(parameter); - public void RaiseCanExecuteChanged() => _relayCommand.NotifyCanExecuteChanged(); - } -} diff --git a/src/Core/Utilities/ExtendedViewModel.cs b/src/Core/Utilities/ExtendedViewModel.cs index ed586d453..6251a5543 100644 --- a/src/Core/Utilities/ExtendedViewModel.cs +++ b/src/Core/Utilities/ExtendedViewModel.cs @@ -1,14 +1,96 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.CompilerServices; +using Bit.App.Abstractions; +using Bit.Core.Abstractions; +using Bit.Core.Exceptions; +using Bit.Core.Resources.Localization; +using CommunityToolkit.Mvvm.Input; namespace Bit.Core.Utilities { public abstract class ExtendedViewModel : INotifyPropertyChanged { + protected LazyResolve _deviceActionService = new LazyResolve(); + protected LazyResolve _platformUtilsService = new LazyResolve(); + protected LazyResolve _logger = new LazyResolve(); + public event PropertyChangedEventHandler PropertyChanged; + protected AsyncRelayCommand CreateDefaultAsyncRelayCommand(Func execute, Func canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) + { + var safeCanExecute = canExecute; + if (canExecute is null) + { + safeCanExecute = () => true; + } + + async Task doAsync() + { + try + { + await execute?.Invoke(); + } + catch (Exception ex) + { + if (onException != null) + { + onException(ex); + } + else + { + HandleException(ex); + } + } + } + + return new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); + } + + protected AsyncRelayCommand CreateDefaultAsyncRelayCommand(Func execute, Predicate canExecute = null, Action onException = null, bool allowsMultipleExecutions = true) + { + var safeCanExecute = canExecute; + if (canExecute is null) + { + safeCanExecute = _ => true; + } + + async Task doAsync(T foo) + { + try + { + await execute?.Invoke(foo); + } + catch (Exception ex) + { + if (onException != null) + { + onException(ex); + } + else + { + HandleException(ex); + } + } + } + + return new AsyncRelayCommand(doAsync, safeCanExecute, allowsMultipleExecutions ? AsyncRelayCommandOptions.AllowConcurrentExecutions : AsyncRelayCommandOptions.None); + } + + protected void HandleException(Exception ex, string message = null) + { + if (ex is ApiException apiException && apiException.Error != null) + { + message = apiException.Error.GetSingleMessage(); + } + + Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () => + { + await _deviceActionService.Value.HideLoadingAsync(); + await _platformUtilsService.Value.ShowDialogAsync(message ?? AppResources.GenericErrorMessage); + }).FireAndForget(); + _logger.Value.Exception(ex); + } + protected bool SetProperty(ref T backingStore, T value, Action onChanged = null, [CallerMemberName] string propertyName = "", string[] additionalPropertyNames = null) {