[SG-79] Mobile Vault Filter (#1928)

* [SG-79] Vault Filter

* Update vault button text after sync

* formatting

* cleanup

* cleanup
This commit is contained in:
mp-bw 2022-05-31 13:34:54 -04:00 committed by GitHub
parent b8b41fe847
commit 8a3d88b3ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 261 additions and 25 deletions

View file

@ -108,7 +108,7 @@
<controls:MiButton <controls:MiButton
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Text="&#xe5d3;" 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"

View file

@ -122,7 +122,7 @@
<controls:MiButton <controls:MiButton
Grid.Row="0" Grid.Row="0"
Grid.Column="2" Grid.Column="2"
Text="&#xe5d3;" 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"

View file

@ -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);
}
}
} }
} }

View file

@ -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"

View file

@ -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)
{ {

View file

@ -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())

View file

@ -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);
}
}
} }
} }

View file

@ -2220,4 +2220,19 @@
<data name="SpecialCharacters" xml:space="preserve"> <data name="SpecialCharacters" xml:space="preserve">
<value>Special Characters (!@#$%^&amp;*)</value> <value>Special Characters (!@#$%^&amp;*)</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>

View file

@ -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);

View file

@ -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();
} }
} }

View file

@ -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";
} }
} }

View file

@ -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)
{ {

View file

@ -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))