mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 18:38:27 +03:00
add/edit/delete custom fields. remove field page.
This commit is contained in:
parent
c3f4d56d1e
commit
1f21a2ecc7
14 changed files with 459 additions and 304 deletions
|
@ -13,5 +13,6 @@
|
||||||
<item name="android:windowBackground">@color/lightgray</item>
|
<item name="android:windowBackground">@color/lightgray</item>
|
||||||
<item name="windowActionModeOverlay">true</item>
|
<item name="windowActionModeOverlay">true</item>
|
||||||
<item name="android:navigationBarColor">@color/darkaccent</item>
|
<item name="android:navigationBarColor">@color/darkaccent</item>
|
||||||
|
<item name="android:actionModeBackground">@color/darkaccent</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -24,6 +24,7 @@ using Bit.Android.Autofill;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Plugin.Settings.Abstractions;
|
using Plugin.Settings.Abstractions;
|
||||||
using Android.Views.InputMethods;
|
using Android.Views.InputMethods;
|
||||||
|
using Android.Widget;
|
||||||
|
|
||||||
namespace Bit.Android.Services
|
namespace Bit.Android.Services
|
||||||
{
|
{
|
||||||
|
@ -469,5 +470,44 @@ namespace Bit.Android.Services
|
||||||
_progressDialog.Dispose();
|
_progressDialog.Dispose();
|
||||||
_progressDialog = null;
|
_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 OpenAccessibilitySettings();
|
||||||
void OpenAutofillSettings();
|
void OpenAutofillSettings();
|
||||||
Task LaunchAppAsync(string appName, Page page);
|
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
|
else
|
||||||
{
|
{
|
||||||
cipher.Fields = null;
|
Fields = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(cipher.Type)
|
switch(cipher.Type)
|
||||||
|
|
|
@ -71,9 +71,12 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings);
|
SliderCell = new SliderViewCell(this, _passwordGenerationService, _settings);
|
||||||
|
|
||||||
var buttonColor = Color.FromHex("3c8dbc");
|
RegenerateCell = new ExtendedTextCell
|
||||||
RegenerateCell = new ExtendedTextCell { Text = AppResources.RegeneratePassword, TextColor = buttonColor };
|
{
|
||||||
CopyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = buttonColor };
|
Text = AppResources.RegeneratePassword,
|
||||||
|
TextColor = Colors.Primary
|
||||||
|
};
|
||||||
|
CopyCell = new ExtendedTextCell { Text = AppResources.CopyPassword, TextColor = Colors.Primary };
|
||||||
|
|
||||||
UppercaseCell = new ExtendedSwitchCell
|
UppercaseCell = new ExtendedSwitchCell
|
||||||
{
|
{
|
||||||
|
|
|
@ -86,12 +86,14 @@ namespace Bit.App.Pages
|
||||||
public TableRoot TableRoot { get; set; }
|
public TableRoot TableRoot { get; set; }
|
||||||
public TableSection TopSection { get; set; }
|
public TableSection TopSection { get; set; }
|
||||||
public TableSection MiddleSection { get; set; }
|
public TableSection MiddleSection { get; set; }
|
||||||
|
public TableSection FieldsSection { get; set; }
|
||||||
public ExtendedTableView Table { get; set; }
|
public ExtendedTableView Table { get; set; }
|
||||||
|
|
||||||
public FormEntryCell NameCell { get; private set; }
|
public FormEntryCell NameCell { get; private set; }
|
||||||
public FormEditorCell NotesCell { get; private set; }
|
public FormEditorCell NotesCell { get; private set; }
|
||||||
public FormPickerCell FolderCell { get; private set; }
|
public FormPickerCell FolderCell { get; private set; }
|
||||||
public ExtendedSwitchCell FavoriteCell { get; set; }
|
public ExtendedSwitchCell FavoriteCell { get; set; }
|
||||||
|
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||||
|
@ -184,6 +186,11 @@ namespace Bit.App.Pages
|
||||||
NotesCell.InitEvents();
|
NotesCell.InitEvents();
|
||||||
FolderCell.InitEvents();
|
FolderCell.InitEvents();
|
||||||
|
|
||||||
|
if(AddFieldCell != null)
|
||||||
|
{
|
||||||
|
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(_type)
|
switch(_type)
|
||||||
{
|
{
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
|
@ -256,6 +263,11 @@ namespace Bit.App.Pages
|
||||||
NotesCell.Dispose();
|
NotesCell.Dispose();
|
||||||
FolderCell.Dispose();
|
FolderCell.Dispose();
|
||||||
|
|
||||||
|
if(AddFieldCell != null)
|
||||||
|
{
|
||||||
|
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(_type)
|
switch(_type)
|
||||||
{
|
{
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
|
@ -301,6 +313,17 @@ namespace Bit.App.Pages
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(FieldsSection != null && FieldsSection.Count > 0)
|
||||||
|
{
|
||||||
|
foreach(var cell in FieldsSection)
|
||||||
|
{
|
||||||
|
if(cell is FormEntryCell entrycell)
|
||||||
|
{
|
||||||
|
entrycell.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnBackButtonPressed()
|
protected override bool OnBackButtonPressed()
|
||||||
|
@ -540,6 +563,14 @@ namespace Bit.App.Pages
|
||||||
NameCell.NextElement = NotesCell.Editor;
|
NameCell.NextElement = NotesCell.Editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldsSection = new TableSection(AppResources.CustomFields);
|
||||||
|
AddFieldCell = new ExtendedTextCell
|
||||||
|
{
|
||||||
|
Text = AppResources.NewCustomField,
|
||||||
|
TextColor = Colors.Primary
|
||||||
|
};
|
||||||
|
FieldsSection.Add(AddFieldCell);
|
||||||
|
|
||||||
// Make table
|
// Make table
|
||||||
TableRoot = new TableRoot
|
TableRoot = new TableRoot
|
||||||
{
|
{
|
||||||
|
@ -548,7 +579,8 @@ namespace Bit.App.Pages
|
||||||
new TableSection(AppResources.Notes)
|
new TableSection(AppResources.Notes)
|
||||||
{
|
{
|
||||||
NotesCell
|
NotesCell
|
||||||
}
|
},
|
||||||
|
FieldsSection
|
||||||
};
|
};
|
||||||
|
|
||||||
Table = new ExtendedTableView
|
Table = new ExtendedTableView
|
||||||
|
@ -744,6 +776,8 @@ namespace Bit.App.Pages
|
||||||
cipher.FolderId = Folders.ElementAt(FolderCell.Picker.SelectedIndex - 1).Id;
|
cipher.FolderId = Folders.ElementAt(FolderCell.Picker.SelectedIndex - 1).Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Helpers.ProcessFieldsSectionForSave(FieldsSection, cipher);
|
||||||
|
|
||||||
_deviceActionService.ShowLoading(AppResources.Saving);
|
_deviceActionService.ShowLoading(AppResources.Saving);
|
||||||
var saveTask = await _cipherService.SaveAsync(cipher);
|
var saveTask = await _cipherService.SaveAsync(cipher);
|
||||||
_deviceActionService.HideLoading();
|
_deviceActionService.HideLoading();
|
||||||
|
@ -782,6 +816,10 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
ToolbarItems.Add(saveToolBarItem);
|
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 TableRoot TableRoot { get; set; }
|
||||||
public TableSection TopSection { get; set; }
|
public TableSection TopSection { get; set; }
|
||||||
public TableSection MiddleSection { get; set; }
|
public TableSection MiddleSection { get; set; }
|
||||||
|
public TableSection FieldsSection { get; set; }
|
||||||
public ExtendedTableView Table { get; set; }
|
public ExtendedTableView Table { get; set; }
|
||||||
|
|
||||||
public FormEntryCell NameCell { get; private set; }
|
public FormEntryCell NameCell { get; private set; }
|
||||||
|
@ -49,8 +50,8 @@ namespace Bit.App.Pages
|
||||||
public FormPickerCell FolderCell { get; private set; }
|
public FormPickerCell FolderCell { get; private set; }
|
||||||
public ExtendedSwitchCell FavoriteCell { get; set; }
|
public ExtendedSwitchCell FavoriteCell { get; 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; }
|
||||||
|
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||||
|
@ -152,12 +153,6 @@ namespace Bit.App.Pages
|
||||||
ShowDisclousure = true
|
ShowDisclousure = true
|
||||||
};
|
};
|
||||||
|
|
||||||
CustomFieldsCell = new ExtendedTextCell
|
|
||||||
{
|
|
||||||
Text = AppResources.CustomFields,
|
|
||||||
ShowDisclousure = true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sections
|
// Sections
|
||||||
TopSection = new TableSection(AppResources.ItemInformation)
|
TopSection = new TableSection(AppResources.ItemInformation)
|
||||||
{
|
{
|
||||||
|
@ -168,8 +163,7 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
FolderCell,
|
FolderCell,
|
||||||
FavoriteCell,
|
FavoriteCell,
|
||||||
AttachmentsCell,
|
AttachmentsCell
|
||||||
CustomFieldsCell
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
|
@ -405,6 +399,27 @@ namespace Bit.App.Pages
|
||||||
NameCell.NextElement = NotesCell.Editor;
|
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
|
// Make table
|
||||||
TableRoot = new TableRoot
|
TableRoot = new TableRoot
|
||||||
{
|
{
|
||||||
|
@ -414,6 +429,7 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
NotesCell
|
NotesCell
|
||||||
},
|
},
|
||||||
|
FieldsSection,
|
||||||
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
new TableSection(Helpers.GetEmptyTableSectionTitle())
|
||||||
{
|
{
|
||||||
DeleteCell
|
DeleteCell
|
||||||
|
@ -614,6 +630,8 @@ namespace Bit.App.Pages
|
||||||
Cipher.FolderId = null;
|
Cipher.FolderId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Helpers.ProcessFieldsSectionForSave(FieldsSection, Cipher);
|
||||||
|
|
||||||
_deviceActionService.ShowLoading(AppResources.Saving);
|
_deviceActionService.ShowLoading(AppResources.Saving);
|
||||||
var saveTask = await _cipherService.SaveAsync(Cipher);
|
var saveTask = await _cipherService.SaveAsync(Cipher);
|
||||||
_deviceActionService.HideLoading();
|
_deviceActionService.HideLoading();
|
||||||
|
@ -653,14 +671,14 @@ 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;
|
||||||
}
|
}
|
||||||
|
if(AddFieldCell != null)
|
||||||
|
{
|
||||||
|
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(Cipher.Type)
|
switch(Cipher.Type)
|
||||||
{
|
{
|
||||||
|
@ -713,6 +731,17 @@ namespace Bit.App.Pages
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(FieldsSection != null && FieldsSection.Count > 0)
|
||||||
|
{
|
||||||
|
foreach(var cell in FieldsSection)
|
||||||
|
{
|
||||||
|
if(cell is FormEntryCell entrycell)
|
||||||
|
{
|
||||||
|
entrycell.InitEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDisappearing()
|
protected override void OnDisappearing()
|
||||||
|
@ -727,14 +756,14 @@ 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;
|
||||||
}
|
}
|
||||||
|
if(AddFieldCell != null)
|
||||||
|
{
|
||||||
|
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(Cipher.Type)
|
switch(Cipher.Type)
|
||||||
{
|
{
|
||||||
|
@ -787,6 +816,17 @@ namespace Bit.App.Pages
|
||||||
default:
|
default:
|
||||||
break;
|
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)
|
private void PasswordButton_Clicked(object sender, EventArgs e)
|
||||||
|
@ -840,12 +880,6 @@ 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(_cipherId));
|
|
||||||
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)
|
||||||
|
@ -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()
|
private void AlertNoConnection()
|
||||||
{
|
{
|
||||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Custom Fields.
|
/// Looks up a localized string similar to Custom Fields.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to December.
|
/// Looks up a localized string similar to December.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to File.
|
/// Looks up a localized string similar to File.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to New item created..
|
/// Looks up a localized string similar to New item created..
|
||||||
/// </summary>
|
/// </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>
|
/// <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>
|
||||||
|
@ -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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Re-type Master Password.
|
/// Looks up a localized string similar to Re-type Master Password.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Self-hosted Environment.
|
/// Looks up a localized string similar to Self-hosted Environment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1023,12 +1023,6 @@
|
||||||
<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>
|
|
||||||
<data name="CopyNumber" xml:space="preserve">
|
<data name="CopyNumber" xml:space="preserve">
|
||||||
<value>Copy Number</value>
|
<value>Copy Number</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -1234,4 +1228,25 @@
|
||||||
<data name="BitwardenAutofillGoToSettings" xml:space="preserve">
|
<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>
|
<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>
|
||||||
|
<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>
|
</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.Abstractions;
|
||||||
|
using Bit.App.Controls;
|
||||||
using Bit.App.Enums;
|
using Bit.App.Enums;
|
||||||
|
using Bit.App.Models;
|
||||||
using Bit.App.Models.Page;
|
using Bit.App.Models.Page;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Plugin.Settings.Abstractions;
|
using Plugin.Settings.Abstractions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using XLabs.Ioc;
|
using XLabs.Ioc;
|
||||||
|
@ -195,5 +198,180 @@ namespace Bit.App.Utilities
|
||||||
var addPage = new VaultAddCipherPage(selectedType, defaultFolderId: folderId);
|
var addPage = new VaultAddCipherPage(selectedType, defaultFolderId: folderId);
|
||||||
await page.Navigation.PushForDeviceAsync(addPage);
|
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 Acr.UserDialogs;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Models.Page;
|
using Bit.App.Models.Page;
|
||||||
|
using Bit.App.Resources;
|
||||||
using Coding4Fun.Toolkit.Controls;
|
using Coding4Fun.Toolkit.Controls;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -164,5 +165,20 @@ namespace Bit.UWP.Services
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
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();
|
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()
|
private UIViewController GetPresentedViewController()
|
||||||
{
|
{
|
||||||
var window = UIApplication.SharedApplication.KeyWindow;
|
var window = UIApplication.SharedApplication.KeyWindow;
|
||||||
|
|
Loading…
Reference in a new issue