1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-11-23 11:45:38 +01:00

[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
This commit is contained in:
aj-rosado 2022-07-27 17:46:56 +01:00 committed by GitHub
parent 90a6850d76
commit 8ec6545bbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 211 additions and 224 deletions

View File

@ -97,11 +97,11 @@
<Compile Update="Pages\Vault\PasswordHistoryPage.xaml.cs"> <Compile Update="Pages\Vault\PasswordHistoryPage.xaml.cs">
<DependentUpon>PasswordHistoryPage.xaml</DependentUpon> <DependentUpon>PasswordHistoryPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Vault\AddEditPage.xaml.cs"> <Compile Update="Pages\Vault\CipherDetailsPage.xaml.cs">
<DependentUpon>AddEditPage.xaml</DependentUpon> <DependentUpon>CipherDetailsPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Vault\ViewPage.xaml.cs"> <Compile Update="Pages\Vault\CipherAddEditPage.xaml.cs">
<DependentUpon>ViewPage.xaml</DependentUpon> <DependentUpon>CipherAddEditPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Settings\SettingsPage\SettingsPage.xaml.cs"> <Compile Update="Pages\Settings\SettingsPage\SettingsPage.xaml.cs">
<DependentUpon>SettingsPage.xaml</DependentUpon> <DependentUpon>SettingsPage.xaml</DependentUpon>

View File

@ -330,20 +330,20 @@ namespace Bit.App
var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1]; var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1];
if (topPage is NavigationPage navPage) if (topPage is NavigationPage navPage)
{ {
if (navPage.CurrentPage is ViewPage viewPage) if (navPage.CurrentPage is CipherDetailsPage cipherDetailsPage)
{ {
lastPageBeforeLock = new PreviousPageInfo lastPageBeforeLock = new PreviousPageInfo
{ {
Page = "view", 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 lastPageBeforeLock = new PreviousPageInfo
{ {
Page = "edit", Page = "edit",
CipherId = addEditPage.ViewModel.CipherId CipherId = cipherAddEditPage.ViewModel.CipherId
}; };
} }
} }
@ -378,7 +378,7 @@ namespace Bit.App
Current.MainPage = new TabsPage(Options); Current.MainPage = new TabsPage(Options);
break; break;
case NavigationTarget.AddEditCipher: case NavigationTarget.AddEditCipher:
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options)); Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
break; break;
case NavigationTarget.AutofillCiphers: case NavigationTarget.AutofillCiphers:
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options)); Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));

View File

@ -137,11 +137,11 @@ namespace Bit.App.Pages
} }
if (_appOptions.FillType.HasValue && _appOptions.FillType != CipherType.Login) 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)); await Navigation.PushModalAsync(new NavigationPage(pageForOther));
return; 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); fromAutofill: true);
await Navigation.PushModalAsync(new NavigationPage(pageForLogin)); await Navigation.PushModalAsync(new NavigationPage(pageForLogin));
} }

View File

@ -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<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_logger = ServiceContainer.Resolve<ILogger>("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);
}
}
}
}

View File

@ -2,7 +2,7 @@
<pages:BaseContentPage <pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.AddEditPage" x:Class="Bit.App.Pages.CipherAddEditPage"
xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
@ -10,11 +10,11 @@
xmlns:behaviors="clr-namespace:Bit.App.Behaviors" xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
xmlns:effects="clr-namespace:Bit.App.Effects" xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore" xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:AddEditPageViewModel" x:DataType="pages:CipherAddEditPageViewModel"
x:Name="_page" x:Name="_page"
Title="{Binding PageTitle}"> Title="{Binding PageTitle}">
<ContentPage.BindingContext> <ContentPage.BindingContext>
<pages:AddEditPageViewModel /> <pages:CipherAddEditPageViewModel />
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
@ -608,7 +608,7 @@
</StackLayout> </StackLayout>
<controls:RepeaterView ItemsSource="{Binding Fields}"> <controls:RepeaterView ItemsSource="{Binding Fields}">
<controls:RepeaterView.ItemTemplate> <controls:RepeaterView.ItemTemplate>
<DataTemplate x:DataType="pages:AddEditPageFieldViewModel"> <DataTemplate x:DataType="pages:CipherAddEditPageFieldViewModel">
<StackLayout Spacing="0" Padding="0"> <StackLayout Spacing="0" Padding="0">
<Grid StyleClass="box-row, box-row-input"> <Grid StyleClass="box-row, box-row-input">
<Grid.RowDefinitions> <Grid.RowDefinitions>

View File

