re-use groupings page for ciphers listing

This commit is contained in:
Kyle Spearrin 2019-04-24 16:50:34 -04:00
parent 3d50133fa8
commit 003092a55b
9 changed files with 190 additions and 179 deletions

View file

@ -28,9 +28,6 @@
<Compile Update="Pages\GeneratorPage.xaml.cs"> <Compile Update="Pages\GeneratorPage.xaml.cs">
<DependentUpon>GeneratorPage.xaml</DependentUpon> <DependentUpon>GeneratorPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Vault\CiphersPage.xaml.cs">
<DependentUpon>CiphersPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Vault\ViewPage.xaml.cs"> <Compile Update="Pages\Vault\ViewPage.xaml.cs">
<DependentUpon>ViewPage.xaml</DependentUpon> <DependentUpon>ViewPage.xaml</DependentUpon>
</Compile> </Compile>

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.ViewPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:ViewPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:ViewPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Icon="cogs.png"
Text="{u:I18n Edit}" />
</ContentPage.ToolbarItems>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n Name}"
StyleClass="box-label" />
<Label
Text="{Binding Cipher.Name, Mode=OneWay}"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n Notes}"
StyleClass="box-label" />
<Label
Text="{Binding Cipher.Notes, Mode=OneWay}"
StyleClass="box-value" />
</StackLayout>
</StackLayout>
</ContentPage>

View file

@ -1,54 +0,0 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Bit.App.Pages
{
public partial class ViewPage : ContentPage
{
private readonly IBroadcasterService _broadcasterService;
private ViewPageViewModel _vm;
public ViewPage(string cipherId)
{
InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_vm = BindingContext as ViewPageViewModel;
_vm.Page = this;
_vm.CipherId = cipherId;
}
protected async override void OnAppearing()
{
base.OnAppearing();
_broadcasterService.Subscribe(nameof(ViewPage), async (message) =>
{
if(message.Command == "syncCompleted")
{
var data = message.Data as Dictionary<string, object>;
if(data.ContainsKey("successfully"))
{
var success = data["successfully"] as bool?;
if(success.HasValue && success.Value)
{
await _vm.LoadAsync();
}
}
}
});
await _vm.LoadAsync();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(ViewPage));
}
}
}

View file

@ -1,50 +0,0 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System.Threading.Tasks;
namespace Bit.App.Pages
{
public class CiphersPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly IUserService _userService;
private CipherView _cipher;
private bool _canAccessPremium;
public CiphersPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
PageTitle = AppResources.ViewItem;
}
public string CipherId { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public bool CanAccessPremium
{
get => _canAccessPremium;
set => SetProperty(ref _canAccessPremium, value);
}
public async Task LoadAsync()
{
// TODO: Cleanup
var cipher = await _cipherService.GetAsync(CipherId);
Cipher = await cipher.DecryptAsync();
CanAccessPremium = await _userService.CanAccessPremiumAsync();
// TODO: Totp
}
}
}

View file

@ -1,4 +1,5 @@
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -17,12 +18,25 @@ namespace Bit.App.Pages
private readonly GroupingsPageViewModel _viewModel; private readonly GroupingsPageViewModel _viewModel;
public GroupingsPage() public GroupingsPage()
: this(true)
{ }
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
string collectionId = null, string pageTitle = null)
{ {
InitializeComponent(); InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_viewModel = BindingContext as GroupingsPageViewModel; _viewModel = BindingContext as GroupingsPageViewModel;
_viewModel.Page = this; _viewModel.Page = this;
_viewModel.MainPage = mainPage;
_viewModel.Type = type;
_viewModel.FolderId = folderId;
_viewModel.CollectionId = collectionId;
if(pageTitle != null)
{
_viewModel.PageTitle = pageTitle;
}
} }
protected async override void OnAppearing() protected async override void OnAppearing()
@ -68,9 +82,13 @@ namespace Bit.App.Pages
{ {
await _viewModel.SelectCipherAsync(item.Cipher); await _viewModel.SelectCipherAsync(item.Cipher);
} }
else if(item.Folder != null || item.Collection != null) else if(item.Folder != null)
{ {
// TODO await _viewModel.SelectFolderAsync(item.Folder);
}
else if(item.Collection != null)
{
await _viewModel.SelectCollectionAsync(item.Collection);
} }
} }
} }

View file

@ -1,5 +1,6 @@
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -28,7 +29,7 @@ namespace Bit.App.Pages
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService"); _collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
PageTitle = "My Vault"; PageTitle = AppResources.MyVault;
GroupedItems = new ExtendedObservableCollection<GroupingsPageListGroup>(); GroupedItems = new ExtendedObservableCollection<GroupingsPageListGroup>();
LoadCommand = new Command(async () => await LoadAsync()); LoadCommand = new Command(async () => await LoadAsync());
} }
@ -36,6 +37,10 @@ namespace Bit.App.Pages
public bool ShowFavorites { get; set; } = true; public bool ShowFavorites { get; set; } = true;
public bool ShowFolders { get; set; } = true; public bool ShowFolders { get; set; } = true;
public bool ShowCollections { get; set; } = true; public bool ShowCollections { get; set; } = true;
public bool MainPage { get; set; }
public CipherType? Type { get; set; }
public string FolderId { get; set; }
public string CollectionId { get; set; }
public List<CipherView> Ciphers { get; set; } public List<CipherView> Ciphers { get; set; }
public List<CipherView> FavoriteCiphers { get; set; } public List<CipherView> FavoriteCiphers { get; set; }
@ -66,11 +71,10 @@ namespace Bit.App.Pages
{ {
try try
{ {
await LoadFoldersAsync(); await LoadDataAsync();
await LoadCollectionsAsync();
await LoadCiphersAsync();
var favListItems = FavoriteCiphers?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList(); var favListItems = FavoriteCiphers?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
var ciphersListItems = Ciphers?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
var folderListItems = NestedFolders?.Select(f => new GroupingsPageListItem { Folder = f.Node }).ToList(); var folderListItems = NestedFolders?.Select(f => new GroupingsPageListItem { Folder = f.Node }).ToList();
var collectionListItems = NestedCollections?.Select(c => var collectionListItems = NestedCollections?.Select(c =>
new GroupingsPageListItem { Collection = c.Node }).ToList(); new GroupingsPageListItem { Collection = c.Node }).ToList();
@ -91,6 +95,11 @@ namespace Bit.App.Pages
groupedItems.Add(new GroupingsPageListGroup(collectionListItems, AppResources.Collections, groupedItems.Add(new GroupingsPageListGroup(collectionListItems, AppResources.Collections,
Device.RuntimePlatform == Device.iOS)); Device.RuntimePlatform == Device.iOS));
} }
if(ciphersListItems?.Any() ?? false)
{
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
Device.RuntimePlatform == Device.iOS));
}
GroupedItems.ResetWithRange(groupedItems); GroupedItems.ResetWithRange(groupedItems);
} }
finally finally
@ -105,50 +114,118 @@ namespace Bit.App.Pages
await Page.Navigation.PushModalAsync(new NavigationPage(page)); await Page.Navigation.PushModalAsync(new NavigationPage(page));
} }
private async Task LoadFoldersAsync() public async Task SelectFolderAsync(CipherType type)
{ {
if(!ShowFolders) string title = null;
switch(Type.Value)
{ {
return; case CipherType.Login:
title = AppResources.Logins;
break;
case CipherType.SecureNote:
title = AppResources.SecureNotes;
break;
case CipherType.Card:
title = AppResources.Cards;
break;
case CipherType.Identity:
title = AppResources.Identities;
break;
default:
break;
} }
Folders = await _folderService.GetAllDecryptedAsync(); var page = new GroupingsPage(false, type, null, null, title);
NestedFolders = await _folderService.GetAllNestedAsync(); await Page.Navigation.PushAsync(page);
} }
private async Task LoadCollectionsAsync() public async Task SelectFolderAsync(FolderView folder)
{ {
if(!ShowCollections) var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name);
{ await Page.Navigation.PushAsync(page);
return;
}
Collections = await _collectionService.GetAllDecryptedAsync();
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
} }
private async Task LoadCiphersAsync() public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
{
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name);
await Page.Navigation.PushAsync(page);
}
private async Task LoadDataAsync()
{ {
_allCiphers = await _cipherService.GetAllDecryptedAsync(); _allCiphers = await _cipherService.GetAllDecryptedAsync();
Ciphers = _allCiphers; if(MainPage)
foreach(var c in _allCiphers)
{ {
if(c.Favorite) if(ShowFolders)
{ {
if(FavoriteCiphers == null) Folders = await _folderService.GetAllDecryptedAsync();
{ NestedFolders = await _folderService.GetAllNestedAsync();
FavoriteCiphers = new List<CipherView>();
}
FavoriteCiphers.Add(c);
} }
if(c.FolderId == null) if(ShowCollections)
{ {
if(NoFolderCiphers == null) Collections = await _collectionService.GetAllDecryptedAsync();
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
}
foreach(var c in _allCiphers)
{
if(c.Favorite)
{ {
NoFolderCiphers = new List<CipherView>(); if(FavoriteCiphers == null)
{
FavoriteCiphers = new List<CipherView>();
}
FavoriteCiphers.Add(c);
} }
NoFolderCiphers.Add(c); if(c.FolderId == null)
{
if(NoFolderCiphers == null)
{
NoFolderCiphers = new List<CipherView>();
}
NoFolderCiphers.Add(c);
}
}
FavoriteCiphers = _allCiphers.Where(c => c.Favorite).ToList();
}
else
{
if(Type != null)
{
Ciphers = _allCiphers.Where(c => c.Type == Type.Value).ToList();
}
else if(FolderId != null)
{
FolderId = FolderId == "none" ? null : FolderId;
if(FolderId != null)
{
var folderNode = await _folderService.GetNestedAsync(FolderId);
if(folderNode?.Node != null)
{
PageTitle = folderNode.Node.Name;
NestedFolders = (folderNode.Children?.Count ?? 0) > 0 ? folderNode.Children : null;
}
}
else
{
PageTitle = AppResources.FolderNone;
}
Ciphers = _allCiphers.Where(c => c.FolderId == FolderId).ToList();
}
else if(CollectionId != null)
{
var collectionNode = await _collectionService.GetNestedAsync(CollectionId);
if(collectionNode?.Node != null)
{
PageTitle = collectionNode.Node.Name;
}
Ciphers = _allCiphers.Where(c => c.CollectionIds?.Contains(CollectionId) ?? false).ToList();
}
else
{
PageTitle = AppResources.AllItems;
Ciphers = _allCiphers;
} }
} }
FavoriteCiphers = _allCiphers.Where(c => c.Favorite).ToList();
} }
} }
} }

