diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 124466205..275ea94ae 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -104,7 +104,7 @@ namespace Bit.Android OpenAccessibilitySettings(); }); - MessagingCenter.Subscribe( + MessagingCenter.Subscribe( 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()?.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) diff --git a/src/App/App.cs b/src/App/App.cs index c946660e8..74735454b 100644 --- a/src/App/App.cs +++ b/src/App/App.cs @@ -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) { diff --git a/src/App/App.csproj b/src/App/App.csproj index 51e6c42b6..e83fab4c7 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -180,7 +180,7 @@ - + @@ -357,7 +357,7 @@ - + diff --git a/src/App/Controls/VaultListViewCell.cs b/src/App/Controls/VaultListViewCell.cs index 01384ad08..699f8f098 100644 --- a/src/App/Controls/VaultListViewCell.cs +++ b/src/App/Controls/VaultListViewCell.cs @@ -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 moreClickedAction) + public VaultListViewCell(Action 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); } } } diff --git a/src/App/Models/Page/VaultListPageModel.cs b/src/App/Models/Page/VaultListPageModel.cs index 70ae3c89e..4e69166ef 100644 --- a/src/App/Models/Page/VaultListPageModel.cs +++ b/src/App/Models/Page/VaultListPageModel.cs @@ -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(() => cipher.Login?.Password?.Decrypt(cipher.OrganizationId)); - Uri = new Lazy(() => cipher.Login?.Uri?.Decrypt(cipher.OrganizationId)); - Totp = new Lazy(() => 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(() => cipher.Login?.Password?.Decrypt(cipher.OrganizationId)); + Uri = new Lazy(() => cipher.Login?.Uri?.Decrypt(cipher.OrganizationId)); + Totp = new Lazy(() => 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(() => 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 Password { get; set; } public Lazy Uri { get; set; } public Lazy Totp { get; set; } + + // Login metadata + public string CardNumber { get; set; } + public Lazy 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 + public class Folder : List { public Folder(Models.Folder folder) { @@ -52,18 +87,18 @@ namespace Bit.App.Models.Page Name = folder.Name?.Decrypt(); } - public Folder(List logins) + public Folder(List ciphers) { - AddRange(logins); + AddRange(ciphers); } public string Id { get; set; } public string Name { get; set; } = AppResources.FolderNone; } - public class AutofillGrouping : List + public class AutofillGrouping : List { - public AutofillGrouping(List logins, string name) + public AutofillGrouping(List logins, string name) { AddRange(logins); Name = name; diff --git a/src/App/Pages/MainPage.cs b/src/App/Pages/MainPage.cs index 5eb4a1c6d..e9c2926cb 100644 --- a/src/App/Pages/MainPage.cs +++ b/src/App/Pages/MainPage.cs @@ -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; diff --git a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs b/src/App/Pages/Vault/VaultAutofillListCiphersPage.cs similarity index 72% rename from src/App/Pages/Vault/VaultAutofillListLoginsPage.cs rename to src/App/Pages/Vault/VaultAutofillListCiphersPage.cs index 946554f1b..094d38add 100644 --- a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs +++ b/src/App/Pages/Vault/VaultAutofillListCiphersPage.cs @@ -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 PresentationLoginsGroup { get; private set; } + public ExtendedObservableCollection PresentationCiphersGroup { get; private set; } = new ExtendedObservableCollection(); 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(); - 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 { 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(); } diff --git a/src/App/Pages/Vault/VaultListLoginsPage.cs b/src/App/Pages/Vault/VaultListCiphersPage.cs similarity index 79% rename from src/App/Pages/Vault/VaultListLoginsPage.cs rename to src/App/Pages/Vault/VaultListCiphersPage.cs index 7b3006928..a26dcc59f 100644 --- a/src/App/Pages/Vault/VaultListLoginsPage.cs +++ b/src/App/Pages/Vault/VaultListCiphersPage.cs @@ -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 PresentationFolders { get; private set; } = new ExtendedObservableCollection(); 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(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 { 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 { diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index 09eb0f0b3..249a9fdbd 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -646,6 +646,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Copy Number. + /// + public static string CopyNumber { + get { + return ResourceManager.GetString("CopyNumber", resourceCulture); + } + } + /// /// Looks up a localized string similar to Copy Password. /// @@ -655,6 +664,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Copy Security Code. + /// + public static string CopySecurityCode { + get { + return ResourceManager.GetString("CopySecurityCode", resourceCulture); + } + } + /// /// Looks up a localized string similar to Copy TOTP. /// @@ -1816,6 +1834,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Number. + /// + public static string Number { + get { + return ResourceManager.GetString("Number", resourceCulture); + } + } + /// /// Looks up a localized string similar to Ok. /// @@ -2077,6 +2104,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Security Code. + /// + public static string SecurityCode { + get { + return ResourceManager.GetString("SecurityCode", resourceCulture); + } + } + /// /// Looks up a localized string similar to See Development Progress. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index ba835f98c..b72120018 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1039,4 +1039,16 @@ No custom fields. You can fully manage custom fields from the web vault or browser extension. + + Copy Number + + + Copy Security Code + + + Number + + + Security Code + \ No newline at end of file