@ -14,7 +14,7 @@ using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public partial class AddEditPage : BaseContentPage public partial class CipherAddEditPage : BaseContentPage
{ {
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
private readonly IStateService _stateService; private readonly IStateService _stateService;
@ -22,10 +22,10 @@ namespace Bit.App.Pages
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IKeyConnectorService _keyConnectorService; private readonly IKeyConnectorService _keyConnectorService;
private AddEditPageViewModel _vm; private CipherAddEditPageViewModel _vm;
private bool _fromAutofill; private bool _fromAutofill;
public AddEditPage( public CipherAddEditPage(
string cipherId = null, string cipherId = null,
CipherType? type = null, CipherType? type = null,
string folderId = null, string folderId = null,
@ -36,7 +36,7 @@ namespace Bit.App.Pages
bool fromAutofill = false, bool fromAutofill = false,
AppOptions appOptions = null, AppOptions appOptions = null,
bool cloneMode = false, bool cloneMode = false,
ViewPage viewPage = null) CipherDetailsPage cipherDetailsPage = null)
{ {
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@ -47,7 +47,7 @@ namespace Bit.App.Pages
_fromAutofill = fromAutofill; _fromAutofill = fromAutofill;
FromAutofillFramework = _appOptions?.FromAutofillFramework ?? false; FromAutofillFramework = _appOptions?.FromAutofillFramework ?? false;
InitializeComponent(); InitializeComponent();
_vm = BindingContext as AddEditPageViewModel; _vm = BindingContext as CipherAddEditPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.CipherId = cipherId; _vm.CipherId = cipherId;
_vm.FolderId = folderId == "none" ? null : folderId; _vm.FolderId = folderId == "none" ? null : folderId;
@ -57,7 +57,7 @@ namespace Bit.App.Pages
_vm.DefaultName = name ?? appOptions?.SaveName; _vm.DefaultName = name ?? appOptions?.SaveName;
_vm.DefaultUri = uri ?? appOptions?.Uri; _vm.DefaultUri = uri ?? appOptions?.Uri;
_vm.CloneMode = cloneMode; _vm.CloneMode = cloneMode;
_vm.ViewPage = viewPage; _vm.CipherDetailsPage = cipherDetailsPage;
_vm.Init(); _vm.Init();
SetActivityIndicator(); SetActivityIndicator();
if (_vm.EditMode && !_vm.CloneMode && Device.RuntimePlatform == Device.Android) if (_vm.EditMode && !_vm.CloneMode && Device.RuntimePlatform == Device.Android)
@ -145,7 +145,7 @@ namespace Bit.App.Pages
} }
public bool FromAutofillFramework { get; set; } public bool FromAutofillFramework { get; set; }
public AddEditPageViewModel ViewModel => _vm; public CipherAddEditPageViewModel ViewModel => _vm;
protected override async void OnAppearing() protected override async void OnAppearing()
{ {

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core; using Bit.Core;
@ -15,22 +14,17 @@ using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class AddEditPageViewModel : BaseViewModel public class CipherAddEditPageViewModel : BaseCipherViewModel
{ {
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService; private readonly ICollectionService _collectionService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly ILogger _logger;
private CipherView _cipher;
private bool _showNotesSeparator; private bool _showNotesSeparator;
private bool _showPassword; private bool _showPassword;
private bool _showCardNumber; private bool _showCardNumber;
@ -44,7 +38,7 @@ namespace Bit.App.Pages
private bool _hasCollections; private bool _hasCollections;
private string _previousCipherId; private string _previousCipherId;
private List<Core.Models.View.CollectionView> _writeableCollections; private List<Core.Models.View.CollectionView> _writeableCollections;
private string[] _additionalCipherProperties = new string[] protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[]
{ {
nameof(IsLogin), nameof(IsLogin),
nameof(IsIdentity), nameof(IsIdentity),
@ -54,6 +48,7 @@ namespace Bit.App.Pages
nameof(ShowAttachments), nameof(ShowAttachments),
nameof(ShowCollections), nameof(ShowCollections),
}; };
private List<KeyValuePair<UriMatchType?, string>> _matchDetectionOptions = private List<KeyValuePair<UriMatchType?, string>> _matchDetectionOptions =
new List<KeyValuePair<UriMatchType?, string>> new List<KeyValuePair<UriMatchType?, string>>
{ {
@ -66,31 +61,26 @@ namespace Bit.App.Pages
new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never) new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never)
}; };
public AddEditPageViewModel() public CipherAddEditPageViewModel()
{ {
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService"); _cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService"); _folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService"); _organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService"); _collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService"); _eventService = ServiceContainer.Resolve<IEventService>("eventService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService"); _policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
GeneratePasswordCommand = new Command(GeneratePassword); GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode); ToggleCardCodeCommand = new Command(ToggleCardCode);
CheckPasswordCommand = new Command(CheckPasswordAsync);
UriOptionsCommand = new Command<LoginUriView>(UriOptions); UriOptionsCommand = new Command<LoginUriView>(UriOptions);
FieldOptionsCommand = new Command<AddEditPageFieldViewModel>(FieldOptions); FieldOptionsCommand = new Command<CipherAddEditPageFieldViewModel>(FieldOptions);
PasswordPromptHelpCommand = new Command(PasswordPromptHelp); PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
Uris = new ExtendedObservableCollection<LoginUriView>(); Uris = new ExtendedObservableCollection<LoginUriView>();
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>(); Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
Collections = new ExtendedObservableCollection<CollectionViewModel>(); Collections = new ExtendedObservableCollection<CollectionViewModel>();
AllowPersonal = true; AllowPersonal = true;
@ -146,7 +136,6 @@ 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 Command CheckPasswordCommand { get; set; }
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; }
@ -164,7 +153,7 @@ namespace Bit.App.Pages
public List<KeyValuePair<string, string>> FolderOptions { get; set; } public List<KeyValuePair<string, string>> FolderOptions { get; set; }
public List<KeyValuePair<string, string>> OwnershipOptions { get; set; } public List<KeyValuePair<string, string>> OwnershipOptions { get; set; }
public ExtendedObservableCollection<LoginUriView> Uris { get; set; } public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
public ExtendedObservableCollection<AddEditPageFieldViewModel> Fields { get; set; } public ExtendedObservableCollection<CipherAddEditPageFieldViewModel> Fields { get; set; }
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; } public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
public int TypeSelectedIndex 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 public bool ShowNotesSeparator
{ {
get => _showNotesSeparator; get => _showNotesSeparator;
@ -285,7 +269,7 @@ namespace Bit.App.Pages
public bool ShowOwnershipOptions => !EditMode || CloneMode; public bool ShowOwnershipOptions => !EditMode || CloneMode;
public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal; public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal;
public bool CloneMode { get; set; } public bool CloneMode { get; set; }
public ViewPage ViewPage { get; set; } public CipherDetailsPage CipherDetailsPage { get; set; }
public bool IsLogin => Cipher?.Type == CipherType.Login; public bool IsLogin => Cipher?.Type == CipherType.Login;
public bool IsIdentity => Cipher?.Type == CipherType.Identity; public bool IsIdentity => Cipher?.Type == CipherType.Identity;
public bool IsCard => Cipher?.Type == CipherType.Card; public bool IsCard => Cipher?.Type == CipherType.Card;
@ -421,7 +405,7 @@ namespace Bit.App.Pages
} }
if (Cipher.Fields != null) 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); EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id); _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 // Close and go back to app
_deviceActionService.CloseAutofill(); _deviceActionService.CloseAutofill();
@ -518,7 +502,7 @@ namespace Bit.App.Pages
{ {
if (CloneMode) 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 the app is tombstoned then PopModalAsync would throw index out of bounds
if (Page.Navigation?.ModalStack?.Count > 0) if (Page.Navigation?.ModalStack?.Count > 0)
@ -603,7 +587,7 @@ namespace Bit.App.Pages
public async void UriOptions(LoginUriView uri) public async void UriOptions(LoginUriView uri)
{ {
if (!(Page as AddEditPage).DoOnce()) if (!(Page as CipherAddEditPage).DoOnce())
{ {
return; return;
} }
@ -639,9 +623,9 @@ namespace Bit.App.Pages
Uris.Add(new LoginUriView()); 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; return;
} }
@ -701,10 +685,10 @@ namespace Bit.App.Pages
} }
if (Fields == null) if (Fields == null)
{ {
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>(); Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
} }
var type = fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key; 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, Type = type,
Name = string.IsNullOrWhiteSpace(name) ? null : name, Name = string.IsNullOrWhiteSpace(name) ? null : name,
@ -832,35 +816,11 @@ namespace Bit.App.Pages
private void TriggerCipherChanged() private void TriggerCipherChanged()
{ {
TriggerPropertyChanged(nameof(Cipher), _additionalCipherProperties); TriggerPropertyChanged(nameof(Cipher), AdditionalPropertiesToRaiseOnCipherChanged);
}
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);
}
} }
} }
public class AddEditPageFieldViewModel : ExtendedViewModel public class CipherAddEditPageFieldViewModel : ExtendedViewModel
{ {
private II18nService _i18nService; private II18nService _i18nService;
private FieldView _field; private FieldView _field;
@ -876,7 +836,7 @@ namespace Bit.App.Pages
nameof(IsLinkedType), nameof(IsLinkedType),
}; };
public AddEditPageFieldViewModel(CipherView cipher, FieldView field) public CipherAddEditPageFieldViewModel(CipherView cipher, FieldView field)
{ {
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cipher = cipher; _cipher = cipher;

View File

@ -2,18 +2,18 @@
<pages:BaseContentPage <pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.ViewPage" x:Class="Bit.App.Pages.CipherDetailsPage"
xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:effects="clr-namespace:Bit.App.Effects" xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore" xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore" xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:ViewPageViewModel" x:DataType="pages:CipherDetailsPageViewModel"
x:Name="_page" x:Name="_page"
Title="{Binding PageTitle}"> Title="{Binding PageTitle}">
<ContentPage.BindingContext> <ContentPage.BindingContext>
<pages:ViewPageViewModel /> <pages:CipherDetailsPageViewModel />
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.Resources> <ContentPage.Resources>
@ -540,7 +540,7 @@
</StackLayout> </StackLayout>
<controls:RepeaterView ItemsSource="{Binding Fields}"> <controls:RepeaterView ItemsSource="{Binding Fields}">
<controls:RepeaterView.ItemTemplate> <controls:RepeaterView.ItemTemplate>
<DataTemplate x:DataType="pages:ViewPageFieldViewModel"> <DataTemplate x:DataType="pages:CipherDetailsPageFieldViewModel">
<StackLayout Spacing="0" Padding="0"> <StackLayout Spacing="0" Padding="0">
<Grid StyleClass="box-row"> <Grid StyleClass="box-row">
<Grid.RowDefinitions> <Grid.RowDefinitions>

View File

@ -9,18 +9,18 @@ using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public partial class ViewPage : BaseContentPage public partial class CipherDetailsPage : BaseContentPage
{ {
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private ViewPageViewModel _vm; private CipherDetailsPageViewModel _vm;
public ViewPage(string cipherId) public CipherDetailsPage(string cipherId)
{ {
InitializeComponent(); InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_vm = BindingContext as ViewPageViewModel; _vm = BindingContext as CipherDetailsPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.CipherId = cipherId; _vm.CipherId = cipherId;
SetActivityIndicator(_mainContent); SetActivityIndicator(_mainContent);
@ -40,7 +40,7 @@ namespace Bit.App.Pages
} }
} }
public ViewPageViewModel ViewModel => _vm; public CipherDetailsPageViewModel ViewModel => _vm;
public void UpdateCipherId(string cipherId) public void UpdateCipherId(string cipherId)
{ {
@ -55,7 +55,7 @@ namespace Bit.App.Pages
IsBusy = true; IsBusy = true;
} }
_broadcasterService.Subscribe(nameof(ViewPage), async (message) => _broadcasterService.Subscribe(nameof(CipherDetailsPage), async (message) =>
{ {
try try
{ {
@ -111,7 +111,7 @@ namespace Bit.App.Pages
{ {
base.OnDisappearing(); base.OnDisappearing();
IsBusy = false; IsBusy = false;
_broadcasterService.Unsubscribe(nameof(ViewPage)); _broadcasterService.Unsubscribe(nameof(CipherDetailsPage));
_vm.CleanUp(); _vm.CleanUp();
} }
@ -140,7 +140,7 @@ namespace Bit.App.Pages
{ {
return; 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; 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)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }
@ -267,7 +267,7 @@ namespace Bit.App.Pages
} }
else if (selection == AppResources.Clone) 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)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }

View File

@ -17,23 +17,18 @@ using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class ViewPageViewModel : BaseViewModel public class CipherDetailsPageViewModel : BaseCipherViewModel
{ {
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly ITotpService _totpService; private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IPasswordRepromptService _passwordRepromptService; private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ILocalizeService _localizeService; private readonly ILocalizeService _localizeService;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly ILogger _logger;
private CipherView _cipher; private List<CipherDetailsPageFieldViewModel> _fields;
private List<ViewPageFieldViewModel> _fields;
private bool _canAccessPremium; private bool _canAccessPremium;
private bool _showPassword; private bool _showPassword;
private bool _showCardNumber; private bool _showCardNumber;
@ -48,20 +43,16 @@ namespace Bit.App.Pages
private string _attachmentFilename; private string _attachmentFilename;
private bool _passwordReprompted; private bool _passwordReprompted;
public ViewPageViewModel() public CipherDetailsPageViewModel()
{ {
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService"); _cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService"); _totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService"); _eventService = ServiceContainer.Resolve<IEventService>("eventService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"); _passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService"); _localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService"); _clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
@ -70,8 +61,7 @@ namespace Bit.App.Pages
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber); ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode); ToggleCardCodeCommand = new Command(ToggleCardCode);
CheckPasswordCommand = new Command(CheckPasswordAsync); DownloadAttachmentCommand = new AsyncCommand<AttachmentView>(DownloadAttachmentAsync, allowsMultipleExecutions: false);
DownloadAttachmentCommand = new Command<AttachmentView>(DownloadAttachmentAsync);
PageTitle = AppResources.ViewItem; PageTitle = AppResources.ViewItem;
} }
@ -83,14 +73,9 @@ 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 Command CheckPasswordCommand { get; set; } public AsyncCommand<AttachmentView> DownloadAttachmentCommand { get; set; }
public Command DownloadAttachmentCommand { get; set; }
public string CipherId { 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(IsLogin),
nameof(IsIdentity), nameof(IsIdentity),
@ -106,9 +91,8 @@ namespace Bit.App.Pages
nameof(ShowIdentityAddress), nameof(ShowIdentityAddress),
nameof(IsDeleted), nameof(IsDeleted),
nameof(CanEdit), nameof(CanEdit),
}); };
} public List<CipherDetailsPageFieldViewModel> Fields
public List<ViewPageFieldViewModel> Fields
{ {
get => _fields; get => _fields;
set => SetProperty(ref _fields, value); set => SetProperty(ref _fields, value);
@ -256,7 +240,7 @@ namespace Bit.App.Pages
} }
Cipher = await cipher.DecryptAsync(); Cipher = await cipher.DecryptAsync();
CanAccessPremium = await _stateService.CanAccessPremiumAsync(); 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) && if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
(Cipher.OrganizationUseTotp || CanAccessPremium)) (Cipher.OrganizationUseTotp || CanAccessPremium))
@ -455,42 +439,10 @@ namespace Bit.App.Pages
} }
} }
private async void CheckPasswordAsync() private async Task DownloadAttachmentAsync(AttachmentView attachment)
{ {
if (!(Page as BaseContentPage).DoOnce()) try
{ {
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) if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage, await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
@ -533,8 +485,6 @@ namespace Bit.App.Pages
} }
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading); await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
try
{
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId); var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
if (data == null) if (data == null)
@ -561,9 +511,11 @@ namespace Bit.App.Pages
OpenAttachment(data, attachment); OpenAttachment(data, attachment);
} }
} }
catch catch (Exception ex)
{ {
_logger.Exception(ex);
await _deviceActionService.HideLoadingAsync(); 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 II18nService _i18nService;
private ViewPageViewModel _vm; private CipherDetailsPageViewModel _vm;
private FieldView _field; private FieldView _field;
private CipherView _cipher; private CipherView _cipher;
private bool _showHiddenValue; private bool _showHiddenValue;
public ViewPageFieldViewModel(ViewPageViewModel vm, CipherView cipher, FieldView field) public CipherDetailsPageFieldViewModel(CipherDetailsPageViewModel vm, CipherView cipher, FieldView field)
{ {
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_vm = vm; _vm = vm;

View File

@ -10,7 +10,6 @@ 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 Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@ -157,7 +156,7 @@ namespace Bit.App.Pages
} }
if (selection == AppResources.View || string.IsNullOrWhiteSpace(AutofillUrl)) 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)); await Page.Navigation.PushModalAsync(new NavigationPage(page));
} }
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave) else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)

View File

@ -271,7 +271,7 @@ namespace Bit.App.Pages
} }
if (!_vm.Deleted && DoOnce()) 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)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }
@ -285,11 +285,11 @@ namespace Bit.App.Pages
await _accountListOverlay.HideAsync(); await _accountListOverlay.HideAsync();
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId)) 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)) 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; _previousPage = null;
} }

View File

@ -378,7 +378,7 @@ namespace Bit.App.Pages
public async Task SelectCipherAsync(CipherView cipher) 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)); await Page.Navigation.PushModalAsync(new NavigationPage(page));
} }

View File

@ -85,13 +85,13 @@ namespace Bit.App.Utilities
} }
else if (selection == AppResources.View) 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) else if (selection == AppResources.Edit)
{ {
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync()) 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) else if (selection == AppResources.CopyUsername)
@ -427,7 +427,7 @@ namespace Bit.App.Utilities
{ {
if (appOptions.FromAutofillFramework && appOptions.SaveType.HasValue) 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; return true;
} }
if (appOptions.Uri != null) if (appOptions.Uri != null)