From 88b406544b1dc4f33f74efe2521df10e49f3d87d Mon Sep 17 00:00:00 2001 From: mp-bw <59324545+mp-bw@users.noreply.github.com> Date: Wed, 8 Jun 2022 09:39:53 -0400 Subject: [PATCH] [SG-79] Add filter to search and preselect org in new cipher (#1944) * Add filter to search and preselect org in new cipher * formatting * fixes --- src/App/Pages/Vault/AddEditPage.xaml.cs | 2 + src/App/Pages/Vault/CiphersPage.xaml | 25 +++++ src/App/Pages/Vault/CiphersPage.xaml.cs | 4 +- src/App/Pages/Vault/CiphersPageViewModel.cs | 97 ++++++++++++++++++- .../Vault/GroupingsPage/GroupingsPage.xaml | 2 +- .../Vault/GroupingsPage/GroupingsPage.xaml.cs | 2 +- .../GroupingsPage/GroupingsPageViewModel.cs | 12 +-- src/App/Styles/Base.xaml | 10 ++ 8 files changed, 141 insertions(+), 13 deletions(-) diff --git a/src/App/Pages/Vault/AddEditPage.xaml.cs b/src/App/Pages/Vault/AddEditPage.xaml.cs index 3329de8dd..106ac7057 100644 --- a/src/App/Pages/Vault/AddEditPage.xaml.cs +++ b/src/App/Pages/Vault/AddEditPage.xaml.cs @@ -30,6 +30,7 @@ namespace Bit.App.Pages CipherType? type = null, string folderId = null, string collectionId = null, + string organizationId = null, string name = null, string uri = null, bool fromAutofill = false, @@ -51,6 +52,7 @@ namespace Bit.App.Pages _vm.CipherId = cipherId; _vm.FolderId = folderId == "none" ? null : folderId; _vm.CollectionIds = collectionId != null ? new HashSet(new List { collectionId }) : null; + _vm.OrganizationId = organizationId; _vm.Type = type; _vm.DefaultName = name ?? appOptions?.SaveName; _vm.DefaultUri = uri ?? appOptions?.Uri; diff --git a/src/App/Pages/Vault/CiphersPage.xaml b/src/App/Pages/Vault/CiphersPage.xaml index b5bce8532..def72c316 100644 --- a/src/App/Pages/Vault/CiphersPage.xaml +++ b/src/App/Pages/Vault/CiphersPage.xaml @@ -49,6 +49,31 @@ + + + + filter, string pageTitle = null, string autofillUrl = null, bool deleted = false) + public CiphersPage(Func filter, string pageTitle = null, string vaultFilterSelection = null, + string autofillUrl = null, bool deleted = false) { InitializeComponent(); _vm = BindingContext as CiphersPageViewModel; @@ -33,6 +34,7 @@ namespace Bit.App.Pages { _vm.PageTitle = AppResources.SearchVault; } + _vm.VaultFilterDescription = vaultFilterSelection; if (Device.RuntimePlatform == Device.iOS) { diff --git a/src/App/Pages/Vault/CiphersPageViewModel.cs b/src/App/Pages/Vault/CiphersPageViewModel.cs index d9a5510e2..acf67c6b9 100644 --- a/src/App/Pages/Vault/CiphersPageViewModel.cs +++ b/src/App/Pages/Vault/CiphersPageViewModel.cs @@ -3,14 +3,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Windows.Input; using Bit.App.Abstractions; using Bit.App.Resources; using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; namespace Bit.App.Pages @@ -23,11 +26,17 @@ namespace Bit.App.Pages private readonly IDeviceActionService _deviceActionService; private readonly IStateService _stateService; private readonly IPasswordRepromptService _passwordRepromptService; + private readonly IOrganizationService _organizationService; + private readonly IPolicyService _policyService; private CancellationTokenSource _searchCancellationTokenSource; + private readonly ILogger _logger; + private bool _showVaultFilter; + private string _vaultFilterSelection; private bool _showNoData; private bool _showList; private bool _websiteIconsEnabled; + private List _organizations; public CiphersPageViewModel() { @@ -37,12 +46,19 @@ namespace Bit.App.Pages _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _stateService = ServiceContainer.Resolve("stateService"); _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); + _organizationService = ServiceContainer.Resolve("organizationService"); + _policyService = ServiceContainer.Resolve("policyService"); + _logger = ServiceContainer.Resolve("logger"); Ciphers = new ExtendedObservableCollection(); CipherOptionsCommand = new Command(CipherOptionsAsync); + VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync, + onException: ex => _logger.Exception(ex), + allowsMultipleExecutions: false); } public Command CipherOptionsCommand { get; set; } + public ICommand VaultFilterCommand { get; } public ExtendedObservableCollection Ciphers { get; set; } public Func Filter { get; set; } public string AutofillUrl { get; set; } @@ -65,6 +81,23 @@ namespace Bit.App.Pages 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; @@ -76,11 +109,14 @@ namespace Bit.App.Pages public async Task InitAsync() { - WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); - if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text)) + _organizations = await _organizationService.GetAllAsync(); + ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync(); + if (ShowVaultFilter && _vaultFilterSelection == null) { - Search((Page as CiphersPage).SearchBar.Text, 200); + _vaultFilterSelection = AppResources.AllVaults; } + WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); + PerformSearchIfPopulated(); } public void Search(string searchText, int? timeout = null) @@ -107,8 +143,9 @@ namespace Bit.App.Pages } try { + var vaultFilteredCiphers = await GetAllCiphersAsync(); ciphers = await _searchService.SearchCiphersAsync(searchText, - Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token); + Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token); cts.Token.ThrowIfCancellationRequested(); } catch (OperationCanceledException) @@ -192,6 +229,58 @@ namespace Bit.App.Pages } } + private void PerformSearchIfPopulated() + { + if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text)) + { + Search((Page as CiphersPage).SearchBar.Text, 200); + } + } + + private async Task VaultFilterOptionsAsync() + { + var options = new List { 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 == null || selection == AppResources.Cancel || + (_vaultFilterSelection == null && selection == AppResources.AllVaults) || + (_vaultFilterSelection != null && _vaultFilterSelection == selection)) + { + return; + } + VaultFilterDescription = selection; + PerformSearchIfPopulated(); + } + + private async Task> 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) { if ((Page as BaseContentPage).DoOnce()) diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml index 5f0f80830..0ba3203b3 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml @@ -123,7 +123,7 @@ AutomationProperties.Name="{u:I18n Filter}" /> o.Name == _vaultFilterSelection)?.Id; + } + public async Task SelectCipherAsync(CipherView cipher) { var page = new ViewPage(cipher.Id); @@ -681,11 +686,6 @@ namespace Bit.App.Pages private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults && _vaultFilterSelection != AppResources.MyVault; - private string GetVaultFilterOrgId() - { - return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id; - } - private List BuildFolders(List decFolders) { var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList(); diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml index dd024bbbb..449f2e7f3 100644 --- a/src/App/Styles/Base.xaml +++ b/src/App/Styles/Base.xaml @@ -268,6 +268,16 @@ +