From 8ec6545bbc4471c71891f31454256bcb7bad1acf Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Wed, 27 Jul 2022 17:46:56 +0100 Subject: [PATCH] [PS-1116] Improved network error handling (#2007) * PS-1116 Improved network error handling on ViewPageViewModel and AddEditPageViewModel * PS-1116 Renamed ViewPage and AddEditPage pages to a more explicit name. Refactored CheckPassword from the CipherPages to a single Base class. * PS-1116 Updated variables relative to the AddEditPage and ViewPage refactor to CipherAddEditPage and CipherDetailPage * Renamed CipherDetailPage to CipherDetailsPage * Code improvement * PS-1116 Improved code formatting * PS-1116 Improved formatting * PS-1116 Improved code formatting --- src/App/App.csproj | 8 +- src/App/App.xaml.cs | 10 +- .../Pages/Vault/AutofillCiphersPage.xaml.cs | 4 +- src/App/Pages/Vault/BaseCipherViewModel.cs | 76 +++++++ ...ddEditPage.xaml => CipherAddEditPage.xaml} | 8 +- ...Page.xaml.cs => CipherAddEditPage.xaml.cs} | 14 +- ...Model.cs => CipherAddEditPageViewModel.cs} | 78 ++----- .../{ViewPage.xaml => CipherDetailsPage.xaml} | 8 +- ...Page.xaml.cs => CipherDetailsPage.xaml.cs} | 20 +- ...Model.cs => CipherDetailsPageViewModel.cs} | 192 +++++++----------- src/App/Pages/Vault/CiphersPageViewModel.cs | 3 +- .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 6 +- .../GroupingsPage/GroupingsPageViewModel.cs | 2 +- src/App/Utilities/AppHelpers.cs | 6 +- 14 files changed, 211 insertions(+), 224 deletions(-) create mode 100644 src/App/Pages/Vault/BaseCipherViewModel.cs rename src/App/Pages/Vault/{AddEditPage.xaml => CipherAddEditPage.xaml} (99%) rename src/App/Pages/Vault/{AddEditPage.xaml.cs => CipherAddEditPage.xaml.cs} (97%) rename src/App/Pages/Vault/{AddEditPageViewModel.cs => CipherAddEditPageViewModel.cs} (92%) rename src/App/Pages/Vault/{ViewPage.xaml => CipherDetailsPage.xaml} (99%) rename src/App/Pages/Vault/{ViewPage.xaml.cs => CipherDetailsPage.xaml.cs} (93%) rename src/App/Pages/Vault/{ViewPageViewModel.cs => CipherDetailsPageViewModel.cs} (84%) diff --git a/src/App/App.csproj b/src/App/App.csproj index 25ac5fc6b..9e96c908d 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -97,11 +97,11 @@ PasswordHistoryPage.xaml - - AddEditPage.xaml + + CipherDetailsPage.xaml - - ViewPage.xaml + + CipherAddEditPage.xaml SettingsPage.xaml diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 8d2b74538..6870f20f2 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -330,20 +330,20 @@ namespace Bit.App var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1]; if (topPage is NavigationPage navPage) { - if (navPage.CurrentPage is ViewPage viewPage) + if (navPage.CurrentPage is CipherDetailsPage cipherDetailsPage) { lastPageBeforeLock = new PreviousPageInfo { Page = "view", - CipherId = viewPage.ViewModel.CipherId + CipherId = cipherDetailsPage.ViewModel.CipherId }; } - else if (navPage.CurrentPage is AddEditPage addEditPage && addEditPage.ViewModel.EditMode) + else if (navPage.CurrentPage is CipherAddEditPage cipherAddEditPage && cipherAddEditPage.ViewModel.EditMode) { lastPageBeforeLock = new PreviousPageInfo { Page = "edit", - CipherId = addEditPage.ViewModel.CipherId + CipherId = cipherAddEditPage.ViewModel.CipherId }; } } @@ -378,7 +378,7 @@ namespace Bit.App Current.MainPage = new TabsPage(Options); break; case NavigationTarget.AddEditCipher: - Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options)); + Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options)); break; case NavigationTarget.AutofillCiphers: Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options)); diff --git a/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs b/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs index 35b85fc9c..2d009a18e 100644 --- a/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs +++ b/src/App/Pages/Vault/AutofillCiphersPage.xaml.cs @@ -137,11 +137,11 @@ namespace Bit.App.Pages } if (_appOptions.FillType.HasValue && _appOptions.FillType != CipherType.Login) { - var pageForOther = new AddEditPage(type: _appOptions.FillType, fromAutofill: true); + var pageForOther = new CipherAddEditPage(type: _appOptions.FillType, fromAutofill: true); await Navigation.PushModalAsync(new NavigationPage(pageForOther)); return; } - var pageForLogin = new AddEditPage(null, CipherType.Login, uri: _vm.Uri, name: _vm.Name, + var pageForLogin = new CipherAddEditPage(null, CipherType.Login, uri: _vm.Uri, name: _vm.Name, fromAutofill: true); await Navigation.PushModalAsync(new NavigationPage(pageForLogin)); } diff --git a/src/App/Pages/Vault/BaseCipherViewModel.cs b/src/App/Pages/Vault/BaseCipherViewModel.cs new file mode 100644 index 000000000..070c52402 --- /dev/null +++ b/src/App/Pages/Vault/BaseCipherViewModel.cs @@ -0,0 +1,76 @@ +using System; +using System.Threading.Tasks; +using Bit.App.Abstractions; +using Bit.App.Resources; +using Bit.Core.Abstractions; +using Bit.Core.Exceptions; +using Bit.Core.Models.View; +using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; + +namespace Bit.App.Pages +{ + public abstract class BaseCipherViewModel : BaseViewModel + { + private readonly IAuditService _auditService; + protected readonly IDeviceActionService _deviceActionService; + protected readonly ILogger _logger; + protected readonly IPlatformUtilsService _platformUtilsService; + private CipherView _cipher; + protected abstract string[] AdditionalPropertiesToRaiseOnCipherChanged { get; } + + public BaseCipherViewModel() + { + _deviceActionService = ServiceContainer.Resolve("deviceActionService"); + _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + _auditService = ServiceContainer.Resolve("auditService"); + _logger = ServiceContainer.Resolve("logger"); + + CheckPasswordCommand = new AsyncCommand(CheckPasswordAsync, allowsMultipleExecutions: false); + } + + public CipherView Cipher + { + get => _cipher; + set => SetProperty(ref _cipher, value, additionalPropertyNames: AdditionalPropertiesToRaiseOnCipherChanged); + } + + public AsyncCommand CheckPasswordCommand { get; } + + protected async Task CheckPasswordAsync() + { + try + { + if (string.IsNullOrWhiteSpace(Cipher?.Login?.Password)) + { + return; + } + + await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword); + var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password); + await _deviceActionService.HideLoadingAsync(); + + await _platformUtilsService.ShowDialogAsync(matches > 0 + ? string.Format(AppResources.PasswordExposed, matches.ToString("N0")) + : AppResources.PasswordSafe); + } + catch (ApiException apiException) + { + _logger.Exception(apiException); + await _deviceActionService.HideLoadingAsync(); + if (apiException?.Error != null) + { + await _platformUtilsService.ShowDialogAsync(apiException.Error.GetSingleMessage(), + AppResources.AnErrorHasOccurred); + } + } + catch (Exception ex) + { + _logger.Exception(ex); + await _deviceActionService.HideLoadingAsync(); + await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred); + } + } + } +} + diff --git a/src/App/Pages/Vault/AddEditPage.xaml b/src/App/Pages/Vault/CipherAddEditPage.xaml similarity index 99% rename from src/App/Pages/Vault/AddEditPage.xaml rename to src/App/Pages/Vault/CipherAddEditPage.xaml index 220deb5ea..37855416f 100644 --- a/src/App/Pages/Vault/AddEditPage.xaml +++ b/src/App/Pages/Vault/CipherAddEditPage.xaml @@ -2,7 +2,7 @@ - + @@ -608,7 +608,7 @@ - + diff --git a/src/App/Pages/Vault/AddEditPage.xaml.cs b/src/App/Pages/Vault/CipherAddEditPage.xaml.cs similarity index 97% rename from src/App/Pages/Vault/AddEditPage.xaml.cs rename to src/App/Pages/Vault/CipherAddEditPage.xaml.cs index 106ac7057..d84d4a733 100644 --- a/src/App/Pages/Vault/AddEditPage.xaml.cs +++ b/src/App/Pages/Vault/CipherAddEditPage.xaml.cs @@ -14,7 +14,7 @@ using Xamarin.Forms.PlatformConfiguration.iOSSpecific; namespace Bit.App.Pages { - public partial class AddEditPage : BaseContentPage + public partial class CipherAddEditPage : BaseContentPage { private readonly AppOptions _appOptions; private readonly IStateService _stateService; @@ -22,10 +22,10 @@ namespace Bit.App.Pages private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IKeyConnectorService _keyConnectorService; - private AddEditPageViewModel _vm; + private CipherAddEditPageViewModel _vm; private bool _fromAutofill; - public AddEditPage( + public CipherAddEditPage( string cipherId = null, CipherType? type = null, string folderId = null, @@ -36,7 +36,7 @@ namespace Bit.App.Pages bool fromAutofill = false, AppOptions appOptions = null, bool cloneMode = false, - ViewPage viewPage = null) + CipherDetailsPage cipherDetailsPage = null) { _stateService = ServiceContainer.Resolve("stateService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); @@ -47,7 +47,7 @@ namespace Bit.App.Pages _fromAutofill = fromAutofill; FromAutofillFramework = _appOptions?.FromAutofillFramework ?? false; InitializeComponent(); - _vm = BindingContext as AddEditPageViewModel; + _vm = BindingContext as CipherAddEditPageViewModel; _vm.Page = this; _vm.CipherId = cipherId; _vm.FolderId = folderId == "none" ? null : folderId; @@ -57,7 +57,7 @@ namespace Bit.App.Pages _vm.DefaultName = name ?? appOptions?.SaveName; _vm.DefaultUri = uri ?? appOptions?.Uri; _vm.CloneMode = cloneMode; - _vm.ViewPage = viewPage; + _vm.CipherDetailsPage = cipherDetailsPage; _vm.Init(); SetActivityIndicator(); if (_vm.EditMode && !_vm.CloneMode && Device.RuntimePlatform == Device.Android) @@ -145,7 +145,7 @@ namespace Bit.App.Pages } public bool FromAutofillFramework { get; set; } - public AddEditPageViewModel ViewModel => _vm; + public CipherAddEditPageViewModel ViewModel => _vm; protected override async void OnAppearing() { diff --git a/src/App/Pages/Vault/AddEditPageViewModel.cs b/src/App/Pages/Vault/CipherAddEditPageViewModel.cs similarity index 92% rename from src/App/Pages/Vault/AddEditPageViewModel.cs rename to src/App/Pages/Vault/CipherAddEditPageViewModel.cs index 240b92361..6d6fc9ae6 100644 --- a/src/App/Pages/Vault/AddEditPageViewModel.cs +++ b/src/App/Pages/Vault/CipherAddEditPageViewModel.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Bit.App.Abstractions; using Bit.App.Models; using Bit.App.Resources; using Bit.Core; @@ -15,22 +14,17 @@ using Xamarin.Forms; namespace Bit.App.Pages { - public class AddEditPageViewModel : BaseViewModel + public class CipherAddEditPageViewModel : BaseCipherViewModel { - private readonly IDeviceActionService _deviceActionService; private readonly ICipherService _cipherService; private readonly IFolderService _folderService; private readonly ICollectionService _collectionService; private readonly IStateService _stateService; private readonly IOrganizationService _organizationService; - private readonly IPlatformUtilsService _platformUtilsService; - private readonly IAuditService _auditService; private readonly IMessagingService _messagingService; private readonly IEventService _eventService; private readonly IPolicyService _policyService; - private readonly ILogger _logger; - private CipherView _cipher; private bool _showNotesSeparator; private bool _showPassword; private bool _showCardNumber; @@ -44,7 +38,7 @@ namespace Bit.App.Pages private bool _hasCollections; private string _previousCipherId; private List _writeableCollections; - private string[] _additionalCipherProperties = new string[] + protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[] { nameof(IsLogin), nameof(IsIdentity), @@ -54,6 +48,7 @@ namespace Bit.App.Pages nameof(ShowAttachments), nameof(ShowCollections), }; + private List> _matchDetectionOptions = new List> { @@ -66,31 +61,26 @@ namespace Bit.App.Pages new KeyValuePair(UriMatchType.Never, AppResources.Never) }; - public AddEditPageViewModel() + public CipherAddEditPageViewModel() { - _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _cipherService = ServiceContainer.Resolve("cipherService"); _folderService = ServiceContainer.Resolve("folderService"); _stateService = ServiceContainer.Resolve("stateService"); _organizationService = ServiceContainer.Resolve("organizationService"); - _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _auditService = ServiceContainer.Resolve("auditService"); _messagingService = ServiceContainer.Resolve("messagingService"); _collectionService = ServiceContainer.Resolve("collectionService"); _eventService = ServiceContainer.Resolve("eventService"); _policyService = ServiceContainer.Resolve("policyService"); - _logger = ServiceContainer.Resolve("logger"); GeneratePasswordCommand = new Command(GeneratePassword); TogglePasswordCommand = new Command(TogglePassword); ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardCodeCommand = new Command(ToggleCardCode); - CheckPasswordCommand = new Command(CheckPasswordAsync); UriOptionsCommand = new Command(UriOptions); - FieldOptionsCommand = new Command(FieldOptions); + FieldOptionsCommand = new Command(FieldOptions); PasswordPromptHelpCommand = new Command(PasswordPromptHelp); Uris = new ExtendedObservableCollection(); - Fields = new ExtendedObservableCollection(); + Fields = new ExtendedObservableCollection(); Collections = new ExtendedObservableCollection(); AllowPersonal = true; @@ -146,7 +136,6 @@ namespace Bit.App.Pages public Command TogglePasswordCommand { get; set; } public Command ToggleCardNumberCommand { get; set; } public Command ToggleCardCodeCommand { get; set; } - public Command CheckPasswordCommand { get; set; } public Command UriOptionsCommand { get; set; } public Command FieldOptionsCommand { get; set; } public Command PasswordPromptHelpCommand { get; set; } @@ -164,7 +153,7 @@ namespace Bit.App.Pages public List> FolderOptions { get; set; } public List> OwnershipOptions { get; set; } public ExtendedObservableCollection Uris { get; set; } - public ExtendedObservableCollection Fields { get; set; } + public ExtendedObservableCollection Fields { get; set; } public ExtendedObservableCollection Collections { get; set; } public int TypeSelectedIndex @@ -233,11 +222,6 @@ namespace Bit.App.Pages } } } - public CipherView Cipher - { - get => _cipher; - set => SetProperty(ref _cipher, value, additionalPropertyNames: _additionalCipherProperties); - } public bool ShowNotesSeparator { get => _showNotesSeparator; @@ -285,7 +269,7 @@ namespace Bit.App.Pages public bool ShowOwnershipOptions => !EditMode || CloneMode; public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal; public bool CloneMode { get; set; } - public ViewPage ViewPage { get; set; } + public CipherDetailsPage CipherDetailsPage { get; set; } public bool IsLogin => Cipher?.Type == CipherType.Login; public bool IsIdentity => Cipher?.Type == CipherType.Identity; public bool IsCard => Cipher?.Type == CipherType.Card; @@ -421,7 +405,7 @@ namespace Bit.App.Pages } if (Cipher.Fields != null) { - Fields.ResetWithRange(Cipher.Fields?.Select(f => new AddEditPageFieldViewModel(Cipher, f))); + Fields.ResetWithRange(Cipher.Fields?.Select(f => new CipherAddEditPageFieldViewModel(Cipher, f))); } } @@ -509,7 +493,7 @@ namespace Bit.App.Pages EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated); _messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id); - if (Page is AddEditPage page && page.FromAutofillFramework) + if (Page is CipherAddEditPage page && page.FromAutofillFramework) { // Close and go back to app _deviceActionService.CloseAutofill(); @@ -518,7 +502,7 @@ namespace Bit.App.Pages { if (CloneMode) { - ViewPage?.UpdateCipherId(this.Cipher.Id); + CipherDetailsPage?.UpdateCipherId(this.Cipher.Id); } // if the app is tombstoned then PopModalAsync would throw index out of bounds if (Page.Navigation?.ModalStack?.Count > 0) @@ -603,7 +587,7 @@ namespace Bit.App.Pages public async void UriOptions(LoginUriView uri) { - if (!(Page as AddEditPage).DoOnce()) + if (!(Page as CipherAddEditPage).DoOnce()) { return; } @@ -639,9 +623,9 @@ namespace Bit.App.Pages Uris.Add(new LoginUriView()); } - public async void FieldOptions(AddEditPageFieldViewModel field) + public async void FieldOptions(CipherAddEditPageFieldViewModel field) { - if (!(Page as AddEditPage).DoOnce()) + if (!(Page as CipherAddEditPage).DoOnce()) { return; } @@ -701,10 +685,10 @@ namespace Bit.App.Pages } if (Fields == null) { - Fields = new ExtendedObservableCollection(); + Fields = new ExtendedObservableCollection(); } var type = fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key; - Fields.Add(new AddEditPageFieldViewModel(Cipher, new FieldView + Fields.Add(new CipherAddEditPageFieldViewModel(Cipher, new FieldView { Type = type, Name = string.IsNullOrWhiteSpace(name) ? null : name, @@ -832,35 +816,11 @@ namespace Bit.App.Pages private void TriggerCipherChanged() { - TriggerPropertyChanged(nameof(Cipher), _additionalCipherProperties); - } - - private async void CheckPasswordAsync() - { - if (!(Page as BaseContentPage).DoOnce()) - { - return; - } - if (string.IsNullOrWhiteSpace(Cipher.Login?.Password)) - { - return; - } - await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword); - var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password); - await _deviceActionService.HideLoadingAsync(); - if (matches > 0) - { - await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.PasswordExposed, - matches.ToString("N0"))); - } - else - { - await _platformUtilsService.ShowDialogAsync(AppResources.PasswordSafe); - } + TriggerPropertyChanged(nameof(Cipher), AdditionalPropertiesToRaiseOnCipherChanged); } } - public class AddEditPageFieldViewModel : ExtendedViewModel + public class CipherAddEditPageFieldViewModel : ExtendedViewModel { private II18nService _i18nService; private FieldView _field; @@ -876,7 +836,7 @@ namespace Bit.App.Pages nameof(IsLinkedType), }; - public AddEditPageFieldViewModel(CipherView cipher, FieldView field) + public CipherAddEditPageFieldViewModel(CipherView cipher, FieldView field) { _i18nService = ServiceContainer.Resolve("i18nService"); _cipher = cipher; diff --git a/src/App/Pages/Vault/ViewPage.xaml b/src/App/Pages/Vault/CipherDetailsPage.xaml similarity index 99% rename from src/App/Pages/Vault/ViewPage.xaml rename to src/App/Pages/Vault/CipherDetailsPage.xaml index a5512fd7d..53f1ca519 100644 --- a/src/App/Pages/Vault/ViewPage.xaml +++ b/src/App/Pages/Vault/CipherDetailsPage.xaml @@ -2,18 +2,18 @@ - + @@ -540,7 +540,7 @@ - + diff --git a/src/App/Pages/Vault/ViewPage.xaml.cs b/src/App/Pages/Vault/CipherDetailsPage.xaml.cs similarity index 93% rename from src/App/Pages/Vault/ViewPage.xaml.cs rename to src/App/Pages/Vault/CipherDetailsPage.xaml.cs index 93e8b5eff..5465d601a 100644 --- a/src/App/Pages/Vault/ViewPage.xaml.cs +++ b/src/App/Pages/Vault/CipherDetailsPage.xaml.cs @@ -9,18 +9,18 @@ using Xamarin.Forms; namespace Bit.App.Pages { - public partial class ViewPage : BaseContentPage + public partial class CipherDetailsPage : BaseContentPage { private readonly IBroadcasterService _broadcasterService; private readonly ISyncService _syncService; - private ViewPageViewModel _vm; + private CipherDetailsPageViewModel _vm; - public ViewPage(string cipherId) + public CipherDetailsPage(string cipherId) { InitializeComponent(); _broadcasterService = ServiceContainer.Resolve("broadcasterService"); _syncService = ServiceContainer.Resolve("syncService"); - _vm = BindingContext as ViewPageViewModel; + _vm = BindingContext as CipherDetailsPageViewModel; _vm.Page = this; _vm.CipherId = cipherId; SetActivityIndicator(_mainContent); @@ -40,7 +40,7 @@ namespace Bit.App.Pages } } - public ViewPageViewModel ViewModel => _vm; + public CipherDetailsPageViewModel ViewModel => _vm; public void UpdateCipherId(string cipherId) { @@ -55,7 +55,7 @@ namespace Bit.App.Pages IsBusy = true; } - _broadcasterService.Subscribe(nameof(ViewPage), async (message) => + _broadcasterService.Subscribe(nameof(CipherDetailsPage), async (message) => { try { @@ -111,7 +111,7 @@ namespace Bit.App.Pages { base.OnDisappearing(); IsBusy = false; - _broadcasterService.Unsubscribe(nameof(ViewPage)); + _broadcasterService.Unsubscribe(nameof(CipherDetailsPage)); _vm.CleanUp(); } @@ -140,7 +140,7 @@ namespace Bit.App.Pages { return; } - await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_vm.CipherId))); + await Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(_vm.CipherId))); } } } @@ -212,7 +212,7 @@ namespace Bit.App.Pages { return; } - var page = new AddEditPage(_vm.CipherId, cloneMode: true, viewPage: this); + var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this); await Navigation.PushModalAsync(new NavigationPage(page)); } } @@ -267,7 +267,7 @@ namespace Bit.App.Pages } else if (selection == AppResources.Clone) { - var page = new AddEditPage(_vm.CipherId, cloneMode: true, viewPage: this); + var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this); await Navigation.PushModalAsync(new NavigationPage(page)); } } diff --git a/src/App/Pages/Vault/ViewPageViewModel.cs b/src/App/Pages/Vault/CipherDetailsPageViewModel.cs similarity index 84% rename from src/App/Pages/Vault/ViewPageViewModel.cs rename to src/App/Pages/Vault/CipherDetailsPageViewModel.cs index 3c7ebce38..15e238414 100644 --- a/src/App/Pages/Vault/ViewPageViewModel.cs +++ b/src/App/Pages/Vault/CipherDetailsPageViewModel.cs @@ -17,23 +17,18 @@ using Xamarin.Forms; namespace Bit.App.Pages { - public class ViewPageViewModel : BaseViewModel + public class CipherDetailsPageViewModel : BaseCipherViewModel { - private readonly IDeviceActionService _deviceActionService; private readonly ICipherService _cipherService; private readonly IStateService _stateService; private readonly ITotpService _totpService; - private readonly IPlatformUtilsService _platformUtilsService; - private readonly IAuditService _auditService; private readonly IMessagingService _messagingService; private readonly IEventService _eventService; private readonly IPasswordRepromptService _passwordRepromptService; private readonly ILocalizeService _localizeService; private readonly IClipboardService _clipboardService; - private readonly ILogger _logger; - private CipherView _cipher; - private List _fields; + private List _fields; private bool _canAccessPremium; private bool _showPassword; private bool _showCardNumber; @@ -48,20 +43,16 @@ namespace Bit.App.Pages private string _attachmentFilename; private bool _passwordReprompted; - public ViewPageViewModel() + public CipherDetailsPageViewModel() { - _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _cipherService = ServiceContainer.Resolve("cipherService"); _stateService = ServiceContainer.Resolve("stateService"); _totpService = ServiceContainer.Resolve("totpService"); - _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); - _auditService = ServiceContainer.Resolve("auditService"); _messagingService = ServiceContainer.Resolve("messagingService"); _eventService = ServiceContainer.Resolve("eventService"); _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); _localizeService = ServiceContainer.Resolve("localizeService"); _clipboardService = ServiceContainer.Resolve("clipboardService"); - _logger = ServiceContainer.Resolve("logger"); CopyCommand = new AsyncCommand((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyUriCommand = new AsyncCommand(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); @@ -70,8 +61,7 @@ namespace Bit.App.Pages TogglePasswordCommand = new Command(TogglePassword); ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardCodeCommand = new Command(ToggleCardCode); - CheckPasswordCommand = new Command(CheckPasswordAsync); - DownloadAttachmentCommand = new Command(DownloadAttachmentAsync); + DownloadAttachmentCommand = new AsyncCommand(DownloadAttachmentAsync, allowsMultipleExecutions: false); PageTitle = AppResources.ViewItem; } @@ -83,32 +73,26 @@ namespace Bit.App.Pages public Command TogglePasswordCommand { get; set; } public Command ToggleCardNumberCommand { get; set; } public Command ToggleCardCodeCommand { get; set; } - public Command CheckPasswordCommand { get; set; } - public Command DownloadAttachmentCommand { get; set; } + public AsyncCommand DownloadAttachmentCommand { get; set; } public string CipherId { get; set; } - public CipherView Cipher + protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[] { - get => _cipher; - set => SetProperty(ref _cipher, value, - additionalPropertyNames: new string[] - { - nameof(IsLogin), - nameof(IsIdentity), - nameof(IsCard), - nameof(IsSecureNote), - nameof(ShowUris), - nameof(ShowAttachments), - nameof(ShowTotp), - nameof(ColoredPassword), - nameof(UpdatedText), - nameof(PasswordUpdatedText), - nameof(PasswordHistoryText), - nameof(ShowIdentityAddress), - nameof(IsDeleted), - nameof(CanEdit), - }); - } - public List Fields + nameof(IsLogin), + nameof(IsIdentity), + nameof(IsCard), + nameof(IsSecureNote), + nameof(ShowUris), + nameof(ShowAttachments), + nameof(ShowTotp), + nameof(ColoredPassword), + nameof(UpdatedText), + nameof(PasswordUpdatedText), + nameof(PasswordHistoryText), + nameof(ShowIdentityAddress), + nameof(IsDeleted), + nameof(CanEdit), + }; + public List Fields { get => _fields; set => SetProperty(ref _fields, value); @@ -256,7 +240,7 @@ namespace Bit.App.Pages } Cipher = await cipher.DecryptAsync(); CanAccessPremium = await _stateService.CanAccessPremiumAsync(); - Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(this, Cipher, f)).ToList(); + Fields = Cipher.Fields?.Select(f => new CipherDetailsPageFieldViewModel(this, Cipher, f)).ToList(); if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) && (Cipher.OrganizationUseTotp || CanAccessPremium)) @@ -455,86 +439,52 @@ namespace Bit.App.Pages } } - private async void CheckPasswordAsync() + private async Task DownloadAttachmentAsync(AttachmentView attachment) { - if (!(Page as BaseContentPage).DoOnce()) - { - return; - } - if (string.IsNullOrWhiteSpace(Cipher.Login?.Password)) - { - return; - } - if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) - { - await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, - AppResources.InternetConnectionRequiredTitle); - return; - } - await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword); - var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password); - await _deviceActionService.HideLoadingAsync(); - if (matches > 0) - { - await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.PasswordExposed, - matches.ToString("N0"))); - } - else - { - await _platformUtilsService.ShowDialogAsync(AppResources.PasswordSafe); - } - } - - private async void DownloadAttachmentAsync(AttachmentView attachment) - { - if (!(Page as BaseContentPage).DoOnce()) - { - return; - } - if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) - { - await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, - AppResources.InternetConnectionRequiredTitle); - return; - } - if (Cipher.OrganizationId == null && !CanAccessPremium) - { - await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired); - return; - } - if (attachment.FileSize >= 10485760) // 10 MB - { - var confirmed = await _platformUtilsService.ShowDialogAsync( - string.Format(AppResources.AttachmentLargeWarning, attachment.SizeName), null, - AppResources.Yes, AppResources.No); - if (!confirmed) - { - return; - } - } - - var canOpenFile = true; - if (!_deviceActionService.CanOpenFile(attachment.FileName)) - { - if (Device.RuntimePlatform == Device.iOS) - { - // iOS is currently hardcoded to always return CanOpenFile == true, but should it ever return false - // for any reason we want to be sure to catch it here. - await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile); - return; - } - - canOpenFile = false; - } - - if (!await PromptPasswordAsync()) - { - return; - } - - await _deviceActionService.ShowLoadingAsync(AppResources.Downloading); try { + if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None) + { + await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, + AppResources.InternetConnectionRequiredTitle); + return; + } + if (Cipher.OrganizationId == null && !CanAccessPremium) + { + await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired); + return; + } + if (attachment.FileSize >= 10485760) // 10 MB + { + var confirmed = await _platformUtilsService.ShowDialogAsync( + string.Format(AppResources.AttachmentLargeWarning, attachment.SizeName), null, + AppResources.Yes, AppResources.No); + if (!confirmed) + { + return; + } + } + + var canOpenFile = true; + if (!_deviceActionService.CanOpenFile(attachment.FileName)) + { + if (Device.RuntimePlatform == Device.iOS) + { + // iOS is currently hardcoded to always return CanOpenFile == true, but should it ever return false + // for any reason we want to be sure to catch it here. + await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile); + return; + } + + canOpenFile = false; + } + + if (!await PromptPasswordAsync()) + { + return; + } + + await _deviceActionService.ShowLoadingAsync(AppResources.Downloading); var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId); await _deviceActionService.HideLoadingAsync(); if (data == null) @@ -561,9 +511,11 @@ namespace Bit.App.Pages OpenAttachment(data, attachment); } } - catch + catch (Exception ex) { + _logger.Exception(ex); await _deviceActionService.HideLoadingAsync(); + await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred); } } @@ -703,15 +655,15 @@ namespace Bit.App.Pages } } - public class ViewPageFieldViewModel : ExtendedViewModel + public class CipherDetailsPageFieldViewModel : ExtendedViewModel { private II18nService _i18nService; - private ViewPageViewModel _vm; + private CipherDetailsPageViewModel _vm; private FieldView _field; private CipherView _cipher; private bool _showHiddenValue; - public ViewPageFieldViewModel(ViewPageViewModel vm, CipherView cipher, FieldView field) + public CipherDetailsPageFieldViewModel(CipherDetailsPageViewModel vm, CipherView cipher, FieldView field) { _i18nService = ServiceContainer.Resolve("i18nService"); _vm = vm; diff --git a/src/App/Pages/Vault/CiphersPageViewModel.cs b/src/App/Pages/Vault/CiphersPageViewModel.cs index c1c8fcd03..bbcc89d9f 100644 --- a/src/App/Pages/Vault/CiphersPageViewModel.cs +++ b/src/App/Pages/Vault/CiphersPageViewModel.cs @@ -10,7 +10,6 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.View; using Bit.Core.Utilities; -using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; namespace Bit.App.Pages @@ -157,7 +156,7 @@ namespace Bit.App.Pages } if (selection == AppResources.View || string.IsNullOrWhiteSpace(AutofillUrl)) { - var page = new ViewPage(cipher.Id); + var page = new CipherDetailsPage(cipher.Id); await Page.Navigation.PushModalAsync(new NavigationPage(page)); } else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave) diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs index 29ae16989..325c6dac6 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml.cs @@ -271,7 +271,7 @@ namespace Bit.App.Pages } if (!_vm.Deleted && DoOnce()) { - var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId()); + var page = new CipherAddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId()); await Navigation.PushModalAsync(new NavigationPage(page)); } } @@ -285,11 +285,11 @@ namespace Bit.App.Pages await _accountListOverlay.HideAsync(); if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId)) { - await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId))); + await Navigation.PushModalAsync(new NavigationPage(new CipherDetailsPage(_previousPage.CipherId))); } else if (_previousPage.Page == "edit" && !string.IsNullOrWhiteSpace(_previousPage.CipherId)) { - await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_previousPage.CipherId))); + await Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(_previousPage.CipherId))); } _previousPage = null; } diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 84f42e67f..87f7d6212 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -378,7 +378,7 @@ namespace Bit.App.Pages public async Task SelectCipherAsync(CipherView cipher) { - var page = new ViewPage(cipher.Id); + var page = new CipherDetailsPage(cipher.Id); await Page.Navigation.PushModalAsync(new NavigationPage(page)); } diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index fd61c4320..fbec32d4a 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -85,13 +85,13 @@ namespace Bit.App.Utilities } else if (selection == AppResources.View) { - await page.Navigation.PushModalAsync(new NavigationPage(new ViewPage(cipher.Id))); + await page.Navigation.PushModalAsync(new NavigationPage(new CipherDetailsPage(cipher.Id))); } else if (selection == AppResources.Edit) { if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync()) { - await page.Navigation.PushModalAsync(new NavigationPage(new AddEditPage(cipher.Id))); + await page.Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(cipher.Id))); } } else if (selection == AppResources.CopyUsername) @@ -427,7 +427,7 @@ namespace Bit.App.Utilities { if (appOptions.FromAutofillFramework && appOptions.SaveType.HasValue) { - Application.Current.MainPage = new NavigationPage(new AddEditPage(appOptions: appOptions)); + Application.Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: appOptions)); return true; } if (appOptions.Uri != null)