1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-12-24 16:38:22 +01:00

view/add/edit login uris

This commit is contained in:
Kyle Spearrin 2018-03-05 22:39:56 -05:00
parent 83fd19784a
commit 4c8204f29a
7 changed files with 340 additions and 140 deletions

View File

@ -3,7 +3,7 @@ using System.ComponentModel;
using Xamarin.Forms;
using System.Collections.Generic;
using Bit.App.Enums;
using System.Linq;
using Bit.App.Resources;
namespace Bit.App.Models.Page
{
@ -12,6 +12,7 @@ namespace Bit.App.Models.Page
private string _name, _notes;
private List<Attachment> _attachments;
private List<Field> _fields;
private List<LoginUri> _loginUris;
// Login
private string _loginUsername, _loginPassword, _loginUri, _loginTotpCode;
@ -117,69 +118,17 @@ namespace Bit.App.Models.Page
public ImageSource LoginShowHideImage => RevealLoginPassword ?
ImageSource.FromFile("eye_slash.png") : ImageSource.FromFile("eye.png");
public string LoginUri
public List<LoginUri> LoginUris
{
get => _loginUri;
get => _loginUris;
set
{
_loginUri = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginUri)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginUriHost)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowLoginUri)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowLoginLaunch)));
}
}
public bool ShowLoginUri => !string.IsNullOrWhiteSpace(LoginUri);
public bool ShowLoginLaunch
{
get
{
if(!ShowLoginUri)
{
return false;
}
if(Device.RuntimePlatform == Device.Android && !LoginUri.StartsWith("http") &&
!LoginUri.StartsWith("androidapp://"))
{
return false;
}
if(Device.RuntimePlatform != Device.Android && !LoginUri.StartsWith("http"))
{
return false;
}
if(!Uri.TryCreate(LoginUri, UriKind.Absolute, out Uri uri))
{
return false;
}
return true;
}
}
public string LoginUriHost
{
get
{
if(!ShowLoginUri)
{
return null;
}
if(!Uri.TryCreate(LoginUri, UriKind.Absolute, out Uri uri))
{
return LoginUri;
}
if(DomainName.TryParseBaseDomain(uri.Host, out string domain))
{
return domain;
}
return uri.Host;
_loginUris = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginUris)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowLoginUris)));
}
}
public bool ShowLoginUris => (LoginUris?.Count ?? 0) > 0;
public string LoginTotpCode
{
@ -601,7 +550,23 @@ namespace Bit.App.Models.Page
case CipherType.Login:
LoginUsername = cipher.Login.Username?.Decrypt(cipher.OrganizationId);
LoginPassword = cipher.Login.Password?.Decrypt(cipher.OrganizationId);
LoginUri = cipher.Login.Uris?.FirstOrDefault()?.Uri?.Decrypt(cipher.OrganizationId);
if(cipher.Login.Uris != null)
{
var uris = new List<LoginUri>();
foreach(var uri in cipher.Login.Uris)
{
uris.Add(new LoginUri
{
Value = uri.Uri?.Decrypt(cipher.OrganizationId)
});
}
LoginUris = uris;
}
else
{
LoginUris = null;
}
break;
case CipherType.Card:
CardName = cipher.Card.CardholderName?.Decrypt(cipher.OrganizationId);
@ -666,5 +631,62 @@ namespace Bit.App.Models.Page
public FieldType Type { get; set; }
public bool Revealed { get; set; }
}
public class LoginUri
{
public string Value { get; set; }
public bool ShowLaunch
{
get
{
if(string.IsNullOrWhiteSpace(Value))
{
return false;
}
if(Device.RuntimePlatform == Device.Android && !IsWebsite && !IsApp)
{
return false;
}
if(Device.RuntimePlatform != Device.Android && !IsWebsite)
{
return false;
}
if(!Uri.TryCreate(Value, UriKind.Absolute, out Uri uri))
{
return false;
}
return true;
}
}
public string Host
{
get
{
if(string.IsNullOrWhiteSpace(Value))
{
return null;
}
if(!Uri.TryCreate(Value, UriKind.Absolute, out Uri uri))
{
return Value;
}
if(DomainName.TryParseBaseDomain(uri.Host, out string domain))
{
return domain;
}
return uri.Host;
}
}
public string Label => IsWebsite ? AppResources.Website : AppResources.URI;
public bool IsWebsite => Value.StartsWith("http://") || Value.StartsWith("https://");
public bool IsApp => Value.StartsWith(Constants.AndroidAppProtocol);
}
}
}

