Add ability to clone personal vault items (#734)

* Add clone ability to personal vault items

* Fixed formatter

* Made requested changes and removed some extra whitespace added by Rider formatter

* Removed formatting on AppResources file

* Fixed casing on UpdateCipherId method

* Update calling method
This commit is contained in:
Vincent Salucci 2020-02-18 15:48:23 -06:00 committed by GitHub
parent 33df456cfd
commit 36fb23d467
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 2415 additions and 3734 deletions

View file

@ -607,7 +607,7 @@
<Button Text="{u:I18n NewCustomField}" StyleClass="box-button-row"
Clicked="NewField_Clicked"></Button>
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}">
<StackLayout StyleClass="box" IsVisible="{Binding ShowOwnershipOptions}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Ownership, Header=True}"
StyleClass="box-header, box-header-platform" />

View file

@ -30,7 +30,9 @@ namespace Bit.App.Pages
string name = null,
string uri = null,
bool fromAutofill = false,
AppOptions appOptions = null)
AppOptions appOptions = null,
bool cloneMode = false,
ViewPage viewPage = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@ -46,9 +48,11 @@ namespace Bit.App.Pages
_vm.Type = type;
_vm.DefaultName = name ?? appOptions?.SaveName;
_vm.DefaultUri = uri ?? appOptions?.Uri;
_vm.CloneMode = cloneMode;
_vm.ViewPage = viewPage;
_vm.Init();
SetActivityIndicator();
if(_vm.EditMode && Device.RuntimePlatform == Device.Android)
if(_vm.EditMode && !_vm.CloneMode && Device.RuntimePlatform == Device.Android)
{
ToolbarItems.Add(_attachmentsItem);
ToolbarItems.Add(_deleteItem);
@ -56,7 +60,7 @@ namespace Bit.App.Pages
if(Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_closeItem);
if(_vm.EditMode)
if(_vm.EditMode && !_vm.CloneMode)
{
ToolbarItems.Add(_moreItem);
}
@ -263,7 +267,7 @@ namespace Bit.App.Pages
options.Add(_vm.Cipher.OrganizationId == null ? AppResources.Share : AppResources.Collections);
}
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
_vm.EditMode ? AppResources.Delete : null, options.ToArray());
(_vm.EditMode && !_vm.CloneMode) ? AppResources.Delete : null, options.ToArray());
if(selection == AppResources.Delete)
{
if(await _vm.DeleteAsync())
@ -334,7 +338,7 @@ namespace Bit.App.Pages
private void AdjustToolbar()
{
if(_vm.EditMode && Device.RuntimePlatform == Device.Android)
if((_vm.EditMode || _vm.CloneMode) && Device.RuntimePlatform == Device.Android)
{
if(_vm.Cipher == null)
{
@ -346,7 +350,7 @@ namespace Bit.App.Pages
{
ToolbarItems.Remove(_collectionsItem);
}
if(!ToolbarItems.Contains(_shareItem))
if(!ToolbarItems.Contains(_shareItem) && !_vm.CloneMode)
{
ToolbarItems.Insert(2, _shareItem);
}

View file

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using View = Xamarin.Forms.View;
namespace Bit.App.Pages
{
@ -260,8 +261,11 @@ namespace Bit.App.Pages
nameof(ShowCollections)
});
}
public bool ShowCollections => !EditMode && Cipher.OrganizationId != null;
public bool ShowCollections => (!EditMode || CloneMode) && Cipher.OrganizationId != null;
public bool EditMode => !string.IsNullOrWhiteSpace(CipherId);
public bool ShowOwnershipOptions => !EditMode || CloneMode;
public bool CloneMode { get; set; }
public ViewPage ViewPage { get; set; }
public bool IsLogin => Cipher?.Type == CipherType.Login;
public bool IsIdentity => Cipher?.Type == CipherType.Identity;
public bool IsCard => Cipher?.Type == CipherType.Card;
@ -273,7 +277,7 @@ namespace Bit.App.Pages
public void Init()
{
PageTitle = EditMode ? AppResources.EditItem : AppResources.AddItem;
PageTitle = EditMode && !CloneMode ? AppResources.EditItem : AppResources.AddItem;
}
public async Task<bool> LoadAsync(AppOptions appOptions = null)
@ -310,6 +314,10 @@ namespace Bit.App.Pages
return false;
}
Cipher = await cipher.DecryptAsync();
if(CloneMode)
{
Cipher.Name += " - " + AppResources.Clone;
}
}
else
{
@ -355,7 +363,7 @@ namespace Bit.App.Pages
OwnershipSelectedIndex = string.IsNullOrWhiteSpace(Cipher.OrganizationId) ? 0 :
OwnershipOptions.FindIndex(k => k.Value == Cipher.OrganizationId);
if(!EditMode && (CollectionIds?.Any() ?? false))
if((!EditMode || CloneMode) && (CollectionIds?.Any() ?? false))
{
foreach(var col in Collections)
{
@ -406,14 +414,14 @@ namespace Bit.App.Pages
if(Cipher.Login != null)
{
Cipher.Login.Uris = Uris?.ToList();
if(!EditMode && Cipher.Type == CipherType.Login && Cipher.Login.Uris != null &&
if((!EditMode || CloneMode) && Cipher.Type == CipherType.Login && Cipher.Login.Uris != null &&
Cipher.Login.Uris.Count == 1 && string.IsNullOrWhiteSpace(Cipher.Login.Uris[0].Uri))
{
Cipher.Login.Uris = null;
}
}
if(!EditMode && Cipher.OrganizationId != null)
if((!EditMode || CloneMode) && Cipher.OrganizationId != null)
{
if(Collections == null || !Collections.Any(c => c != null && c.Checked))
{
@ -427,6 +435,10 @@ namespace Bit.App.Pages
.Select(c => c.Collection.Id)) : null;
}
if(CloneMode)
{
Cipher.Id = null;
}
var cipher = await _cipherService.EncryptAsync(Cipher);
if(cipher == null)
{
@ -439,8 +451,8 @@ namespace Bit.App.Pages
Cipher.Id = cipher.Id;
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode ? "editedCipher" : "addedCipher", Cipher.Id);
EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id);
if(Page is AddEditPage page && page.FromAutofillFramework)
{
@ -449,6 +461,10 @@ namespace Bit.App.Pages
}
else
{
if(CloneMode)
{
ViewPage?.UpdateCipherId(this.Cipher.Id);
}
await Page.Navigation.PopModalAsync();
}
return true;

View file

@ -43,6 +43,8 @@
x:Name="_attachmentsItem" x:Key="attachmentsItem" />
<ToolbarItem Text="{u:I18n Delete}" Clicked="Delete_Clicked" Order="Secondary" IsDestructive="True"
x:Name="_deleteItem" x:Key="deleteItem" />
<ToolbarItem Text="{u:I18n Clone}" Clicked="Clone_Clicked" Order="Secondary"
x:Name="_cloneItem" x:Key="cloneItem" />
<ScrollView x:Key="scrollView" x:Name="_scrollView">
<StackLayout Spacing="20" x:Name="_mainLayout">
@ -185,7 +187,7 @@
Grid.RowSpan="2"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand"/>
VerticalOptions="CenterAndExpand" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf0ea;"
@ -210,7 +212,7 @@
StyleClass="box-value" />
</StackLayout>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Card.CardholderName, Converter={StaticResource stringHasValue}}"/>
IsVisible="{Binding Cipher.Card.CardholderName, Converter={StaticResource stringHasValue}}" />
<Grid StyleClass="box-row"
IsVisible="{Binding Cipher.Card.Number, Converter={StaticResource stringHasValue}}">
<Grid.RowDefinitions>
@ -361,7 +363,7 @@
StyleClass="box-value" />
</StackLayout>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Identity.SSN, Converter={StaticResource stringHasValue}}"/>
IsVisible="{Binding Cipher.Identity.SSN, Converter={StaticResource stringHasValue}}" />
<StackLayout StyleClass="box-row"
IsVisible="{Binding Cipher.Identity.PassportNumber, Converter={StaticResource stringHasValue}}">
<Label

View file

@ -41,6 +41,11 @@ namespace Bit.App.Pages
public ViewPageViewModel ViewModel => _vm;
public void UpdateCipherId(string cipherId)
{
_vm.CipherId = cipherId;
}
protected override async void OnAppearing()
{
base.OnAppearing();
@ -149,14 +154,33 @@ namespace Bit.App.Pages
}
}
private async void Clone_Clicked(object sender, System.EventArgs e)
{
if(DoOnce())
{
var page = new AddEditPage(_vm.CipherId, cloneMode: true, viewPage: this);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void More_Clicked(object sender, System.EventArgs e)
{
if(!DoOnce())
{
return;
}
var options = new List<string> { AppResources.Attachments };
options.Add(_vm.Cipher.OrganizationId == null ? AppResources.Share : AppResources.Collections);
var options = new List<string> {AppResources.Attachments};
if(_vm.Cipher.OrganizationId == null)
{
options.Add(AppResources.Clone);
options.Add(AppResources.Share);
}
else
{
options.Add(AppResources.Collections);
}
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
AppResources.Delete, options.ToArray());
if(selection == AppResources.Delete)
@ -181,6 +205,11 @@ namespace Bit.App.Pages
var page = new SharePage(_vm.CipherId);
await Navigation.PushModalAsync(new NavigationPage(page));
}
else if(selection == AppResources.Clone)
{
var page = new AddEditPage(_vm.CipherId, cloneMode: true, viewPage: this);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void Close_Clicked(object sender, System.EventArgs e)
@ -203,13 +232,21 @@ namespace Bit.App.Pages
{
ToolbarItems.Remove(_collectionsItem);
}
if(!ToolbarItems.Contains(_cloneItem))
{
ToolbarItems.Insert(1, _cloneItem);
}
if(!ToolbarItems.Contains(_shareItem))
{
ToolbarItems.Insert(1, _shareItem);
ToolbarItems.Insert(2, _shareItem);
}
}
else
{
if(ToolbarItems.Contains(_cloneItem))
{
ToolbarItems.Remove(_cloneItem);
}
if(ToolbarItems.Contains(_shareItem))
{
ToolbarItems.Remove(_shareItem);

File diff suppressed because it is too large Load diff

View file

@ -1608,4 +1608,8 @@
<data name="ExportVaultSuccess" xml:space="preserve">
<value>Vault exported successfully</value>
</data>
<data name="Clone" xml:space="preserve">
<value>Clone</value>
<comment>Clone an entity (verb).</comment>
</data>
</root>