move favorites to top of grouping page

This commit is contained in:
Kyle Spearrin 2017-12-19 23:59:12 -05:00
parent a4fbd521e3
commit b6a4efa7ba
5 changed files with 132 additions and 39 deletions

View file

@ -28,7 +28,8 @@ namespace Bit.App.Controls
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)), FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
HorizontalOptions = LayoutOptions.StartAndExpand HorizontalOptions = LayoutOptions.StartAndExpand
}; };
Label.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Grouping.Name)); Label.SetBinding(Label.TextProperty, string.Format("{0}.{1}",
nameof(VaultListPageModel.GroupingOrCipher.Grouping), nameof(VaultListPageModel.Grouping.Name)));
CountLabel = new Label CountLabel = new Label
{ {
@ -37,7 +38,8 @@ namespace Bit.App.Controls
Style = (Style)Application.Current.Resources["text-muted"], Style = (Style)Application.Current.Resources["text-muted"],
HorizontalOptions = LayoutOptions.End HorizontalOptions = LayoutOptions.End
}; };
CountLabel.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Grouping.Count)); CountLabel.SetBinding(Label.TextProperty, string.Format("{0}.{1}",
nameof(VaultListPageModel.GroupingOrCipher.Grouping), nameof(VaultListPageModel.Grouping.Count)));
var stackLayout = new StackLayout var stackLayout = new StackLayout
{ {
@ -68,9 +70,10 @@ namespace Bit.App.Controls
protected override void OnBindingContextChanged() protected override void OnBindingContextChanged()
{ {
if(BindingContext is VaultListPageModel.Grouping grouping) if(BindingContext is VaultListPageModel.GroupingOrCipher model)
{ {
Icon.Source = grouping.Folder ? $"folder{(grouping.Id == null ? "_o" : string.Empty)}.png" : "cube.png"; Icon.Source = model.Grouping.Folder ?
$"folder{(model.Grouping.Id == null ? "_o" : string.Empty)}.png" : "cube.png";
} }
base.OnBindingContextChanged(); base.OnBindingContextChanged();

View file

@ -8,17 +8,33 @@ namespace Bit.App.Controls
{ {
public static readonly BindableProperty CipherParameterProperty = BindableProperty.Create(nameof(CipherParameter), public static readonly BindableProperty CipherParameterProperty = BindableProperty.Create(nameof(CipherParameter),
typeof(VaultListPageModel.Cipher), typeof(VaultListViewCell), null); typeof(VaultListPageModel.Cipher), typeof(VaultListViewCell), null);
public static readonly BindableProperty GroupingOrCipherParameterProperty =
BindableProperty.Create(nameof(GroupingOrCipherParameter), typeof(VaultListPageModel.GroupingOrCipher),
typeof(VaultListViewCell), null);
public VaultListViewCell(Action<VaultListPageModel.Cipher> moreClickedAction) public VaultListViewCell(Action<VaultListPageModel.Cipher> moreClickedAction, bool groupingOrCipherBinding = false)
{
string bindingPrefix = null;
if(groupingOrCipherBinding)
{
SetBinding(GroupingOrCipherParameterProperty, new Binding("."));
bindingPrefix = string.Concat(nameof(VaultListPageModel.GroupingOrCipher.Cipher), ".");
Button.Command = new Command(() => moreClickedAction?.Invoke(GroupingOrCipherParameter?.Cipher));
}
else
{ {
SetBinding(CipherParameterProperty, new Binding(".")); SetBinding(CipherParameterProperty, new Binding("."));
Label.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Cipher.Name)); Button.Command = new Command(() => moreClickedAction?.Invoke(CipherParameter));
Detail.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Cipher.Subtitle)); }
LabelIcon.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Cipher.Shared));
LabelIcon2.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Cipher.HasAttachments)); Label.SetBinding(Label.TextProperty, bindingPrefix + nameof(VaultListPageModel.Cipher.Name));
Detail.SetBinding(Label.TextProperty, bindingPrefix + nameof(VaultListPageModel.Cipher.Subtitle));
LabelIcon.SetBinding(VisualElement.IsVisibleProperty,
bindingPrefix + nameof(VaultListPageModel.Cipher.Shared));
LabelIcon2.SetBinding(VisualElement.IsVisibleProperty,
bindingPrefix + nameof(VaultListPageModel.Cipher.HasAttachments));
Button.Image = "more.png"; Button.Image = "more.png";
Button.Command = new Command(() => moreClickedAction?.Invoke(CipherParameter));
Button.BackgroundColor = Color.Transparent; Button.BackgroundColor = Color.Transparent;
LabelIcon.Source = "share.png"; LabelIcon.Source = "share.png";
@ -33,16 +49,33 @@ namespace Bit.App.Controls
set { SetValue(CipherParameterProperty, value); } set { SetValue(CipherParameterProperty, value); }
} }
public VaultListPageModel.GroupingOrCipher GroupingOrCipherParameter
{
get { return GetValue(GroupingOrCipherParameterProperty) as VaultListPageModel.GroupingOrCipher; }
set { SetValue(GroupingOrCipherParameterProperty, value); }
}
protected override void OnBindingContextChanged() protected override void OnBindingContextChanged()
{ {
Icon.Source = null; Icon.Source = null;
VaultListPageModel.Cipher cipher = null;
if(BindingContext is VaultListPageModel.Cipher item) if(BindingContext is VaultListPageModel.Cipher item)
{ {
if(item.Type == Enums.CipherType.Login) cipher = item;
}
else if(BindingContext is VaultListPageModel.GroupingOrCipher groupingOrCipherItem)
{
cipher = groupingOrCipherItem.Cipher;
}
if(cipher != null)
{
if(cipher.Type == Enums.CipherType.Login)
{ {
Icon.LoadingPlaceholder = "login.png"; Icon.LoadingPlaceholder = "login.png";
} }
Icon.Source = item.Icon; Icon.Source = cipher.Icon;
} }
base.OnBindingContextChanged(); base.OnBindingContextChanged();

View file

@ -185,6 +185,24 @@ namespace Bit.App.Models.Page
public string Name { get; set; } public string Name { get; set; }
} }
public class GroupingOrCipher
{
public GroupingOrCipher(Grouping grouping)
{
Grouping = grouping;
Cipher = null;
}
public GroupingOrCipher(Cipher cipher)
{
Cipher = cipher;
Grouping = null;
}
public Grouping Grouping { get; set; }
public Cipher Cipher { get; set; }
}
public class Grouping public class Grouping
{ {
public Grouping(string name, int count) public Grouping(string name, int count)

View file

@ -13,16 +13,13 @@ namespace Bit.App.Pages
TintColor = Color.FromHex("3c8dbc"); TintColor = Color.FromHex("3c8dbc");
var settingsNavigation = new ExtendedNavigationPage(new SettingsPage()); var settingsNavigation = new ExtendedNavigationPage(new SettingsPage());
var favoritesNavigation = new ExtendedNavigationPage(new VaultListCiphersPage(favorites: true));
var vaultNavigation = new ExtendedNavigationPage(new VaultListGroupingsPage()); var vaultNavigation = new ExtendedNavigationPage(new VaultListGroupingsPage());
var toolsNavigation = new ExtendedNavigationPage(new ToolsPage()); var toolsNavigation = new ExtendedNavigationPage(new ToolsPage());
favoritesNavigation.Icon = "star.png";
vaultNavigation.Icon = "fa_lock.png"; vaultNavigation.Icon = "fa_lock.png";
toolsNavigation.Icon = "tools.png"; toolsNavigation.Icon = "tools.png";
settingsNavigation.Icon = "cogs.png"; settingsNavigation.Icon = "cogs.png";
Children.Add(favoritesNavigation);
Children.Add(vaultNavigation); Children.Add(vaultNavigation);
Children.Add(toolsNavigation); Children.Add(toolsNavigation);
Children.Add(settingsNavigation); Children.Add(settingsNavigation);

View file

@ -51,8 +51,8 @@ namespace Bit.App.Pages
Init(); Init();
} }
public ExtendedObservableCollection<Section<Grouping>> PresentationSections { get; private set; } public ExtendedObservableCollection<Section<GroupingOrCipher>> PresentationSections { get; private set; }
= new ExtendedObservableCollection<Section<Grouping>>(); = new ExtendedObservableCollection<Section<GroupingOrCipher>>();
public ListView ListView { get; set; } public ListView ListView { get; set; }
public StackLayout NoDataStackLayout { get; set; } public StackLayout NoDataStackLayout { get; set; }
public ActivityIndicator LoadingIndicator { get; set; } public ActivityIndicator LoadingIndicator { get; set; }
@ -73,7 +73,7 @@ namespace Bit.App.Pages
HasUnevenRows = true, HasUnevenRows = true,
GroupHeaderTemplate = new DataTemplate(() => new SectionHeaderViewCell( GroupHeaderTemplate = new DataTemplate(() => new SectionHeaderViewCell(
nameof(Section<Grouping>.Name), nameof(Section<Grouping>.Count), new Thickness(16, 12))), nameof(Section<Grouping>.Name), nameof(Section<Grouping>.Count), new Thickness(16, 12))),
ItemTemplate = new DataTemplate(() => new VaultGroupingViewCell()) ItemTemplate = new GroupingOrCipherDataTemplateSelector(this)
}; };
if(Device.RuntimePlatform == Device.iOS) if(Device.RuntimePlatform == Device.iOS)
@ -130,7 +130,7 @@ namespace Bit.App.Pages
} }
}); });
ListView.ItemSelected += GroupingSelected; ListView.ItemSelected += GroupingOrCipherSelected;
AddCipherItem?.InitEvents(); AddCipherItem?.InitEvents();
SearchItem?.InitEvents(); SearchItem?.InitEvents();
@ -173,7 +173,7 @@ namespace Bit.App.Pages
base.OnDisappearing(); base.OnDisappearing();
MessagingCenter.Unsubscribe<Application, bool>(Application.Current, "SyncCompleted"); MessagingCenter.Unsubscribe<Application, bool>(Application.Current, "SyncCompleted");
ListView.ItemSelected -= GroupingSelected; ListView.ItemSelected -= GroupingOrCipherSelected;
AddCipherItem?.Dispose(); AddCipherItem?.Dispose();
SearchItem?.Dispose(); SearchItem?.Dispose();
} }
@ -185,7 +185,8 @@ namespace Bit.App.Pages
Task.Run(async () => Task.Run(async () =>
{ {
var sections = new List<Section<Grouping>>(); var sections = new List<Section<GroupingOrCipher>>();
var favoriteCipherGroupings = new List<GroupingOrCipher>();
var ciphers = await _cipherService.GetAllAsync(); var ciphers = await _cipherService.GetAllAsync();
var collectionsDict = (await _collectionService.GetAllCipherAssociationsAsync()) var collectionsDict = (await _collectionService.GetAllCipherAssociationsAsync())
.GroupBy(c => c.Item2).ToDictionary(g => g.Key, v => v.ToList()); .GroupBy(c => c.Item2).ToDictionary(g => g.Key, v => v.ToList());
@ -193,6 +194,11 @@ namespace Bit.App.Pages
var folderCounts = new Dictionary<string, int> { ["none"] = 0 }; var folderCounts = new Dictionary<string, int> { ["none"] = 0 };
foreach(var cipher in ciphers) foreach(var cipher in ciphers)
{ {
if(cipher.Favorite)
{
favoriteCipherGroupings.Add(new GroupingOrCipher(new Cipher(cipher, _appSettingsService)));
}
if(cipher.FolderId != null) if(cipher.FolderId != null)
{ {
if(!folderCounts.ContainsKey(cipher.FolderId)) if(!folderCounts.ContainsKey(cipher.FolderId))
@ -207,21 +213,28 @@ namespace Bit.App.Pages
} }
} }
if(favoriteCipherGroupings?.Any() ?? false)
{
sections.Add(new Section<GroupingOrCipher>(
favoriteCipherGroupings.OrderBy(g => g.Cipher.Name).ThenBy(g => g.Cipher.Subtitle).ToList(),
AppResources.Favorites));
}
var folders = await _folderService.GetAllAsync(); var folders = await _folderService.GetAllAsync();
var folderGroupings = folders? var folderGroupings = folders?
.Select(f => new Grouping(f, folderCounts.ContainsKey(f.Id) ? folderCounts[f.Id] : 0)) .Select(f => new GroupingOrCipher(new Grouping(f, folderCounts.ContainsKey(f.Id) ? folderCounts[f.Id] : 0)))
.OrderBy(g => g.Name).ToList(); .OrderBy(g => g.Grouping.Name).ToList();
folderGroupings.Add(new Grouping(AppResources.FolderNone, folderCounts["none"])); folderGroupings.Add(new GroupingOrCipher(new Grouping(AppResources.FolderNone, folderCounts["none"])));
sections.Add(new Section<Grouping>(folderGroupings, AppResources.Folders)); sections.Add(new Section<GroupingOrCipher>(folderGroupings, AppResources.Folders));
var collections = await _collectionService.GetAllAsync(); var collections = await _collectionService.GetAllAsync();
var collectionGroupings = collections? var collectionGroupings = collections?
.Select(c => new Grouping(c, .Select(c => new GroupingOrCipher(new Grouping(c,
collectionsDict.ContainsKey(c.Id) ? collectionsDict[c.Id].Count() : 0)) collectionsDict.ContainsKey(c.Id) ? collectionsDict[c.Id].Count() : 0)))
.OrderBy(g => g.Name).ToList(); .OrderBy(g => g.Grouping.Name).ToList();
if(collectionGroupings?.Any() ?? false) if(collectionGroupings?.Any() ?? false)
{ {
sections.Add(new Section<Grouping>(collectionGroupings, AppResources.Collections)); sections.Add(new Section<GroupingOrCipher>(collectionGroupings, AppResources.Collections));
} }
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
@ -246,25 +259,36 @@ namespace Bit.App.Pages
return cts; return cts;
} }
private async void GroupingSelected(object sender, SelectedItemChangedEventArgs e) private async void GroupingOrCipherSelected(object sender, SelectedItemChangedEventArgs e)
{ {
var grouping = e.SelectedItem as Grouping; var groupingOrCipher = e.SelectedItem as GroupingOrCipher;
if(grouping == null) if(groupingOrCipher == null)
{ {
return; return;
} }
Page page; if(groupingOrCipher.Grouping != null)
if(grouping.Folder)
{ {
page = new VaultListCiphersPage(folder: true, folderId: grouping.Id, groupingName: grouping.Name); Page page;
if(groupingOrCipher.Grouping.Folder)
{
page = new VaultListCiphersPage(folder: true,
folderId: groupingOrCipher.Grouping.Id, groupingName: groupingOrCipher.Grouping.Name);
} }
else else
{ {
page = new VaultListCiphersPage(collectionId: grouping.Id, groupingName: grouping.Name); page = new VaultListCiphersPage(collectionId: groupingOrCipher.Grouping.Id,
groupingName: groupingOrCipher.Grouping.Name);
} }
await Navigation.PushAsync(page); await Navigation.PushAsync(page);
}
else if(groupingOrCipher.Cipher != null)
{
var page = new VaultViewCipherPage(groupingOrCipher.Cipher.Type, groupingOrCipher.Cipher.Id);
await Navigation.PushForDeviceAsync(page);
}
((ListView)sender).SelectedItem = null; ((ListView)sender).SelectedItem = null;
} }
@ -283,5 +307,23 @@ namespace Bit.App.Pages
Icon = "search.png"; Icon = "search.png";
} }
} }
public class GroupingOrCipherDataTemplateSelector : DataTemplateSelector
{
public GroupingOrCipherDataTemplateSelector(VaultListGroupingsPage page)
{
GroupingTemplate = new DataTemplate(() => new VaultGroupingViewCell());
CipherTemplate = new DataTemplate(() => new VaultListViewCell(
(Cipher c) => Helpers.CipherMoreClickedAsync(page, c, false), true));
}
public DataTemplate GroupingTemplate { get; set; }
public DataTemplate CipherTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return ((GroupingOrCipher)item).Cipher == null ? GroupingTemplate : CipherTemplate;
}
}
} }
} }