View File

@ -85,6 +85,7 @@ namespace Bit.App.Pages
public List<Folder> Folders { get; set; }
public TableRoot TableRoot { get; set; }
public TableSection TopSection { get; set; }
public TableSection UrisSection { get; set; }
public TableSection MiddleSection { get; set; }
public TableSection FieldsSection { get; set; }
public ExtendedTableView Table { get; set; }
@ -94,11 +95,11 @@ namespace Bit.App.Pages
public FormPickerCell FolderCell { get; private set; }
public ExtendedSwitchCell FavoriteCell { get; set; }
public ExtendedTextCell AddFieldCell { get; private set; }
public ExtendedTextCell AddUriCell { get; private set; }
// Login
public FormEntryCell LoginPasswordCell { get; private set; }
public FormEntryCell LoginUsernameCell { get; private set; }
public FormEntryCell LoginUriCell { get; private set; }
public FormEntryCell LoginTotpCell { get; private set; }
public ExtendedTextCell LoginGenerateCell { get; private set; }
@ -190,13 +191,16 @@ namespace Bit.App.Pages
{
AddFieldCell.Tapped += AddFieldCell_Tapped;
}
if(AddUriCell != null)
{
AddUriCell.Tapped += AddUriCell_Tapped;
}
switch(_type)
{
case CipherType.Login:
LoginPasswordCell.InitEvents();
LoginUsernameCell.InitEvents();
LoginUriCell.InitEvents();
LoginTotpCell.InitEvents();
LoginPasswordCell.Button.Clicked += PasswordButton_Clicked;
LoginGenerateCell.Tapped += GenerateCell_Tapped;
@ -237,6 +241,9 @@ namespace Bit.App.Pages
break;
}
Helpers.InitSectionEvents(FieldsSection);
Helpers.InitSectionEvents(UrisSection);
if(_type == CipherType.Login && !_fromAutofill && !_settings.GetValueOrDefault(AddedLoginAlertKey, false))
{
_settings.AddOrUpdateValue(AddedLoginAlertKey, true);
@ -267,6 +274,10 @@ namespace Bit.App.Pages
{
AddFieldCell.Tapped -= AddFieldCell_Tapped;
}
if(AddUriCell != null)
{
AddUriCell.Tapped -= AddUriCell_Tapped;
}
switch(_type)
{
@ -274,7 +285,6 @@ namespace Bit.App.Pages
LoginTotpCell.Dispose();
LoginPasswordCell.Dispose();
LoginUsernameCell.Dispose();
LoginUriCell.Dispose();
LoginPasswordCell.Button.Clicked -= PasswordButton_Clicked;
LoginGenerateCell.Tapped -= GenerateCell_Tapped;
if(LoginTotpCell?.Button != null)
@ -314,16 +324,8 @@ namespace Bit.App.Pages
break;
}
if(FieldsSection != null && FieldsSection.Count > 0)
{
foreach(var cell in FieldsSection)
{
if(cell is FormEntryCell entrycell)
{
entrycell.Dispose();
}
}
}
Helpers.DisposeSectionEvents(FieldsSection);
Helpers.DisposeSectionEvents(UrisSection);
}
protected override bool OnBackButtonPressed()
@ -398,8 +400,7 @@ namespace Bit.App.Pages
if(_type == CipherType.Login)
{
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, nextElement: NotesCell.Editor,
useButton: _deviceInfo.HasCamera);
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, useButton: _deviceInfo.HasCamera);
if(_deviceInfo.HasCamera)
{
LoginTotpCell.Button.Image = "camera.png";
@ -435,20 +436,23 @@ namespace Bit.App.Pages
LoginUsernameCell.Entry.Text = _defaultUsername;
}
LoginUriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: LoginUsernameCell.Entry);
if(!string.IsNullOrWhiteSpace(_defaultUri))
{
LoginUriCell.Entry.Text = _defaultUri;
}
NameCell.NextElement = LoginUriCell.Entry;
NameCell.NextElement = LoginUsernameCell.Entry;
// Build sections
TopSection.Add(LoginUriCell);
TopSection.Add(LoginUsernameCell);
TopSection.Add(LoginPasswordCell);
TopSection.Add(LoginGenerateCell);
MiddleSection.Insert(0, LoginTotpCell);
TopSection.Add(LoginTotpCell);
// Uris
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
AddUriCell = new ExtendedTextCell
{
Text = AppResources.NewUri,
TextColor = Colors.Primary
};
UrisSection.Add(AddUriCell);
UrisSection.Insert(0, Helpers.MakeUriCell(string.Empty, UrisSection));
}
else if(_type == CipherType.Card)
{
@ -583,6 +587,11 @@ namespace Bit.App.Pages
FieldsSection
};
if(UrisSection != null)
{
TableRoot.Insert(1, UrisSection);
}
Table = new ExtendedTableView
{
Intent = TableIntent.Settings,
@ -638,8 +647,6 @@ namespace Bit.App.Pages
case CipherType.Login:
cipher.Login = new Login
{
Uri = string.IsNullOrWhiteSpace(LoginUriCell.Entry.Text) ? null :
LoginUriCell.Entry.Text.Encrypt(),
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
LoginUsernameCell.Entry.Text.Encrypt(),
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
@ -647,6 +654,8 @@ namespace Bit.App.Pages
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
LoginTotpCell.Entry.Text.Encrypt(),
};
Helpers.ProcessUrisSectionForSave(UrisSection, cipher);
break;
case CipherType.SecureNote:
cipher.SecureNote = new SecureNote
@ -821,5 +830,15 @@ namespace Bit.App.Pages
{
await Helpers.AddField(this, FieldsSection);
}
private void AddUriCell_Tapped(object sender, EventArgs e)
{
var cell = Helpers.MakeUriCell(string.Empty, UrisSection);
if(cell != null)
{
UrisSection.Insert(UrisSection.Count - 1, cell);
cell.InitEvents();
}
}
}
}

View File

@ -41,6 +41,7 @@ namespace Bit.App.Pages
public List<Folder> Folders { get; set; }
public TableRoot TableRoot { get; set; }
public TableSection TopSection { get; set; }
public TableSection UrisSection { get; set; }
public TableSection MiddleSection { get; set; }
public TableSection FieldsSection { get; set; }
public ExtendedTableView Table { get; set; }
@ -52,11 +53,11 @@ namespace Bit.App.Pages
public ExtendedTextCell AttachmentsCell { get; private set; }
public ExtendedTextCell DeleteCell { get; private set; }
public ExtendedTextCell AddFieldCell { get; private set; }
public ExtendedTextCell AddUriCell { get; private set; }
// Login
public FormEntryCell LoginPasswordCell { get; private set; }
public FormEntryCell LoginUsernameCell { get; private set; }
public FormEntryCell LoginUriCell { get; private set; }
public FormEntryCell LoginTotpCell { get; private set; }
public ExtendedTextCell LoginGenerateCell { get; private set; }
@ -169,8 +170,7 @@ namespace Bit.App.Pages
// Types
if(Cipher.Type == CipherType.Login)
{
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, nextElement: NotesCell.Editor,
useButton: _deviceInfo.HasCamera);
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, useButton: _deviceInfo.HasCamera);
if(_deviceInfo.HasCamera)
{
LoginTotpCell.Button.Image = "camera.png";
@ -201,18 +201,31 @@ namespace Bit.App.Pages
LoginUsernameCell.Entry.DisableAutocapitalize = true;
LoginUsernameCell.Entry.Autocorrect = false;
LoginUriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: LoginUsernameCell.Entry);
LoginUriCell.Entry.Text = Cipher.Login?.Uri?.Decrypt(Cipher.OrganizationId);
// Name
NameCell.NextElement = LoginUriCell.Entry;
NameCell.NextElement = LoginUsernameCell.Entry;
// Build sections
TopSection.Add(LoginUriCell);
TopSection.Add(LoginUsernameCell);
TopSection.Add(LoginPasswordCell);
TopSection.Add(LoginGenerateCell);
MiddleSection.Insert(0, LoginTotpCell);
TopSection.Add(LoginTotpCell);
// Uris
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
AddUriCell = new ExtendedTextCell
{
Text = AppResources.NewUri,
TextColor = Colors.Primary
};
UrisSection.Add(AddUriCell);
if(Cipher.Login?.Uris != null)
{
foreach(var uri in Cipher.Login.Uris)
{
var value = uri.Uri?.Decrypt(Cipher.OrganizationId);
UrisSection.Insert(UrisSection.Count - 1, Helpers.MakeUriCell(value, UrisSection));
}
}
}
else if(Cipher.Type == CipherType.Card)
{
@ -436,6 +449,11 @@ namespace Bit.App.Pages
}
};
if(UrisSection != null)
{
TableRoot.Insert(1, UrisSection);
}
Table = new ExtendedTableView
{
Intent = TableIntent.Settings,
@ -488,8 +506,6 @@ namespace Bit.App.Pages
case CipherType.Login:
Cipher.Login = new Login
{
Uri = string.IsNullOrWhiteSpace(LoginUriCell.Entry.Text) ? null :
LoginUriCell.Entry.Text.Encrypt(Cipher.OrganizationId),
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
LoginUsernameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
@ -497,6 +513,8 @@ namespace Bit.App.Pages
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
LoginTotpCell.Entry.Text.Encrypt(Cipher.OrganizationId),
};
Helpers.ProcessUrisSectionForSave(UrisSection, Cipher);
break;
case CipherType.SecureNote:
Cipher.SecureNote = new SecureNote
@ -679,13 +697,16 @@ namespace Bit.App.Pages
{
AddFieldCell.Tapped += AddFieldCell_Tapped;
}
if(AddUriCell != null)
{
AddUriCell.Tapped += AddUriCell_Tapped;
}
switch(Cipher.Type)
{
case CipherType.Login:
LoginPasswordCell?.InitEvents();
LoginUsernameCell?.InitEvents();
LoginUriCell?.InitEvents();
LoginTotpCell?.InitEvents();
if(LoginPasswordCell?.Button != null)
{
@ -732,16 +753,8 @@ namespace Bit.App.Pages
break;
}
if(FieldsSection != null && FieldsSection.Count > 0)
{
foreach(var cell in FieldsSection)
{
if(cell is FormEntryCell entrycell)
{
entrycell.InitEvents();
}
}
}
Helpers.InitSectionEvents(FieldsSection);
Helpers.InitSectionEvents(UrisSection);
}
protected override void OnDisappearing()
@ -764,6 +777,10 @@ namespace Bit.App.Pages
{
AddFieldCell.Tapped -= AddFieldCell_Tapped;
}
if(AddUriCell != null)
{
AddUriCell.Tapped -= AddUriCell_Tapped;
}
switch(Cipher.Type)
{
@ -771,7 +788,6 @@ namespace Bit.App.Pages
LoginTotpCell?.Dispose();
LoginPasswordCell?.Dispose();
LoginUsernameCell?.Dispose();
LoginUriCell?.Dispose();
if(LoginPasswordCell?.Button != null)
{
LoginPasswordCell.Button.Clicked -= PasswordButton_Clicked;
@ -817,16 +833,8 @@ namespace Bit.App.Pages
break;
}
if(FieldsSection != null && FieldsSection.Count > 0)
{
foreach(var cell in FieldsSection)
{
if(cell is FormEntryCell entrycell)
{
entrycell.Dispose();
}
}
}
Helpers.DisposeSectionEvents(FieldsSection);
Helpers.DisposeSectionEvents(UrisSection);
}
private void PasswordButton_Clicked(object sender, EventArgs e)
@ -920,6 +928,16 @@ namespace Bit.App.Pages
await Helpers.AddField(this, FieldsSection);
}
private void AddUriCell_Tapped(object sender, EventArgs e)
{
var cell = Helpers.MakeUriCell(string.Empty, UrisSection);
if(cell != null)
{
UrisSection.Insert(UrisSection.Count - 1, cell);
cell.InitEvents();
}
}
private void AlertNoConnection()
{
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,

View File

@ -39,6 +39,7 @@ namespace Bit.App.Pages
private VaultViewCipherPageModel Model { get; set; } = new VaultViewCipherPageModel();
private ExtendedTableView Table { get; set; }
private TableSection ItemInformationSection { get; set; }
public TableSection UrisSection { get; set; }
private TableSection NotesSection { get; set; }
private TableSection AttachmentsSection { get; set; }
private TableSection FieldsSection { get; set; }
@ -50,7 +51,6 @@ namespace Bit.App.Pages
// Login
public LabeledValueCell LoginUsernameCell { get; set; }
public LabeledValueCell LoginPasswordCell { get; set; }
public LabeledValueCell LoginUriCell { get; set; }
public LabeledValueCell LoginTotpCodeCell { get; set; }
// Card
@ -141,22 +141,6 @@ namespace Bit.App.Pages
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
LoginPasswordCell.Value.LineBreakMode = LineBreakMode.WordWrap;
// URI
LoginUriCell = new LabeledValueCell(AppResources.Website, button1Image: "launch.png");
LoginUriCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.LoginUriHost));
LoginUriCell.Button1.SetBinding(IsVisibleProperty, nameof(VaultViewCipherPageModel.ShowLoginLaunch));
LoginUriCell.Button1.Command = new Command(async () =>
{
if(Device.RuntimePlatform == Device.Android && Model.LoginUri.StartsWith("androidapp://"))
{
await _deviceActionService.LaunchAppAsync(Model.LoginUri, this);
}
else if(Model.LoginUri.StartsWith("http://") || Model.LoginUri.StartsWith("https://"))
{
Device.OpenUri(new Uri(Model.LoginUri));
}
});
// Totp
LoginTotpCodeCell = new LabeledValueCell(
AppResources.VerificationCodeTotp, button1Image: "clipboard.png", subText: "--");
@ -299,6 +283,22 @@ namespace Bit.App.Pages
private void BuildTable(Cipher cipher)
{
// URIs
if(UrisSection != null && Table.Root.Contains(UrisSection))
{
Table.Root.Remove(UrisSection);
}
if(Model.ShowLoginUris)
{
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
foreach(var uri in Model.LoginUris)
{
UrisSection.Add(new UriViewCell(this, uri));
}
Table.Root.Add(UrisSection);
}
// Notes
if(Table.Root.Contains(NotesSection))
{
Table.Root.Remove(NotesSection);
@ -365,7 +365,6 @@ namespace Bit.App.Pages
switch(cipher.Type)
{
case CipherType.Login:
AddSectionCell(LoginUriCell, Model.ShowLoginUri);
AddSectionCell(LoginUsernameCell, Model.ShowLoginUsername);
AddSectionCell(LoginPasswordCell, Model.ShowLoginPassword);
@ -575,8 +574,7 @@ namespace Bit.App.Pages
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, bool? a, bool? b)
: base(field.Name, field.MaskedValue, string.Empty, "clipboard.png")
{
Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular",
Android: "monospace", Windows: "Courier");
Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
if(Device.RuntimePlatform == Device.iOS)
{
Button1.Margin = new Thickness(10, 0);
@ -610,5 +608,29 @@ namespace Bit.App.Pages
}
}
}
public class UriViewCell : LabeledValueCell
{
public UriViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.LoginUri uri)
: base(uri.Label, uri.Host, uri.ShowLaunch ? "launch.png" : null, "clipboard.png")
{
Value.LineBreakMode = LineBreakMode.TailTruncation;
if(Button1 != null)
{
Button1.Command = new Command(async () =>
{
if(Device.RuntimePlatform == Device.Android && uri.IsApp)
{
await page._deviceActionService.LaunchAppAsync(uri.Value, page);
}
else if(uri.IsWebsite)
{
Device.OpenUri(new Uri(uri.Value));
}
});
}
Button2.Command = new Command(() => page.Copy(uri.Value, AppResources.URI));
}
}
}
}

