[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
This commit is contained in:
mp-bw 2022-06-08 09:39:53 -04:00 committed by GitHub
parent 3438ed94ce
commit 88b406544b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 141 additions and 13 deletions

View file

@ -30,6 +30,7 @@ namespace Bit.App.Pages
CipherType? type = null, CipherType? type = null,
string folderId = null, string folderId = null,
string collectionId = null, string collectionId = null,
string organizationId = null,
string name = null, string name = null,
string uri = null, string uri = null,
bool fromAutofill = false, bool fromAutofill = false,
@ -51,6 +52,7 @@ namespace Bit.App.Pages
_vm.CipherId = cipherId; _vm.CipherId = cipherId;
_vm.FolderId = folderId == "none" ? null : folderId; _vm.FolderId = folderId == "none" ? null : folderId;
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null; _vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
_vm.OrganizationId = organizationId;
_vm.Type = type; _vm.Type = type;
_vm.DefaultName = name ?? appOptions?.SaveName; _vm.DefaultName = name ?? appOptions?.SaveName;
_vm.DefaultUri = uri ?? appOptions?.Uri; _vm.DefaultUri = uri ?? appOptions?.Uri;

View file

@ -49,6 +49,31 @@
</ContentPage.Resources> </ContentPage.Resources>
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0"> <StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
<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-text, list-row-button-platform"
Command="{Binding VaultFilterCommand}"
VerticalOptions="Center"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<controls:IconLabel IsVisible="{Binding ShowSearchDirection}" <controls:IconLabel IsVisible="{Binding ShowSearchDirection}"
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}" Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
StyleClass="text-muted" StyleClass="text-muted"

View file

@ -17,7 +17,8 @@ namespace Bit.App.Pages
private CiphersPageViewModel _vm; private CiphersPageViewModel _vm;
private bool _hasFocused; private bool _hasFocused;
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string autofillUrl = null, bool deleted = false) public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string vaultFilterSelection = null,
string autofillUrl = null, bool deleted = false)
{ {
InitializeComponent(); InitializeComponent();
_vm = BindingContext as CiphersPageViewModel; _vm = BindingContext as CiphersPageViewModel;
@ -33,6 +34,7 @@ namespace Bit.App.Pages
{ {
_vm.PageTitle = AppResources.SearchVault; _vm.PageTitle = AppResources.SearchVault;
} }
_vm.VaultFilterDescription = vaultFilterSelection;
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {

View file

@ -3,14 +3,17 @@ 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;
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.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@ -23,11 +26,17 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
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 CancellationTokenSource _searchCancellationTokenSource; private CancellationTokenSource _searchCancellationTokenSource;
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()
{ {
@ -37,12 +46,19 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_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");
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; }
@ -65,6 +81,23 @@ 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;
@ -76,11 +109,14 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); _organizations = await _organizationService.GetAllAsync();
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text)) 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) public void Search(string searchText, int? timeout = null)
@ -107,8 +143,9 @@ namespace Bit.App.Pages
} }
try try
{ {
var vaultFilteredCiphers = await GetAllCiphersAsync();
ciphers = await _searchService.SearchCiphersAsync(searchText, ciphers = await _searchService.SearchCiphersAsync(searchText,
Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token); Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
cts.Token.ThrowIfCancellationRequested(); cts.Token.ThrowIfCancellationRequested();
} }
catch (OperationCanceledException) 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<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 == null || selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
{
return;
}
VaultFilterDescription = selection;
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

@ -123,7 +123,7 @@
AutomationProperties.Name="{u:I18n Filter}" /> AutomationProperties.Name="{u:I18n Filter}" />
<controls:MiButton <controls:MiButton
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}" Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled" StyleClass="list-row-button-text, list-row-button-platform"
Command="{Binding VaultFilterCommand}" Command="{Binding VaultFilterCommand}"
VerticalOptions="Center" VerticalOptions="Center"
HorizontalOptions="End" HorizontalOptions="End"

View file

@ -263,7 +263,7 @@ namespace Bit.App.Pages
} }
if (!_vm.Deleted && DoOnce()) if (!_vm.Deleted && DoOnce())
{ {
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId); var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
} }

View file

@ -403,7 +403,7 @@ namespace Bit.App.Pages
} }
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null, var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
options.ToArray()); options.ToArray());
if (selection == AppResources.Cancel || if (selection == null || selection == AppResources.Cancel ||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) || (_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
(_vaultFilterSelection != null && _vaultFilterSelection == selection)) (_vaultFilterSelection != null && _vaultFilterSelection == selection))
{ {
@ -413,6 +413,11 @@ namespace Bit.App.Pages
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);
@ -681,11 +686,6 @@ namespace Bit.App.Pages
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults && private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
_vaultFilterSelection != AppResources.MyVault; _vaultFilterSelection != AppResources.MyVault;
private string GetVaultFilterOrgId()
{
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
}
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

@ -268,6 +268,16 @@
<Setter Property="TextColor" <Setter Property="TextColor"
Value="{DynamicResource ButtonColor}" /> Value="{DynamicResource ButtonColor}" />
</Style> </Style>
<Style TargetType="Button"
ApplyToDerivedTypes="True"
Class="list-row-button-text">
<Setter Property="BackgroundColor"
Value="Transparent" />
<Setter Property="Padding"
Value="0" />
<Setter Property="TextColor"
Value="{DynamicResource ButtonTextColor}" />
</Style>
<Style TargetType="Button" <Style TargetType="Button"
ApplyToDerivedTypes="True" ApplyToDerivedTypes="True"
Class="segmented-button"> Class="segmented-button">