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

414 lines
15 KiB
C#
Raw Normal View History

2016-05-02 09:52:09 +03:00
using System;
using System.Collections.ObjectModel;
using System.Linq;
2016-05-04 02:49:49 +03:00
using System.Threading.Tasks;
using Acr.UserDialogs;
2016-05-02 09:52:09 +03:00
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Page;
using Bit.App.Resources;
2016-05-02 09:52:09 +03:00
using Xamarin.Forms;
using XLabs.Ioc;
using Bit.App.Utilities;
2016-06-28 07:55:53 +03:00
using PushNotification.Plugin.Abstractions;
using Plugin.Settings.Abstractions;
using Plugin.Connectivity.Abstractions;
using System.Collections.Generic;
2016-05-02 09:52:09 +03:00
2016-05-03 00:50:16 +03:00
namespace Bit.App.Pages
2016-05-02 09:52:09 +03:00
{
public class VaultListSitesPage : ExtendedContentPage
2016-05-02 09:52:09 +03:00
{
private readonly IFolderService _folderService;
private readonly ISiteService _siteService;
2016-05-04 02:49:49 +03:00
private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity;
private readonly IClipboardService _clipboardService;
private readonly ISyncService _syncService;
2016-06-28 07:55:53 +03:00
private readonly IPushNotification _pushNotification;
private readonly ISettings _settings;
private readonly bool _favorites;
2016-05-02 09:52:09 +03:00
public VaultListSitesPage(bool favorites)
2016-05-02 09:52:09 +03:00
{
_favorites = favorites;
2016-05-02 09:52:09 +03:00
_folderService = Resolver.Resolve<IFolderService>();
_siteService = Resolver.Resolve<ISiteService>();
_connectivity = Resolver.Resolve<IConnectivity>();
2016-05-04 02:49:49 +03:00
_userDialogs = Resolver.Resolve<IUserDialogs>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_syncService = Resolver.Resolve<ISyncService>();
2016-06-28 07:55:53 +03:00
_pushNotification = Resolver.Resolve<IPushNotification>();
_settings = Resolver.Resolve<ISettings>();
2016-05-02 09:52:09 +03:00
2016-05-04 02:49:49 +03:00
Init();
}
2016-07-11 08:12:31 +03:00
public ExtendedObservableCollection<VaultListPageModel.Folder> PresentationFolders { get; private set; }
= new ExtendedObservableCollection<VaultListPageModel.Folder>();
public ListView ListView { get; set; }
public IEnumerable<VaultListPageModel.Site> Sites { get; set; } = new List<VaultListPageModel.Site>();
public IEnumerable<VaultListPageModel.Folder> Folders { get; set; } = new List<VaultListPageModel.Folder>();
public SearchBar Search { get; set; }
2016-05-02 09:52:09 +03:00
2016-05-04 02:49:49 +03:00
private void Init()
{
2016-07-15 08:52:33 +03:00
MessagingCenter.Subscribe<Application, bool>(Application.Current, "SyncCompleted", (sender, success) =>
{
if(success)
{
2016-07-15 08:52:33 +03:00
FetchAndLoadVault();
}
});
if(!_favorites)
{
ToolbarItems.Add(new AddSiteToolBarItem(this));
}
2016-05-02 09:52:09 +03:00
2016-07-11 08:12:31 +03:00
ListView = new ListView
{
IsGroupingEnabled = true,
2016-07-11 08:12:31 +03:00
ItemsSource = PresentationFolders,
HasUnevenRows = true,
GroupHeaderTemplate = new DataTemplate(() => new VaultListHeaderViewCell(this)),
ItemTemplate = new DataTemplate(() => new VaultListViewCell(this))
};
2016-06-29 07:34:20 +03:00
2016-06-18 01:14:24 +03:00
if(Device.OS == TargetPlatform.iOS)
{
2016-07-11 08:12:31 +03:00
ListView.RowHeight = -1;
2016-06-18 01:14:24 +03:00
}
2016-06-29 07:34:20 +03:00
2016-07-11 08:12:31 +03:00
ListView.ItemSelected += SiteSelected;
Search = new SearchBar
2016-07-11 08:12:31 +03:00
{
Placeholder = "Search vault",
2016-07-19 02:16:27 +03:00
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Button)),
CancelButtonColor = Color.FromHex("3c8dbc")
2016-07-11 08:12:31 +03:00
};
Search.TextChanged += SearchBar_TextChanged;
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
2016-05-02 09:52:09 +03:00
Title = _favorites ? AppResources.Favorites : AppResources.MyVault;
2016-07-11 08:12:31 +03:00
Content = new StackLayout
{
Children = { Search, ListView },
2016-07-11 08:12:31 +03:00
Spacing = 0
};
}
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
{
FilterResultsBackground(((SearchBar)sender).Text);
2016-07-11 08:12:31 +03:00
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
var oldLength = e.OldTextValue?.Length ?? 0;
var newLength = e.NewTextValue?.Length ?? 0;
if(oldLength < 2 && newLength < 2 && oldLength < newLength)
{
return;
}
FilterResultsBackground(e.NewTextValue);
2016-07-11 08:12:31 +03:00
}
private void FilterResultsBackground(string searchFilter)
2016-07-11 08:12:31 +03:00
{
Task.Run(async () =>
2016-07-11 08:12:31 +03:00
{
if(!string.IsNullOrWhiteSpace(searchFilter))
{
await Task.Delay(300);
if(searchFilter != Search.Text)
{
return;
}
}
FilterResults(searchFilter);
});
2016-05-02 09:52:09 +03:00
}
private void FilterResults(string searchFilter)
{
if(string.IsNullOrWhiteSpace(searchFilter))
{
LoadFolders(Sites);
}
else
{
searchFilter = searchFilter.ToLower();
var filteredSites = Sites.Where(s => s.Name.ToLower().Contains(searchFilter) || s.Username.ToLower().Contains(searchFilter));
LoadFolders(filteredSites);
}
}
2016-07-15 08:52:33 +03:00
protected override void OnAppearing()
2016-05-02 09:52:09 +03:00
{
base.OnAppearing();
2016-07-15 08:52:33 +03:00
FetchAndLoadVault();
2016-06-28 07:55:53 +03:00
if(_connectivity.IsConnected && Device.OS == TargetPlatform.iOS && !_favorites)
2016-06-28 07:55:53 +03:00
{
var pushPromptShow = _settings.GetValueOrDefault<bool>(Constants.PushInitialPromptShown);
Action registerAction = () =>
{
var lastPushRegistration = _settings.GetValueOrDefault<DateTime?>(Constants.PushLastRegistrationDate);
if(!pushPromptShow || !lastPushRegistration.HasValue || (DateTime.UtcNow - lastPushRegistration) > TimeSpan.FromDays(1))
{
_pushNotification.Register();
}
};
2016-07-02 02:16:47 +03:00
if(!pushPromptShow)
2016-06-28 07:55:53 +03:00
{
_settings.AddOrUpdateValue(Constants.PushInitialPromptShown, true);
_userDialogs.Alert(new AlertConfig
{
Message = "bitwarden keeps your vault automatically synced by using push notifications."
2016-07-05 06:19:16 +03:00
+ " For the best possible experience, please select \"Ok\" on the following prompt when asked to enable push notifications.",
Title = "Enable Automatic Syncing",
OnOk = registerAction,
OkText = "Ok, got it!"
});
2016-06-28 07:55:53 +03:00
}
else
2016-07-02 02:16:47 +03:00
{
// Check push registration once per day
registerAction();
2016-07-02 02:16:47 +03:00
}
2016-06-28 07:55:53 +03:00
}
2016-05-04 02:49:49 +03:00
}
2016-05-02 09:52:09 +03:00
2016-07-15 08:52:33 +03:00
private void FetchAndLoadVault()
2016-07-11 08:12:31 +03:00
{
if(PresentationFolders.Count > 0 && _syncService.SyncInProgress)
{
return;
}
2016-07-15 08:52:33 +03:00
Task.Run(async () =>
{
var foldersTask = _folderService.GetAllAsync();
var sitesTask = _favorites ? _siteService.GetAllAsync(true) : _siteService.GetAllAsync();
await Task.WhenAll(foldersTask, sitesTask);
2016-07-11 08:12:31 +03:00
var folders = await foldersTask;
var sites = await sitesTask;
2016-07-11 08:12:31 +03:00
Folders = folders.Select(f => new VaultListPageModel.Folder(f));
Sites = sites.Select(s => new VaultListPageModel.Site(s));
2016-07-11 08:12:31 +03:00
FilterResults(Search.Text);
});
2016-07-11 08:12:31 +03:00
}
private void LoadFolders(IEnumerable<VaultListPageModel.Site> sites)
2016-05-04 02:49:49 +03:00
{
2016-07-11 08:12:31 +03:00
var folders = new List<VaultListPageModel.Folder>(Folders);
foreach(var folder in folders)
{
if(folder.Any())
{
folder.Clear();
}
folder.AddRange(sites.Where(s => s.FolderId == folder.Id));
}
2016-05-04 02:49:49 +03:00
var noneFolder = new VaultListPageModel.Folder(sites.Where(s => s.FolderId == null));
2016-07-11 08:12:31 +03:00
folders.Add(noneFolder);
2016-07-11 08:12:31 +03:00
PresentationFolders.ResetWithRange(folders.Where(f => f.Any()));
2016-05-02 09:52:09 +03:00
}
2016-05-04 02:49:49 +03:00
private void SiteSelected(object sender, SelectedItemChangedEventArgs e)
2016-05-02 09:52:09 +03:00
{
var site = e.SelectedItem as VaultListPageModel.Site;
var page = new ExtendedNavigationPage(new VaultViewSitePage(site.Id));
Navigation.PushModalAsync(page);
2016-05-02 09:52:09 +03:00
}
private async void MoreClickedAsync(VaultListPageModel.Site site)
2016-05-02 09:52:09 +03:00
{
var buttons = new List<string> { AppResources.View, AppResources.Edit };
if(!string.IsNullOrWhiteSpace(site.Password.Value))
{
buttons.Add(AppResources.CopyPassword);
}
if(!string.IsNullOrWhiteSpace(site.Username))
{
buttons.Add(AppResources.CopyUsername);
}
if(!string.IsNullOrWhiteSpace(site.Uri.Value) && (site.Uri.Value.StartsWith("http://") || site.Uri.Value.StartsWith("https://")))
{
buttons.Add(AppResources.GoToWebsite);
}
var selection = await DisplayActionSheet(site.Name, AppResources.Cancel, null, buttons.ToArray());
2016-05-02 09:52:09 +03:00
if(selection == AppResources.View)
2016-05-04 02:49:49 +03:00
{
var page = new ExtendedNavigationPage(new VaultViewSitePage(site.Id));
await Navigation.PushModalAsync(page);
2016-05-04 02:49:49 +03:00
}
else if(selection == AppResources.Edit)
{
var page = new ExtendedNavigationPage(new VaultEditSitePage(site.Id));
await Navigation.PushModalAsync(page);
}
else if(selection == AppResources.CopyPassword)
{
Copy(site.Password.Value, AppResources.Password);
}
else if(selection == AppResources.CopyUsername)
{
Copy(site.Username, AppResources.Username);
}
else if(selection == AppResources.GoToWebsite)
{
Device.OpenUri(new Uri(site.Uri.Value));
}
}
private void Copy(string copyText, string alertLabel)
{
_clipboardService.CopyToClipboard(copyText);
_userDialogs.SuccessToast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
2016-05-04 02:49:49 +03:00
}
private async void DeleteClickedAsync(object sender, EventArgs e)
{
if(!await _userDialogs.ConfirmAsync(AppResources.DoYouReallyWantToDelete, null, AppResources.Yes, AppResources.No))
{
return;
}
2016-05-04 02:49:49 +03:00
var mi = sender as MenuItem;
var site = mi.CommandParameter as VaultListPageModel.Site;
2016-05-04 02:49:49 +03:00
var deleteCall = await _siteService.DeleteAsync(site.Id);
if(deleteCall.Succeeded)
{
2016-07-11 08:12:31 +03:00
var folder = PresentationFolders.Single(f => f.Id == site.FolderId);
2016-05-04 02:49:49 +03:00
var siteIndex = folder.Select((s, i) => new { s, i }).First(s => s.s.Id == site.Id).i;
folder.RemoveAt(siteIndex);
_userDialogs.SuccessToast(AppResources.SiteDeleted);
2016-05-04 02:49:49 +03:00
}
else if(deleteCall.Errors.Count() > 0)
{
await DisplayAlert(AppResources.AnErrorHasOccurred, deleteCall.Errors.First().Message, AppResources.Ok);
2016-05-04 02:49:49 +03:00
}
}
private class AddSiteToolBarItem : ToolbarItem
{
private readonly VaultListSitesPage _page;
2016-05-04 02:49:49 +03:00
public AddSiteToolBarItem(VaultListSitesPage page)
2016-05-04 02:49:49 +03:00
{
_page = page;
Text = AppResources.Add;
2016-06-14 05:09:16 +03:00
Icon = "plus";
2016-05-04 02:49:49 +03:00
Clicked += ClickedItem;
}
private async void ClickedItem(object sender, EventArgs e)
{
var page = new ExtendedNavigationPage(new VaultAddSitePage());
await _page.Navigation.PushModalAsync(page);
2016-05-04 02:49:49 +03:00
}
}
2016-06-29 06:44:47 +03:00
private class VaultListViewCell : LabeledDetailCell
2016-05-04 02:49:49 +03:00
{
private VaultListSitesPage _page;
2016-07-02 02:16:47 +03:00
public static readonly BindableProperty SiteParameterProperty = BindableProperty.Create(nameof(SiteParameter),
2016-06-29 07:34:20 +03:00
typeof(VaultListPageModel.Site), typeof(VaultListViewCell), null);
public VaultListViewCell(VaultListSitesPage page)
2016-05-04 02:49:49 +03:00
{
_page = page;
var deleteAction = new MenuItem { Text = AppResources.Delete, IsDestructive = true };
deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
deleteAction.Clicked += page.DeleteClickedAsync;
var moreAction = new MenuItem { Text = AppResources.More };
moreAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
moreAction.Clicked += MoreAction_Clicked;
2016-06-29 07:34:20 +03:00
SetBinding(SiteParameterProperty, new Binding("."));
2016-06-29 06:44:47 +03:00
Label.SetBinding<VaultListPageModel.Site>(Label.TextProperty, s => s.Name);
Detail.SetBinding<VaultListPageModel.Site>(Label.TextProperty, s => s.Username);
ContextActions.Add(deleteAction);
ContextActions.Add(moreAction);
Button.Image = "more";
Button.Command = new Command(() => ShowMore());
2016-07-01 02:08:09 +03:00
Button.BackgroundColor = Color.Transparent;
BackgroundColor = Color.White;
}
2016-06-29 07:34:20 +03:00
public VaultListPageModel.Site SiteParameter
{
get { return GetValue(SiteParameterProperty) as VaultListPageModel.Site; }
set { SetValue(SiteParameterProperty, value); }
}
private void MoreAction_Clicked(object sender, EventArgs e)
{
var menuItem = sender as MenuItem;
var site = menuItem.CommandParameter as VaultListPageModel.Site;
_page.MoreClickedAsync(site);
}
private void ShowMore()
{
_page.MoreClickedAsync(SiteParameter);
}
}
2016-07-01 02:08:09 +03:00
private class VaultListHeaderViewCell : ExtendedViewCell
{
public VaultListHeaderViewCell(VaultListSitesPage page)
{
var image = new Image
{
Source = "fa_folder_open.png",
VerticalOptions = LayoutOptions.CenterAndExpand
};
var label = new Label
{
VerticalTextAlignment = TextAlignment.Center,
2016-05-25 03:14:37 +03:00
VerticalOptions = LayoutOptions.CenterAndExpand,
2016-06-17 07:01:25 +03:00
Style = (Style)Application.Current.Resources["text-muted"]
};
label.SetBinding<VaultListPageModel.Folder>(Label.TextProperty, s => s.Name);
var stackLayout = new StackLayout
{
Orientation = StackOrientation.Horizontal,
2016-05-25 03:14:37 +03:00
VerticalOptions = LayoutOptions.FillAndExpand,
2016-06-28 03:56:59 +03:00
Children = { image, label },
2016-06-29 07:34:20 +03:00
Padding = new Thickness(16, 0, 0, 0)
};
2016-05-25 03:14:37 +03:00
View = stackLayout;
Height = 40;
2016-07-01 02:08:09 +03:00
BackgroundColor = Color.FromHex("efeff4");
2016-05-04 02:49:49 +03:00
}
2016-05-02 09:52:09 +03:00
}
}
}