1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 07:43:37 +00:00

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
41 changed files with 282 additions and 349 deletions

View File

@@ -1,7 +1,7 @@
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using CommunityToolkit.Mvvm.Input;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
@@ -37,17 +37,14 @@ namespace Bit.App.Controls
{ {
InitializeComponent(); InitializeComponent();
ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync, ToggleVisibililtyCommand = new AsyncRelayCommand(ToggleVisibilityAsync,
onException: ex => _logger.Value.Exception(ex), AsyncRelayCommandOptions.None);
allowsMultipleExecutions: false);
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync, SelectAccountCommand = new AsyncRelayCommand<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => _logger.Value.Exception(ex), AsyncRelayCommandOptions.None);
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncCommand<AccountViewCellViewModel>(LongPressAccountAsync, LongPressAccountCommand = new AsyncRelayCommand<AccountViewCellViewModel>(LongPressAccountAsync,
onException: ex => _logger.Value.Exception(ex), AsyncRelayCommandOptions.None);
allowsMultipleExecutions: false);
} }
public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel; public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
@@ -70,13 +67,20 @@ namespace Bit.App.Controls
public async Task ToggleVisibilityAsync() 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) private async Task LongPressAccountAsync(AccountViewCellViewModel item)
{ {
if (!LongPressAccountEnabled || item == null || !item.IsAccount)
{
return;
}
try try
{ {
if (!LongPressAccountEnabled || item == null || !item.IsAccount)
{
return;
}
await Task.Delay(100); await Task.Delay(100);
await HideAsync(); await HideAsync();

View File

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

View File

@@ -30,7 +30,7 @@ namespace Bit.App.Lists.ItemViewModels.CustomFields
_eventService = eventService; _eventService = eventService;
CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field)); CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field));
ToggleHiddenValueCommand = new AsyncCommand(ToggleHiddenValueAsync, null, ex => ToggleHiddenValueCommand = CreateDefaultAsyncRelayCommand(ToggleHiddenValueAsync, null, ex =>
{ {
//#if !FDROID //#if !FDROID
// Microsoft.AppCenter.Crashes.Crashes.TrackError(ex); // Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);

View File

@@ -1,11 +1,8 @@
using System; using System.Windows.Input;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -26,7 +23,7 @@ namespace Bit.App.Pages
IdentityUrl = _environmentService.IdentityUrl; IdentityUrl = _environmentService.IdentityUrl;
IconsUrl = _environmentService.IconsUrl; IconsUrl = _environmentService.IconsUrl;
NotificationsUrls = _environmentService.NotificationsUrl; NotificationsUrls = _environmentService.NotificationsUrl;
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync, onException: OnSubmitException, allowsMultipleExecutions: false);
} }
public ICommand SubmitCommand { get; } 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.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -25,7 +23,7 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
PageTitle = AppResources.PasswordHint; PageTitle = AppResources.PasswordHint;
SubmitCommand = new AsyncCommand(SubmitAsync, SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync,
onException: ex => onException: ex =>
{ {
_logger.Exception(ex); _logger.Exception(ex);

View File

@@ -11,6 +11,7 @@ using Bit.Core.Utilities;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui; using Microsoft.Maui;
using CommunityToolkit.Mvvm.Input;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -50,12 +51,12 @@ namespace Bit.App.Pages
AllowActiveAccountSelection = true AllowActiveAccountSelection = true
}; };
RememberEmailCommand = new Command(() => RememberEmail = !RememberEmail); RememberEmailCommand = new Command(() => RememberEmail = !RememberEmail);
ContinueCommand = new AsyncCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false); ContinueCommand = CreateDefaultAsyncRelayCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false);
CreateAccountCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(StartRegisterAction), CreateAccountCommand = CreateDefaultAsyncRelayCommand(async () => Device.InvokeOnMainThreadAsync(StartRegisterAction),
onException: _logger.Exception, allowsMultipleExecutions: false); onException: _logger.Exception, allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(CloseAction), CloseCommand = CreateDefaultAsyncRelayCommand(async () => Device.InvokeOnMainThreadAsync(CloseAction),
onException: _logger.Exception, allowsMultipleExecutions: false); onException: _logger.Exception, allowsMultipleExecutions: false);
ShowEnvironmentPickerCommand = new AsyncCommand(ShowEnvironmentPickerAsync, ShowEnvironmentPickerCommand = CreateDefaultAsyncRelayCommand(ShowEnvironmentPickerAsync,
onException: _logger.Exception, allowsMultipleExecutions: false); onException: _logger.Exception, allowsMultipleExecutions: false);
InitAsync().FireAndForget(); InitAsync().FireAndForget();
} }
@@ -113,10 +114,10 @@ namespace Bit.App.Pages
public Action StartEnvironmentAction { get; set; } public Action StartEnvironmentAction { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
public Command RememberEmailCommand { get; set; } public Command RememberEmailCommand { get; set; }
public AsyncCommand ContinueCommand { get; } public AsyncRelayCommand ContinueCommand { get; }
public AsyncCommand CloseCommand { get; } public AsyncRelayCommand CloseCommand { get; }
public AsyncCommand CreateAccountCommand { get; } public AsyncRelayCommand CreateAccountCommand { get; }
public AsyncCommand ShowEnvironmentPickerCommand { get; } public AsyncRelayCommand ShowEnvironmentPickerCommand { get; }
public async Task InitAsync() public async Task InitAsync()
{ {

View File

@@ -55,19 +55,19 @@ namespace Bit.App.Pages
PageTitle = AppResources.LogInInitiated; PageTitle = AppResources.LogInInitiated;
RememberThisDevice = true; RememberThisDevice = true;
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction), ApproveWithMyOtherDeviceCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction), RequestAdminApprovalCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction), ApproveWithMasterPasswordCommand = CreateDefaultAsyncRelayCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync, ContinueCommand = CreateDefaultAsyncRelayCommand(CreateNewSsoUserAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);