View File

@ -2130,6 +2130,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to New URI.
/// </summary>
public static string NewUri {
get {
return ResourceManager.GetString("NewUri", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
@ -2940,6 +2949,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to URI {0}.
/// </summary>
public static string URIPosition {
get {
return ResourceManager.GetString("URIPosition", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use another two-step login method.
/// </summary>

View File

@ -1249,4 +1249,11 @@
<data name="Remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="NewUri" xml:space="preserve">
<value>New URI</value>
</data>
<data name="URIPosition" xml:space="preserve">
<value>URI {0}</value>
<comment>Label for a uri/url with position. i.e. URI 1, URI 2, etc</comment>
</data>
</root>

View File

@ -373,5 +373,99 @@ namespace Bit.App.Utilities
cipher.Fields = null;
}
}
public static FormEntryCell MakeUriCell(string value, TableSection urisSection)
{
var label = string.Format(AppResources.URIPosition, urisSection.Count);
var cell = new FormEntryCell(label, entryKeyboard: Keyboard.Url);
cell.Entry.Text = value;
cell.Entry.DisableAutocapitalize = true;
cell.Entry.Autocorrect = false;
var deleteAction = new MenuItem { Text = AppResources.Remove, IsDestructive = true };
deleteAction.Clicked += (sender, e) =>
{
if(urisSection.Contains(cell))
{
urisSection.Remove(cell);
if(cell is FormEntryCell feCell)
{
feCell.Dispose();
}
cell = null;
for(int i = 0; i < urisSection.Count; i++)
{
if(urisSection[i] is FormEntryCell uriCell)
{
uriCell.Label.Text = string.Format(AppResources.URIPosition, i + 1);
}
}
}
};
var optionsAction = new MenuItem { Text = AppResources.Options };
optionsAction.Clicked += async (sender, e) =>
{
};
cell.ContextActions.Add(optionsAction);
cell.ContextActions.Add(deleteAction);
return cell;
}
public static void ProcessUrisSectionForSave(TableSection urisSection, Cipher cipher)
{
if(urisSection != null && urisSection.Count > 0)
{
var uris = new List<LoginUri>();
foreach(var cell in urisSection)
{
if(cell is FormEntryCell entryCell && !string.IsNullOrWhiteSpace(entryCell.Entry.Text))
{
uris.Add(new LoginUri
{
Uri = entryCell.Entry.Text.Encrypt(cipher.OrganizationId),
Match = null
});
}
}
cipher.Login.Uris = uris;
}
if(!cipher.Login.Uris?.Any() ?? true)
{
cipher.Login.Uris = null;
}
}
public static void InitSectionEvents(TableSection section)
{
if(section != null && section.Count > 0)
{
foreach(var cell in section)
{
if(cell is FormEntryCell entrycell)
{
entrycell.InitEvents();
}
}
}
}
public static void DisposeSectionEvents(TableSection section)
{
if(section != null && section.Count > 0)
{
foreach(var cell in section)
{
if(cell is FormEntryCell entrycell)
{
entrycell.Dispose();
}
}
}
}
}
}