add other cipher type support to vault listings

This commit is contained in:
Kyle Spearrin 2017-10-18 21:35:33 -04:00
parent 0020bd0fb7
commit 74ac9cbbbe
10 changed files with 267 additions and 138 deletions

View file

@ -104,7 +104,7 @@ namespace Bit.Android
OpenAccessibilitySettings();
});
MessagingCenter.Subscribe<Xamarin.Forms.Application, VaultListPageModel.Login>(
MessagingCenter.Subscribe<Xamarin.Forms.Application, VaultListPageModel.Cipher>(
Xamarin.Forms.Application.Current, "Autofill", (sender, args) =>
{
ReturnCredentials(args);
@ -128,10 +128,10 @@ namespace Bit.Android
});
}
private void ReturnCredentials(VaultListPageModel.Login login)
private void ReturnCredentials(VaultListPageModel.Cipher cipher)
{
Intent data = new Intent();
if(login == null)
if(cipher == null)
{
data.PutExtra("canceled", "true");
}
@ -139,14 +139,14 @@ namespace Bit.Android
{
var isPremium = Resolver.Resolve<ITokenService>()?.TokenPremium ?? false;
var autoCopyEnabled = !_settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false);
if(isPremium && autoCopyEnabled && _deviceActionService != null && login.Totp.Value != null)
if(isPremium && autoCopyEnabled && _deviceActionService != null && cipher.Totp.Value != null)
{
_deviceActionService.CopyToClipboard(App.Utilities.Crypto.Totp(login.Totp.Value));
_deviceActionService.CopyToClipboard(App.Utilities.Crypto.Totp(cipher.Totp.Value));
}
data.PutExtra("uri", login.Uri.Value);
data.PutExtra("username", login.Username);
data.PutExtra("password", login.Password.Value);
data.PutExtra("uri", cipher.Uri.Value);
data.PutExtra("username", cipher.Username);
data.PutExtra("password", cipher.Password.Value);
}
if(Parent == null)

View file

@ -68,7 +68,7 @@ namespace Bit.App
if(authService.IsAuthenticated && _uri != null)
{
MainPage = new ExtendedNavigationPage(new VaultAutofillListLoginsPage(_uri));
MainPage = new ExtendedNavigationPage(new VaultAutofillListCiphersPage(_uri));
}
else if(authService.IsAuthenticated)
{

View file

@ -180,7 +180,7 @@
<Compile Include="Pages\Settings\SettingsPage.cs" />
<Compile Include="Pages\Settings\SettingsListFoldersPage.cs" />
<Compile Include="Pages\Vault\VaultCustomFieldsPage.cs" />
<Compile Include="Pages\Vault\VaultAutofillListLoginsPage.cs" />
<Compile Include="Pages\Vault\VaultAutofillListCiphersPage.cs" />
<Compile Include="Pages\Vault\VaultAttachmentsPage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Abstractions\Repositories\ICipherRepository.cs" />
@ -357,7 +357,7 @@
<Compile Include="Pages\Vault\VaultAddLoginPage.cs" />
<Compile Include="Pages\Vault\VaultViewLoginPage.cs" />
<Compile Include="Pages\Vault\VaultEditLoginPage.cs" />
<Compile Include="Pages\Vault\VaultListLoginsPage.cs" />
<Compile Include="Pages\Vault\VaultListCiphersPage.cs" />
<Compile Include="Services\PasswordGenerationService.cs" />
<Compile Include="Utilities\Base32.cs" />
<Compile Include="Utilities\Crypto.cs" />

View file

@ -7,15 +7,15 @@ namespace Bit.App.Controls
public class VaultListViewCell : LabeledDetailCell
{
public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter),
typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null);
typeof(VaultListPageModel.Cipher), typeof(VaultListViewCell), null);
public VaultListViewCell(Action<VaultListPageModel.Login> moreClickedAction)
public VaultListViewCell(Action<VaultListPageModel.Cipher> moreClickedAction)
{
SetBinding(LoginParameterProperty, new Binding("."));
Label.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Login.Name));
Detail.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Login.Username));
LabelIcon.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Login.Shared));
LabelIcon2.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Login.HasAttachments));
Label.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Cipher.Name));
Detail.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Cipher.Subtitle));
LabelIcon.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Cipher.Shared));
LabelIcon2.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Cipher.HasAttachments));
Button.Image = "more";
Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter));
@ -27,9 +27,9 @@ namespace Bit.App.Controls
BackgroundColor = Color.White;
}
public VaultListPageModel.Login LoginParameter
public VaultListPageModel.Cipher LoginParameter
{
get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; }
get { return GetValue(LoginParameterProperty) as VaultListPageModel.Cipher; }
set { SetValue(LoginParameterProperty, value); }
}
}

