PM-3349 PM-3350 Removed AsyncCommand "wrapper" and added AsyncRelayCommand directly in all ViewModels that were using the other one.

This commit is contained in:
Dinis Vieira 2023-11-16 22:31:01 +00:00
parent f02b3415a3
commit 2c7870d660
No known key found for this signature in database
GPG Key ID: 9389160FF6C295F3
41 changed files with 282 additions and 349 deletions

View File

@ -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<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
SelectAccountCommand = new AsyncRelayCommand<AccountViewCellViewModel>(SelectAccountAsync,
AsyncRelayCommandOptions.None);
LongPressAccountCommand = new AsyncCommand<AccountViewCellViewModel>(LongPressAccountAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncRelayCommand<AccountViewCellViewModel>(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();

View File

@ -17,11 +17,11 @@ namespace Bit.App.Controls
_stateService = stateService;
_messagingService = messagingService;
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
SelectAccountCommand = CreateDefaultAsyncRelayCommand<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
LongPressAccountCommand = CreateDefaultAsyncRelayCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
}

View File

@ -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);

View File

@ -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; }

View File

@ -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<ILogger>();
PageTitle = AppResources.PasswordHint;
SubmitCommand = new AsyncCommand(SubmitAsync,
SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync,
onException: ex =>
{
_logger.Exception(ex);

View File

@ -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()
{

View File

@ -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);

View File

@ -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)
{

View File

@ -56,11 +56,11 @@ namespace Bit.App.Pages
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
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);
}

View File

@ -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);
}

View File

@ -54,7 +54,7 @@ namespace Bit.App.Pages
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.Bitwarden;
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
LogInCommand = CreateDefaultAsyncRelayCommand(LogInAsync, allowsMultipleExecutions: false);
}
public string OrgIdentifier

View File

@ -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

View File

@ -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<IUserVerificationService>();
}
public AsyncCommand SubmitCommand { get; }
public AsyncRelayCommand SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public Action UpdateTempPasswordSuccessAction { get; set; }

View File

@ -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

View File

@ -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<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
private LazyResolve<IPlatformUtilsService> _platformUtilsService = new LazyResolve<IPlatformUtilsService>();
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
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<Task> execute, Func<bool> canExecute = null)
{
return new AsyncCommand(execute,
canExecute,
ex => HandleException(ex),
allowsMultipleExecutions: false);
}
protected async Task<bool> HasConnectivityAsync()
{
if (Connectivity.NetworkAccess == NetworkAccess.None)

View File

@ -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<GeneratorType> GeneratorTypeOptions { get; set; }

View File

@ -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;

View File

@ -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<IEnvironmentService>();
var clipboardService = ServiceContainer.Resolve<IClipboardService>();
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();
});
}

View File

@ -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<string> 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();
});
}

View File

@ -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()

View File

@ -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<UriMatchType> 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();
});
}

View File

@ -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);
}
}
}

View File

@ -28,11 +28,11 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
AddUriCommand = new AsyncCommand(AddUriAsync,
AddUriCommand = CreateDefaultAsyncRelayCommand(AddUriAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
EditUriCommand = new AsyncCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
EditUriCommand = CreateDefaultAsyncRelayCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}

View File

@ -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<PasswordlessLoginResponse>();
AnswerRequestCommand = new AsyncCommand<PasswordlessLoginResponse>(PasswordlessLoginAsync,
AnswerRequestCommand = CreateDefaultAsyncRelayCommand<PasswordlessLoginResponse>(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<PasswordlessLoginResponse> AnswerRequestCommand { get; }
public AsyncRelayCommand<PasswordlessLoginResponse> AnswerRequestCommand { get; }
public AsyncCommand DeclineAllRequestsCommand { get; }
public AsyncRelayCommand DeclineAllRequestsCommand { get; }
public ObservableRangeCollection<PasswordlessLoginResponse> LoginRequests { get; }

View File

@ -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<IWatchDeviceService>();
_logger = ServiceContainer.Resolve<ILogger>();
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<int>(
_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);

View File

@ -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<int> VaultTimeoutPickerViewModel { get; }
public PickerViewModel<VaultTimeoutAction> 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)));

View File

@ -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<SettingsPageListItem>(item => item.ExecuteAsync(),
ExecuteSettingItemCommand = CreateDefaultAsyncRelayCommand<SettingsPageListItem>(item => item.ExecuteAsync(),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
@ -24,7 +25,7 @@ namespace Bit.App.Pages
public List<SettingsPageListItem> SettingsItems { get; }
public AsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
public AsyncRelayCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
private async Task NavigateToAsync(Page page)
{

View File

@ -22,15 +22,15 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
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);
}

View File

@ -45,7 +45,7 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>();
Attachments = new ExtendedObservableCollection<AttachmentView>();
DeleteAttachmentCommand = new Command<AttachmentView>(DeleteAsync);
SubmitAsyncCommand = new AsyncCommand(SubmitAsync, allowsMultipleExecutions: false);
SubmitAsyncCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, allowsMultipleExecutions: false);
PageTitle = AppResources.Attachments;
}

View File

@ -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<IAuditService>("auditService");
_logger = ServiceContainer.Resolve<ILogger>("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()
{

View File

@ -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<LoginUriView>(UriOptions);
FieldOptionsCommand = new Command<ICustomFieldItemViewModel>(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<LoginUriView>();
Fields = new ExtendedObservableCollection<ICustomFieldItemViewModel>();
Collections = new ExtendedObservableCollection<CollectionViewModel>();
@ -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; }

View File

@ -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<IClipboardService>("clipboardService");
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyFieldCommand = new AsyncCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyCommand = CreateDefaultAsyncRelayCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyUriCommand = CreateDefaultAsyncRelayCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyFieldCommand = CreateDefaultAsyncRelayCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
LaunchUriCommand = new Command<ILaunchableView>(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<AttachmentView>(DownloadAttachmentAsync, allowsMultipleExecutions: false);
DownloadAttachmentCommand = CreateDefaultAsyncRelayCommand<AttachmentView>(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<AttachmentView> DownloadAttachmentCommand { get; set; }
public AsyncRelayCommand<AttachmentView> DownloadAttachmentCommand { get; set; }
public string CipherId { get; set; }
protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[]
{

View File

@ -45,13 +45,13 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>();
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
CipherOptionsCommand = new AsyncCommand<CipherView>(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
CipherOptionsCommand = CreateDefaultAsyncRelayCommand<CipherView>(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
SelectCipherCommand = new AsyncCommand<IGroupingsPageListItem>(SelectCipherAsync,
SelectCipherCommand = CreateDefaultAsyncRelayCommand<IGroupingsPageListItem>(SelectCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AddCipherCommand = new AsyncCommand(AddCipherAsync,
AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);

View File

@ -52,10 +52,10 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger");
Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new AsyncCommand<CipherView>(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
CipherOptionsCommand = CreateDefaultAsyncRelayCommand<CipherView>(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);
}

View File

@ -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
{

View File

@ -71,10 +71,10 @@ namespace Bit.App.Pages
Refreshing = true;
await LoadAsync();
});
CipherOptionsCommand = new AsyncCommand<CipherView>(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
CipherOptionsCommand = CreateDefaultAsyncRelayCommand<CipherView>(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);

View File

@ -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<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");

View File

@ -37,7 +37,7 @@ namespace Bit.App.Pages
OrganizationOptions = new List<KeyValuePair<string, string>>();
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; }

View File

@ -25,7 +25,7 @@ namespace Bit.App.Pages
public VaultFilterViewModel()
{
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
VaultFilterCommand = CreateDefaultAsyncRelayCommand(VaultFilterOptionsAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
}

View File

@ -1,80 +0,0 @@
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
namespace Bit.App.Utilities
{
// TODO: [MAUI-Migration] DELETE WHEN MIGRATION IS DONE
/// <summary>
/// Wrapper of <see cref="AsyncRelayCommand"/> just to ease with the MAUI migration process.
/// After the process is done, remove this and use AsyncRelayCommand directly
/// </summary>
public class AsyncCommand : ICommand
{
readonly AsyncRelayCommand _relayCommand;
public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null, Action<Exception> 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 <see cref="AsyncRelayCommand"/> just to ease with the MAUI migration process.
/// After the process is done, remove this and use AsyncRelayCommand directly
/// </summary>
public class AsyncCommand<T> : ICommand
{
readonly AsyncRelayCommand<T> _relayCommand;
public AsyncCommand(Func<T, Task> execute, Predicate<T?> canExecute = null, Action<Exception> 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<T>(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();
}
}

View File

@ -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<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
protected LazyResolve<IPlatformUtilsService> _platformUtilsService = new LazyResolve<IPlatformUtilsService>();
protected LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public event PropertyChangedEventHandler PropertyChanged;
protected AsyncRelayCommand CreateDefaultAsyncRelayCommand(Func<Task> execute, Func<bool> canExecute = null, Action<Exception> 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<T> CreateDefaultAsyncRelayCommand<T>(Func<T, Task> execute, Predicate<T?> canExecute = null, Action<Exception> 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<T>(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<T>(ref T backingStore, T value, Action onChanged = null,
[CallerMemberName] string propertyName = "", string[] additionalPropertyNames = null)
{