mirror of
https://github.com/bitwarden/android.git
synced 2024-12-24 18:08:26 +03:00
edit custom fields
This commit is contained in:
parent
78cda03d61
commit
590fe211c4
7 changed files with 300 additions and 2 deletions
|
@ -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" />
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
236
src/App/Pages/Vault/VaultCustomFieldsPage.cs
Normal file
236
src/App/Pages/Vault/VaultCustomFieldsPage.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
18
src/App/Resources/AppResources.Designer.cs
generated
18
src/App/Resources/AppResources.Designer.cs
generated
|
@ -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>
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue