mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-26 12:16:07 +01:00
add/edit/delete custom fields. remove field page.
This commit is contained in:
parent
c3f4d56d1e
commit
1f21a2ecc7
@ -13,5 +13,6 @@
|
||||
<item name="android:windowBackground">@color/lightgray</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="android:navigationBarColor">@color/darkaccent</item>
|
||||
<item name="android:actionModeBackground">@color/darkaccent</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
@ -24,6 +24,7 @@ using Bit.Android.Autofill;
|
||||
using System.Linq;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Android.Views.InputMethods;
|
||||
using Android.Widget;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
@ -469,5 +470,44 @@ namespace Bit.Android.Services
|
||||
_progressDialog.Dispose();
|
||||
_progressDialog = null;
|
||||
}
|
||||
|
||||
public Task<string> DisplayPromptAync(string title = null, string description = null, string text = null)
|
||||
{
|
||||
var activity = (MainActivity)CurrentContext;
|
||||
var alertBuilder = new AlertDialog.Builder(activity);
|
||||
alertBuilder.SetTitle(title);
|
||||
alertBuilder.SetMessage(description);
|
||||
|
||||
var input = new EditText(activity)
|
||||
{
|
||||
InputType = global::Android.Text.InputTypes.ClassText
|
||||
};
|
||||
if(text != null)
|
||||
{
|
||||
input.Text = text;
|
||||
input.SetSelection(text.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
input.FocusedByDefault = true;
|
||||
}
|
||||
alertBuilder.SetView(input);
|
||||
|
||||
var result = new TaskCompletionSource<string>();
|
||||
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
|
||||
{
|
||||
result.TrySetResult(input.Text ?? string.Empty);
|
||||
});
|
||||
|
||||
alertBuilder.SetNegativeButton(AppResources.Cancel, (sender, args) =>
|
||||
{
|
||||
result.TrySetResult(null);
|
||||
});
|
||||
|
||||
var alert = alertBuilder.Create();
|
||||
alert.Window.SetSoftInputMode(global::Android.Views.SoftInput.StateVisible);
|
||||
alert.Show();
|
||||
return result.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,5 +22,6 @@ namespace Bit.App.Abstractions
|
||||
void OpenAccessibilitySettings();
|
||||
void OpenAutofillSettings();
|
||||
Task LaunchAppAsync(string appName, Page page);
|
||||
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null);
|
||||
}
|
||||
}
|
||||
|
@ -592,7 +592,7 @@ namespace Bit.App.Models.Page
|
||||
}
|
||||
else
|
||||
{
|
||||
cipher.Fields = null;
|
||||
Fields = null;
|
||||
}
|
||||
|
||||
switch(cipher.Type)
|
||||
|
@ -71,9 +71,12 @@ namespace Bit.App.Pages
|
||||
|
||||
SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings);
|
||||
|
||||
var buttonColor = Color.FromHex("3c8dbc");
|
||||
RegenerateCell = new ExtendedTextCell { Text = AppResources.RegeneratePassword, TextColor = buttonColor };
|
||||
CopyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = buttonColor };
|
||||
RegenerateCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.RegeneratePassword,
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
CopyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = Colors.Primary };
|
||||
|
||||
UppercaseCell = new ExtendedSwitchCell
|
||||
{
|
||||
|
@ -86,12 +86,14 @@ namespace Bit.App.Pages
|
||||
public TableRoot TableRoot { get; set; }
|
||||
public TableSection TopSection { get; set; }
|
||||
public TableSection MiddleSection { get; set; }
|
||||
public TableSection FieldsSection { get; set; }
|
||||
public ExtendedTableView Table { get; set; }
|
||||
|
||||
public FormEntryCell NameCell { get; private set; }
|
||||
public FormEditorCell NotesCell { get; private set; }
|
||||
public FormPickerCell FolderCell { get; private set; }
|
||||
public ExtendedSwitchCell FavoriteCell { get; set; }
|
||||
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||
|
||||
// Login
|
||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||
@ -184,6 +186,11 @@ namespace Bit.App.Pages
|
||||
NotesCell.InitEvents();
|
||||
FolderCell.InitEvents();
|
||||
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||
}
|
||||
|
||||
switch(_type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
@ -256,6 +263,11 @@ namespace Bit.App.Pages
|
||||
NotesCell.Dispose();
|
||||
FolderCell.Dispose();
|
||||
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||
}
|
||||
|
||||
switch(_type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
@ -301,6 +313,17 @@ namespace Bit.App.Pages
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(FieldsSection != null && FieldsSection.Count > 0)
|
||||
{
|
||||
foreach(var cell in FieldsSection)
|
||||
{
|
||||
if(cell is FormEntryCell entrycell)
|
||||
{
|
||||
entrycell.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
@ -540,6 +563,14 @@ namespace Bit.App.Pages
|
||||
NameCell.NextElement = NotesCell.Editor;
|
||||
}
|
||||
|
||||
FieldsSection = new TableSection(AppResources.CustomFields);
|
||||
AddFieldCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.NewCustomField,
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
FieldsSection.Add(AddFieldCell);
|
||||
|
||||
// Make table
|
||||
TableRoot = new TableRoot
|
||||
{
|
||||
@ -548,7 +579,8 @@ namespace Bit.App.Pages
|
||||
new TableSection(AppResources.Notes)
|
||||
{
|
||||
NotesCell
|
||||
}
|
||||
},
|
||||
FieldsSection
|
||||
};
|
||||
|
||||
Table = new ExtendedTableView
|
||||
@ -744,6 +776,8 @@ namespace Bit.App.Pages
|
||||
cipher.FolderId = Folders.ElementAt(FolderCell.Picker.SelectedIndex - 1).Id;
|
||||
}
|
||||
|
||||
Helpers.ProcessFieldsSectionForSave(FieldsSection, cipher);
|
||||
|
||||
_deviceActionService.ShowLoading(AppResources.Saving);
|
||||
var saveTask = await _cipherService.SaveAsync(cipher);
|
||||
_deviceActionService.HideLoading();
|
||||
@ -782,6 +816,10 @@ namespace Bit.App.Pages
|
||||
|
||||
ToolbarItems.Add(saveToolBarItem);
|
||||
}
|
||||
|
||||
private async void AddFieldCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
await Helpers.AddField(this, FieldsSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,251 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
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 ICipherService _cipherService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IConnectivity _connectivity;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly string _cipherId;
|
||||
private Cipher _cipher;
|
||||
private DateTime? _lastAction;
|
||||
|
||||
public VaultCustomFieldsPage(string cipherId)
|
||||
: base(true)
|
||||
{
|
||||
_cipherId = cipherId;
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
public ToolbarItem SaveToolbarItem { get; set; }
|
||||
public ToolbarItem CloseToolbarItem { get; set; }
|
||||
public Label NoDataLabel { get; set; }
|
||||
public TableSection FieldsSection { get; set; }
|
||||
public ExtendedTableView Table { get; set; }
|
||||
|
||||
private void Init()
|
||||
{
|
||||
FieldsSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||
|
||||
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, Helpers.ToolbarImage("envelope.png"), async () =>
|
||||
{
|
||||
if(_lastAction.LastActionWasRecent() || _cipher == 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(_cipher.OrganizationId),
|
||||
Value = string.IsNullOrWhiteSpace(entryCell.Entry.Text) ? null :
|
||||
entryCell.Entry.Text.Encrypt(_cipher.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(_cipher.OrganizationId),
|
||||
Value = value.Encrypt(_cipher.OrganizationId),
|
||||
Type = FieldType.Boolean
|
||||
});
|
||||
}
|
||||
}
|
||||
_cipher.Fields = fields;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cipher.Fields = null;
|
||||
}
|
||||
|
||||
_deviceActionService.ShowLoading(AppResources.Saving);
|
||||
var saveTask = await _cipherService.SaveAsync(_cipher);
|
||||
_deviceActionService.HideLoading();
|
||||
|
||||
if(saveTask.Succeeded)
|
||||
{
|
||||
_deviceActionService.Toast(AppResources.CustomFieldsUpdated);
|
||||
_googleAnalyticsService.TrackAppEvent("UpdatedCustomFields");
|
||||
await Navigation.PopForDeviceAsync();
|
||||
}
|
||||
else if(saveTask.Errors.Count() > 0)
|
||||
{
|
||||
await DisplayAlert(AppResources.AnErrorHasOccurred, saveTask.Errors.First().Message, AppResources.Ok);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
}
|
||||
}, ToolbarItemOrder.Default, 0);
|
||||
ToolbarItems.Add(SaveToolbarItem);
|
||||
|
||||
Title = AppResources.CustomFields;
|
||||
Content = Table;
|
||||
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
CloseToolbarItem = new DismissModalToolBarItem(this, AppResources.Close);
|
||||
ToolbarItems.Add(CloseToolbarItem);
|
||||
|
||||
Table.RowHeight = -1;
|
||||
Table.EstimatedRowHeight = 44;
|
||||
}
|
||||
else if(Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
Table.BottomPadding = 50;
|
||||
}
|
||||
}
|
||||
|
||||
protected async override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
_cipher = await _cipherService.GetByIdAsync(_cipherId);
|
||||
if(_cipher == null)
|
||||
{
|
||||
await Navigation.PopForDeviceAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var hasFields = _cipher.Fields?.Any() ?? false;
|
||||
|
||||
if(hasFields)
|
||||
{
|
||||
Content = Table;
|
||||
if(CloseToolbarItem != null)
|
||||
{
|
||||
CloseToolbarItem.Text = AppResources.Cancel;
|
||||
}
|
||||
|
||||
foreach(var field in _cipher.Fields)
|
||||
{
|
||||
var label = field.Name?.Decrypt(_cipher.OrganizationId) ?? string.Empty;
|
||||
var value = field.Value?.Decrypt(_cipher.OrganizationId);
|
||||
switch(field.Type)
|
||||
{
|
||||
case FieldType.Text:
|
||||
case FieldType.Hidden:
|
||||
var hidden = field.Type == FieldType.Hidden;
|
||||
|
||||
var textFieldCell = new FormEntryCell(label, isPassword: hidden, useButton: 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", Windows: "Courier");
|
||||
textFieldCell.Button.Image = "eye.png";
|
||||
textFieldCell.Button.Command = new Command(() =>
|
||||
{
|
||||
textFieldCell.Entry.InvokeToggleIsPassword();
|
||||
textFieldCell.Button.Image =
|
||||
"eye" + (!textFieldCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty) + ".png";
|
||||
});
|
||||
}
|
||||
|
||||
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(ToolbarItems.Count > 0)
|
||||
{
|
||||
ToolbarItems.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -42,6 +42,7 @@ namespace Bit.App.Pages
|
||||
public TableRoot TableRoot { get; set; }
|
||||
public TableSection TopSection { get; set; }
|
||||
public TableSection MiddleSection { get; set; }
|
||||
public TableSection FieldsSection { get; set; }
|
||||
public ExtendedTableView Table { get; set; }
|
||||
|
||||
public FormEntryCell NameCell { get; private set; }
|
||||
@ -49,8 +50,8 @@ namespace Bit.App.Pages
|
||||
public FormPickerCell FolderCell { get; private set; }
|
||||
public ExtendedSwitchCell FavoriteCell { get; set; }
|
||||
public ExtendedTextCell AttachmentsCell { get; private set; }
|
||||
public ExtendedTextCell CustomFieldsCell { get; private set; }
|
||||
public ExtendedTextCell DeleteCell { get; private set; }
|
||||
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||
|
||||
// Login
|
||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||
@ -152,12 +153,6 @@ namespace Bit.App.Pages
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
CustomFieldsCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.CustomFields,
|
||||
ShowDisclousure = true
|
||||
};
|
||||
|
||||
// Sections
|
||||
TopSection = new TableSection(AppResources.ItemInformation)
|
||||
{
|
||||
@ -168,8 +163,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
FolderCell,
|
||||
FavoriteCell,
|
||||
AttachmentsCell,
|
||||
CustomFieldsCell
|
||||
AttachmentsCell
|
||||
};
|
||||
|
||||
// Types
|
||||
@ -405,6 +399,27 @@ namespace Bit.App.Pages
|
||||
NameCell.NextElement = NotesCell.Editor;
|
||||
}
|
||||
|
||||
FieldsSection = new TableSection(AppResources.CustomFields);
|
||||
if(Cipher.Fields != null)
|
||||
{
|
||||
foreach(var field in Cipher.Fields)
|
||||
{
|
||||
var label = field.Name?.Decrypt(Cipher.OrganizationId) ?? string.Empty;
|
||||
var value = field.Value?.Decrypt(Cipher.OrganizationId);
|
||||
var cell = Helpers.MakeFieldCell(field.Type, label, value, FieldsSection);
|
||||
if(cell != null)
|
||||
{
|
||||
FieldsSection.Add(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
AddFieldCell = new ExtendedTextCell
|
||||
{
|
||||
Text = AppResources.NewCustomField,
|
||||
TextColor = Colors.Primary
|
||||
};
|
||||
FieldsSection.Add(AddFieldCell);
|
||||
|
||||
// Make table
|
||||
TableRoot = new TableRoot
|
||||
{
|
||||
@ -414,6 +429,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
NotesCell
|
||||
},
|
||||
FieldsSection,
|
||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||
{
|
||||
DeleteCell
|
||||
@ -614,6 +630,8 @@ namespace Bit.App.Pages
|
||||
Cipher.FolderId = null;
|
||||
}
|
||||
|
||||
Helpers.ProcessFieldsSectionForSave(FieldsSection, Cipher);
|
||||
|
||||
_deviceActionService.ShowLoading(AppResources.Saving);
|
||||
var saveTask = await _cipherService.SaveAsync(Cipher);
|
||||
_deviceActionService.HideLoading();
|
||||
@ -653,14 +671,14 @@ namespace Bit.App.Pages
|
||||
{
|
||||
AttachmentsCell.Tapped += AttachmentsCell_Tapped;
|
||||
}
|
||||
if(CustomFieldsCell != null)
|
||||
{
|
||||
CustomFieldsCell.Tapped += CustomFieldsCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped += DeleteCell_Tapped;
|
||||
}
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||
}
|
||||
|
||||
switch(Cipher.Type)
|
||||
{
|
||||
@ -713,6 +731,17 @@ namespace Bit.App.Pages
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(FieldsSection != null && FieldsSection.Count > 0)
|
||||
{
|
||||
foreach(var cell in FieldsSection)
|
||||
{
|
||||
if(cell is FormEntryCell entrycell)
|
||||
{
|
||||
entrycell.InitEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
@ -727,14 +756,14 @@ namespace Bit.App.Pages
|
||||
{
|
||||
AttachmentsCell.Tapped -= AttachmentsCell_Tapped;
|
||||
}
|
||||
if(CustomFieldsCell != null)
|
||||
{
|
||||
CustomFieldsCell.Tapped -= CustomFieldsCell_Tapped;
|
||||
}
|
||||
if(DeleteCell != null)
|
||||
{
|
||||
DeleteCell.Tapped -= DeleteCell_Tapped;
|
||||
}
|
||||
if(AddFieldCell != null)
|
||||
{
|
||||
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||
}
|
||||
|
||||
switch(Cipher.Type)
|
||||
{
|
||||
@ -787,6 +816,17 @@ namespace Bit.App.Pages
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(FieldsSection != null && FieldsSection.Count > 0)
|
||||
{
|
||||
foreach(var cell in FieldsSection)
|
||||
{
|
||||
if(cell is FormEntryCell entrycell)
|
||||
{
|
||||
entrycell.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PasswordButton_Clicked(object sender, EventArgs e)
|
||||
@ -840,12 +880,6 @@ namespace Bit.App.Pages
|
||||
await Navigation.PushModalAsync(page);
|
||||
}
|
||||
|
||||
private async void CustomFieldsCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var page = new ExtendedNavigationPage(new VaultCustomFieldsPage(_cipherId));
|
||||
await Navigation.PushModalAsync(page);
|
||||
}
|
||||
|
||||
private async void DeleteCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if(!_connectivity.IsConnected)
|
||||
@ -881,6 +915,11 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddFieldCell_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
await Helpers.AddField(this, FieldsSection);
|
||||
}
|
||||
|
||||
private void AlertNoConnection()
|
||||
{
|
||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||
|
81
src/App/Resources/AppResources.Designer.cs
generated
81
src/App/Resources/AppResources.Designer.cs
generated
@ -897,6 +897,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom Field Name.
|
||||
/// </summary>
|
||||
public static string CustomFieldName {
|
||||
get {
|
||||
return ResourceManager.GetString("CustomFieldName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom Fields.
|
||||
/// </summary>
|
||||
@ -906,15 +915,6 @@ 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 December.
|
||||
/// </summary>
|
||||
@ -1347,6 +1347,33 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Boolean.
|
||||
/// </summary>
|
||||
public static string FieldTypeBoolean {
|
||||
get {
|
||||
return ResourceManager.GetString("FieldTypeBoolean", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hidden.
|
||||
/// </summary>
|
||||
public static string FieldTypeHidden {
|
||||
get {
|
||||
return ResourceManager.GetString("FieldTypeHidden", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Text.
|
||||
/// </summary>
|
||||
public static string FieldTypeText {
|
||||
get {
|
||||
return ResourceManager.GetString("FieldTypeText", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to File.
|
||||
/// </summary>
|
||||
@ -2085,6 +2112,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New Custom Field.
|
||||
/// </summary>
|
||||
public static string NewCustomField {
|
||||
get {
|
||||
return ResourceManager.GetString("NewCustomField", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New item created..
|
||||
/// </summary>
|
||||
@ -2112,15 +2148,6 @@ 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>
|
||||
@ -2445,6 +2472,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove.
|
||||
/// </summary>
|
||||
public static string Remove {
|
||||
get {
|
||||
return ResourceManager.GetString("Remove", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Re-type Master Password.
|
||||
/// </summary>
|
||||
@ -2544,6 +2580,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to What type of custom field do you want to add?.
|
||||
/// </summary>
|
||||
public static string SelectTypeField {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectTypeField", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Self-hosted Environment.
|
||||
/// </summary>
|
||||
|
@ -1023,12 +1023,6 @@
|
||||
<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>
|
||||
<data name="CopyNumber" xml:space="preserve">
|
||||
<value>Copy Number</value>
|
||||
</data>
|
||||
@ -1234,4 +1228,25 @@
|
||||
<data name="BitwardenAutofillGoToSettings" xml:space="preserve">
|
||||
<value>We were unable to automatically open the Android autofill settings menu for you. You can navigate to the autofill settings menu manually from Android Settings > System > Languages and input > Advanced > Autofill service.</value>
|
||||
</data>
|
||||
<data name="CustomFieldName" xml:space="preserve">
|
||||
<value>Custom Field Name</value>
|
||||
</data>
|
||||
<data name="FieldTypeBoolean" xml:space="preserve">
|
||||
<value>Boolean</value>
|
||||
</data>
|
||||
<data name="FieldTypeHidden" xml:space="preserve">
|
||||
<value>Hidden</value>
|
||||
</data>
|
||||
<data name="FieldTypeText" xml:space="preserve">
|
||||
<value>Text</value>
|
||||
</data>
|
||||
<data name="NewCustomField" xml:space="preserve">
|
||||
<value>New Custom Field</value>
|
||||
</data>
|
||||
<data name="SelectTypeField" xml:space="preserve">
|
||||
<value>What type of custom field do you want to add?</value>
|
||||
</data>
|
||||
<data name="Remove" xml:space="preserve">
|
||||
<value>Remove</value>
|
||||
</data>
|
||||
</root>
|
9
src/App/Utilities/Colors.cs
Normal file
9
src/App/Utilities/Colors.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
public static class Colors
|
||||
{
|
||||
public static Color Primary = Color.FromHex("3c8dbc");
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Enums;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
@ -195,5 +198,180 @@ namespace Bit.App.Utilities
|
||||
var addPage = new VaultAddCipherPage(selectedType, defaultFolderId: folderId);
|
||||
await page.Navigation.PushForDeviceAsync(addPage);
|
||||
}
|
||||
|
||||
public static async Task AddField(Page page, TableSection fieldsSection)
|
||||
{
|
||||
var type = await page.DisplayActionSheet(AppResources.SelectTypeField, AppResources.Cancel, null,
|
||||
AppResources.FieldTypeText, AppResources.FieldTypeHidden, AppResources.FieldTypeBoolean);
|
||||
|
||||
FieldType fieldType;
|
||||
if(type == AppResources.FieldTypeText)
|
||||
{
|
||||
fieldType = FieldType.Text;
|
||||
}
|
||||
else if(type == AppResources.FieldTypeHidden)
|
||||
{
|
||||
fieldType = FieldType.Hidden;
|
||||
}
|
||||
else if(type == AppResources.FieldTypeBoolean)
|
||||
{
|
||||
fieldType = FieldType.Boolean;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var daService = Resolver.Resolve<IDeviceActionService>();
|
||||
var label = await daService.DisplayPromptAync(AppResources.CustomFieldName);
|
||||
if(label == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cell = MakeFieldCell(fieldType, label, string.Empty, fieldsSection);
|
||||
if(cell != null)
|
||||
{
|
||||
fieldsSection.Insert(fieldsSection.Count - 1, cell);
|
||||
if(cell is FormEntryCell feCell)
|
||||
{
|
||||
feCell.InitEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Cell MakeFieldCell(FieldType type, string label, string value, TableSection fieldsSection)
|
||||
{
|
||||
Cell cell;
|
||||
switch(type)
|
||||
{
|
||||
case FieldType.Text:
|
||||
case FieldType.Hidden:
|
||||
var hidden = type == FieldType.Hidden;
|
||||
var textFieldCell = new FormEntryCell(label, isPassword: hidden, useButton: 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", Windows: "Courier");
|
||||
textFieldCell.Button.Image = "eye.png";
|
||||
textFieldCell.Button.Command = new Command(() =>
|
||||
{
|
||||
textFieldCell.Entry.InvokeToggleIsPassword();
|
||||
textFieldCell.Button.Image =
|
||||
"eye" + (!textFieldCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty) + ".png";
|
||||
});
|
||||
}
|
||||
cell = textFieldCell;
|
||||
break;
|
||||
case FieldType.Boolean:
|
||||
var switchFieldCell = new ExtendedSwitchCell
|
||||
{
|
||||
Text = label,
|
||||
On = value == "true"
|
||||
};
|
||||
cell = switchFieldCell;
|
||||
break;
|
||||
default:
|
||||
cell = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if(cell != null)
|
||||
{
|
||||
var deleteAction = new MenuItem { Text = AppResources.Remove, IsDestructive = true };
|
||||
deleteAction.Clicked += (sender, e) =>
|
||||
{
|
||||
if(fieldsSection.Contains(cell))
|
||||
{
|
||||
fieldsSection.Remove(cell);
|
||||
}
|
||||
|
||||
if(cell is FormEntryCell feCell)
|
||||
{
|
||||
feCell.Dispose();
|
||||
}
|
||||
cell = null;
|
||||
};
|
||||
|
||||
var editNameAction = new MenuItem { Text = AppResources.Edit };
|
||||
editNameAction.Clicked += async (sender, e) =>
|
||||
{
|
||||
string existingLabel = null;
|
||||
var feCell = cell as FormEntryCell;
|
||||
var esCell = cell as ExtendedSwitchCell;
|
||||
if(feCell != null)
|
||||
{
|
||||
existingLabel = feCell.Label.Text;
|
||||
}
|
||||
else if(esCell != null)
|
||||
{
|
||||
existingLabel = esCell.Text;
|
||||
}
|
||||
|
||||
var daService = Resolver.Resolve<IDeviceActionService>();
|
||||
var editLabel = await daService.DisplayPromptAync(AppResources.CustomFieldName,
|
||||
null, existingLabel);
|
||||
if(editLabel != null)
|
||||
{
|
||||
if(feCell != null)
|
||||
{
|
||||
feCell.Label.Text = editLabel;
|
||||
}
|
||||
else if(esCell != null)
|
||||
{
|
||||
esCell.Text = editLabel;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cell.ContextActions.Add(editNameAction);
|
||||
cell.ContextActions.Add(deleteAction);
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static void ProcessFieldsSectionForSave(TableSection fieldsSection, Cipher cipher)
|
||||
{
|
||||
if(fieldsSection != null && 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(cipher.OrganizationId),
|
||||
Value = string.IsNullOrWhiteSpace(entryCell.Entry.Text) ? null :
|
||||
entryCell.Entry.Text.Encrypt(cipher.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(cipher.OrganizationId),
|
||||
Value = value.Encrypt(cipher.OrganizationId),
|
||||
Type = FieldType.Boolean
|
||||
});
|
||||
}
|
||||
}
|
||||
cipher.Fields = fields;
|
||||
}
|
||||
|
||||
if(!cipher.Fields?.Any() ?? true)
|
||||
{
|
||||
cipher.Fields = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Acr.UserDialogs;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models.Page;
|
||||
using Bit.App.Resources;
|
||||
using Coding4Fun.Toolkit.Controls;
|
||||
using System;
|
||||
using System.Linq;
|
||||
@ -164,5 +165,20 @@ namespace Bit.UWP.Services
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<string> DisplayPromptAync(string title = null, string description = null, string text = null)
|
||||
{
|
||||
var result = await _userDialogs.PromptAsync(new PromptConfig
|
||||
{
|
||||
Title = title,
|
||||
InputType = InputType.Default,
|
||||
OkText = AppResources.Ok,
|
||||
CancelText = AppResources.Cancel,
|
||||
Message = description,
|
||||
Text = text
|
||||
});
|
||||
|
||||
return result.Ok ? result.Value ?? string.Empty : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -323,6 +323,27 @@ namespace Bit.iOS.Services
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<string> DisplayPromptAync(string title = null, string description = null, string text = null)
|
||||
{
|
||||
var result = new TaskCompletionSource<string>();
|
||||
var alert = UIAlertController.Create(title ?? string.Empty, description, UIAlertControllerStyle.Alert);
|
||||
UITextField input = null;
|
||||
alert.AddAction(UIAlertAction.Create(AppResources.Cancel, UIAlertActionStyle.Cancel, x =>
|
||||
{
|
||||
result.TrySetResult(null);
|
||||
}));
|
||||
alert.AddAction(UIAlertAction.Create(AppResources.Ok, UIAlertActionStyle.Default, x =>
|
||||
{
|
||||
result.TrySetResult(input.Text ?? string.Empty);
|
||||
}));
|
||||
alert.AddTextField(x =>
|
||||
{
|
||||
input = x;
|
||||
input.Text = text ?? string.Empty;
|
||||
});
|
||||
return result.Task;
|
||||
}
|
||||
|
||||
private UIViewController GetPresentedViewController()
|
||||
{
|
||||
var window = UIApplication.SharedApplication.KeyWindow;
|
||||
|
Loading…
Reference in New Issue
Block a user