mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 23:25:45 +03:00
grouping listing
This commit is contained in:
parent
a4cbf3bee3
commit
7548122e2d
5 changed files with 182 additions and 56 deletions
|
@ -57,14 +57,22 @@
|
||||||
|
|
||||||
<StackLayout>
|
<StackLayout>
|
||||||
<ListView x:Name="ItemsListView"
|
<ListView x:Name="ItemsListView"
|
||||||
ItemsSource="{Binding Items}"
|
ItemsSource="{Binding GroupedItems}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
HasUnevenRows="true"
|
HasUnevenRows="true"
|
||||||
RefreshCommand="{Binding LoadCommand}"
|
RefreshCommand="{Binding LoadCommand}"
|
||||||
IsPullToRefreshEnabled="true"
|
IsPullToRefreshEnabled="true"
|
||||||
IsRefreshing="{Binding Loading, Mode=OneWay}"
|
IsRefreshing="{Binding Loading, Mode=OneWay}"
|
||||||
CachingStrategy="RecycleElement"
|
CachingStrategy="RecycleElement"
|
||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}">
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
|
IsGroupingEnabled="True">
|
||||||
|
<ListView.GroupHeaderTemplate>
|
||||||
|
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
|
||||||
|
<ViewCell>
|
||||||
|
<Label Text="{Binding Name}" />
|
||||||
|
</ViewCell>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.GroupHeaderTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -10,18 +12,48 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public partial class GroupingsPage : ContentPage
|
public partial class GroupingsPage : ContentPage
|
||||||
{
|
{
|
||||||
private GroupingsPageViewModel _viewModel;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
|
private readonly GroupingsPageViewModel _viewModel;
|
||||||
|
|
||||||
public GroupingsPage()
|
public GroupingsPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||||
_viewModel = BindingContext as GroupingsPageViewModel;
|
_viewModel = BindingContext as GroupingsPageViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async override void OnAppearing()
|
protected async override void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
_broadcasterService.Subscribe(nameof(GroupingsPage), async (message) =>
|
||||||
|
{
|
||||||
|
if(message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
// await _viewModel.LoadAsync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!_syncService.SyncInProgress)
|
||||||
|
{
|
||||||
|
await _viewModel.LoadAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(5000);
|
||||||
|
if(!_viewModel.Loaded)
|
||||||
|
{
|
||||||
await _viewModel.LoadAsync();
|
await _viewModel.LoadAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
_broadcasterService.Unsubscribe(nameof(GroupingsPage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
27
src/App/Pages/GroupingsPage/GroupingsPageListGroup.cs
Normal file
27
src/App/Pages/GroupingsPage/GroupingsPageListGroup.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class GroupingsPageListGroup : List<GroupingsPageListItem>
|
||||||
|
{
|
||||||
|
public GroupingsPageListGroup(List<GroupingsPageListItem> groupItems, string name, bool doUpper = true)
|
||||||
|
{
|
||||||
|
AddRange(groupItems);
|
||||||
|
if(string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
Name = "-";
|
||||||
|
}
|
||||||
|
else if(doUpper)
|
||||||
|
{
|
||||||
|
Name = name.ToUpperInvariant();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string NameShort => string.IsNullOrWhiteSpace(Name) || Name.Length == 0 ? "-" : Name[0].ToString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ namespace Bit.App.Pages
|
||||||
public class GroupingsPageListItem
|
public class GroupingsPageListItem
|
||||||
{
|
{
|
||||||
public FolderView Folder { get; set; }
|
public FolderView Folder { get; set; }
|
||||||
public Core.Models.View.CollectionView Collection { get; set; }
|
public CollectionView Collection { get; set; }
|
||||||
public CipherView Cipher { get; set; }
|
public CipherView Cipher { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
using Bit.Core.Models.View;
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
@ -9,78 +13,133 @@ namespace Bit.App.Pages
|
||||||
public class GroupingsPageViewModel : BaseViewModel
|
public class GroupingsPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private bool _loading = false;
|
private bool _loading = false;
|
||||||
|
private bool _loaded = false;
|
||||||
|
private List<CipherView> _allCiphers;
|
||||||
|
|
||||||
|
private readonly ICipherService _cipherService;
|
||||||
|
private readonly IFolderService _folderService;
|
||||||
|
private readonly ICollectionService _collectionService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
|
|
||||||
public GroupingsPageViewModel()
|
public GroupingsPageViewModel()
|
||||||
{
|
{
|
||||||
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
|
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
|
||||||
|
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
||||||
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||||
|
|
||||||
PageTitle = "My Vault";
|
PageTitle = "My Vault";
|
||||||
Items = new ExtendedObservableCollection<GroupingsPageListItem>();
|
GroupedItems = new ExtendedObservableCollection<GroupingsPageListGroup>();
|
||||||
LoadCommand = new Command(async () => await LoadAsync());
|
LoadCommand = new Command(async () => await LoadAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ShowFavorites { get; set; } = true;
|
||||||
|
public bool ShowFolders { get; set; } = true;
|
||||||
|
public bool ShowCollections { get; set; } = true;
|
||||||
|
|
||||||
|
public List<CipherView> Ciphers { get; set; }
|
||||||
|
public List<CipherView> FavoriteCiphers { get; set; }
|
||||||
|
public List<CipherView> NoFolderCiphers { get; set; }
|
||||||
|
public List<FolderView> Folders { get; set; }
|
||||||
|
public List<TreeNode<FolderView>> NestedFolders { get; set; }
|
||||||
|
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
||||||
|
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
||||||
|
|
||||||
public bool Loading
|
public bool Loading
|
||||||
{
|
{
|
||||||
get => _loading;
|
get => _loading;
|
||||||
set => SetProperty(ref _loading, value);
|
set => SetProperty(ref _loading, value);
|
||||||
}
|
}
|
||||||
public ExtendedObservableCollection<GroupingsPageListItem> Items { get; set; }
|
public bool Loaded
|
||||||
|
{
|
||||||
|
get => _loaded;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _loaded, value);
|
||||||
|
SetProperty(ref _loading, !value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ExtendedObservableCollection<GroupingsPageListGroup> GroupedItems { get; set; }
|
||||||
public Command LoadCommand { get; set; }
|
public Command LoadCommand { get; set; }
|
||||||
|
|
||||||
public Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
{
|
{
|
||||||
if(Loading)
|
|
||||||
{
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
Loading = true;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Items.ResetWithRange(new List<GroupingsPageListItem>
|
await LoadFoldersAsync();
|
||||||
|
await LoadCollectionsAsync();
|
||||||
|
await LoadCiphersAsync();
|
||||||
|
|
||||||
|
var favListItems = FavoriteCiphers?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
|
||||||
|
var folderListItems = NestedFolders?.Select(f => new GroupingsPageListItem { Folder = f.Node }).ToList();
|
||||||
|
var collectionListItems = NestedCollections?.Select(c =>
|
||||||
|
new GroupingsPageListItem { Collection = c.Node }).ToList();
|
||||||
|
|
||||||
|
var groupedItems = new List<GroupingsPageListGroup>();
|
||||||
|
if(favListItems?.Any() ?? false)
|
||||||
{
|
{
|
||||||
new GroupingsPageListItem
|
groupedItems.Add(new GroupingsPageListGroup(favListItems, AppResources.Favorites));
|
||||||
|
}
|
||||||
|
if(folderListItems?.Any() ?? false)
|
||||||
{
|
{
|
||||||
Cipher = new CipherView { Name = "Cipher 1" }
|
groupedItems.Add(new GroupingsPageListGroup(folderListItems, AppResources.Folders));
|
||||||
},
|
}
|
||||||
new GroupingsPageListItem
|
if(collectionListItems?.Any() ?? false)
|
||||||
{
|
{
|
||||||
Cipher = new CipherView { Name = "Cipher 2" }
|
groupedItems.Add(new GroupingsPageListGroup(collectionListItems, AppResources.Collections));
|
||||||
},
|
}
|
||||||
new GroupingsPageListItem
|
GroupedItems.ResetWithRange(groupedItems);
|
||||||
{
|
|
||||||
Cipher = new CipherView { Name = "Cipher 3" }
|
|
||||||
},
|
|
||||||
new GroupingsPageListItem
|
|
||||||
{
|
|
||||||
Cipher = new CipherView { Name = "Cipher 4" }
|
|
||||||
},
|
|
||||||
new GroupingsPageListItem
|
|
||||||
{
|
|
||||||
Folder = new FolderView { Name = "Folder 1" }
|
|
||||||
},
|
|
||||||
new GroupingsPageListItem
|
|
||||||
{
|
|
||||||
Folder = new FolderView { Name = "Folder 2" }
|
|
||||||
},
|
|
||||||
new GroupingsPageListItem
|
|
||||||
{
|
|
||||||
Folder = new FolderView { Name = "Folder 3" }
|
|
||||||
},
|
|
||||||
new GroupingsPageListItem
|
|
||||||
{
|
|
||||||
Collection = new Core.Models.View.CollectionView { Name = "Collection 1" }
|
|
||||||
},
|
|
||||||
new GroupingsPageListItem
|
|
||||||
{
|
|
||||||
Collection = new Core.Models.View.CollectionView { Name = "Collection 2" }
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Loading = false;
|
Loaded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(0);
|
private async Task LoadFoldersAsync()
|
||||||
|
{
|
||||||
|
if(!ShowFolders)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Folders = await _folderService.GetAllDecryptedAsync();
|
||||||
|
NestedFolders = await _folderService.GetAllNestedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadCollectionsAsync()
|
||||||
|
{
|
||||||
|
if(!ShowCollections)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Collections = await _collectionService.GetAllDecryptedAsync();
|
||||||
|
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadCiphersAsync()
|
||||||
|
{
|
||||||
|
_allCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
Ciphers = _allCiphers;
|
||||||
|
foreach(var c in _allCiphers)
|
||||||
|
{
|
||||||
|
if(c.Favorite)
|
||||||
|
{
|
||||||
|
if(FavoriteCiphers == null)
|
||||||
|
{
|
||||||
|
FavoriteCiphers = new List<CipherView>();
|
||||||
|
}
|
||||||
|
FavoriteCiphers.Add(c);
|
||||||
|
}
|
||||||
|
if(c.FolderId == null)
|
||||||
|
{
|
||||||
|
if(NoFolderCiphers == null)
|
||||||
|
{
|
||||||
|
NoFolderCiphers = new List<CipherView>();
|
||||||
|
}
|
||||||
|
NoFolderCiphers.Add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FavoriteCiphers = _allCiphers.Where(c => c.Favorite).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue