mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 02:48:29 +03:00
[SG-79] Mobile Vault Filter (#1928)
* [SG-79] Vault Filter * Update vault button text after sync * formatting * cleanup * cleanup
This commit is contained in:
parent
b8b41fe847
commit
8a3d88b3ce
13 changed files with 261 additions and 25 deletions
|
@ -108,7 +108,7 @@
|
||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text=""
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||||
Clicked="MoreButton_Clicked"
|
Clicked="MoreButton_Clicked"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text=""
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
||||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||||
Clicked="MoreButton_Clicked"
|
Clicked="MoreButton_Clicked"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using Bit.App.Effects;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Effects;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
@ -10,8 +12,10 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class TabsPage : TabbedPage
|
public class TabsPage : TabbedPage
|
||||||
{
|
{
|
||||||
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
private NavigationPage _groupingsPage;
|
private NavigationPage _groupingsPage;
|
||||||
private NavigationPage _sendGroupingsPage;
|
private NavigationPage _sendGroupingsPage;
|
||||||
|
@ -19,6 +23,7 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
||||||
{
|
{
|
||||||
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
|
||||||
|
@ -78,12 +83,26 @@ namespace Bit.App.Pages
|
||||||
protected override async void OnAppearing()
|
protected override async void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
|
||||||
|
{
|
||||||
|
if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await UpdateVaultButtonTitleAsync();
|
||||||
if (await _keyConnectorService.UserNeedsMigration())
|
if (await _keyConnectorService.UserNeedsMigration())
|
||||||
{
|
{
|
||||||
_messagingService.Send("convertAccountToKeyConnector");
|
_messagingService.Send("convertAccountToKeyConnector");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
_broadcasterService.Unsubscribe(nameof(TabsPage));
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetToVaultPage()
|
public void ResetToVaultPage()
|
||||||
{
|
{
|
||||||
CurrentPage = _groupingsPage;
|
CurrentPage = _groupingsPage;
|
||||||
|
@ -131,5 +150,19 @@ namespace Bit.App.Pages
|
||||||
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
|
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateVaultButtonTitleAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
|
var isShowingVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
||||||
|
_groupingsPage.Title = isShowingVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||||
x:DataType="pages:GroupingsPageViewModel"
|
x:DataType="pages:GroupingsPageViewModel"
|
||||||
Title="{Binding PageTitle}"
|
Title="{Binding PageTitle}"
|
||||||
x:Name="_page">
|
x:Name="_page">
|
||||||
|
@ -106,6 +107,30 @@
|
||||||
GroupTemplate="{StaticResource groupTemplate}" />
|
GroupTemplate="{StaticResource groupTemplate}" />
|
||||||
|
|
||||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||||
|
<StackLayout
|
||||||
|
IsVisible="{Binding ShowVaultFilter}"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{Binding VaultFilterDescription}"
|
||||||
|
LineBreakMode="TailTruncation"
|
||||||
|
Margin="10,0"
|
||||||
|
StyleClass="text-md, text-muted"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
|
<controls:MiButton
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
|
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||||
|
Command="{Binding VaultFilterCommand}"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
|
</StackLayout>
|
||||||
|
|
||||||
<StackLayout
|
<StackLayout
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Padding="20, 0"
|
Padding="20, 0"
|
||||||
|
|
|
@ -27,8 +27,8 @@ namespace Bit.App.Pages
|
||||||
private PreviousPageInfo _previousPage;
|
private PreviousPageInfo _previousPage;
|
||||||
|
|
||||||
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
||||||
string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null,
|
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
|
||||||
bool deleted = false)
|
PreviousPageInfo previousPage = null, bool deleted = false)
|
||||||
{
|
{
|
||||||
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -52,6 +52,10 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
_vm.PageTitle = pageTitle;
|
_vm.PageTitle = pageTitle;
|
||||||
}
|
}
|
||||||
|
if (vaultFilterSelection != null)
|
||||||
|
{
|
||||||
|
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||||
|
}
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
@ -29,7 +30,10 @@ namespace Bit.App.Pages
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
private bool _syncRefreshing;
|
private bool _syncRefreshing;
|
||||||
|
private bool _showVaultFilter;
|
||||||
|
private string _vaultFilterSelection;
|
||||||
private string _noDataText;
|
private string _noDataText;
|
||||||
|
private List<Organization> _organizations;
|
||||||
private List<CipherView> _allCiphers;
|
private List<CipherView> _allCiphers;
|
||||||
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
||||||
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
||||||
|
@ -46,6 +50,8 @@ namespace Bit.App.Pages
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public GroupingsPageViewModel()
|
public GroupingsPageViewModel()
|
||||||
|
@ -60,10 +66,11 @@ namespace Bit.App.Pages
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||||
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
Loading = true;
|
Loading = true;
|
||||||
PageTitle = AppResources.MyVault;
|
|
||||||
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
||||||
RefreshCommand = new Command(async () =>
|
RefreshCommand = new Command(async () =>
|
||||||
{
|
{
|
||||||
|
@ -71,6 +78,9 @@ namespace Bit.App.Pages
|
||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
});
|
});
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
{
|
{
|
||||||
|
@ -87,8 +97,9 @@ namespace Bit.App.Pages
|
||||||
public bool HasCiphers { get; set; }
|
public bool HasCiphers { get; set; }
|
||||||
public bool HasFolders { get; set; }
|
public bool HasFolders { get; set; }
|
||||||
public bool HasCollections { get; set; }
|
public bool HasCollections { get; set; }
|
||||||
public bool ShowNoFolderCiphers => (NoFolderCiphers?.Count ?? int.MaxValue) < NoFolderListSize &&
|
public bool ShowNoFolderCipherGroup => NoFolderCiphers != null
|
||||||
(!Collections?.Any() ?? true);
|
&& NoFolderCiphers.Count < NoFolderListSize
|
||||||
|
&& (Collections is null || !Collections.Any());
|
||||||
public List<CipherView> Ciphers { get; set; }
|
public List<CipherView> Ciphers { get; set; }
|
||||||
public List<CipherView> FavoriteCiphers { get; set; }
|
public List<CipherView> FavoriteCiphers { get; set; }
|
||||||
public List<CipherView> NoFolderCiphers { get; set; }
|
public List<CipherView> NoFolderCiphers { get; set; }
|
||||||
|
@ -142,12 +153,30 @@ namespace Bit.App.Pages
|
||||||
get => _websiteIconsEnabled;
|
get => _websiteIconsEnabled;
|
||||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
}
|
}
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||||
|
public ICommand VaultFilterCommand { get; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
|
@ -172,6 +201,17 @@ namespace Bit.App.Pages
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_organizations = await _organizationService.GetAllAsync();
|
||||||
|
if (MainPage)
|
||||||
|
{
|
||||||
|
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||||
|
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||||
|
{
|
||||||
|
_vaultFilterSelection = AppResources.AllVaults;
|
||||||
|
}
|
||||||
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
|
}
|
||||||
|
|
||||||
_doingLoad = true;
|
_doingLoad = true;
|
||||||
LoadedOnce = true;
|
LoadedOnce = true;
|
||||||
ShowNoData = false;
|
ShowNoData = false;
|
||||||
|
@ -185,9 +225,9 @@ namespace Bit.App.Pages
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
if (ShowNoFolderCiphers && (NestedFolders?.Any() ?? false))
|
if (ShowNoFolderCipherGroup && (NestedFolders?.Any() ?? false))
|
||||||
{
|
{
|
||||||
// Remove "No Folder" from folder listing
|
// Remove "No Folder" folder from folders group
|
||||||
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +302,7 @@ namespace Bit.App.Pages
|
||||||
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
||||||
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
||||||
}
|
}
|
||||||
if (ShowNoFolderCiphers)
|
if (ShowNoFolderCipherGroup)
|
||||||
{
|
{
|
||||||
var noFolderCiphersListItems = NoFolderCiphers.Select(
|
var noFolderCiphersListItems = NoFolderCiphers.Select(
|
||||||
c => new GroupingsPageListItem { Cipher = c }).ToList();
|
c => new GroupingsPageListItem { Cipher = c }).ToList();
|
||||||
|
@ -354,6 +394,25 @@ namespace Bit.App.Pages
|
||||||
SyncRefreshing = false;
|
SyncRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task VaultFilterOptionsAsync()
|
||||||
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher)
|
public async Task SelectCipherAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
var page = new ViewPage(cipher.Id);
|
var page = new ViewPage(cipher.Id);
|
||||||
|
@ -380,25 +439,26 @@ namespace Bit.App.Pages
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var page = new GroupingsPage(false, type, null, null, title);
|
var page = new GroupingsPage(false, type, null, null, title, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectFolderAsync(FolderView folder)
|
public async Task SelectFolderAsync(FolderView folder)
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name);
|
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
|
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name);
|
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectTrashAsync()
|
public async Task SelectTrashAsync()
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, null, true);
|
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, _vaultFilterSelection, null,
|
||||||
|
true);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,8 +496,8 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
|
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
|
||||||
NoDataText = AppResources.NoItems;
|
NoDataText = AppResources.NoItems;
|
||||||
_allCiphers = await _cipherService.GetAllDecryptedAsync();
|
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
|
@ -451,12 +511,11 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
Folders = await _folderService.GetAllDecryptedAsync();
|
await FillFoldersAndCollectionsAsync(orgId);
|
||||||
NestedFolders = await _folderService.GetAllNestedAsync();
|
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||||
Collections = await _collectionService.GetAllDecryptedAsync();
|
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||||
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
|
HasCollections = NestedCollections?.Any() ?? false;
|
||||||
HasCollections = NestedCollections.Any();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -576,6 +635,63 @@ namespace Bit.App.Pages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
||||||
|
{
|
||||||
|
string orgId = null;
|
||||||
|
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
else if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
orgId = GetVaultFilterOrgId();
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers;
|
||||||
|
}
|
||||||
|
return orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FillFoldersAndCollectionsAsync(string orgId)
|
||||||
|
{
|
||||||
|
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||||
|
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
Folders = BuildFolders(decFolders);
|
||||||
|
Collections = null;
|
||||||
|
}
|
||||||
|
else if (IsVaultFilterOrgVault && !string.IsNullOrWhiteSpace(orgId))
|
||||||
|
{
|
||||||
|
Folders = BuildFolders(decFolders);
|
||||||
|
Collections = decCollections?.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Folders = decFolders;
|
||||||
|
Collections = decCollections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
|
private string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||||
|
{
|
||||||
|
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||||
|
return folders.Any() ? folders : null;
|
||||||
|
}
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|
30
src/App/Resources/AppResources.Designer.cs
generated
30
src/App/Resources/AppResources.Designer.cs
generated
|
@ -3970,5 +3970,35 @@ namespace Bit.App.Resources {
|
||||||
return ResourceManager.GetString("SpecialCharacters", resourceCulture);
|
return ResourceManager.GetString("SpecialCharacters", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string FilterByVault {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FilterByVault", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AllVaults {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AllVaults", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Vaults {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Vaults", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string VaultFilterDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VaultFilterDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string All {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("All", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2220,4 +2220,19 @@
|
||||||
<data name="SpecialCharacters" xml:space="preserve">
|
<data name="SpecialCharacters" xml:space="preserve">
|
||||||
<value>Special Characters (!@#$%^&*)</value>
|
<value>Special Characters (!@#$%^&*)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilterByVault" xml:space="preserve">
|
||||||
|
<value>Filter items by vault</value>
|
||||||
|
</data>
|
||||||
|
<data name="AllVaults" xml:space="preserve">
|
||||||
|
<value>All Vaults</value>
|
||||||
|
</data>
|
||||||
|
<data name="Vaults" xml:space="preserve">
|
||||||
|
<value>Vaults</value>
|
||||||
|
</data>
|
||||||
|
<data name="VaultFilterDescription" xml:space="preserve">
|
||||||
|
<value>Vault: {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="All" xml:space="preserve">
|
||||||
|
<value>All</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
|
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
|
||||||
Task<List<Folder>> GetAllAsync();
|
Task<List<Folder>> GetAllAsync();
|
||||||
Task<List<FolderView>> GetAllDecryptedAsync();
|
Task<List<FolderView>> GetAllDecryptedAsync();
|
||||||
Task<List<TreeNode<FolderView>>> GetAllNestedAsync();
|
Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null);
|
||||||
Task<Folder> GetAsync(string id);
|
Task<Folder> GetAsync(string id);
|
||||||
Task<TreeNode<FolderView>> GetNestedAsync(string id);
|
Task<TreeNode<FolderView>> GetNestedAsync(string id);
|
||||||
Task ReplaceAsync(Dictionary<string, FolderData> folders);
|
Task ReplaceAsync(Dictionary<string, FolderData> folders);
|
||||||
|
|
|
@ -20,5 +20,6 @@ namespace Bit.Core.Abstractions
|
||||||
string orgId);
|
string orgId);
|
||||||
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
||||||
int? GetPolicyInt(Policy policy, string key);
|
int? GetPolicyInt(Policy policy, string key);
|
||||||
|
Task<bool> ShouldShowVaultFilterAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,5 +111,6 @@
|
||||||
public const string EyeSlash = "\xe96d";
|
public const string EyeSlash = "\xe96d";
|
||||||
public const string File = "\xe96e";
|
public const string File = "\xe96e";
|
||||||
public const string Paste = "\xe96f";
|
public const string Paste = "\xe96f";
|
||||||
|
public const string ViewCellMenu = "\xe5d3";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,9 +107,12 @@ namespace Bit.Core.Services
|
||||||
return _decryptedFolderCache;
|
return _decryptedFolderCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync()
|
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null)
|
||||||
{
|
{
|
||||||
var folders = await GetAllDecryptedAsync();
|
if (folders == null)
|
||||||
|
{
|
||||||
|
folders = await GetAllDecryptedAsync();
|
||||||
|
}
|
||||||
var nodes = new List<TreeNode<FolderView>>();
|
var nodes = new List<TreeNode<FolderView>>();
|
||||||
foreach (var f in folders)
|
foreach (var f in folders)
|
||||||
{
|
{
|
||||||
|
|
|
@ -193,7 +193,8 @@ namespace Bit.Core.Services
|
||||||
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter, string userId = null)
|
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null,
|
||||||
|
string userId = null)
|
||||||
{
|
{
|
||||||
var policies = await GetAll(policyType, userId);
|
var policies = await GetAll(policyType, userId);
|
||||||
if (policies == null)
|
if (policies == null)
|
||||||
|
@ -246,6 +247,13 @@ namespace Bit.Core.Services
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||||
|
{
|
||||||
|
var organizations = await _organizationService.GetAllAsync();
|
||||||
|
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||||
|
return (organizations?.Any() ?? false) && !personalOwnershipPolicyApplies;
|
||||||
|
}
|
||||||
|
|
||||||
private bool? GetPolicyBool(Policy policy, string key)
|
private bool? GetPolicyBool(Policy policy, string key)
|
||||||
{
|
{
|
||||||
if (policy.Data.ContainsKey(key))
|
if (policy.Data.ContainsKey(key))
|
||||||
|
|
Loading…
Reference in a new issue