mirror of
https://github.com/bitwarden/android.git
synced 2024-12-24 18:08:26 +03:00
add other cipher type support to vault listings
This commit is contained in:
parent
0020bd0fb7
commit
74ac9cbbbe
10 changed files with 267 additions and 138 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
{
|
36
src/App/Resources/AppResources.Designer.cs
generated
36
src/App/Resources/AppResources.Designer.cs
generated
|
@ -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>
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue