mirror of
https://github.com/bitwarden/android.git
synced 2024-11-01 07:35:52 +03:00
move favorites to top of grouping page
This commit is contained in:
parent
a4fbd521e3
commit
b6a4efa7ba
5 changed files with 132 additions and 39 deletions
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue