edit custom fields

This commit is contained in:
Kyle Spearrin 2017-09-25 17:13:20 -04:00
parent 78cda03d61
commit 590fe211c4
7 changed files with 300 additions and 2 deletions

View file

@ -168,6 +168,7 @@
<Compile Include="Pages\Settings\SettingsSyncPage.cs" /> <Compile Include="Pages\Settings\SettingsSyncPage.cs" />
<Compile Include="Pages\Settings\SettingsPage.cs" /> <Compile Include="Pages\Settings\SettingsPage.cs" />
<Compile Include="Pages\Settings\SettingsListFoldersPage.cs" /> <Compile Include="Pages\Settings\SettingsListFoldersPage.cs" />
<Compile Include="Pages\Vault\VaultCustomFieldsPage.cs" />
<Compile Include="Pages\Vault\VaultAutofillListLoginsPage.cs" /> <Compile Include="Pages\Vault\VaultAutofillListLoginsPage.cs" />
<Compile Include="Pages\Vault\VaultAttachmentsPage.cs" /> <Compile Include="Pages\Vault\VaultAttachmentsPage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -1,4 +1,6 @@
using Bit.App.Enums; using Bit.App.Enums;
using System.Collections.Generic;
using System.Linq;
namespace Bit.App.Models.Api namespace Bit.App.Models.Api
{ {
@ -13,6 +15,16 @@ namespace Bit.App.Models.Api
Notes = login.Notes?.EncryptedString; Notes = login.Notes?.EncryptedString;
Favorite = login.Favorite; Favorite = login.Favorite;
if(login.Fields != null)
{
Fields = login.Fields.Select(f => new FieldDataModel
{
Name = f.Name?.EncryptedString,
Value = f.Value?.EncryptedString,
Type = f.Type
});
}
switch(Type) switch(Type)
{ {
case CipherType.Login: case CipherType.Login:
@ -29,6 +41,7 @@ namespace Bit.App.Models.Api
public bool Favorite { get; set; } public bool Favorite { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public IEnumerable<FieldDataModel> Fields { get; set; }
public LoginType Login { get; set; } public LoginType Login { get; set; }
public class LoginType public class LoginType

View file

@ -5,6 +5,8 @@ namespace Bit.App.Models
{ {
public class Field public class Field
{ {
public Field() { }
public Field(FieldDataModel model) public Field(FieldDataModel model)
{ {
Type = model.Type; Type = model.Type;

View file

@ -0,0 +1,236 @@
using System;
using System.Linq;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
using Bit.App.Utilities;
using Plugin.Connectivity.Abstractions;
using Bit.App.Models;
using Bit.App.Enums;
using System.Collections.Generic;
namespace Bit.App.Pages
{
public class VaultCustomFieldsPage : ExtendedContentPage
{
private readonly ILoginService _loginService;
private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly string _loginId;
private Login _login;
private DateTime? _lastAction;
public VaultCustomFieldsPage(string loginId)
: base(true)
{
_loginId = loginId;
_loginService = Resolver.Resolve<ILoginService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init();
}
public ToolbarItem SaveToolbarItem { get; set; }
public Label NoDataLabel { get; set; }
public TableSection FieldsSection { get; set; }
public ExtendedTableView Table { get; set; }
private void Init()
{
FieldsSection = new TableSection(" ");
Table = new ExtendedTableView
{
Intent = TableIntent.Settings,
EnableScrolling = true,
HasUnevenRows = true,
Root = new TableRoot
{
FieldsSection
}
};
NoDataLabel = new Label
{
Text = AppResources.NoCustomFields,
HorizontalTextAlignment = TextAlignment.Center,
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
Margin = new Thickness(10, 40, 10, 0)
};
SaveToolbarItem = new ToolbarItem(AppResources.Save, null, async () =>
{
if(_lastAction.LastActionWasRecent() || _login == null)
{
return;
}
_lastAction = DateTime.UtcNow;
if(!_connectivity.IsConnected)
{
AlertNoConnection();
return;
}
if(FieldsSection.Count > 0)
{
var fields = new List<Field>();
foreach(var cell in FieldsSection)
{
if(cell is FormEntryCell entryCell)
{
fields.Add(new Field
{
Name = string.IsNullOrWhiteSpace(entryCell.Label.Text) ? null :
entryCell.Label.Text.Encrypt(_login.OrganizationId),
Value = string.IsNullOrWhiteSpace(entryCell.Entry.Text) ? null :
entryCell.Entry.Text.Encrypt(_login.OrganizationId),
Type = entryCell.Entry.IsPassword ? FieldType.Hidden : FieldType.Text
});
}
else if(cell is ExtendedSwitchCell switchCell)
{
var value = switchCell.On ? "true" : "false";
fields.Add(new Field
{
Name = string.IsNullOrWhiteSpace(switchCell.Text) ? null :
switchCell.Text.Encrypt(_login.OrganizationId),
Value = value.Encrypt(_login.OrganizationId),
Type = FieldType.Boolean
});
}
}
_login.Fields = fields;
}
else
{
_login.Fields = null;
}
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
var saveTask = await _loginService.SaveAsync(_login);
_userDialogs.HideLoading();
if(saveTask.Succeeded)
{
_userDialogs.Toast(AppResources.CustomFieldsUpdated);
_googleAnalyticsService.TrackAppEvent("UpdatedCustomFields");
await Navigation.PopForDeviceAsync();
}
else if(saveTask.Errors.Count() > 0)
{
await _userDialogs.AlertAsync(saveTask.Errors.First().Message, AppResources.AnErrorHasOccurred);
}
else
{
await _userDialogs.AlertAsync(AppResources.AnErrorHasOccurred);
}
}, ToolbarItemOrder.Default, 0);
Title = AppResources.CustomFields;
Content = Table;
if(Device.RuntimePlatform == Device.iOS)
{
Table.RowHeight = -1;
Table.EstimatedRowHeight = 44;
}
}
protected async override void OnAppearing()
{
base.OnAppearing();
_login = await _loginService.GetByIdAsync(_loginId);
if(_login == null)
{
await Navigation.PopForDeviceAsync();
return;
}
if(_login.Fields != null && _login.Fields.Any())
{
Content = Table;
ToolbarItems.Add(SaveToolbarItem);
if(Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
}
foreach(var field in _login.Fields)
{
var label = field.Name?.Decrypt(_login.OrganizationId) ?? string.Empty;
var value = field.Value?.Decrypt(_login.OrganizationId);
switch(field.Type)
{
case FieldType.Text:
case FieldType.Hidden:
var hidden = field.Type == FieldType.Hidden;
var textFieldCell = new FormEntryCell(label, isPassword: hidden);
textFieldCell.Entry.Text = value;
textFieldCell.Entry.DisableAutocapitalize = true;
textFieldCell.Entry.Autocorrect = false;
if(hidden)
{
textFieldCell.Entry.FontFamily = Helpers.OnPlatform(
iOS: "Menlo-Regular", Android: "monospace", WinPhone: "Courier");
}
textFieldCell.InitEvents();
FieldsSection.Add(textFieldCell);
break;
case FieldType.Boolean:
var switchFieldCell = new ExtendedSwitchCell
{
Text = label,
On = value == "true"
};
FieldsSection.Add(switchFieldCell);
break;
default:
continue;
}
}
}
else
{
Content = NoDataLabel;
if(Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
}
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
if(FieldsSection != null && FieldsSection.Count > 0)
{
foreach(var cell in FieldsSection)
{
if(cell is FormEntryCell entrycell)
{
entrycell.Dispose();
}
}
}
}
private void AlertNoConnection()
{
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
AppResources.Ok);
}
}
}

View file

@ -45,6 +45,7 @@ namespace Bit.App.Pages
public FormPickerCell FolderCell { get; private set; } public FormPickerCell FolderCell { get; private set; }
public ExtendedTextCell GenerateCell { get; private set; } public ExtendedTextCell GenerateCell { get; private set; }
public ExtendedTextCell AttachmentsCell { get; private set; } public ExtendedTextCell AttachmentsCell { get; private set; }
public ExtendedTextCell CustomFieldsCell { get; private set; }
public ExtendedTextCell DeleteCell { get; private set; } public ExtendedTextCell DeleteCell { get; private set; }
private void Init() private void Init()
@ -125,6 +126,12 @@ namespace Bit.App.Pages
ShowDisclousure = true ShowDisclousure = true
}; };
CustomFieldsCell = new ExtendedTextCell
{
Text = AppResources.CustomFields,
ShowDisclousure = true
};
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red }; DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
var table = new ExtendedTableView var table = new ExtendedTableView
@ -147,7 +154,8 @@ namespace Bit.App.Pages
TotpCell, TotpCell,
FolderCell, FolderCell,
favoriteCell, favoriteCell,
AttachmentsCell AttachmentsCell,
CustomFieldsCell
}, },
new TableSection(AppResources.Notes) new TableSection(AppResources.Notes)
{ {
@ -226,7 +234,7 @@ namespace Bit.App.Pages
if(saveTask.Succeeded) if(saveTask.Succeeded)
{ {
_userDialogs.Toast(AppResources.LoginUpdated); _userDialogs.Toast(AppResources.LoginUpdated);
_googleAnalyticsService.TrackAppEvent("EditeLogin"); _googleAnalyticsService.TrackAppEvent("EditedLogin");
await Navigation.PopForDeviceAsync(); await Navigation.PopForDeviceAsync();
} }
else if(saveTask.Errors.Count() > 0) else if(saveTask.Errors.Count() > 0)
@ -280,6 +288,10 @@ namespace Bit.App.Pages
{ {
AttachmentsCell.Tapped += AttachmentsCell_Tapped; AttachmentsCell.Tapped += AttachmentsCell_Tapped;
} }
if(CustomFieldsCell != null)
{
CustomFieldsCell.Tapped += CustomFieldsCell_Tapped;
}
if(DeleteCell != null) if(DeleteCell != null)
{ {
DeleteCell.Tapped += DeleteCell_Tapped; DeleteCell.Tapped += DeleteCell_Tapped;
@ -313,6 +325,10 @@ namespace Bit.App.Pages
{ {
AttachmentsCell.Tapped -= AttachmentsCell_Tapped; AttachmentsCell.Tapped -= AttachmentsCell_Tapped;
} }
if(CustomFieldsCell != null)
{
CustomFieldsCell.Tapped -= CustomFieldsCell_Tapped;
}
if(DeleteCell != null) if(DeleteCell != null)
{ {
DeleteCell.Tapped -= DeleteCell_Tapped; DeleteCell.Tapped -= DeleteCell_Tapped;
@ -369,6 +385,12 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(page); await Navigation.PushModalAsync(page);
} }
private async void CustomFieldsCell_Tapped(object sender, EventArgs e)
{
var page = new ExtendedNavigationPage(new VaultCustomFieldsPage(_loginId));
await Navigation.PushModalAsync(page);
}
private async void DeleteCell_Tapped(object sender, EventArgs e) private async void DeleteCell_Tapped(object sender, EventArgs e)
{ {
if(!_connectivity.IsConnected) if(!_connectivity.IsConnected)

View file

@ -727,6 +727,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Custom fields updated..
/// </summary>
public static string CustomFieldsUpdated {
get {
return ResourceManager.GetString("CustomFieldsUpdated", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Delete. /// Looks up a localized string similar to Delete.
/// </summary> /// </summary>
@ -1726,6 +1735,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to No custom fields. You can fully manage custom fields from the web vault or browser extension..
/// </summary>
public static string NoCustomFields {
get {
return ResourceManager.GetString("NoCustomFields", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to There are no favorites in your vault.. /// Looks up a localized string similar to There are no favorites in your vault..
/// </summary> /// </summary>

View file

@ -1033,4 +1033,10 @@
<data name="CustomFields" xml:space="preserve"> <data name="CustomFields" xml:space="preserve">
<value>Custom Fields</value> <value>Custom Fields</value>
</data> </data>
<data name="CustomFieldsUpdated" xml:space="preserve">
<value>Custom fields updated.</value>
</data>
<data name="NoCustomFields" xml:space="preserve">
<value>No custom fields. You can fully manage custom fields from the web vault or browser extension.</value>
</data>
</root> </root>