View File

@@ -62,8 +62,8 @@ namespace Bit.App.Pages
PageTitle = AppResources.Bitwarden; PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
LogInCommand = new Command(async () => await LogInAsync()); LogInCommand = new Command(async () => await LogInAsync());
MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false);
LogInWithDeviceCommand = new AsyncCommand(() => Device.InvokeOnMainThreadAsync(LogInWithDeviceAction), onException: _logger.Exception, allowsMultipleExecutions: false); LogInWithDeviceCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(LogInWithDeviceAction), onException: _logger.Exception, allowsMultipleExecutions: false);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{ {

View File

@@ -56,11 +56,11 @@ namespace Bit.App.Pages
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>(); _cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>(); _cryptoService = ServiceContainer.Resolve<ICryptoService>();
CreatePasswordlessLoginCommand = new AsyncCommand(CreatePasswordlessLoginAsync, CreatePasswordlessLoginCommand = CreateDefaultAsyncRelayCommand(CreatePasswordlessLoginAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(() => MainThread.InvokeOnMainThreadAsync(CloseAction), CloseCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(CloseAction),
onException: _logger.Exception, onException: _logger.Exception,
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
} }

View File

@@ -39,10 +39,10 @@ namespace Bit.App.Pages
PageTitle = AppResources.LogInRequested; PageTitle = AppResources.LogInRequested;
AcceptRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(true), AcceptRequestCommand = CreateDefaultAsyncRelayCommand(() => PasswordlessLoginAsync(true),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
RejectRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(false), RejectRequestCommand = CreateDefaultAsyncRelayCommand(() => PasswordlessLoginAsync(false),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
} }

View File

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

View File

@@ -62,7 +62,7 @@ namespace Bit.App.Pages
PageTitle = AppResources.TwoStepLogin; PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync()); 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 public string TotpInstruction

View File

@@ -11,6 +11,7 @@ using Bit.Core.Utilities;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui; using Microsoft.Maui;
using Bit.App.Utilities; using Bit.App.Utilities;
using CommunityToolkit.Mvvm.Input;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -25,14 +26,14 @@ namespace Bit.App.Pages
PageTitle = AppResources.UpdateMasterPassword; PageTitle = AppResources.UpdateMasterPassword;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new AsyncCommand(SubmitAsync, SubmitCommand = CreateDefaultAsyncRelayCommand(SubmitAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>(); _userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
} }
public AsyncCommand SubmitCommand { get; } public AsyncRelayCommand SubmitCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; } public Command ToggleConfirmPasswordCommand { get; }
public Action UpdateTempPasswordSuccessAction { get; set; } public Action UpdateTempPasswordSuccessAction { get; set; }

View File

@@ -39,8 +39,8 @@ namespace Bit.App.Pages
PageTitle = AppResources.VerificationCode; PageTitle = AppResources.VerificationCode;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false); MainActionCommand = CreateDefaultAsyncRelayCommand(MainActionAsync, allowsMultipleExecutions: false);
RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false); RequestOTPCommand = CreateDefaultAsyncRelayCommand(RequestOTPAsync, allowsMultipleExecutions: false);
} }
public bool ShowPassword public bool ShowPassword

View File

@@ -1,27 +1,13 @@
using System; using Bit.App.Controls;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.Networking;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public abstract class BaseViewModel : ExtendedViewModel public abstract class BaseViewModel : ExtendedViewModel
{ {
private string _pageTitle = string.Empty; private string _pageTitle = string.Empty;
private AvatarImageSource _avatar; 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 public string PageTitle
{ {
@@ -37,29 +23,6 @@ namespace Bit.App.Pages
public ContentPage Page { get; set; } 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() protected async Task<bool> HasConnectivityAsync()
{ {
if (Connectivity.NetworkAccess == NetworkAccess.None) if (Connectivity.NetworkAccess == NetworkAccess.None)

View File

@@ -89,11 +89,11 @@ namespace Bit.App.Pages
}; };
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp); UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); RegenerateCommand = CreateDefaultAsyncRelayCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); RegenerateUsernameCommand = CreateDefaultAsyncRelayCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret); ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret);
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false); CopyCommand = CreateDefaultAsyncRelayCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(CloseAsync, 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; } public List<GeneratorType> GeneratorTypeOptions { get; set; }

View File

@@ -2,8 +2,7 @@
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using CommunityToolkit.Mvvm.Input;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -33,10 +32,10 @@ namespace Bit.App.Pages
_onSelectionChangingAsync = onSelectionChangingAsync; _onSelectionChangingAsync = onSelectionChangingAsync;
_title = title; _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; public TKey SelectedKey => _selectedKey;

View File

@@ -1,13 +1,10 @@
using System; using System.Windows.Input;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.ApplicationModel; using CommunityToolkit.Mvvm.Input;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -29,32 +26,32 @@ namespace Bit.App.Pages
var environmentService = ServiceContainer.Resolve<IEnvironmentService>(); var environmentService = ServiceContainer.Resolve<IEnvironmentService>();
var clipboardService = ServiceContainer.Resolve<IClipboardService>(); var clipboardService = ServiceContainer.Resolve<IClipboardService>();
ToggleSubmitCrashLogsCommand = CreateDefaultAsyncCommnad(ToggleSubmitCrashLogsAsync); ToggleSubmitCrashLogsCommand = CreateDefaultAsyncRelayCommand(ToggleSubmitCrashLogsAsync, allowsMultipleExecutions: false);
GoToHelpCenterCommand = CreateDefaultAsyncCommnad( GoToHelpCenterCommand = CreateDefaultAsyncRelayCommand(
() => LaunchUriAsync(AppResources.LearnMoreAboutHowToUseBitwardenOnTheHelpCenter, () => LaunchUriAsync(AppResources.LearnMoreAboutHowToUseBitwardenOnTheHelpCenter,
AppResources.ContinueToHelpCenter, AppResources.ContinueToHelpCenter,
ExternalLinksConstants.HELP_CENTER)); ExternalLinksConstants.HELP_CENTER), allowsMultipleExecutions: false);
ContactBitwardenSupportCommand = CreateDefaultAsyncCommnad( ContactBitwardenSupportCommand = CreateDefaultAsyncRelayCommand(
() => LaunchUriAsync(AppResources.ContactSupportDescriptionLong, () => LaunchUriAsync(AppResources.ContactSupportDescriptionLong,
AppResources.ContinueToContactSupport, AppResources.ContinueToContactSupport,
ExternalLinksConstants.CONTACT_SUPPORT)); ExternalLinksConstants.CONTACT_SUPPORT), allowsMultipleExecutions: false);
GoToWebVaultCommand = CreateDefaultAsyncCommnad( GoToWebVaultCommand = CreateDefaultAsyncRelayCommand(
() => LaunchUriAsync(AppResources.ExploreMoreFeaturesOfYourBitwardenAccountOnTheWebApp, () => LaunchUriAsync(AppResources.ExploreMoreFeaturesOfYourBitwardenAccountOnTheWebApp,
AppResources.ContinueToWebApp, AppResources.ContinueToWebApp,
environmentService.GetWebVaultUrl())); environmentService.GetWebVaultUrl()), allowsMultipleExecutions: false);
GoToLearnAboutOrgsCommand = CreateDefaultAsyncCommnad( GoToLearnAboutOrgsCommand = CreateDefaultAsyncRelayCommand(
() => LaunchUriAsync(AppResources.LearnAboutOrganizationsDescriptionLong, () => LaunchUriAsync(AppResources.LearnAboutOrganizationsDescriptionLong,
string.Format(AppResources.ContinueToX, ExternalLinksConstants.BITWARDEN_WEBSITE), 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( CopyAppInfoCommand = CreateDefaultAsyncRelayCommand(
() => clipboardService.CopyTextAsync(AppInfo)); () => clipboardService.CopyTextAsync(AppInfo), allowsMultipleExecutions: false);
} }
public bool ShouldSubmitCrashLogs 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 GoToHelpCenterCommand { get; }
public ICommand ContactBitwardenSupportCommand { get; } public ICommand ContactBitwardenSupportCommand { get; }
public ICommand GoToWebVaultCommand { get; } public ICommand GoToWebVaultCommand { get; }
@@ -97,7 +94,7 @@ namespace Bit.App.Pages
MainThread.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
TriggerPropertyChanged(nameof(ShouldSubmitCrashLogs)); TriggerPropertyChanged(nameof(ShouldSubmitCrashLogs));
ToggleSubmitCrashLogsCommand.RaiseCanExecuteChanged(); ToggleSubmitCrashLogsCommand.NotifyCanExecuteChanged();
}); });
} }

View File

