mirror of
https://github.com/bitwarden/mobile.git
synced 2024-12-17 15:27:43 +01:00
cipher searching
This commit is contained in:
parent
128935eb9f
commit
4ed12a859b
@ -41,17 +41,17 @@ namespace Bit.App.Pages
|
||||
});
|
||||
}
|
||||
|
||||
protected void RequestFocus(Entry entry)
|
||||
protected void RequestFocus(InputView input)
|
||||
{
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
entry.Focus();
|
||||
input.Focus();
|
||||
return;
|
||||
}
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(AndroidShowModalAnimationDelay);
|
||||
Device.BeginInvokeOnMainThread(() => entry.Focus());
|
||||
Device.BeginInvokeOnMainThread(() => input.Focus());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:DateTimeConverter x:Key="dateTime" />
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
@ -30,32 +29,34 @@
|
||||
Spacing="0"
|
||||
Padding="0">
|
||||
<controls:MiButton
|
||||
StyleClass="btn-title"
|
||||
Text=""
|
||||
BackgroundColor="Transparent"
|
||||
Padding="0"
|
||||
WidthRequest="37"
|
||||
FontSize="25"
|
||||
VerticalOptions="CenterAndExpand" />
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Clicked="BackButton_Clicked" />
|
||||
<SearchBar
|
||||
x:Name="_searchBar"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
BackgroundColor="Transparent"
|
||||
Placeholder="{u:I18n SearchVault}" />
|
||||
TextChanged="SearchBar_TextChanged"
|
||||
SearchButtonPressed="SearchBar_SearchButtonPressed"
|
||||
Placeholder="{Binding PageTitle}" />
|
||||
</StackLayout>
|
||||
</NavigationPage.TitleView>
|
||||
|
||||
<StackLayout x:Name="_mainLayout">
|
||||
<Label IsVisible="{Binding ShowNoData}"
|
||||
Text="{Binding NoDataText}"
|
||||
Text="{u:I18n NoItemsToList}"
|
||||
Margin="20, 0"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
HorizontalTextAlignment="Center"></Label>
|
||||
<ListView x:Name="_listView"
|
||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||
IsVisible="{Binding ShowList}"
|
||||
ItemsSource="{Binding Ciphers}"
|
||||
VerticalOptions="FillAndExpand"
|
||||
HasUnevenRows="true"
|
||||
CachingStrategy="RecycleElement"
|
||||
ItemSelected="RowSelected"
|
||||
StyleClass="list, list-platform">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="views:CipherView">
|
||||
|
@ -1,25 +1,91 @@
|
||||
using System;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Models.View;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class CiphersPage : BaseContentPage
|
||||
{
|
||||
private CiphersPageViewModel _vm;
|
||||
private bool _hasFocused;
|
||||
|
||||
public CiphersPage()
|
||||
public CiphersPage(Func<CipherView, bool> filter, bool folder = false, bool collection = false,
|
||||
bool type = false)
|
||||
{
|
||||
InitializeComponent();
|
||||
SetActivityIndicator();
|
||||
_vm = BindingContext as CiphersPageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.Filter = filter;
|
||||
if(folder)
|
||||
{
|
||||
_vm.PageTitle = AppResources.SearchFolder;
|
||||
}
|
||||
else if(collection)
|
||||
{
|
||||
_vm.PageTitle = AppResources.SearchCollection;
|
||||
}
|
||||
else if(type)
|
||||
{
|
||||
_vm.PageTitle = AppResources.SearchType;
|
||||
}
|
||||
else
|
||||
{
|
||||
_vm.PageTitle = AppResources.SearchVault;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
public SearchBar SearchBar => _searchBar;
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
await LoadOnAppearedAsync(_mainLayout, true, async () => {
|
||||
await _vm.LoadAsync();
|
||||
});
|
||||
if(!_hasFocused)
|
||||
{
|
||||
_hasFocused = true;
|
||||
RequestFocus(_searchBar);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var oldLength = e.OldTextValue?.Length ?? 0;
|
||||
var newLength = e.NewTextValue?.Length ?? 0;
|
||||
if(oldLength < 2 && newLength < 2 && oldLength < newLength)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_vm.Search(e.NewTextValue, 300);
|
||||
}
|
||||
|
||||
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
|
||||
{
|
||||
_vm.Search((sender as SearchBar).Text);
|
||||
}
|
||||
|
||||
private void BackButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
GoBack();
|
||||
}
|
||||
|
||||
protected override bool OnBackButtonPressed()
|
||||
{
|
||||
GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void GoBack()
|
||||
{
|
||||
Navigation.PopModalAsync(false);
|
||||
}
|
||||
|
||||
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
((ListView)sender).SelectedItem = null;
|
||||
if(e.SelectedItem is CipherView cipher)
|
||||
{
|
||||
await _vm.SelectCipherAsync(cipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@ -11,23 +14,26 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly ISearchService _searchService;
|
||||
private CancellationTokenSource _searchCancellationTokenSource;
|
||||
|
||||
private string _searchText;
|
||||
private string _noDataText;
|
||||
private bool _showNoData;
|
||||
private bool _showList;
|
||||
|
||||
public CiphersPageViewModel()
|
||||
{
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
||||
|
||||
PageTitle = AppResources.SearchVault;
|
||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||
}
|
||||
|
||||
public Command CipherOptionsCommand { get; set; }
|
||||
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
||||
public Func<CipherView, bool> Filter { get; set; }
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
@ -35,24 +41,68 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _searchText, value);
|
||||
}
|
||||
|
||||
public string NoDataText
|
||||
{
|
||||
get => _noDataText;
|
||||
set => SetProperty(ref _noDataText, value);
|
||||
}
|
||||
|
||||
public bool ShowNoData
|
||||
{
|
||||
get => _showNoData;
|
||||
set => SetProperty(ref _showNoData, value);
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
public bool ShowList
|
||||
{
|
||||
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||
get => _showList;
|
||||
set => SetProperty(ref _showList, value);
|
||||
}
|
||||
|
||||
public void Search(string searchText, int? timeout = null)
|
||||
{
|
||||
var previousCts = _searchCancellationTokenSource;
|
||||
var cts = new CancellationTokenSource();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
List<CipherView> ciphers = null;
|
||||
var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1;
|
||||
if(searchable)
|
||||
{
|
||||
if(timeout != null)
|
||||
{
|
||||
await Task.Delay(timeout.Value);
|
||||
}
|
||||
if(searchText != (Page as CiphersPage).SearchBar.Text)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousCts?.Cancel();
|
||||
}
|
||||
try
|
||||
{
|
||||
ciphers = await _searchService.SearchCiphersAsync(searchText, Filter, null, cts.Token);
|
||||
cts.Token.ThrowIfCancellationRequested();
|
||||
Ciphers.ResetWithRange(ciphers);
|
||||
ShowNoData = Ciphers.Count == 0;
|
||||
}
|
||||
catch(OperationCanceledException)
|
||||
{
|
||||
ciphers = new List<CipherView>();
|
||||
}
|
||||
}
|
||||
if(ciphers == null)
|
||||
{
|
||||
ciphers = new List<CipherView>();
|
||||
}
|
||||
Ciphers.ResetWithRange(ciphers);
|
||||
ShowNoData = searchable && Ciphers.Count == 0;
|
||||
ShowList = searchable && !ShowNoData;
|
||||
}, cts.Token);
|
||||
_searchCancellationTokenSource = cts;
|
||||
}
|
||||
|
||||
public async Task SelectCipherAsync(CipherView cipher)
|
||||
{
|
||||
var page = new ViewPage(cipher.Id);
|
||||
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async void CipherOptionsAsync(CipherView cipher)
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly GroupingsPageViewModel _viewModel;
|
||||
private readonly GroupingsPageViewModel _vm;
|
||||
|
||||
public GroupingsPage()
|
||||
: this(true)
|
||||
@ -23,15 +23,15 @@ namespace Bit.App.Pages
|
||||
SetActivityIndicator();
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_viewModel = BindingContext as GroupingsPageViewModel;
|
||||
_viewModel.Page = this;
|
||||
_viewModel.MainPage = mainPage;
|
||||
_viewModel.Type = type;
|
||||
_viewModel.FolderId = folderId;
|
||||
_viewModel.CollectionId = collectionId;
|
||||
_vm = BindingContext as GroupingsPageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.MainPage = mainPage;
|
||||
_vm.Type = type;
|
||||
_vm.FolderId = folderId;
|
||||
_vm.CollectionId = collectionId;
|
||||
if(pageTitle != null)
|
||||
{
|
||||
_viewModel.PageTitle = pageTitle;
|
||||
_vm.PageTitle = pageTitle;
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,14 +52,14 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if(!_syncService.SyncInProgress)
|
||||
{
|
||||
await _viewModel.LoadAsync();
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
if(!_viewModel.Loaded)
|
||||
if(!_vm.Loaded)
|
||||
{
|
||||
await _viewModel.LoadAsync();
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -73,6 +73,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
|
||||
{
|
||||
((ListView)sender).SelectedItem = null;
|
||||
if(!(e.SelectedItem is GroupingsPageListItem item))
|
||||
{
|
||||
return;
|
||||
@ -80,25 +81,26 @@ namespace Bit.App.Pages
|
||||
|
||||
if(item.Cipher != null)
|
||||
{
|
||||
await _viewModel.SelectCipherAsync(item.Cipher);
|
||||
await _vm.SelectCipherAsync(item.Cipher);
|
||||
}
|
||||
else if(item.Folder != null)
|
||||
{
|
||||
await _viewModel.SelectFolderAsync(item.Folder);
|
||||
await _vm.SelectFolderAsync(item.Folder);
|
||||
}
|
||||
else if(item.Collection != null)
|
||||
{
|
||||
await _viewModel.SelectCollectionAsync(item.Collection);
|
||||
await _vm.SelectCollectionAsync(item.Collection);
|
||||
}
|
||||
else if(item.Type != null)
|
||||
{
|
||||
await _viewModel.SelectTypeAsync(item.Type.Value);
|
||||
await _vm.SelectTypeAsync(item.Type.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Search_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
await Navigation.PushModalAsync(new NavigationPage(new CiphersPage()), false);
|
||||
await Navigation.PushModalAsync(new NavigationPage(
|
||||
new CiphersPage(_vm.Filter,_vm.FolderId != null, _vm.CollectionId != null, _vm.Type != null)), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -56,6 +57,7 @@ namespace Bit.App.Pages
|
||||
public CipherType? Type { get; set; }
|
||||
public string FolderId { get; set; }
|
||||
public string CollectionId { get; set; }
|
||||
public Func<CipherView, bool> Filter { get; set; }
|
||||
|
||||
public List<CipherView> Ciphers { get; set; }
|
||||
public List<CipherView> FavoriteCiphers { get; set; }
|
||||
@ -249,6 +251,7 @@ namespace Bit.App.Pages
|
||||
_folderCounts.Clear();
|
||||
_collectionCounts.Clear();
|
||||
_typeCounts.Clear();
|
||||
Filter = null;
|
||||
|
||||
if(MainPage)
|
||||
{
|
||||
@ -267,7 +270,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if(Type != null)
|
||||
{
|
||||
Ciphers = _allCiphers.Where(c => c.Type == Type.Value).ToList();
|
||||
Filter = c => c.Type == Type.Value;
|
||||
}
|
||||
else if(FolderId != null)
|
||||
{
|
||||
@ -286,7 +289,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
PageTitle = AppResources.FolderNone;
|
||||
}
|
||||
Ciphers = _allCiphers.Where(c => c.FolderId == folderId).ToList();
|
||||
Filter = c => c.FolderId == folderId;
|
||||
}
|
||||
else if(CollectionId != null)
|
||||
{
|
||||
@ -297,13 +300,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
PageTitle = collectionNode.Node.Name;
|
||||
}
|
||||
Ciphers = _allCiphers.Where(c => c.CollectionIds?.Contains(CollectionId) ?? false).ToList();
|
||||
Filter = c => c.CollectionIds?.Contains(CollectionId) ?? false;
|
||||
}
|
||||
else
|
||||
{
|
||||
PageTitle = AppResources.AllItems;
|
||||
Ciphers = _allCiphers;
|
||||
}
|
||||
Ciphers = Filter != null ? _allCiphers.Where(Filter).ToList() : _allCiphers;
|
||||
}
|
||||
|
||||
foreach(var c in _allCiphers)
|
||||
|
38
src/App/Resources/AppResources.Designer.cs
generated
38
src/App/Resources/AppResources.Designer.cs
generated
@ -2472,6 +2472,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There are no items to list..
|
||||
/// </summary>
|
||||
public static string NoItemsToList {
|
||||
get {
|
||||
return ResourceManager.GetString("NoItemsToList", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No passwords to list..
|
||||
/// </summary>
|
||||
@ -2851,7 +2860,34 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search Vault.
|
||||
/// Looks up a localized string similar to Search collection.
|
||||
/// </summary>
|
||||
public static string SearchCollection {
|
||||
get {
|
||||
return ResourceManager.GetString("SearchCollection", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search folder.
|
||||
/// </summary>
|
||||
public static string SearchFolder {
|
||||
get {
|
||||
return ResourceManager.GetString("SearchFolder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search type.
|
||||
/// </summary>
|
||||
public static string SearchType {
|
||||
get {
|
||||
return ResourceManager.GetString("SearchType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search vault.
|
||||
/// </summary>
|
||||
public static string SearchVault {
|
||||
get {
|
||||
|
@ -656,7 +656,7 @@
|
||||
<value>Re-type Master Password</value>
|
||||
</data>
|
||||
<data name="SearchVault" xml:space="preserve">
|
||||
<value>Search Vault</value>
|
||||
<value>Search vault</value>
|
||||
</data>
|
||||
<data name="Security" xml:space="preserve">
|
||||
<value>Security</value>
|
||||
@ -1396,4 +1396,16 @@
|
||||
<data name="NoPasswordsToList" xml:space="preserve">
|
||||
<value>No passwords to list.</value>
|
||||
</data>
|
||||
<data name="NoItemsToList" xml:space="preserve">
|
||||
<value>There are no items to list.</value>
|
||||
</data>
|
||||
<data name="SearchCollection" xml:space="preserve">
|
||||
<value>Search collection</value>
|
||||
</data>
|
||||
<data name="SearchFolder" xml:space="preserve">
|
||||
<value>Search folder</value>
|
||||
</data>
|
||||
<data name="SearchType" xml:space="preserve">
|
||||
<value>Search type</value>
|
||||
</data>
|
||||
</root>
|
@ -48,16 +48,6 @@
|
||||
<Setter Property="FontAttributes"
|
||||
Value="Bold" />
|
||||
</Style>
|
||||
<Style TargetType="SearchBar">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="Transparent" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{StaticResource HeaderEntryTextColor}" />
|
||||
<Setter Property="CancelButtonColor"
|
||||
Value="{StaticResource HeaderEntryTextColor}" />
|
||||
<Setter Property="PlaceholderColor"
|
||||
Value="{StaticResource HeaderEntryPlaceholderColor}" />
|
||||
</Style>
|
||||
|
||||
<!-- Buttons -->
|
||||
<Style TargetType="Button"
|
||||
@ -79,6 +69,31 @@
|
||||
Value="{StaticResource DisabledIconColor}" />
|
||||
</Style>
|
||||
|
||||
<!-- Title -->
|
||||
<Style TargetType="Button"
|
||||
Class="btn-title"
|
||||
ApplyToDerivedTypes="True">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="Transparent" />
|
||||
<Setter Property="Padding"
|
||||
Value="0" />
|
||||
<Setter Property="WidthRequest"
|
||||
Value="37" />
|
||||
<Setter Property="FontSize"
|
||||
Value="25" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{StaticResource TitleTextColor}" />
|
||||
</Style>
|
||||
<Style TargetType="SearchBar">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="Transparent" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{StaticResource TitleEntryTextColor}" />
|
||||
<Setter Property="CancelButtonColor"
|
||||
Value="{StaticResource TitleEntryTextColor}" />
|
||||
<Setter Property="PlaceholderColor"
|
||||
Value="{StaticResource TitleEntryPlaceholderColor}" />
|
||||
</Style>
|
||||
|
||||
<!-- List -->
|
||||
<Style TargetType="ListView"
|
||||
|
@ -18,6 +18,7 @@
|
||||
<Color x:Key="BoxBorderColor">#f0f0f0</Color>
|
||||
<Color x:Key="BoxHeaderTextColor">#3c8dbc</Color>
|
||||
|
||||
<Color x:Key="HeaderTextColor">#ffffff</Color>
|
||||
<Color x:Key="HeaderEntryTextColor">#ffffff</Color>
|
||||
<Color x:Key="HeaderEntryPlaceholderColor">#707070</Color>
|
||||
|
||||
|
@ -18,8 +18,9 @@
|
||||
<Color x:Key="BoxBorderColor">#dddddd</Color>
|
||||
<Color x:Key="BoxHeaderTextColor">#3c8dbc</Color>
|
||||
|
||||
<Color x:Key="HeaderEntryTextColor">#ffffff</Color>
|
||||
<Color x:Key="HeaderEntryPlaceholderColor">#c0dbeb</Color>
|
||||
<Color x:Key="TitleTextColor">#ffffff</Color>
|
||||
<Color x:Key="TitleEntryTextColor">#ffffff</Color>
|
||||
<Color x:Key="TitleEntryPlaceholderColor">#c0dbeb</Color>
|
||||
|
||||
<Color x:Key="ListItemBorderColor">#f0f0f0</Color>
|
||||
<Color x:Key="ListHeaderTextColor">#3c8dbc</Color>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
@ -11,7 +12,8 @@ namespace Bit.Core.Abstractions
|
||||
Task IndexCiphersAsync();
|
||||
bool IsSearchable(string query);
|
||||
Task<List<CipherView>> SearchCiphersAsync(string query, Func<CipherView, bool> filter = null,
|
||||
List<CipherView> ciphers = null);
|
||||
List<CipherView> SearchCiphersBasic(List<CipherView> ciphers, string query);
|
||||
List<CipherView> ciphers = null, CancellationToken ct = default(CancellationToken));
|
||||
List<CipherView> SearchCiphersBasic(List<CipherView> ciphers, string query,
|
||||
CancellationToken ct = default(CancellationToken));
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using Bit.Core.Models.View;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
@ -24,7 +25,7 @@ namespace Bit.Core.Services
|
||||
|
||||
public bool IsSearchable(string query)
|
||||
{
|
||||
return true;
|
||||
return (query?.Length ?? 0) > 1;
|
||||
}
|
||||
|
||||
public Task IndexCiphersAsync()
|
||||
@ -34,7 +35,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
public async Task<List<CipherView>> SearchCiphersAsync(string query, Func<CipherView, bool> filter = null,
|
||||
List<CipherView> ciphers = null)
|
||||
List<CipherView> ciphers = null, CancellationToken ct = default(CancellationToken))
|
||||
{
|
||||
var results = new List<CipherView>();
|
||||
if(query != null)
|
||||
@ -49,10 +50,14 @@ namespace Bit.Core.Services
|
||||
{
|
||||
ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if(filter != null)
|
||||
{
|
||||
ciphers = ciphers.Where(filter).ToList();
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if(!IsSearchable(query))
|
||||
{
|
||||
return ciphers;
|
||||
@ -62,11 +67,14 @@ namespace Bit.Core.Services
|
||||
// TODO: advanced searching with index
|
||||
}
|
||||
|
||||
public List<CipherView> SearchCiphersBasic(List<CipherView> ciphers, string query)
|
||||
public List<CipherView> SearchCiphersBasic(List<CipherView> ciphers, string query,
|
||||
CancellationToken ct = default(CancellationToken))
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
query = query.Trim().ToLower();
|
||||
return ciphers.Where(c =>
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if(c.Name?.ToLower().Contains(query) ?? false)
|
||||
{
|
||||
return true;
|
||||
|
Loading…
Reference in New Issue
Block a user