2017-01-28 07:13:28 +03:00
|
|
|
|
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;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
|
|
|
|
namespace Bit.App.Pages
|
|
|
|
|
{
|
|
|
|
|
public class VaultAutofillListLoginsPage : ExtendedContentPage
|
|
|
|
|
{
|
|
|
|
|
private readonly ILoginService _loginService;
|
2017-02-03 06:20:45 +03:00
|
|
|
|
private readonly IDeviceInfoService _deviceInfoService;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
private readonly IUserDialogs _userDialogs;
|
|
|
|
|
private readonly IClipboardService _clipboardService;
|
2017-02-09 07:58:37 +03:00
|
|
|
|
private readonly ISettingsService _settingsService;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
private CancellationTokenSource _filterResultsCancellationTokenSource;
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private readonly string _name;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
|
|
|
|
public VaultAutofillListLoginsPage(string uriString)
|
|
|
|
|
: base(true)
|
|
|
|
|
{
|
2017-02-06 07:55:46 +03:00
|
|
|
|
Uri = uriString;
|
2017-02-09 07:58:37 +03:00
|
|
|
|
|
2017-01-28 07:13:28 +03:00
|
|
|
|
Uri uri;
|
2017-02-09 07:58:37 +03:00
|
|
|
|
DomainName domainName;
|
2017-02-06 07:55:46 +03:00
|
|
|
|
if(!System.Uri.TryCreate(uriString, UriKind.Absolute, out uri) ||
|
2017-02-09 07:58:37 +03:00
|
|
|
|
!DomainName.TryParse(uri.Host, out domainName))
|
2017-01-31 03:26:39 +03:00
|
|
|
|
{
|
|
|
|
|
if(uriString != null && uriString.StartsWith(Constants.AndroidAppProtocol))
|
|
|
|
|
{
|
|
|
|
|
_name = uriString.Substring(Constants.AndroidAppProtocol.Length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-02-09 07:58:37 +03:00
|
|
|
|
_name = domainName.BaseDomain;
|
2017-01-31 03:26:39 +03:00
|
|
|
|
}
|
2017-02-03 06:20:45 +03:00
|
|
|
|
|
2017-01-28 07:13:28 +03:00
|
|
|
|
_loginService = Resolver.Resolve<ILoginService>();
|
2017-02-03 06:20:45 +03:00
|
|
|
|
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
2017-01-28 07:13:28 +03:00
|
|
|
|
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
|
|
|
|
_clipboardService = Resolver.Resolve<IClipboardService>();
|
2017-02-09 07:58:37 +03:00
|
|
|
|
_settingsService = Resolver.Resolve<ISettingsService>();
|
2017-02-06 07:55:46 +03:00
|
|
|
|
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
|
|
|
|
Init();
|
|
|
|
|
}
|
2017-02-06 07:55:46 +03:00
|
|
|
|
|
2017-02-14 03:12:02 +03:00
|
|
|
|
public ExtendedObservableCollection<VaultListPageModel.AutofillGrouping> PresentationLogins { get; private set; }
|
|
|
|
|
= new ExtendedObservableCollection<VaultListPageModel.AutofillGrouping>();
|
2017-01-31 03:26:39 +03:00
|
|
|
|
public StackLayout NoDataStackLayout { get; set; }
|
2017-01-28 07:13:28 +03:00
|
|
|
|
public ListView ListView { get; set; }
|
2017-01-31 03:26:39 +03:00
|
|
|
|
public ActivityIndicator LoadingIndicator { get; set; }
|
2017-02-06 07:55:46 +03:00
|
|
|
|
private IGoogleAnalyticsService GoogleAnalyticsService { get; set; }
|
|
|
|
|
private string Uri { get; set; }
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
|
|
|
|
private void Init()
|
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
|
var noDataLabel = new Label
|
|
|
|
|
{
|
|
|
|
|
Text = string.Format(AppResources.NoLoginsForUri, _name ?? "--"),
|
|
|
|
|
HorizontalTextAlignment = TextAlignment.Center,
|
|
|
|
|
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
|
|
|
|
|
Style = (Style)Application.Current.Resources["text-muted"]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var addLoginButton = new ExtendedButton
|
|
|
|
|
{
|
|
|
|
|
Text = AppResources.AddALogin,
|
|
|
|
|
Command = new Command(() => AddLoginAsync()),
|
|
|
|
|
Style = (Style)Application.Current.Resources["btn-primaryAccent"]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
NoDataStackLayout = new StackLayout
|
|
|
|
|
{
|
|
|
|
|
Children = { noDataLabel, addLoginButton },
|
|
|
|
|
VerticalOptions = LayoutOptions.CenterAndExpand,
|
|
|
|
|
Padding = new Thickness(20, 0),
|
|
|
|
|
Spacing = 20
|
|
|
|
|
};
|
|
|
|
|
|
2017-01-28 07:13:28 +03:00
|
|
|
|
ToolbarItems.Add(new AddLoginToolBarItem(this));
|
2017-02-03 06:20:45 +03:00
|
|
|
|
ToolbarItems.Add(new CloseToolBarItem(this));
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
|
|
|
|
ListView = new ListView(ListViewCachingStrategy.RecycleElement)
|
|
|
|
|
{
|
2017-02-14 03:12:02 +03:00
|
|
|
|
IsGroupingEnabled = true,
|
2017-01-28 07:13:28 +03:00
|
|
|
|
ItemsSource = PresentationLogins,
|
|
|
|
|
HasUnevenRows = true,
|
2017-02-14 03:12:02 +03:00
|
|
|
|
GroupHeaderTemplate = new DataTemplate(() => new HeaderViewCell()),
|
2017-01-31 03:26:39 +03:00
|
|
|
|
ItemTemplate = new DataTemplate(() => new VaultListViewCell(
|
|
|
|
|
(VaultListPageModel.Login l) => MoreClickedAsync(l)))
|
2017-01-28 07:13:28 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if(Device.OS == TargetPlatform.iOS)
|
|
|
|
|
{
|
|
|
|
|
ListView.RowHeight = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ListView.ItemSelected += LoginSelected;
|
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
Title = string.Format(AppResources.LoginsForUri, _name ?? "--");
|
|
|
|
|
|
|
|
|
|
LoadingIndicator = new ActivityIndicator
|
|
|
|
|
{
|
|
|
|
|
IsRunning = true,
|
|
|
|
|
VerticalOptions = LayoutOptions.CenterAndExpand,
|
|
|
|
|
HorizontalOptions = LayoutOptions.Center
|
|
|
|
|
};
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
Content = LoadingIndicator;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnAppearing()
|
|
|
|
|
{
|
|
|
|
|
base.OnAppearing();
|
|
|
|
|
_filterResultsCancellationTokenSource = FetchAndLoadVault();
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
protected override bool OnBackButtonPressed()
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-02-06 07:55:46 +03:00
|
|
|
|
GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
|
2017-01-31 03:26:39 +03:00
|
|
|
|
MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Login)null);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private void AdjustContent()
|
|
|
|
|
{
|
|
|
|
|
if(PresentationLogins.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
Content = ListView;
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
|
Content = NoDataStackLayout;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}
|
2017-01-31 03:26:39 +03:00
|
|
|
|
}
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private CancellationTokenSource FetchAndLoadVault()
|
|
|
|
|
{
|
|
|
|
|
var cts = new CancellationTokenSource();
|
2017-01-28 07:13:28 +03:00
|
|
|
|
_filterResultsCancellationTokenSource?.Cancel();
|
|
|
|
|
|
|
|
|
|
Task.Run(async () =>
|
|
|
|
|
{
|
2017-02-09 07:58:37 +03:00
|
|
|
|
var logins = await _loginService.GetAllAsync(Uri);
|
2017-02-14 03:12:02 +03:00
|
|
|
|
var normalLogins = logins.Item1.Select(l => new VaultListPageModel.Login(l))
|
2017-01-28 07:13:28 +03:00
|
|
|
|
.OrderBy(s => s.Name)
|
2017-02-14 03:12:02 +03:00
|
|
|
|
.ThenBy(s => s.Username)
|
|
|
|
|
.ToList();
|
|
|
|
|
var fuzzyLogins = logins.Item2.Select(l => new VaultListPageModel.Login(l))
|
|
|
|
|
.OrderBy(s => s.Name)
|
|
|
|
|
.ThenBy(s => s.Username)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
var autofillGroupings = new List<VaultListPageModel.AutofillGrouping>
|
|
|
|
|
{
|
|
|
|
|
new VaultListPageModel.AutofillGrouping(normalLogins, "Matching"),
|
|
|
|
|
new VaultListPageModel.AutofillGrouping(fuzzyLogins, "Possible Matches")
|
|
|
|
|
};
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
Device.BeginInvokeOnMainThread(() =>
|
|
|
|
|
{
|
2017-02-14 03:12:02 +03:00
|
|
|
|
PresentationLogins.ResetWithRange(autofillGroupings);
|
2017-01-31 03:26:39 +03:00
|
|
|
|
AdjustContent();
|
|
|
|
|
});
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}, cts.Token);
|
|
|
|
|
|
|
|
|
|
return cts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void LoginSelected(object sender, SelectedItemChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
var login = e.SelectedItem as VaultListPageModel.Login;
|
2017-02-03 06:20:45 +03:00
|
|
|
|
|
2017-02-06 07:55:46 +03:00
|
|
|
|
if(Uri.StartsWith("http") && _deviceInfoService.Version < 21)
|
2017-02-03 06:20:45 +03:00
|
|
|
|
{
|
|
|
|
|
MoreClickedAsync(login);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-06 07:55:46 +03:00
|
|
|
|
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
|
2017-01-28 07:13:28 +03:00
|
|
|
|
MessagingCenter.Send(Application.Current, "Autofill", login);
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private async void AddLoginAsync()
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-02-06 07:55:46 +03:00
|
|
|
|
var page = new VaultAddLoginPage(Uri, _name, true);
|
2017-01-28 07:13:28 +03:00
|
|
|
|
await Navigation.PushForDeviceAsync(page);
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private async void MoreClickedAsync(VaultListPageModel.Login login)
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
|
var buttons = new List<string> { AppResources.View, AppResources.Edit };
|
|
|
|
|
if(!string.IsNullOrWhiteSpace(login.Password.Value))
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
|
buttons.Add(AppResources.CopyPassword);
|
|
|
|
|
}
|
|
|
|
|
if(!string.IsNullOrWhiteSpace(login.Username))
|
|
|
|
|
{
|
|
|
|
|
buttons.Add(AppResources.CopyUsername);
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
var selection = await DisplayActionSheet(login.Name, AppResources.Cancel, null, buttons.ToArray());
|
|
|
|
|
|
|
|
|
|
if(selection == AppResources.View)
|
|
|
|
|
{
|
|
|
|
|
var page = new VaultViewLoginPage(login.Id);
|
|
|
|
|
await Navigation.PushForDeviceAsync(page);
|
|
|
|
|
}
|
|
|
|
|
else if(selection == AppResources.Edit)
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
|
var page = new VaultEditLoginPage(login.Id);
|
|
|
|
|
await Navigation.PushForDeviceAsync(page);
|
|
|
|
|
}
|
|
|
|
|
else if(selection == AppResources.CopyPassword)
|
|
|
|
|
{
|
|
|
|
|
Copy(login.Password.Value, AppResources.Password);
|
|
|
|
|
}
|
|
|
|
|
else if(selection == AppResources.CopyUsername)
|
|
|
|
|
{
|
|
|
|
|
Copy(login.Username, AppResources.Username);
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private void Copy(string copyText, string alertLabel)
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
|
_clipboardService.CopyToClipboard(copyText);
|
|
|
|
|
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
|
|
|
|
|
}
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private class AddLoginToolBarItem : ToolbarItem
|
|
|
|
|
{
|
|
|
|
|
private readonly VaultAutofillListLoginsPage _page;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
public AddLoginToolBarItem(VaultAutofillListLoginsPage page)
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
|
|
|
|
_page = page;
|
2017-01-31 03:26:39 +03:00
|
|
|
|
Text = AppResources.Add;
|
|
|
|
|
Icon = "plus";
|
|
|
|
|
Clicked += ClickedItem;
|
2017-02-03 06:20:45 +03:00
|
|
|
|
Priority = 1;
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 03:26:39 +03:00
|
|
|
|
private void ClickedItem(object sender, EventArgs e)
|
2017-01-28 07:13:28 +03:00
|
|
|
|
{
|
2017-01-31 03:26:39 +03:00
|
|
|
|
_page.AddLoginAsync();
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-03 06:20:45 +03:00
|
|
|
|
|
|
|
|
|
private class CloseToolBarItem : ToolbarItem
|
|
|
|
|
{
|
|
|
|
|
private readonly VaultAutofillListLoginsPage _page;
|
|
|
|
|
|
|
|
|
|
public CloseToolBarItem(VaultAutofillListLoginsPage page)
|
|
|
|
|
{
|
|
|
|
|
_page = page;
|
|
|
|
|
Text = AppResources.Close;
|
|
|
|
|
Icon = "close";
|
|
|
|
|
Clicked += ClickedItem;
|
|
|
|
|
Priority = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ClickedItem(object sender, EventArgs e)
|
|
|
|
|
{
|
2017-02-14 03:12:02 +03:00
|
|
|
|
_page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch", _page.Uri.StartsWith("http") ? "Website" : "App");
|
|
|
|
|
Application.Current.MainPage = new MainPage(_page.Uri);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
label.SetBinding<VaultListPageModel.AutofillGrouping>(Label.TextProperty, s => s.Name);
|
|
|
|
|
|
|
|
|
|
var grid = new ContentView
|
|
|
|
|
{
|
|
|
|
|
Padding = new Thickness(16, 8, 0, 8),
|
|
|
|
|
Content = label
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
View = grid;
|
|
|
|
|
BackgroundColor = Color.FromHex("efeff4");
|
2017-02-03 06:20:45 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-01-28 07:13:28 +03:00
|
|
|
|
}
|
|
|
|
|
}
|