@@ -1,16 +1,10 @@
using System; using System.Windows.Input;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.ApplicationModel; using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -64,7 +58,7 @@ namespace Bit.App.Pages
() => _inited, () => _inited,
ex => HandleException(ex)); ex => HandleException(ex));
ToggleShowWebsiteIconsCommand = CreateDefaultAsyncCommnad(ToggleShowWebsiteIconsAsync, () => _inited); ToggleShowWebsiteIconsCommand = CreateDefaultAsyncRelayCommand(ToggleShowWebsiteIconsAsync, () => _inited, allowsMultipleExecutions: false);
} }
public PickerViewModel<string> LanguagePickerViewModel { get; } public PickerViewModel<string> LanguagePickerViewModel { get; }
@@ -87,7 +81,7 @@ namespace Bit.App.Pages
public bool IsShowWebsiteIconsEnabled => ToggleShowWebsiteIconsCommand.CanExecute(null); public bool IsShowWebsiteIconsEnabled => ToggleShowWebsiteIconsCommand.CanExecute(null);
public AsyncCommand ToggleShowWebsiteIconsCommand { get; } public AsyncRelayCommand ToggleShowWebsiteIconsCommand { get; }
public async Task InitAsync() public async Task InitAsync()
{ {
@@ -102,10 +96,10 @@ namespace Bit.App.Pages
MainThread.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
ToggleShowWebsiteIconsCommand.RaiseCanExecuteChanged(); ToggleShowWebsiteIconsCommand.NotifyCanExecuteChanged();
LanguagePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); LanguagePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged();
ThemePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); ThemePickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged();
DefaultDarkThemePickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); 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 Bit.Core.Resources.Localization;
using Microsoft.Maui.ApplicationModel; using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -16,8 +12,7 @@ namespace Bit.App.Pages
private bool _useDrawOver; private bool _useDrawOver;
private bool _askToAddLogin; 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 public bool SupportsAndroidAutofillServices => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsAutofillServices();
Device.RuntimePlatform == Device.Android && _deviceActionService.SupportsAutofillServices();
public bool UseAutofillServices 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 public bool ShowUseAccessibilityToggle => DeviceInfo.Platform == DevicePlatform.Android;
Device.RuntimePlatform == Device.Android;
public string UseAccessibilityDescription => _deviceActionService.GetAutofillAccessibilityDescription(); public string UseAccessibilityDescription => _deviceActionService.GetAutofillAccessibilityDescription();
@@ -90,21 +84,21 @@ Device.RuntimePlatform == Device.Android;
} }
} }
public AsyncCommand ToggleUseAutofillServicesCommand { get; private set; } public AsyncRelayCommand ToggleUseAutofillServicesCommand { get; private set; }
public AsyncCommand ToggleUseInlineAutofillCommand { get; private set; } public AsyncRelayCommand ToggleUseInlineAutofillCommand { get; private set; }
public AsyncCommand ToggleUseAccessibilityCommand { get; private set; } public AsyncRelayCommand ToggleUseAccessibilityCommand { get; private set; }
public AsyncCommand ToggleUseDrawOverCommand { get; private set; } public AsyncRelayCommand ToggleUseDrawOverCommand { get; private set; }
public AsyncCommand ToggleAskToAddLoginCommand { get; private set; } public AsyncRelayCommand ToggleAskToAddLoginCommand { get; private set; }
public ICommand GoToBlockAutofillUrisCommand { get; private set; } public ICommand GoToBlockAutofillUrisCommand { get; private set; }
private void InitAndroidCommands() private void InitAndroidCommands()
{ {
ToggleUseAutofillServicesCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited); ToggleUseAutofillServicesCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseAutofillServices()), () => _inited, allowsMultipleExecutions: false);
ToggleUseInlineAutofillCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited); ToggleUseInlineAutofillCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleUseInlineAutofillEnabledAsync()), () => _inited, allowsMultipleExecutions: false);
ToggleUseAccessibilityCommand = CreateDefaultAsyncCommnad(ToggleUseAccessibilityAsync, () => _inited); ToggleUseAccessibilityCommand = CreateDefaultAsyncRelayCommand(ToggleUseAccessibilityAsync, () => _inited, allowsMultipleExecutions: false);
ToggleUseDrawOverCommand = CreateDefaultAsyncCommnad(() => MainThread.InvokeOnMainThreadAsync(() => ToggleDrawOver()), () => _inited); ToggleUseDrawOverCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleDrawOver()), () => _inited, allowsMultipleExecutions: false);
ToggleAskToAddLoginCommand = CreateDefaultAsyncCommnad(ToggleAskToAddLoginAsync, () => _inited); ToggleAskToAddLoginCommand = CreateDefaultAsyncRelayCommand(ToggleAskToAddLoginAsync, () => _inited, allowsMultipleExecutions: false);
GoToBlockAutofillUrisCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage())); GoToBlockAutofillUrisCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()), allowsMultipleExecutions: false);
} }
private async Task InitAndroidAutofillSettingsAsync() private async Task InitAndroidAutofillSettingsAsync()

View File

@@ -1,13 +1,10 @@
using System.Collections.Generic; using System.Windows.Input;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.ApplicationModel; using CommunityToolkit.Mvvm.Input;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -36,7 +33,7 @@ namespace Bit.App.Pages
() => _inited, () => _inited,
ex => HandleException(ex)); ex => HandleException(ex));
ToggleCopyTotpAutomaticallyCommand = CreateDefaultAsyncCommnad(ToggleCopyTotpAutomaticallyAsync, () => _inited); ToggleCopyTotpAutomaticallyCommand = CreateDefaultAsyncRelayCommand(ToggleCopyTotpAutomaticallyAsync, () => _inited, allowsMultipleExecutions: false);
InitAndroidCommands(); InitAndroidCommands();
InitIOSCommands(); InitIOSCommands();
@@ -56,7 +53,7 @@ namespace Bit.App.Pages
public PickerViewModel<UriMatchType> DefaultUriMatchDetectionPickerViewModel { get; } public PickerViewModel<UriMatchType> DefaultUriMatchDetectionPickerViewModel { get; }
public AsyncCommand ToggleCopyTotpAutomaticallyCommand { get; private set; } public AsyncRelayCommand ToggleCopyTotpAutomaticallyCommand { get; private set; }
public async Task InitAsync() public async Task InitAsync()
{ {
@@ -72,11 +69,11 @@ namespace Bit.App.Pages
{ {
TriggerPropertyChanged(nameof(CopyTotpAutomatically)); TriggerPropertyChanged(nameof(CopyTotpAutomatically));
ToggleUseAutofillServicesCommand.RaiseCanExecuteChanged(); ToggleUseAutofillServicesCommand.NotifyCanExecuteChanged();
ToggleUseInlineAutofillCommand.RaiseCanExecuteChanged(); ToggleUseInlineAutofillCommand.NotifyCanExecuteChanged();
ToggleUseAccessibilityCommand.RaiseCanExecuteChanged(); ToggleUseAccessibilityCommand.NotifyCanExecuteChanged();
ToggleUseDrawOverCommand.RaiseCanExecuteChanged(); ToggleUseDrawOverCommand.NotifyCanExecuteChanged();
DefaultUriMatchDetectionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); DefaultUriMatchDetectionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged();
}); });
} }

View File

@@ -1,21 +1,18 @@
using System.Windows.Input; using System.Windows.Input;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public partial class AutofillSettingsPageViewModel 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 public bool SupportsiOSAutofill => DeviceInfo.Platform == DevicePlatform.iOS && _deviceActionService.SupportsAutofillServices();
Device.RuntimePlatform == Device.iOS && _deviceActionService.SupportsAutofillServices();
public ICommand GoToPasswordAutofillCommand { get; private set; } public ICommand GoToPasswordAutofillCommand { get; private set; }
public ICommand GoToAppExtensionCommand { get; private set; } public ICommand GoToAppExtensionCommand { get; private set; }
private void InitIOSCommands() private void InitIOSCommands()
{ {
GoToPasswordAutofillCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage()))); GoToPasswordAutofillCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage())), allowsMultipleExecutions: false);
GoToAppExtensionCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()))); 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>(); _stateService = ServiceContainer.Resolve<IStateService>();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>(); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
AddUriCommand = new AsyncCommand(AddUriAsync, AddUriCommand = CreateDefaultAsyncRelayCommand(AddUriAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
EditUriCommand = new AsyncCommand<BlockAutofillUriItemViewModel>(EditUriAsync, EditUriCommand = CreateDefaultAsyncRelayCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
} }

View File

@@ -13,6 +13,7 @@ using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui; using Microsoft.Maui;
using Bit.App.Utilities; using Bit.App.Utilities;
using CommunityToolkit.Mvvm.Input;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -34,11 +35,11 @@ namespace Bit.App.Pages
PageTitle = AppResources.PendingLogInRequests; PageTitle = AppResources.PendingLogInRequests;
LoginRequests = new ObservableRangeCollection<PasswordlessLoginResponse>(); LoginRequests = new ObservableRangeCollection<PasswordlessLoginResponse>();
AnswerRequestCommand = new AsyncCommand<PasswordlessLoginResponse>(PasswordlessLoginAsync, AnswerRequestCommand = CreateDefaultAsyncRelayCommand<PasswordlessLoginResponse>(PasswordlessLoginAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
DeclineAllRequestsCommand = new AsyncCommand(DeclineAllRequestsAsync, DeclineAllRequestsCommand = CreateDefaultAsyncRelayCommand(DeclineAllRequestsAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
@@ -47,9 +48,9 @@ namespace Bit.App.Pages
public ICommand RefreshCommand { get; } 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; } public ObservableRangeCollection<PasswordlessLoginResponse> LoginRequests { get; }

View File

@@ -1,16 +1,9 @@
using System; using System.Windows.Input;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -42,19 +35,21 @@ namespace Bit.App.Pages
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>(); _watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
SyncCommand = CreateDefaultAsyncCommnad(SyncAsync, () => _inited); SyncCommand = CreateDefaultAsyncRelayCommand(SyncAsync, CanExecuteIfInited, allowsMultipleExecutions: false);
ToggleIsScreenCaptureAllowedCommand = CreateDefaultAsyncCommnad(ToggleIsScreenCaptureAllowedAsync, () => _inited); ToggleIsScreenCaptureAllowedCommand = CreateDefaultAsyncRelayCommand(ToggleIsScreenCaptureAllowedAsync, CanExecuteIfInited, allowsMultipleExecutions: false);
ToggleShouldConnectToWatchCommand = CreateDefaultAsyncCommnad(ToggleShouldConnectToWatchAsync, () => _inited); ToggleShouldConnectToWatchCommand = CreateDefaultAsyncRelayCommand(ToggleShouldConnectToWatchAsync, CanExecuteIfInited, allowsMultipleExecutions: false);
ClearClipboardPickerViewModel = new PickerViewModel<int>( ClearClipboardPickerViewModel = new PickerViewModel<int>(
_deviceActionService, _deviceActionService,
_logger, _logger,
OnClearClipboardChangingAsync, OnClearClipboardChangingAsync,
AppResources.ClearClipboard, AppResources.ClearClipboard,
() => _inited, CanExecuteIfInited,
ex => HandleException(ex)); ex => HandleException(ex));
} }
private bool CanExecuteIfInited() => _inited;
public bool EnableSyncOnRefresh public bool EnableSyncOnRefresh
{ {
get => _syncOnRefresh; get => _syncOnRefresh;
@@ -103,9 +98,9 @@ namespace Bit.App.Pages
public bool CanToggleShouldConnectToWatch => ToggleShouldConnectToWatchCommand.CanExecute(null); public bool CanToggleShouldConnectToWatch => ToggleShouldConnectToWatchCommand.CanExecute(null);
public AsyncCommand SyncCommand { get; } public AsyncRelayCommand SyncCommand { get; }
public AsyncCommand ToggleIsScreenCaptureAllowedCommand { get; } public AsyncRelayCommand ToggleIsScreenCaptureAllowedCommand { get; }
public AsyncCommand ToggleShouldConnectToWatchCommand { get; } public AsyncRelayCommand ToggleShouldConnectToWatchCommand { get; }
public async Task InitAsync() public async Task InitAsync()
{ {
@@ -124,10 +119,10 @@ namespace Bit.App.Pages
{ {
TriggerPropertyChanged(nameof(IsScreenCaptureAllowed)); TriggerPropertyChanged(nameof(IsScreenCaptureAllowed));
TriggerPropertyChanged(nameof(ShouldConnectToWatch)); TriggerPropertyChanged(nameof(ShouldConnectToWatch));
SyncCommand.RaiseCanExecuteChanged(); SyncCommand.NotifyCanExecuteChanged();
ClearClipboardPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); ClearClipboardPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged();
ToggleIsScreenCaptureAllowedCommand.RaiseCanExecuteChanged(); ToggleIsScreenCaptureAllowedCommand.NotifyCanExecuteChanged();
ToggleShouldConnectToWatchCommand.RaiseCanExecuteChanged(); ToggleShouldConnectToWatchCommand.NotifyCanExecuteChanged();
}); });
} }
@@ -141,8 +136,7 @@ namespace Bit.App.Pages
[30] = AppResources.ThirtySeconds, [30] = AppResources.ThirtySeconds,
[60] = AppResources.OneMinute [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 (DeviceInfo.Platform != DevicePlatform.iOS)
if (Device.RuntimePlatform != Device.iOS)
{ {
clearClipboardOptions.Add(120, AppResources.TwoMinutes); clearClipboardOptions.Add(120, AppResources.TwoMinutes);
clearClipboardOptions.Add(300, AppResources.FiveMinutes); clearClipboardOptions.Add(300, AppResources.FiveMinutes);

View File

@@ -1,8 +1,4 @@
using System; using System.Windows.Input;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Pages.Accounts; using Bit.App.Pages.Accounts;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
@@ -12,10 +8,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -80,16 +73,16 @@ namespace Bit.App.Pages
() => _inited && !HasVaultTimeoutActionPolicy, () => _inited && !HasVaultTimeoutActionPolicy,
ex => HandleException(ex)); ex => HandleException(ex));
ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncCommnad(ToggleUseThisDeviceToApproveLoginRequestsAsync, () => _inited); ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncRelayCommand(ToggleUseThisDeviceToApproveLoginRequestsAsync, () => _inited, allowsMultipleExecutions: false);
GoToPendingLogInRequestsCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage()))); GoToPendingLogInRequestsCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage())), allowsMultipleExecutions: false);
ToggleCanUnlockWithBiometricsCommand = CreateDefaultAsyncCommnad(ToggleCanUnlockWithBiometricsAsync, () => _inited); ToggleCanUnlockWithBiometricsCommand = CreateDefaultAsyncRelayCommand(ToggleCanUnlockWithBiometricsAsync, () => _inited, allowsMultipleExecutions: false);
ToggleCanUnlockWithPinCommand = CreateDefaultAsyncCommnad(ToggleCanUnlockWithPinAsync, () => _inited); ToggleCanUnlockWithPinCommand = CreateDefaultAsyncRelayCommand(ToggleCanUnlockWithPinAsync, () => _inited, allowsMultipleExecutions: false);
ShowAccountFingerprintPhraseCommand = CreateDefaultAsyncCommnad(ShowAccountFingerprintPhraseAsync); ShowAccountFingerprintPhraseCommand = CreateDefaultAsyncRelayCommand(ShowAccountFingerprintPhraseAsync, allowsMultipleExecutions: false);
GoToTwoStepLoginCommand = CreateDefaultAsyncCommnad(() => GoToWebVaultSettingsAsync(AppResources.TwoStepLoginDescriptionLong, AppResources.ContinueToWebApp)); GoToTwoStepLoginCommand = CreateDefaultAsyncRelayCommand(() => GoToWebVaultSettingsAsync(AppResources.TwoStepLoginDescriptionLong, AppResources.ContinueToWebApp), allowsMultipleExecutions: false);
GoToChangeMasterPasswordCommand = CreateDefaultAsyncCommnad(() => GoToWebVaultSettingsAsync(AppResources.ChangeMasterPasswordDescriptionLong, AppResources.ContinueToWebApp)); GoToChangeMasterPasswordCommand = CreateDefaultAsyncRelayCommand(() => GoToWebVaultSettingsAsync(AppResources.ChangeMasterPasswordDescriptionLong, AppResources.ContinueToWebApp), allowsMultipleExecutions: false);
LockCommand = CreateDefaultAsyncCommnad(() => _vaultTimeoutService.LockAsync(true, true)); LockCommand = CreateDefaultAsyncRelayCommand(() => _vaultTimeoutService.LockAsync(true, true), allowsMultipleExecutions: false);
LogOutCommand = CreateDefaultAsyncCommnad(LogOutAsync); LogOutCommand = CreateDefaultAsyncRelayCommand(LogOutAsync, allowsMultipleExecutions: false);
DeleteAccountCommand = CreateDefaultAsyncCommnad(() => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()))); DeleteAccountCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage())), allowsMultipleExecutions: false);
} }
public bool UseThisDeviceToApproveLoginRequests public bool UseThisDeviceToApproveLoginRequests
@@ -114,8 +107,7 @@ namespace Bit.App.Pages
} }
var biometricName = AppResources.Biometrics; 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 (DeviceInfo.Platform == DevicePlatform.iOS)
if (Device.RuntimePlatform == Device.iOS)
{ {
biometricName = _deviceActionService.SupportsFaceBiometric() biometricName = _deviceActionService.SupportsFaceBiometric()
? AppResources.FaceID ? AppResources.FaceID
@@ -209,18 +201,17 @@ namespace Bit.App.Pages
private int? CurrentVaultTimeout => GetRawVaultTimeoutFrom(VaultTimeoutPickerViewModel.SelectedKey); 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 private bool IncludeLinksWithSubscriptionInfo => DeviceInfo.Platform != DevicePlatform.iOS;
Device.RuntimePlatform != Device.iOS;
private bool HasVaultTimeoutActionPolicy => !string.IsNullOrEmpty(_vaultTimeoutActionPolicy); private bool HasVaultTimeoutActionPolicy => !string.IsNullOrEmpty(_vaultTimeoutActionPolicy);
public PickerViewModel<int> VaultTimeoutPickerViewModel { get; } public PickerViewModel<int> VaultTimeoutPickerViewModel { get; }
public PickerViewModel<VaultTimeoutAction> VaultTimeoutActionPickerViewModel { get; } public PickerViewModel<VaultTimeoutAction> VaultTimeoutActionPickerViewModel { get; }
public AsyncCommand ToggleUseThisDeviceToApproveLoginRequestsCommand { get; } public AsyncRelayCommand ToggleUseThisDeviceToApproveLoginRequestsCommand { get; }
public ICommand GoToPendingLogInRequestsCommand { get; } public ICommand GoToPendingLogInRequestsCommand { get; }
public AsyncCommand ToggleCanUnlockWithBiometricsCommand { get; } public AsyncRelayCommand ToggleCanUnlockWithBiometricsCommand { get; }
public AsyncCommand ToggleCanUnlockWithPinCommand { get; } public AsyncRelayCommand ToggleCanUnlockWithPinCommand { get; }
public ICommand ShowAccountFingerprintPhraseCommand { get; } public ICommand ShowAccountFingerprintPhraseCommand { get; }
public ICommand GoToTwoStepLoginCommand { get; } public ICommand GoToTwoStepLoginCommand { get; }
public ICommand GoToChangeMasterPasswordCommand { get; } public ICommand GoToChangeMasterPasswordCommand { get; }
@@ -256,11 +247,11 @@ Device.RuntimePlatform != Device.iOS;
TriggerPropertyChanged(nameof(VaultTimeoutPolicyDescription)); TriggerPropertyChanged(nameof(VaultTimeoutPolicyDescription));
TriggerPropertyChanged(nameof(ShowChangeMasterPassword)); TriggerPropertyChanged(nameof(ShowChangeMasterPassword));
TriggerUpdateCustomVaultTimeoutPicker(); TriggerUpdateCustomVaultTimeoutPicker();
ToggleUseThisDeviceToApproveLoginRequestsCommand.RaiseCanExecuteChanged(); ToggleUseThisDeviceToApproveLoginRequestsCommand.NotifyCanExecuteChanged();
ToggleCanUnlockWithBiometricsCommand.RaiseCanExecuteChanged(); ToggleCanUnlockWithBiometricsCommand.NotifyCanExecuteChanged();
ToggleCanUnlockWithPinCommand.RaiseCanExecuteChanged(); ToggleCanUnlockWithPinCommand.NotifyCanExecuteChanged();
VaultTimeoutPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); VaultTimeoutPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged();
VaultTimeoutActionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged(); VaultTimeoutActionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged();
}); });
} }
@@ -275,7 +266,7 @@ Device.RuntimePlatform != Device.iOS;
_maximumVaultTimeoutPolicy = maximumVaultTimeoutPolicy?.GetInt(Policy.MINUTES_KEY); _maximumVaultTimeoutPolicy = maximumVaultTimeoutPolicy?.GetInt(Policy.MINUTES_KEY);
_vaultTimeoutActionPolicy = maximumVaultTimeoutPolicy?.GetString(Policy.ACTION_KEY); _vaultTimeoutActionPolicy = maximumVaultTimeoutPolicy?.GetString(Policy.ACTION_KEY);
MainThread.BeginInvokeOnMainThread(VaultTimeoutActionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged); MainThread.BeginInvokeOnMainThread(VaultTimeoutActionPickerViewModel.SelectOptionCommand.NotifyCanExecuteChanged);
} }
private async Task InitVaultTimeoutPickerAsync() private async Task InitVaultTimeoutPickerAsync()
@@ -368,10 +359,9 @@ Device.RuntimePlatform != Device.iOS;
return; 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 if (!_supportsBiometric
|| ||
!await _platformUtilsService.AuthenticateBiometricAsync(null, Device.RuntimePlatform == Device.Android ? "." : null)) !await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null))
{ {
_canUnlockWithBiometrics = false; _canUnlockWithBiometrics = false;
MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics))); MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics)));

