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)