View file

@ -19,7 +19,7 @@ namespace Bit.App.Resources {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class AppResources { internal class AppResources {
@ -168,6 +168,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to All Items.
/// </summary>
internal static string AllItems {
get {
return ResourceManager.GetString("AllItems", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to An error has occurred.. /// Looks up a localized string similar to An error has occurred..
/// </summary> /// </summary>
@ -780,6 +789,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Cards.
/// </summary>
internal static string Cards {
get {
return ResourceManager.GetString("Cards", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Change Email. /// Looks up a localized string similar to Change Email.
/// </summary> /// </summary>
@ -1734,6 +1752,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Identities.
/// </summary>
internal static string Identities {
get {
return ResourceManager.GetString("Identities", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Identity Server URL. /// Looks up a localized string similar to Identity Server URL.
/// </summary> /// </summary>
@ -2040,6 +2067,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Logins.
/// </summary>
internal static string Logins {
get {
return ResourceManager.GetString("Logins", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Login Unavailable. /// Looks up a localized string similar to Login Unavailable.
/// </summary> /// </summary>
@ -2760,6 +2796,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Secure Notes.
/// </summary>
internal static string SecureNotes {
get {
return ResourceManager.GetString("SecureNotes", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Security. /// Looks up a localized string similar to Security.
/// </summary> /// </summary>

View file

@ -1349,4 +1349,19 @@
<data name="InvalidEmail" xml:space="preserve"> <data name="InvalidEmail" xml:space="preserve">
<value>Invalid email address.</value> <value>Invalid email address.</value>
</data> </data>
<data name="Cards" xml:space="preserve">
<value>Cards</value>
</data>
<data name="Identities" xml:space="preserve">
<value>Identities</value>
</data>
<data name="Logins" xml:space="preserve">
<value>Logins</value>
</data>
<data name="SecureNotes" xml:space="preserve">
<value>Secure Notes</value>
</data>
<data name="AllItems" xml:space="preserve">
<value>All Items</value>
</data>
</root> </root>

View file

@ -25,7 +25,7 @@ namespace Bit.Core.Utilities
var cryptoPrimitiveService = Resolve<ICryptoPrimitiveService>("cryptoPrimitiveService"); var cryptoPrimitiveService = Resolve<ICryptoPrimitiveService>("cryptoPrimitiveService");
var i18nService = Resolve<II18nService>("i18nService"); var i18nService = Resolve<II18nService>("i18nService");
var messagingService = Resolve<IMessagingService>("messagingService"); var messagingService = Resolve<IMessagingService>("messagingService");
ISearchService searchService = null; SearchService searchService = null;
var stateService = new StateService(); var stateService = new StateService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
@ -65,6 +65,7 @@ namespace Bit.Core.Utilities
Register<ICipherService>("cipherService", cipherService); Register<ICipherService>("cipherService", cipherService);
Register<IFolderService>("folderService", folderService); Register<IFolderService>("folderService", folderService);
Register<ICollectionService>("collectionService", collectionService); Register<ICollectionService>("collectionService", collectionService);
Register<ISearchService>("searchService", searchService);
Register<ISyncService>("syncService", syncService); Register<ISyncService>("syncService", syncService);
Register<IPasswordGenerationService>("passwordGenerationService", passwordGenerationService); Register<IPasswordGenerationService>("passwordGenerationService", passwordGenerationService);
Register<ITotpService>("totpService", totpService); Register<ITotpService>("totpService", totpService);