View File

@@ -1,5 +1,6 @@
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using CommunityToolkit.Mvvm.Input;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -7,7 +8,7 @@ namespace Bit.App.Pages
{ {
public SettingsPageViewModel() public SettingsPageViewModel()
{ {
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), ExecuteSettingItemCommand = CreateDefaultAsyncRelayCommand<SettingsPageListItem>(item => item.ExecuteAsync(),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
@@ -24,7 +25,7 @@ namespace Bit.App.Pages
public List<SettingsPageListItem> SettingsItems { get; } public List<SettingsPageListItem> SettingsItems { get; }
public AsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; } public AsyncRelayCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
private async Task NavigateToAsync(Page page) private async Task NavigateToAsync(Page page)
{ {

View File

@@ -22,15 +22,15 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>(); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>(); _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), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); 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), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
GoToImportItemsCommand = new AsyncCommand(GoToImportItemsAsync, GoToImportItemsCommand = CreateDefaultAsyncRelayCommand(GoToImportItemsAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
} }

View File

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

View File

@@ -7,6 +7,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.App.Utilities; using Bit.App.Utilities;
using CommunityToolkit.Mvvm.Input;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -28,7 +29,7 @@ namespace Bit.App.Pages
_auditService = ServiceContainer.Resolve<IAuditService>("auditService"); _auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
CheckPasswordCommand = new AsyncCommand(CheckPasswordAsync, allowsMultipleExecutions: false); CheckPasswordCommand = CreateDefaultAsyncRelayCommand(CheckPasswordAsync, allowsMultipleExecutions: false);
} }
public CipherView Cipher 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 string CreationDate => string.Format(AppResources.CreatedXY, Cipher?.CreationDate.ToShortDateString(), Cipher?.CreationDate.ToShortTimeString());
public AsyncCommand CheckPasswordCommand { get; } public AsyncRelayCommand CheckPasswordCommand { get; }
protected async Task CheckPasswordAsync() protected async Task CheckPasswordAsync()
{ {

View File

@@ -16,6 +16,7 @@ using Bit.Core.Utilities;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui; using Microsoft.Maui;
using Bit.App.Utilities; using Bit.App.Utilities;
using CommunityToolkit.Mvvm.Input;
#nullable enable #nullable enable
@@ -99,8 +100,8 @@ namespace Bit.App.Pages
UriOptionsCommand = new Command<LoginUriView>(UriOptions); UriOptionsCommand = new Command<LoginUriView>(UriOptions);
FieldOptionsCommand = new Command<ICustomFieldItemViewModel>(FieldOptions); FieldOptionsCommand = new Command<ICustomFieldItemViewModel>(FieldOptions);
PasswordPromptHelpCommand = new Command(PasswordPromptHelp); PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
CopyCommand = new AsyncCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyCommand = CreateDefaultAsyncRelayCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
GenerateUsernameCommand = new AsyncCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false); GenerateUsernameCommand = CreateDefaultAsyncRelayCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false);
Uris = new ExtendedObservableCollection<LoginUriView>(); Uris = new ExtendedObservableCollection<LoginUriView>();
Fields = new ExtendedObservableCollection<ICustomFieldItemViewModel>(); Fields = new ExtendedObservableCollection<ICustomFieldItemViewModel>();
Collections = new ExtendedObservableCollection<CollectionViewModel>(); Collections = new ExtendedObservableCollection<CollectionViewModel>();
@@ -163,8 +164,8 @@ namespace Bit.App.Pages
public Command UriOptionsCommand { get; set; } public Command UriOptionsCommand { get; set; }
public Command FieldOptionsCommand { get; set; } public Command FieldOptionsCommand { get; set; }
public Command PasswordPromptHelpCommand { get; set; } public Command PasswordPromptHelpCommand { get; set; }
public AsyncCommand CopyCommand { get; set; } public AsyncRelayCommand CopyCommand { get; set; }
public AsyncCommand GenerateUsernameCommand { get; set; } public AsyncRelayCommand GenerateUsernameCommand { get; set; }
public string CipherId { get; set; } public string CipherId { get; set; }
public string OrganizationId { get; set; } public string OrganizationId { get; set; }
public string FolderId { get; set; } public string FolderId { get; set; }

