mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 02:48:29 +03:00
[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:
parent
3438ed94ce
commit
88b406544b
8 changed files with 141 additions and 13 deletions
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue