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\SettingsPage.cs" />
|
||||
<Compile Include="Pages\Settings\SettingsListFoldersPage.cs" />
|
||||
<Compile Include="Pages\Vault\VaultCustomFieldsPage.cs" />
|
||||
<Compile Include="Pages\Vault\VaultAutofillListLoginsPage.cs" />
|
||||
<Compile Include="Pages\Vault\VaultAttachmentsPage.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Bit.App.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
|
@ -13,6 +15,16 @@ namespace Bit.App.Models.Api
|
|||
Notes = login.Notes?.EncryptedString;
|
||||
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)
|
||||
{
|
||||
case CipherType.Login:
|
||||
|
@ -29,6 +41,7 @@ namespace Bit.App.Models.Api
|
|||
public bool Favorite { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public IEnumerable<FieldDataModel> Fields { get; set; }
|
||||
public LoginType Login { get; set; }
|
||||
|
||||
public class LoginType
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Bit.App.Models
|
|||
{
|
||||
public class Field
|
||||
{
|
||||
public Field() { }
|
||||
|
||||
public Field(FieldDataModel model)
|
||||
{
|
||||
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 ExtendedTextCell GenerateCell { get; private set; }
|
||||
public ExtendedTextCell AttachmentsCell { get; private set; }
|
||||
public ExtendedTextCell CustomFieldsCell { get; private set; }
|
||||
public ExtendedTextCell DeleteCell { get; private set; }
|
||||
|
||||
private void Init()
|
||||
|
@ -125,6 +126,12 @@ namespace Bit.App.Pages
|
|||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
CustomFieldsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.CustomFields,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
|
||||
|
||||
var table = new ExtendedTableView
|
||||
|
@ -147,7 +154,8 @@ namespace Bit.App.Pages
|
|||
TotpCell,
|
||||
FolderCell,
|
||||
favoriteCell,
|
||||
AttachmentsCell
|
||||
AttachmentsCell,
|
||||
CustomFieldsCell
|
||||
},
|
||||
new TableSection(AppResources.Notes)
|
||||
{
|
||||
|
@ -226,7 +234,7 @@ namespace Bit.App.Pages
|
|||
if(saveTask.Succeeded)
|
||||
{
|
||||
_userDialogs.Toast(AppResources.LoginUpdated);
|
||||
_googleAnalyticsService.TrackAppEvent("EditeLogin");
|
||||
_googleAnalyticsService.TrackAppEvent("EditedLogin");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(saveTask.Errors.Count() > 0)
|
||||
|
@ -280,6 +288,10 @@ namespace Bit.App.Pages
|
|||
{
|
||||
AttachmentsCell.Tapped += AttachmentsCell_Tapped;
|
||||
}
|
||||
if(CustomFieldsCell != null)
|
||||
{
|
||||
CustomFieldsCell.Tapped += CustomFieldsCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
|
@ -313,6 +325,10 @@ namespace Bit.App.Pages
|
|||
{
|
||||
AttachmentsCell.Tapped -= AttachmentsCell_Tapped;
|
||||
}
|
||||
if(CustomFieldsCell != null)
|
||||
{
|
||||
CustomFieldsCell.Tapped -= CustomFieldsCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
|
@ -369,6 +385,12 @@ namespace Bit.App.Pages
|
|||
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)
|
||||
{
|
||||
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>
|
||||
/// Looks up a localized string similar to Delete.
|
||||
/// </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>
|
||||
/// Looks up a localized string similar to There are no favorites in your vault..
|
||||
/// </summary>
|
||||
|
|
|
@ -1033,4 +1033,10 @@
|
|||
<data name="CustomFields" xml:space="preserve">
|
||||
<value>Custom Fields</value>
|
||||
</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>
|
Loading…
Reference in a new issue