View File

@@ -14,7 +14,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui; using Microsoft.Maui;
@@ -66,15 +66,15 @@ namespace Bit.App.Pages
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService"); _clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>(); _watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyCommand = CreateDefaultAsyncRelayCommand<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); CopyUriCommand = CreateDefaultAsyncRelayCommand<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); 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); 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); TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode); ToggleCardCodeCommand = new Command(ToggleCardCode);
DownloadAttachmentCommand = new AsyncCommand<AttachmentView>(DownloadAttachmentAsync, allowsMultipleExecutions: false); DownloadAttachmentCommand = CreateDefaultAsyncRelayCommand<AttachmentView>(DownloadAttachmentAsync, allowsMultipleExecutions: false);
PageTitle = AppResources.ViewItem; PageTitle = AppResources.ViewItem;
} }
@@ -87,7 +87,7 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; set; } public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { get; set; } public Command ToggleCardNumberCommand { get; set; }
public Command ToggleCardCodeCommand { get; set; } public Command ToggleCardCodeCommand { get; set; }
public AsyncCommand<AttachmentView> DownloadAttachmentCommand { get; set; } public AsyncRelayCommand<AttachmentView> DownloadAttachmentCommand { get; set; }
public string CipherId { get; set; } public string CipherId { get; set; }
protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[] protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[]
{ {

View File

@@ -45,13 +45,13 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>(); 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), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
SelectCipherCommand = new AsyncCommand<IGroupingsPageListItem>(SelectCipherAsync, SelectCipherCommand = CreateDefaultAsyncRelayCommand<IGroupingsPageListItem>(SelectCipherAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
AddCipherCommand = new AsyncCommand(AddCipherAsync, AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);

View File

@@ -52,10 +52,10 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
Ciphers = new ExtendedObservableCollection<CipherView>(); 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), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
AddCipherCommand = new AsyncCommand(AddCipherAsync, AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync,
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
} }

View File

@@ -5,7 +5,7 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.ApplicationModel; using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Microsoft.Maui; using Microsoft.Maui;
@@ -37,13 +37,13 @@ namespace Bit.App.Pages
Cipher = cipherView; Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled; WebsiteIconsEnabled = websiteIconsEnabled;
CopyCommand = new AsyncCommand(CopyToClipboardAsync, CopyCommand = CreateDefaultAsyncRelayCommand(CopyToClipboardAsync,
onException: ex => _logger.Value.Exception(ex), onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
_totpTickHelper = new TotpHelper(cipherView); _totpTickHelper = new TotpHelper(cipherView);
} }
public AsyncCommand CopyCommand { get; set; } public AsyncRelayCommand CopyCommand { get; set; }
public CipherView Cipher public CipherView Cipher
{ {

View File

@@ -71,10 +71,10 @@ namespace Bit.App.Pages
Refreshing = true; Refreshing = true;
await LoadAsync(); 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), onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, VaultFilterCommand = CreateDefaultAsyncRelayCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex), onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);

