Additional logic around filter display (#1951)

This commit is contained in:
mp-bw 2022-06-14 13:02:03 -04:00 committed by GitHub
parent e51233bf9b
commit 448758a697
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 144 additions and 154 deletions

View file

@ -3,14 +3,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
@ -18,7 +15,7 @@ using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class CiphersPageViewModel : BaseViewModel public class CiphersPageViewModel : VaultFilterViewModel
{ {
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
@ -31,12 +28,9 @@ namespace Bit.App.Pages
private CancellationTokenSource _searchCancellationTokenSource; private CancellationTokenSource _searchCancellationTokenSource;
private readonly ILogger _logger; private readonly ILogger _logger;
private bool _showVaultFilter;
private string _vaultFilterSelection;
private bool _showNoData; private bool _showNoData;
private bool _showList; private bool _showList;
private bool _websiteIconsEnabled; private bool _websiteIconsEnabled;
private List<Organization> _organizations;
public CiphersPageViewModel() public CiphersPageViewModel()
{ {
@ -52,18 +46,19 @@ namespace Bit.App.Pages
Ciphers = new ExtendedObservableCollection<CipherView>(); Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync); CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
} }
public Command CipherOptionsCommand { get; set; } public Command CipherOptionsCommand { get; set; }
public ICommand VaultFilterCommand { get; }
public ExtendedObservableCollection<CipherView> Ciphers { get; set; } public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
public Func<CipherView, bool> Filter { get; set; } public Func<CipherView, bool> Filter { get; set; }
public string AutofillUrl { get; set; } public string AutofillUrl { get; set; }
public bool Deleted { get; set; } public bool Deleted { get; set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService;
protected override ILogger logger => _logger;
public bool ShowNoData public bool ShowNoData
{ {
get => _showNoData; get => _showNoData;
@ -81,23 +76,6 @@ namespace Bit.App.Pages
nameof(ShowSearchDirection) nameof(ShowSearchDirection)
}); });
} }
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 bool ShowSearchDirection => !ShowList && !ShowNoData; public bool ShowSearchDirection => !ShowList && !ShowNoData;
@ -109,12 +87,7 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
_organizations = await _organizationService.GetAllAsync(); await InitVaultFilterAsync();
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
if (ShowVaultFilter && _vaultFilterSelection == null)
{
_vaultFilterSelection = AppResources.AllVaults;
}
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
PerformSearchIfPopulated(); PerformSearchIfPopulated();
} }
@ -237,50 +210,11 @@ namespace Bit.App.Pages
} }
} }
private async Task VaultFilterOptionsAsync() protected override async Task OnVaultFilterSelectedAsync()
{ {
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
if (_organizations.Any())
{
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
}
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
options.ToArray());
if (selection == null || selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
{
return;
}
VaultFilterDescription = selection;
PerformSearchIfPopulated(); PerformSearchIfPopulated();
} }
private async Task<List<CipherView>> GetAllCiphersAsync()
{
var decCiphers = await _cipherService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault)
{
return decCiphers.Where(c => c.OrganizationId == null).ToList();
}
if (IsVaultFilterOrgVault)
{
var orgId = GetVaultFilterOrgId();
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
}
return decCiphers;
}
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 async void CipherOptionsAsync(CipherView cipher) private async void CipherOptionsAsync(CipherView cipher)
{ {
if ((Page as BaseContentPage).DoOnce()) if ((Page as BaseContentPage).DoOnce())

View file

@ -2,7 +2,6 @@
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;
@ -17,7 +16,7 @@ using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class GroupingsPageViewModel : BaseViewModel public class GroupingsPageViewModel : VaultFilterViewModel
{ {
private const int NoFolderListSize = 100; private const int NoFolderListSize = 100;
@ -30,10 +29,7 @@ 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>();
@ -78,9 +74,6 @@ 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)
{ {
@ -108,6 +101,11 @@ namespace Bit.App.Pages
public List<Core.Models.View.CollectionView> Collections { get; set; } public List<Core.Models.View.CollectionView> Collections { get; set; }
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; } public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService;
protected override ILogger logger => _logger;
public bool Refreshing public bool Refreshing
{ {
get => _refreshing; get => _refreshing;
@ -153,30 +151,12 @@ 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()
@ -201,14 +181,9 @@ namespace Bit.App.Pages
return; return;
} }
_organizations = await _organizationService.GetAllAsync();
if (MainPage) if (MainPage)
{ {
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync(); await InitVaultFilterAsync();
if (ShowVaultFilter && _vaultFilterSelection == null)
{
_vaultFilterSelection = AppResources.AllVaults;
}
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault; PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
} }
@ -394,30 +369,11 @@ namespace Bit.App.Pages
SyncRefreshing = false; SyncRefreshing = false;
} }
public async Task VaultFilterOptionsAsync() protected override async Task OnVaultFilterSelectedAsync()
{ {
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
if (_organizations.Any())
{
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
}
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
options.ToArray());
if (selection == null || selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
{
return;
}
VaultFilterDescription = selection;
await LoadAsync(); await LoadAsync();
} }
public string GetVaultFilterOrgId()
{
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
}
public async Task SelectCipherAsync(CipherView cipher) public async Task SelectCipherAsync(CipherView cipher)
{ {
var page = new ViewPage(cipher.Id); var page = new ViewPage(cipher.Id);
@ -501,8 +457,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 GetAllCiphersAsync();
HasCiphers = _allCiphers.Any(); HasCiphers = _allCiphers.Any();
FavoriteCiphers?.Clear(); FavoriteCiphers?.Clear();
NoFolderCiphers?.Clear(); NoFolderCiphers?.Clear();
@ -516,7 +472,7 @@ namespace Bit.App.Pages
if (MainPage) if (MainPage)
{ {
await FillFoldersAndCollectionsAsync(orgId); await FillFoldersAndCollectionsAsync();
NestedFolders = await _folderService.GetAllNestedAsync(Folders); NestedFolders = await _folderService.GetAllNestedAsync(Folders);
HasFolders = NestedFolders.Any(f => f.Node?.Id != null); HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null; NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
@ -640,28 +596,9 @@ namespace Bit.App.Pages
} }
} }
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync() private async Task FillFoldersAndCollectionsAsync()
{
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 orgId = GetVaultFilterOrgId();
var decFolders = await _folderService.GetAllDecryptedAsync(); var decFolders = await _folderService.GetAllDecryptedAsync();
var decCollections = await _collectionService.GetAllDecryptedAsync(); var decCollections = await _collectionService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault) if (IsVaultFilterMyVault)
@ -681,11 +618,6 @@ namespace Bit.App.Pages
} }
} }
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
_vaultFilterSelection != AppResources.MyVault;
private List<FolderView> BuildFolders(List<FolderView> decFolders) private List<FolderView> BuildFolders(List<FolderView> decFolders)
{ {
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList(); var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();

View file

@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
using Xamarin.CommunityToolkit.ObjectModel;
namespace Bit.App.Pages
{
public abstract class VaultFilterViewModel : BaseViewModel
{
protected abstract ICipherService cipherService { get; }
protected abstract IPolicyService policyService { get; }
protected abstract IOrganizationService organizationService { get; }
protected abstract ILogger logger { get; }
protected bool _showVaultFilter;
protected bool _hideMyVaultFilterOption;
protected string _vaultFilterSelection;
protected List<Organization> _organizations;
public VaultFilterViewModel()
{
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
}
public ICommand VaultFilterCommand { get; set; }
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 string GetVaultFilterOrgId()
{
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
}
protected bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
protected bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
_vaultFilterSelection != AppResources.MyVault;
protected async Task InitVaultFilterAsync()
{
_organizations = await organizationService.GetAllAsync();
ShowVaultFilter = await policyService.ShouldShowVaultFilterAsync();
if (ShowVaultFilter)
{
_hideMyVaultFilterOption = await policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
if (_vaultFilterSelection == null)
{
_vaultFilterSelection = AppResources.AllVaults;
}
}
}
protected async Task<List<CipherView>> GetAllCiphersAsync()
{
var decCiphers = await cipherService.GetAllDecryptedAsync();
if (IsVaultFilterMyVault)
{
return decCiphers.Where(c => c.OrganizationId == null).ToList();
}
if (IsVaultFilterOrgVault)
{
var orgId = GetVaultFilterOrgId();
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
}
return decCiphers;
}
protected async Task VaultFilterOptionsAsync()
{
var options = new List<string> { AppResources.AllVaults };
if (!_hideMyVaultFilterOption)
{
options.Add(AppResources.MyVault);
}
if (_organizations.Any())
{
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
}
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
options.ToArray());
if (selection == null || selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
{
return;
}
VaultFilterDescription = selection;
await OnVaultFilterSelectedAsync();
}
protected abstract Task OnVaultFilterSelectedAsync();
}
}

View file

@ -249,6 +249,12 @@ namespace Bit.Core.Services
public async Task<bool> ShouldShowVaultFilterAsync() public async Task<bool> ShouldShowVaultFilterAsync()
{ {
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg);
if (personalOwnershipPolicyApplies && singleOrgPolicyApplies)
{
return false;
}
var organizations = await _organizationService.GetAllAsync(); var organizations = await _organizationService.GetAllAsync();
return organizations?.Any() ?? false; return organizations?.Any() ?? false;
} }