bitwarden-android/src/App/Pages/Vault/VaultAutofillListCiphersPage.cs

388 lines
14 KiB
C#
Raw Normal View History

using System;
using System.Linq;
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Page;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
using Bit.App.Utilities;
using System.Threading;
using Bit.App.Models;
2017-01-31 03:26:39 +03:00
using System.Collections.Generic;
using Bit.App.Enums;
namespace Bit.App.Pages
{
public class VaultAutofillListCiphersPage : ExtendedContentPage
{
private readonly ICipherService _cipherService;
private readonly IDeviceInfoService _deviceInfoService;
private readonly IDeviceActionService _deviceActionService;
private readonly ISettingsService _settingsService;
2017-10-20 23:10:22 +03:00
private readonly IAppSettingsService _appSettingsService;
private CancellationTokenSource _filterResultsCancellationTokenSource;
2017-01-31 03:26:39 +03:00
private readonly string _name;
private readonly AppOptions _appOptions;
public VaultAutofillListCiphersPage(AppOptions appOptions)
: base(true)
{
_appOptions = appOptions;
Uri = appOptions.Uri;
if(Uri.StartsWith(Constants.AndroidAppProtocol))
{
_name = Uri.Substring(Constants.AndroidAppProtocol.Length);
}
else if(!System.Uri.TryCreate(Uri, UriKind.Absolute, out Uri uri) ||
2017-09-07 06:08:24 +03:00
!DomainName.TryParseBaseDomain(uri.Host, out _name))
2017-01-31 03:26:39 +03:00
{
_name = "--";
2017-01-31 03:26:39 +03:00
}
_cipherService = Resolver.Resolve<ICipherService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_settingsService = Resolver.Resolve<ISettingsService>();
2017-02-14 06:35:16 +03:00
UserDialogs = Resolver.Resolve<IUserDialogs>();
2017-10-20 23:10:22 +03:00
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init();
}
public ExtendedObservableCollection<VaultListPageModel.AutofillGrouping> PresentationCiphersGroup { get; private set; }
= new ExtendedObservableCollection<VaultListPageModel.AutofillGrouping>();
2017-01-31 03:26:39 +03:00
public StackLayout NoDataStackLayout { get; set; }
public ListView ListView { get; set; }
2017-01-31 03:26:39 +03:00
public ActivityIndicator LoadingIndicator { get; set; }
private SearchToolBarItem SearchItem { get; set; }
private AddCipherToolBarItem AddCipherItem { get; set; }
private IGoogleAnalyticsService GoogleAnalyticsService { get; set; }
2017-02-14 06:35:16 +03:00
private IUserDialogs UserDialogs { get; set; }
private string Uri { get; set; }
private void Init()
{
2017-01-31 03:26:39 +03:00
var noDataLabel = new Label
{
Text = string.Format(AppResources.NoItemsForUri, _name ?? "--"),
2017-01-31 03:26:39 +03:00
HorizontalTextAlignment = TextAlignment.Center,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
Style = (Style)Application.Current.Resources["text-muted"]
};
var addCipherButton = new ExtendedButton
2017-01-31 03:26:39 +03:00
{
Text = AppResources.AddAnItem,
Command = new Command(() => AddCipherAsync()),
2017-01-31 03:26:39 +03:00
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
};
NoDataStackLayout = new StackLayout
{
Children = { noDataLabel, addCipherButton },
2017-01-31 03:26:39 +03:00
VerticalOptions = LayoutOptions.CenterAndExpand,
Padding = new Thickness(20, 0),
Spacing = 20
};
AddCipherItem = new AddCipherToolBarItem(this);
ToolbarItems.Add(AddCipherItem);
SearchItem = new SearchToolBarItem(this);
ToolbarItems.Add(SearchItem);
ListView = new ListView(ListViewCachingStrategy.RecycleElement)
{
IsGroupingEnabled = true,
ItemsSource = PresentationCiphersGroup,
HasUnevenRows = true,
GroupHeaderTemplate = new DataTemplate(() => new HeaderViewCell()),
2017-01-31 03:26:39 +03:00
ItemTemplate = new DataTemplate(() => new VaultListViewCell(
(VaultListPageModel.Cipher l) => MoreClickedAsync(l)))
};
2017-05-30 21:13:53 +03:00
if(Device.RuntimePlatform == Device.iOS)
{
ListView.RowHeight = -1;
}
Title = string.Format(AppResources.ItemsForUri, _name ?? "--");
2017-01-31 03:26:39 +03:00
LoadingIndicator = new ActivityIndicator
{
IsRunning = true,
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center
};
2017-01-31 03:26:39 +03:00
Content = LoadingIndicator;
}
protected override void OnAppearing()
{
base.OnAppearing();
ListView.ItemSelected += CipherSelected;
AddCipherItem.InitEvents();
SearchItem.InitEvents();
_filterResultsCancellationTokenSource = FetchAndLoadVault();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
ListView.ItemSelected -= CipherSelected;
AddCipherItem.Dispose();
SearchItem.Dispose();
}
2017-01-31 03:26:39 +03:00
protected override bool OnBackButtonPressed()
{
GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
_deviceActionService.CloseAutofill();
2017-01-31 03:26:39 +03:00
return true;
}
2017-01-31 03:26:39 +03:00
private void AdjustContent()
{
if(PresentationCiphersGroup.Count > 0)
2017-01-31 03:26:39 +03:00
{
Content = ListView;
}
else
{
2017-01-31 03:26:39 +03:00
Content = NoDataStackLayout;
}
2017-01-31 03:26:39 +03:00
}
2017-01-31 03:26:39 +03:00
private CancellationTokenSource FetchAndLoadVault()
{
var cts = new CancellationTokenSource();
_filterResultsCancellationTokenSource?.Cancel();
Task.Run(async () =>
{
2017-02-14 06:10:34 +03:00
var autofillGroupings = new List<VaultListPageModel.AutofillGrouping>();
var ciphers = await _cipherService.GetAllAsync(Uri);
2017-02-14 06:10:34 +03:00
if(_appOptions.FillType.HasValue && _appOptions.FillType.Value != CipherType.Login)
2017-02-14 06:10:34 +03:00
{
var others = ciphers?.Item3.Where(c => c.Type == _appOptions.FillType.Value)
.Select(c => new VaultListPageModel.AutofillCipher(c, _appSettingsService, false))
.OrderBy(s => s.Name)
.ThenBy(s => s.Subtitle)
.ToList();
if(others?.Any() ?? false)
{
autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(others, AppResources.Items));
}
2017-02-14 06:10:34 +03:00
}
else
{
var normalLogins = ciphers?.Item1
.Select(l => new VaultListPageModel.AutofillCipher(l, _appSettingsService, false))
.OrderBy(s => s.Name)
.ThenBy(s => s.Subtitle)
.ToList();
if(normalLogins?.Any() ?? false)
{
autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(normalLogins,
AppResources.MatchingItems));
}
var fuzzyLogins = ciphers?.Item2
.Select(l => new VaultListPageModel.AutofillCipher(l, _appSettingsService, true))
.OrderBy(s => s.Name)
.ThenBy(s => s.Subtitle)
.ToList();
if(fuzzyLogins?.Any() ?? false)
{
autofillGroupings.Add(new VaultListPageModel.AutofillGrouping(fuzzyLogins,
AppResources.PossibleMatchingItems));
}
2017-02-14 06:10:34 +03:00
}
2017-01-31 03:26:39 +03:00
Device.BeginInvokeOnMainThread(() =>
{
2017-02-14 06:10:34 +03:00
if(autofillGroupings.Any())
{
PresentationCiphersGroup.ResetWithRange(autofillGroupings);
2017-02-14 06:10:34 +03:00
}
2017-01-31 03:26:39 +03:00
AdjustContent();
});
}, cts.Token);
return cts;
}
private async void CipherSelected(object sender, SelectedItemChangedEventArgs e)
{
var cipher = e.SelectedItem as VaultListPageModel.AutofillCipher;
if(cipher == null)
2017-02-14 06:10:34 +03:00
{
return;
}
2017-09-08 16:16:21 +03:00
if(_deviceInfoService.Version < 21)
{
MoreClickedAsync(cipher);
2017-02-14 06:10:34 +03:00
}
else
{
bool doAutofill = true;
if(cipher.Fuzzy)
2017-02-14 06:10:34 +03:00
{
2017-02-14 06:35:16 +03:00
doAutofill = await UserDialogs.ConfirmAsync(
2017-02-14 06:10:34 +03:00
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, _name),
okText: AppResources.Yes, cancelText: AppResources.No);
}
if(doAutofill)
{
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
_deviceActionService.Autofill(cipher);
2017-02-14 06:10:34 +03:00
}
}
2017-02-14 06:10:34 +03:00
((ListView)sender).SelectedItem = null;
}
private async void AddCipherAsync()
{
if(_appOptions.FillType.HasValue && _appOptions.FillType != CipherType.Login)
{
var pageForOther = new VaultAddCipherPage(_appOptions.FillType.Value, null, null, true);
await Navigation.PushForDeviceAsync(pageForOther);
return;
}
var pageForLogin = new VaultAddCipherPage(CipherType.Login, Uri, _name, true);
await Navigation.PushForDeviceAsync(pageForLogin);
}
private async void MoreClickedAsync(VaultListPageModel.Cipher cipher)
{
2017-01-31 03:26:39 +03:00
var buttons = new List<string> { AppResources.View, AppResources.Edit };
if(cipher.Type == CipherType.Login)
{
2017-10-20 19:47:05 +03:00
if(!string.IsNullOrWhiteSpace(cipher.LoginPassword.Value))
{
buttons.Add(AppResources.CopyPassword);
}
2017-10-20 19:47:05 +03:00
if(!string.IsNullOrWhiteSpace(cipher.LoginUsername))
{
buttons.Add(AppResources.CopyUsername);
}
2017-01-31 03:26:39 +03:00
}
else if(cipher.Type == CipherType.Card)
2017-01-31 03:26:39 +03:00
{
if(!string.IsNullOrWhiteSpace(cipher.CardNumber))
{
buttons.Add(AppResources.CopyNumber);
}
if(!string.IsNullOrWhiteSpace(cipher.CardCode.Value))
{
buttons.Add(AppResources.CopySecurityCode);
}
}
var selection = await DisplayActionSheet(cipher.Name, AppResources.Cancel, null, buttons.ToArray());
2017-01-31 03:26:39 +03:00
if(selection == AppResources.View)
{
var page = new VaultViewCipherPage(cipher.Type, cipher.Id);
2017-01-31 03:26:39 +03:00
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.Edit)
{
var page = new VaultEditCipherPage(cipher.Id);
2017-01-31 03:26:39 +03:00
await Navigation.PushForDeviceAsync(page);
}
else if(selection == AppResources.CopyPassword)
{
2017-10-20 19:47:05 +03:00
Copy(cipher.LoginPassword.Value, AppResources.Password);
2017-01-31 03:26:39 +03:00
}
else if(selection == AppResources.CopyUsername)
{
2017-10-20 19:47:05 +03:00
Copy(cipher.LoginUsername, AppResources.Username);
}
else if(selection == AppResources.CopyNumber)
{
Copy(cipher.CardNumber, AppResources.Number);
}
else if(selection == AppResources.CopySecurityCode)
{
Copy(cipher.CardCode.Value, AppResources.SecurityCode);
}
}
2017-01-31 03:26:39 +03:00
private void Copy(string copyText, string alertLabel)
{
_deviceActionService.CopyToClipboard(copyText);
2017-02-14 06:35:16 +03:00
UserDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
2017-01-31 03:26:39 +03:00
}
private class AddCipherToolBarItem : ExtendedToolbarItem
2017-01-31 03:26:39 +03:00
{
public AddCipherToolBarItem(VaultAutofillListCiphersPage page)
: base(() => page.AddCipherAsync())
{
2017-01-31 03:26:39 +03:00
Text = AppResources.Add;
Icon = "plus.png";
2017-02-14 06:35:16 +03:00
Priority = 2;
}
}
private class SearchToolBarItem : ExtendedToolbarItem
{
private readonly VaultAutofillListCiphersPage _page;
public SearchToolBarItem(VaultAutofillListCiphersPage page)
{
_page = page;
2017-02-14 06:35:16 +03:00
Text = AppResources.Search;
Icon = "search.png";
2017-02-14 06:35:16 +03:00
Priority = 1;
ClickAction = () => DoClick();
}
private void DoClick()
{
2017-02-14 06:10:34 +03:00
_page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch",
_page.Uri.StartsWith("http") ? "Website" : "App");
Application.Current.MainPage = new ExtendedNavigationPage(new VaultListCiphersPage(false, _page.Uri));
2017-02-14 06:35:16 +03:00
_page.UserDialogs.Toast(string.Format(AppResources.BitwardenAutofillServiceSearch, _page._name),
TimeSpan.FromSeconds(10));
}
}
private class HeaderViewCell : ExtendedViewCell
{
public HeaderViewCell()
{
var label = new Label
{
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
Style = (Style)Application.Current.Resources["text-muted"],
VerticalTextAlignment = TextAlignment.Center
};
2017-05-30 21:13:53 +03:00
label.SetBinding(Label.TextProperty, nameof(VaultListPageModel.AutofillGrouping.Name));
var grid = new ContentView
{
Padding = new Thickness(16, 8, 0, 8),
Content = label
};
View = grid;
BackgroundColor = Color.FromHex("efeff4");
}
}
}
}