View file

@ -2,24 +2,51 @@
using System.Collections.Generic;
using Bit.App.Resources;
using System.Linq;
using Bit.App.Enums;
namespace Bit.App.Models.Page
{
public class VaultListPageModel
{
public class Login
public class Cipher
{
public Login(Models.Cipher cipher)
public Cipher(Models.Cipher cipher)
{
Id = cipher.Id;
Shared = !string.IsNullOrWhiteSpace(cipher.OrganizationId);
HasAttachments = cipher.Attachments?.Any() ?? false;
FolderId = cipher.FolderId;
Name = cipher.Name?.Decrypt(cipher.OrganizationId);
Username = cipher.Login?.Username?.Decrypt(cipher.OrganizationId) ?? " ";
Password = new Lazy<string>(() => cipher.Login?.Password?.Decrypt(cipher.OrganizationId));
Uri = new Lazy<string>(() => cipher.Login?.Uri?.Decrypt(cipher.OrganizationId));
Totp = new Lazy<string>(() => cipher.Login?.Totp?.Decrypt(cipher.OrganizationId));
Type = cipher.Type;
switch(cipher.Type)
{
case CipherType.Login:
Username = cipher.Login?.Username?.Decrypt(cipher.OrganizationId) ?? " ";
Password = new Lazy<string>(() => cipher.Login?.Password?.Decrypt(cipher.OrganizationId));
Uri = new Lazy<string>(() => cipher.Login?.Uri?.Decrypt(cipher.OrganizationId));
Totp = new Lazy<string>(() => cipher.Login?.Totp?.Decrypt(cipher.OrganizationId));
Subtitle = Username;
break;
case CipherType.SecureNote:
Subtitle = " ";
break;
case CipherType.Card:
var cardNumber = cipher.Card?.Number?.Decrypt(cipher.OrganizationId) ?? " ";
var cardBrand = cipher.Card?.Brand?.Decrypt(cipher.OrganizationId) ?? " ";
CardCode = new Lazy<string>(() => cipher.Card?.Code?.Decrypt(cipher.OrganizationId));
Subtitle = $"{cardBrand}, *{cardNumber}";
break;
case CipherType.Identity:
var firstName = cipher.Identity?.FirstName?.Decrypt(cipher.OrganizationId) ?? " ";
var lastName = cipher.Identity?.LastName?.Decrypt(cipher.OrganizationId) ?? " ";
Subtitle = $"{firstName} {lastName}";
break;
default:
break;
}
}
public string Id { get; set; }
@ -27,16 +54,24 @@ namespace Bit.App.Models.Page
public bool HasAttachments { get; set; }
public string FolderId { get; set; }
public string Name { get; set; }
public string Subtitle { get; set; }
public CipherType Type { get; set; }
// Login metadata
public string Username { get; set; }
public Lazy<string> Password { get; set; }
public Lazy<string> Uri { get; set; }
public Lazy<string> Totp { get; set; }
// Login metadata
public string CardNumber { get; set; }
public Lazy<string> CardCode { get; set; }
}
public class AutofillLogin : Login
public class AutofillCipher : Cipher
{
public AutofillLogin(Models.Cipher login, bool fuzzy = false)
: base(login)
public AutofillCipher(Models.Cipher cipher, bool fuzzy = false)
: base(cipher)
{
Fuzzy = fuzzy;
}
@ -44,7 +79,7 @@ namespace Bit.App.Models.Page
public bool Fuzzy { get; set; }
}
public class Folder : List<Login>
public class Folder : List<Cipher>
{
public Folder(Models.Folder folder)
{
@ -52,18 +87,18 @@ namespace Bit.App.Models.Page
Name = folder.Name?.Decrypt();
}
public Folder(List<Login> logins)
public Folder(List<Cipher> ciphers)
{
AddRange(logins);
AddRange(ciphers);
}
public string Id { get; set; }
public string Name { get; set; } = AppResources.FolderNone;
}
public class AutofillGrouping : List<AutofillLogin>
public class AutofillGrouping : List<AutofillCipher>
{
public AutofillGrouping(List<AutofillLogin> logins, string name)
public AutofillGrouping(List<AutofillCipher> logins, string name)
{
AddRange(logins);
Name = name;

View file

@ -12,8 +12,8 @@ namespace Bit.App.Pages
TintColor = Color.FromHex("3c8dbc");
var settingsNavigation = new ExtendedNavigationPage(new SettingsPage());
var favoritesNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(true, uri));
var vaultNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(false, uri));
var favoritesNavigation = new ExtendedNavigationPage(new VaultListCiphersPage(true, uri));
var vaultNavigation = new ExtendedNavigationPage(new VaultListCiphersPage(false, uri));
var toolsNavigation = new ExtendedNavigationPage(new ToolsPage());
favoritesNavigation.Title = AppResources.Favorites;

View file

@ -15,7 +15,7 @@ using System.Collections.Generic;
namespace Bit.App.Pages
{
public class VaultAutofillListLoginsPage : ExtendedContentPage
public class VaultAutofillListCiphersPage : ExtendedContentPage
{
private readonly ICipherService _cipherService;
private readonly IDeviceInfoService _deviceInfoService;
@ -24,7 +24,7 @@ namespace Bit.App.Pages
private CancellationTokenSource _filterResultsCancellationTokenSource;
private readonly string _name;
public VaultAutofillListLoginsPage(string uriString)
public VaultAutofillListCiphersPage(string uriString)
: base(true)
{
Uri = uriString;
@ -50,13 +50,13 @@ namespace Bit.App.Pages
Init();
}
public ExtendedObservableCollection<VaultListPageModel.AutofillGrouping> PresentationLoginsGroup { get; private set; }
public ExtendedObservableCollection<VaultListPageModel.AutofillGrouping> PresentationCiphersGroup { get; private set; }
= new ExtendedObservableCollection<VaultListPageModel.AutofillGrouping>();
public StackLayout NoDataStackLayout { get; set; }
public ListView ListView { get; set; }
public ActivityIndicator LoadingIndicator { get; set; }
private SearchToolBarItem SearchItem { get; set; }
private AddLoginToolBarItem AddLoginItem { get; set; }
private AddCipherToolBarItem AddCipherItem { get; set; }
private IGoogleAnalyticsService GoogleAnalyticsService { get; set; }
private IUserDialogs UserDialogs { get; set; }
private string Uri { get; set; }
@ -71,34 +71,34 @@ namespace Bit.App.Pages
Style = (Style)Application.Current.Resources["text-muted"]
};
var addLoginButton = new ExtendedButton
var addCipherButton = new ExtendedButton
{
Text = AppResources.AddALogin,
Command = new Command(() => AddLoginAsync()),
Command = new Command(() => AddCipherAsync()),
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
};
NoDataStackLayout = new StackLayout
{
Children = { noDataLabel, addLoginButton },
Children = { noDataLabel, addCipherButton },
VerticalOptions = LayoutOptions.CenterAndExpand,
Padding = new Thickness(20, 0),
Spacing = 20
};
AddLoginItem = new AddLoginToolBarItem(this);
ToolbarItems.Add(AddLoginItem);
AddCipherItem = new AddCipherToolBarItem(this);
ToolbarItems.Add(AddCipherItem);
SearchItem = new SearchToolBarItem(this);
ToolbarItems.Add(SearchItem);
ListView = new ListView(ListViewCachingStrategy.RecycleElement)
{
IsGroupingEnabled = true,
ItemsSource = PresentationLoginsGroup,
ItemsSource = PresentationCiphersGroup,
HasUnevenRows = true,
GroupHeaderTemplate = new DataTemplate(() => new HeaderViewCell()),
ItemTemplate = new DataTemplate(() => new VaultListViewCell(
(VaultListPageModel.Login l) => MoreClickedAsync(l)))
(VaultListPageModel.Cipher l) => MoreClickedAsync(l)))
};
if(Device.RuntimePlatform == Device.iOS)
@ -121,8 +121,8 @@ namespace Bit.App.Pages
protected override void OnAppearing()
{
base.OnAppearing();
ListView.ItemSelected += LoginSelected;
AddLoginItem.InitEvents();
ListView.ItemSelected += CipherSelected;
AddCipherItem.InitEvents();
SearchItem.InitEvents();
_filterResultsCancellationTokenSource = FetchAndLoadVault();
}
@ -130,21 +130,21 @@ namespace Bit.App.Pages
protected override void OnDisappearing()
{
base.OnDisappearing();
ListView.ItemSelected -= LoginSelected;
AddLoginItem.Dispose();
ListView.ItemSelected -= CipherSelected;
AddCipherItem.Dispose();
SearchItem.Dispose();
}
protected override bool OnBackButtonPressed()
{
GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Login)null);
MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Cipher)null);
return true;
}
private void AdjustContent()
{
if(PresentationLoginsGroup.Count > 0)
if(PresentationCiphersGroup.Count > 0)
{
Content = ListView;
}
@ -162,18 +162,18 @@ namespace Bit.App.Pages
Task.Run(async () =>
{
var autofillGroupings = new List<VaultListPageModel.AutofillGrouping>();
var logins = await _cipherService.GetAllAsync(Uri);
var ciphers = await _cipherService.GetAllAsync(Uri);
var normalLogins = logins?.Item1.Select(l => new VaultListPageModel.AutofillLogin(l, false))
var normalLogins = ciphers?.Item1.Select(l => new VaultListPageModel.AutofillCipher(l, false))
.OrderBy(s => s.Name)
.ThenBy(s => s.Username)
.ThenBy(s => s.Subtitle)
.ToList();
if(normalLogins?.Any() ?? false)
{
autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(normalLogins, AppResources.MatchingLogins));
}
var fuzzyLogins = logins?.Item2.Select(l => new VaultListPageModel.AutofillLogin(l, true))
var fuzzyLogins = ciphers?.Item2.Select(l => new VaultListPageModel.AutofillCipher(l, true))
.OrderBy(s => s.Name)
.ThenBy(s => s.Username)
.ToList();
@ -187,7 +187,7 @@ namespace Bit.App.Pages
{
if(autofillGroupings.Any())
{
PresentationLoginsGroup.ResetWithRange(autofillGroupings);
PresentationCiphersGroup.ResetWithRange(autofillGroupings);
}
AdjustContent();
@ -197,22 +197,22 @@ namespace Bit.App.Pages
return cts;
}
private async void LoginSelected(object sender, SelectedItemChangedEventArgs e)
private async void CipherSelected(object sender, SelectedItemChangedEventArgs e)
{
var login = e.SelectedItem as VaultListPageModel.AutofillLogin;
if(login == null)
var cipher = e.SelectedItem as VaultListPageModel.AutofillCipher;
if(cipher == null)
{
return;
}
if(_deviceInfoService.Version < 21)
{
MoreClickedAsync(login);
MoreClickedAsync(cipher);
}
else
{
bool doAutofill = true;
if(login.Fuzzy)
if(cipher.Fuzzy)
{
doAutofill = await UserDialogs.ConfirmAsync(
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, _name),
@ -222,50 +222,73 @@ namespace Bit.App.Pages
if(doAutofill)
{
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
MessagingCenter.Send(Application.Current, "Autofill", login as VaultListPageModel.Login);
MessagingCenter.Send(Application.Current, "Autofill", cipher as VaultListPageModel.Cipher);
}
}
((ListView)sender).SelectedItem = null;
}
private async void AddLoginAsync()
private async void AddCipherAsync()
{
var page = new VaultAddLoginPage(Uri, _name, true);
await Navigation.PushForDeviceAsync(page);
}
private async void MoreClickedAsync(VaultListPageModel.Login login)
private async void MoreClickedAsync(VaultListPageModel.Cipher cipher)
{
var buttons = new List<string> { AppResources.View, AppResources.Edit };
if(!string.IsNullOrWhiteSpace(login.Password.Value))
if(cipher.Type == Enums.CipherType.Login)
{
buttons.Add(AppResources.CopyPassword);
if(!string.IsNullOrWhiteSpace(cipher.Password.Value))
{
buttons.Add(AppResources.CopyPassword);
}
if(!string.IsNullOrWhiteSpace(cipher.Username))
{
buttons.Add(AppResources.CopyUsername);
}
}
if(!string.IsNullOrWhiteSpace(login.Username))
else if(cipher.Type == Enums.CipherType.Card)
{
buttons.Add(AppResources.CopyUsername);
if(!string.IsNullOrWhiteSpace(cipher.CardNumber))
{
buttons.Add(AppResources.CopyNumber);
}
if(!string.IsNullOrWhiteSpace(cipher.CardCode.Value))
{
buttons.Add(AppResources.CopySecurityCode);
}
}
var selection = await DisplayActionSheet(login.Name, AppResources.Cancel, null, buttons.ToArray());
var selection = await DisplayActionSheet(cipher.Name, AppResources.Cancel, null, buttons.ToArray());
if(selection == AppResources.View)
{
var page = new VaultViewLoginPage(login.Id);
var page = new VaultViewLoginPage(cipher.Id);
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.Edit)
{
var page = new VaultEditLoginPage(login.Id);
var page = new VaultEditLoginPage(cipher.Id);
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.CopyPassword)
{
Copy(login.Password.Value, AppResources.Password);
Copy(cipher.Password.Value, AppResources.Password);
}
else if(selection == AppResources.CopyUsername)
{
Copy(login.Username, AppResources.Username);
Copy(cipher.Username, AppResources.Username);
}
else if(selection == AppResources.CopyNumber)
{
Copy(cipher.CardNumber, AppResources.Number);
}
else if(selection == AppResources.CopySecurityCode)
{
Copy(cipher.CardCode.Value, AppResources.SecurityCode);
}
}
@ -275,26 +298,26 @@ namespace Bit.App.Pages
UserDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
}
private class AddLoginToolBarItem : ExtendedToolbarItem
private class AddCipherToolBarItem : ExtendedToolbarItem
{
public AddLoginToolBarItem(VaultAutofillListLoginsPage page)
: base(() => page.AddLoginAsync())
public AddCipherToolBarItem(VaultAutofillListCiphersPage page)
: base(() => page.AddCipherAsync())
{
Text = AppResources.Add;
Icon = "plus";
Icon = "plus.png";
Priority = 2;
}
}
private class SearchToolBarItem : ExtendedToolbarItem
{
private readonly VaultAutofillListLoginsPage _page;
private readonly VaultAutofillListCiphersPage _page;
public SearchToolBarItem(VaultAutofillListLoginsPage page)
public SearchToolBarItem(VaultAutofillListCiphersPage page)
{
_page = page;
Text = AppResources.Search;
Icon = "search";
Icon = "search.png";
Priority = 1;
ClickAction = () => DoClick();
}

View file

@ -17,7 +17,7 @@ using FFImageLoading.Forms;
namespace Bit.App.Pages
{
public class VaultListLoginsPage : ExtendedContentPage
public class VaultListCiphersPage : ExtendedContentPage
{
private readonly IFolderService _folderService;
private readonly ICipherService _cipherService;
@ -32,7 +32,7 @@ namespace Bit.App.Pages
private readonly bool _favorites;
private CancellationTokenSource _filterResultsCancellationTokenSource;
public VaultListLoginsPage(bool favorites, string uri = null)
public VaultListCiphersPage(bool favorites, string uri = null)
: base(true)
{
_favorites = favorites;
@ -57,13 +57,13 @@ namespace Bit.App.Pages
public ExtendedObservableCollection<VaultListPageModel.Folder> PresentationFolders { get; private set; }
= new ExtendedObservableCollection<VaultListPageModel.Folder>();
public ListView ListView { get; set; }
public VaultListPageModel.Login[] Logins { get; set; } = new VaultListPageModel.Login[] { };
public VaultListPageModel.Cipher[] Ciphers { get; set; } = new VaultListPageModel.Cipher[] { };
public VaultListPageModel.Folder[] Folders { get; set; } = new VaultListPageModel.Folder[] { };
public SearchBar Search { get; set; }
public StackLayout NoDataStackLayout { get; set; }
public StackLayout ResultsStackLayout { get; set; }
public ActivityIndicator LoadingIndicator { get; set; }
private AddLoginToolBarItem AddLoginItem { get; set; }
private AddCipherToolBarItem AddCipherItem { get; set; }
public string Uri { get; set; }
private void Init()
@ -78,8 +78,8 @@ namespace Bit.App.Pages
if(!_favorites)
{
AddLoginItem = new AddLoginToolBarItem(this);
ToolbarItems.Add(AddLoginItem);
AddCipherItem = new AddCipherToolBarItem(this);
ToolbarItems.Add(AddCipherItem);
}
ListView = new ListView(ListViewCachingStrategy.RecycleElement)
@ -89,7 +89,7 @@ namespace Bit.App.Pages
HasUnevenRows = true,
GroupHeaderTemplate = new DataTemplate(() => new VaultListHeaderViewCell(this)),
ItemTemplate = new DataTemplate(() => new VaultListViewCell(
(VaultListPageModel.Login l) => MoreClickedAsync(l)))
(VaultListPageModel.Cipher c) => MoreClickedAsync(c)))
};
if(Device.RuntimePlatform == Device.iOS)
@ -135,14 +135,14 @@ namespace Bit.App.Pages
if(!_favorites)
{
var addLoginButton = new ExtendedButton
var addCipherButton = new ExtendedButton
{
Text = AppResources.AddALogin,
Command = new Command(() => AddLogin()),
Command = new Command(() => AddCipher()),
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
};
NoDataStackLayout.Children.Add(addLoginButton);
NoDataStackLayout.Children.Add(addCipherButton);
}
LoadingIndicator = new ActivityIndicator
@ -208,28 +208,28 @@ namespace Bit.App.Pages
if(string.IsNullOrWhiteSpace(searchFilter))
{
LoadFolders(Logins, ct);
LoadFolders(Ciphers, ct);
}
else
{
searchFilter = searchFilter.ToLower();
var filteredLogins = Logins
.Where(s => s.Name.ToLower().Contains(searchFilter) || s.Username.ToLower().Contains(searchFilter))
var filteredCiphers = Ciphers
.Where(s => s.Name.ToLower().Contains(searchFilter) || s.Subtitle.ToLower().Contains(searchFilter))
.TakeWhile(s => !ct.IsCancellationRequested)
.ToArray();
ct.ThrowIfCancellationRequested();
LoadFolders(filteredLogins, ct);
LoadFolders(filteredCiphers, ct);
}
}
protected override void OnAppearing()
{
base.OnAppearing();
ListView.ItemSelected += LoginSelected;
ListView.ItemSelected += CipherSelected;
Search.TextChanged += SearchBar_TextChanged;
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
AddLoginItem?.InitEvents();
AddCipherItem?.InitEvents();
_filterResultsCancellationTokenSource = FetchAndLoadVault();
@ -267,10 +267,10 @@ namespace Bit.App.Pages
protected override void OnDisappearing()
{
base.OnDisappearing();
ListView.ItemSelected -= LoginSelected;
ListView.ItemSelected -= CipherSelected;
Search.TextChanged -= SearchBar_TextChanged;
Search.SearchButtonPressed -= SearchBar_SearchButtonPressed;
AddLoginItem?.Dispose();
AddCipherItem?.Dispose();
}
protected override bool OnBackButtonPressed()
@ -281,7 +281,7 @@ namespace Bit.App.Pages
}
_googleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Login)null);
MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Cipher)null);
return true;
}
@ -310,21 +310,21 @@ namespace Bit.App.Pages
Task.Run(async () =>
{
var foldersTask = _folderService.GetAllAsync();
var loginsTask = _favorites ? _cipherService.GetAllAsync(true) : _cipherService.GetAllAsync();
await Task.WhenAll(foldersTask, loginsTask);
var ciphersTask = _favorites ? _cipherService.GetAllAsync(true) : _cipherService.GetAllAsync();
await Task.WhenAll(foldersTask, ciphersTask);
var folders = await foldersTask;
var logins = await loginsTask;
var ciphers = await ciphersTask;
Folders = folders
.Select(f => new VaultListPageModel.Folder(f))
.OrderBy(s => s.Name)
.ToArray();
Logins = logins
.Select(s => new VaultListPageModel.Login(s))
Ciphers = ciphers
.Select(s => new VaultListPageModel.Cipher(s))
.OrderBy(s => s.Name)
.ThenBy(s => s.Username)
.ThenBy(s => s.Subtitle)
.ToArray();
try
@ -337,7 +337,7 @@ namespace Bit.App.Pages
return cts;
}
private void LoadFolders(VaultListPageModel.Login[] logins, CancellationToken ct)
private void LoadFolders(VaultListPageModel.Cipher[] ciphers, CancellationToken ct)
{
var folders = new List<VaultListPageModel.Folder>(Folders);
@ -348,16 +348,16 @@ namespace Bit.App.Pages
folder.Clear();
}
var loginsToAdd = logins
var ciphersToAdd = ciphers
.Where(s => s.FolderId == folder.Id)
.TakeWhile(s => !ct.IsCancellationRequested)
.ToList();
ct.ThrowIfCancellationRequested();
folder.AddRange(loginsToAdd);
folder.AddRange(ciphersToAdd);
}
var noneToAdd = logins
var noneToAdd = ciphers
.Where(s => s.FolderId == null)
.TakeWhile(s => !ct.IsCancellationRequested)
.ToList();
@ -380,10 +380,10 @@ namespace Bit.App.Pages
});
}
private async void LoginSelected(object sender, SelectedItemChangedEventArgs e)
private async void CipherSelected(object sender, SelectedItemChangedEventArgs e)
{
var login = e.SelectedItem as VaultListPageModel.Login;
if(login == null)
var cipher = e.SelectedItem as VaultListPageModel.Cipher;
if(cipher == null)
{
return;
}
@ -397,65 +397,88 @@ namespace Bit.App.Pages
if(selection == AppResources.View || string.IsNullOrWhiteSpace(Uri))
{
var page = new VaultViewLoginPage(login.Id);
var page = new VaultViewLoginPage(cipher.Id);
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.Autofill)
{
if(_deviceInfoService.Version < 21)
{
MoreClickedAsync(login);
MoreClickedAsync(cipher);
}
else
{
_googleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
MessagingCenter.Send(Application.Current, "Autofill", login);
MessagingCenter.Send(Application.Current, "Autofill", cipher);
}
}
((ListView)sender).SelectedItem = null;
}
private async void MoreClickedAsync(VaultListPageModel.Login login)
private async void MoreClickedAsync(VaultListPageModel.Cipher cipher)
{
var buttons = new List<string> { AppResources.View, AppResources.Edit };
if(!string.IsNullOrWhiteSpace(login.Password.Value))
if(cipher.Type == Enums.CipherType.Login)
{
buttons.Add(AppResources.CopyPassword);
if(!string.IsNullOrWhiteSpace(cipher.Password.Value))
{
buttons.Add(AppResources.CopyPassword);
}
if(!string.IsNullOrWhiteSpace(cipher.Username))
{
buttons.Add(AppResources.CopyUsername);
}
if(!string.IsNullOrWhiteSpace(cipher.Uri.Value) && (cipher.Uri.Value.StartsWith("http://")
|| cipher.Uri.Value.StartsWith("https://")))
{
buttons.Add(AppResources.GoToWebsite);
}
}
if(!string.IsNullOrWhiteSpace(login.Username))
else if(cipher.Type == Enums.CipherType.Card)
{
buttons.Add(AppResources.CopyUsername);
}
if(!string.IsNullOrWhiteSpace(login.Uri.Value) && (login.Uri.Value.StartsWith("http://")
|| login.Uri.Value.StartsWith("https://")))
{
buttons.Add(AppResources.GoToWebsite);
if(!string.IsNullOrWhiteSpace(cipher.CardNumber))
{
buttons.Add(AppResources.CopyNumber);
}
if(!string.IsNullOrWhiteSpace(cipher.CardCode.Value))
{
buttons.Add(AppResources.CopySecurityCode);
}
}
var selection = await DisplayActionSheet(login.Name, AppResources.Cancel, null, buttons.ToArray());
var selection = await DisplayActionSheet(cipher.Name, AppResources.Cancel, null, buttons.ToArray());
if(selection == AppResources.View)
{
var page = new VaultViewLoginPage(login.Id);
var page = new VaultViewLoginPage(cipher.Id);
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.Edit)
{
var page = new VaultEditLoginPage(login.Id);
var page = new VaultEditLoginPage(cipher.Id);
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.CopyPassword)
{
Copy(login.Password.Value, AppResources.Password);
Copy(cipher.Password.Value, AppResources.Password);
}
else if(selection == AppResources.CopyUsername)
{
Copy(login.Username, AppResources.Username);
Copy(cipher.Username, AppResources.Username);
}
else if(selection == AppResources.GoToWebsite)
{
Device.OpenUri(new Uri(login.Uri.Value));
Device.OpenUri(new Uri(cipher.Uri.Value));
}
else if(selection == AppResources.CopyNumber)
{
Copy(cipher.CardNumber, AppResources.Number);
}
else if(selection == AppResources.CopySecurityCode)
{
Copy(cipher.CardCode.Value, AppResources.SecurityCode);
}
}
@ -465,18 +488,18 @@ namespace Bit.App.Pages
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
}
private async void AddLogin()
private async void AddCipher()
{
var page = new VaultAddLoginPage(Uri);
await Navigation.PushForDeviceAsync(page);
}
private class AddLoginToolBarItem : ExtendedToolbarItem
private class AddCipherToolBarItem : ExtendedToolbarItem
{
private readonly VaultListLoginsPage _page;
private readonly VaultListCiphersPage _page;
public AddLoginToolBarItem(VaultListLoginsPage page)
: base(() => page.AddLogin())
public AddCipherToolBarItem(VaultListCiphersPage page)
: base(() => page.AddCipher())
{
_page = page;
Text = AppResources.Add;
@ -486,7 +509,7 @@ namespace Bit.App.Pages
private class VaultListHeaderViewCell : ExtendedViewCell
{
public VaultListHeaderViewCell(VaultListLoginsPage page)
public VaultListHeaderViewCell(VaultListCiphersPage page)
{
var image = new CachedImage
{

View file

@ -646,6 +646,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Copy Number.
/// </summary>
public static string CopyNumber {
get {
return ResourceManager.GetString("CopyNumber", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy Password.
/// </summary>
@ -655,6 +664,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Copy Security Code.
/// </summary>
public static string CopySecurityCode {
get {
return ResourceManager.GetString("CopySecurityCode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy TOTP.
/// </summary>
@ -1816,6 +1834,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Number.
/// </summary>
public static string Number {
get {
return ResourceManager.GetString("Number", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Ok.
/// </summary>
@ -2077,6 +2104,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Security Code.
/// </summary>
public static string SecurityCode {
get {
return ResourceManager.GetString("SecurityCode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to See Development Progress.
/// </summary>

View file

@ -1039,4 +1039,16 @@
<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>
<data name="CopySecurityCode" xml:space="preserve">
<value>Copy Security Code</value>
</data>
<data name="Number" xml:space="preserve">
<value>Number</value>
</data>
<data name="SecurityCode" xml:space="preserve">
<value>Security Code</value>
</data>
</root>