View File

@@ -20,7 +20,7 @@ namespace Bit.App.Pages
public ScanPageViewModel() public ScanPageViewModel()
{ {
ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException); ToggleScanModeCommand = CreateDefaultAsyncRelayCommand(ToggleScanMode, onException: HandleException);
StartCameraCommand = new Command(StartCamera); StartCameraCommand = new Command(StartCamera);
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");

View File

@@ -37,7 +37,7 @@ namespace Bit.App.Pages
OrganizationOptions = new List<KeyValuePair<string, string>>(); OrganizationOptions = new List<KeyValuePair<string, string>>();
PageTitle = AppResources.MoveToOrganization; 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; } public string CipherId { get; set; }

View File

@@ -25,7 +25,7 @@ namespace Bit.App.Pages
public VaultFilterViewModel() public VaultFilterViewModel()
{ {
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, VaultFilterCommand = CreateDefaultAsyncRelayCommand(VaultFilterOptionsAsync,
onException: ex => logger.Exception(ex), onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false); 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.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices; 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 namespace Bit.Core.Utilities
{ {
public abstract class ExtendedViewModel : INotifyPropertyChanged 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; 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, protected bool SetProperty<T>(ref T backingStore, T value, Action onChanged = null,
[CallerMemberName] string propertyName = "", string[] additionalPropertyNames = null) [CallerMemberName] string propertyName = "", string[] additionalPropertyNames = null)
{ {