1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-12-17 15:27:43 +01:00

cipher searching

This commit is contained in:
Kyle Spearrin 2019-05-06 22:35:42 -04:00
parent 128935eb9f
commit 4ed12a859b
13 changed files with 270 additions and 73 deletions

View File

@ -41,17 +41,17 @@ namespace Bit.App.Pages
}); });
} }
protected void RequestFocus(Entry entry) protected void RequestFocus(InputView input)
{ {
if(Device.RuntimePlatform == Device.iOS) if(Device.RuntimePlatform == Device.iOS)
{ {
entry.Focus(); input.Focus();
return; return;
} }
Task.Run(async () => Task.Run(async () =>
{ {
await Task.Delay(AndroidShowModalAnimationDelay); await Task.Delay(AndroidShowModalAnimationDelay);
Device.BeginInvokeOnMainThread(() => entry.Focus()); Device.BeginInvokeOnMainThread(() => input.Focus());
}); });
} }
} }

View File

@ -17,7 +17,6 @@
<ContentPage.Resources> <ContentPage.Resources>
<ResourceDictionary> <ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:DateTimeConverter x:Key="dateTime" /> <u:DateTimeConverter x:Key="dateTime" />
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>
@ -30,32 +29,34 @@
Spacing="0" Spacing="0"
Padding="0"> Padding="0">
<controls:MiButton <controls:MiButton
StyleClass="btn-title"
Text="&#xe5c4;" Text="&#xe5c4;"
BackgroundColor="Transparent" VerticalOptions="CenterAndExpand"
Padding="0" Clicked="BackButton_Clicked" />
WidthRequest="37"
FontSize="25"
VerticalOptions="CenterAndExpand" />
<SearchBar <SearchBar
x:Name="_searchBar"
HorizontalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent" BackgroundColor="Transparent"
Placeholder="{u:I18n SearchVault}" /> TextChanged="SearchBar_TextChanged"
SearchButtonPressed="SearchBar_SearchButtonPressed"
Placeholder="{Binding PageTitle}" />
</StackLayout> </StackLayout>
</NavigationPage.TitleView> </NavigationPage.TitleView>
<StackLayout x:Name="_mainLayout"> <StackLayout x:Name="_mainLayout">
<Label IsVisible="{Binding ShowNoData}" <Label IsVisible="{Binding ShowNoData}"
Text="{Binding NoDataText}" Text="{u:I18n NoItemsToList}"
Margin="20, 0" Margin="20, 0"
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"></Label> HorizontalTextAlignment="Center"></Label>
<ListView x:Name="_listView" <ListView x:Name="_listView"
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}" IsVisible="{Binding ShowList}"
ItemsSource="{Binding Ciphers}" ItemsSource="{Binding Ciphers}"
VerticalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
HasUnevenRows="true" HasUnevenRows="true"
CachingStrategy="RecycleElement" CachingStrategy="RecycleElement"
ItemSelected="RowSelected"
StyleClass="list, list-platform"> StyleClass="list, list-platform">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate x:DataType="views:CipherView"> <DataTemplate x:DataType="views:CipherView">

View File

@ -1,25 +1,91 @@
using System; using Bit.App.Resources;
using Bit.Core.Models.View;
using System;
using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public partial class CiphersPage : BaseContentPage public partial class CiphersPage : BaseContentPage
{ {
private CiphersPageViewModel _vm; private CiphersPageViewModel _vm;
private bool _hasFocused;
public CiphersPage() public CiphersPage(Func<CipherView, bool> filter, bool folder = false, bool collection = false,
bool type = false)
{ {
InitializeComponent(); InitializeComponent();
SetActivityIndicator();
_vm = BindingContext as CiphersPageViewModel; _vm = BindingContext as CiphersPageViewModel;
_vm.Page = this; _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(); base.OnAppearing();
await LoadOnAppearedAsync(_mainLayout, true, async () => { if(!_hasFocused)
await _vm.LoadAsync(); {
}); _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);
}
} }
} }
} }

View File

@ -2,6 +2,9 @@
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
@ -11,23 +14,26 @@ namespace Bit.App.Pages
{ {
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly ISearchService _searchService;
private CancellationTokenSource _searchCancellationTokenSource;
private string _searchText; private string _searchText;
private string _noDataText;
private bool _showNoData; private bool _showNoData;
private bool _showList;
public CiphersPageViewModel() public CiphersPageViewModel()
{ {
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService"); _cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
PageTitle = AppResources.SearchVault;
Ciphers = new ExtendedObservableCollection<CipherView>(); Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync); CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
} }
public Command CipherOptionsCommand { get; set; } public Command CipherOptionsCommand { get; set; }
public ExtendedObservableCollection<CipherView> Ciphers { get; set; } public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
public Func<CipherView, bool> Filter { get; set; }
public string SearchText public string SearchText
{ {
@ -35,23 +41,67 @@ namespace Bit.App.Pages
set => SetProperty(ref _searchText, value); set => SetProperty(ref _searchText, value);
} }
public string NoDataText
{
get => _noDataText;
set => SetProperty(ref _noDataText, value);
}
public bool ShowNoData public bool ShowNoData
{ {
get => _showNoData; get => _showNoData;
set => SetProperty(ref _showNoData, value); set => SetProperty(ref _showNoData, value);
} }
public async Task LoadAsync() public bool ShowList
{ {
var ciphers = await _cipherService.GetAllDecryptedAsync(); get => _showList;
Ciphers.ResetWithRange(ciphers); set => SetProperty(ref _showList, value);
ShowNoData = Ciphers.Count == 0; }
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) private async void CipherOptionsAsync(CipherView cipher)

View File

@ -10,7 +10,7 @@ namespace Bit.App.Pages
{ {
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly GroupingsPageViewModel _viewModel; private readonly GroupingsPageViewModel _vm;
public GroupingsPage() public GroupingsPage()
: this(true) : this(true)
@ -23,15 +23,15 @@ namespace Bit.App.Pages
SetActivityIndicator(); SetActivityIndicator();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_viewModel = BindingContext as GroupingsPageViewModel; _vm = BindingContext as GroupingsPageViewModel;
_viewModel.Page = this; _vm.Page = this;
_viewModel.MainPage = mainPage; _vm.MainPage = mainPage;
_viewModel.Type = type; _vm.Type = type;
_viewModel.FolderId = folderId; _vm.FolderId = folderId;
_viewModel.CollectionId = collectionId; _vm.CollectionId = collectionId;
if(pageTitle != null) if(pageTitle != null)
{ {
_viewModel.PageTitle = pageTitle; _vm.PageTitle = pageTitle;
} }
} }
@ -52,14 +52,14 @@ namespace Bit.App.Pages
{ {
if(!_syncService.SyncInProgress) if(!_syncService.SyncInProgress)
{ {
await _viewModel.LoadAsync(); await _vm.LoadAsync();
} }
else else
{ {
await Task.Delay(5000); 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) private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
{ {
((ListView)sender).SelectedItem = null;
if(!(e.SelectedItem is GroupingsPageListItem item)) if(!(e.SelectedItem is GroupingsPageListItem item))
{ {
return; return;
@ -80,25 +81,26 @@ namespace Bit.App.Pages
if(item.Cipher != null) if(item.Cipher != null)
{ {
await _viewModel.SelectCipherAsync(item.Cipher); await _vm.SelectCipherAsync(item.Cipher);
} }
else if(item.Folder != null) else if(item.Folder != null)
{ {
await _viewModel.SelectFolderAsync(item.Folder); await _vm.SelectFolderAsync(item.Folder);
} }
else if(item.Collection != null) else if(item.Collection != null)
{ {
await _viewModel.SelectCollectionAsync(item.Collection); await _vm.SelectCollectionAsync(item.Collection);
} }
else if(item.Type != null) 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) 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);
} }
} }
} }

View File

@ -4,6 +4,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -56,6 +57,7 @@ namespace Bit.App.Pages
public CipherType? Type { get; set; } public CipherType? Type { get; set; }
public string FolderId { get; set; } public string FolderId { get; set; }
public string CollectionId { get; set; } public string CollectionId { get; set; }
public Func<CipherView, bool> Filter { get; set; }
public List<CipherView> Ciphers { get; set; } public List<CipherView> Ciphers { get; set; }
public List<CipherView> FavoriteCiphers { get; set; } public List<CipherView> FavoriteCiphers { get; set; }
@ -249,6 +251,7 @@ namespace Bit.App.Pages
_folderCounts.Clear(); _folderCounts.Clear();
_collectionCounts.Clear(); _collectionCounts.Clear();
_typeCounts.Clear(); _typeCounts.Clear();
Filter = null;
if(MainPage) if(MainPage)
{ {
@ -267,7 +270,7 @@ namespace Bit.App.Pages
{ {
if(Type != null) if(Type != null)
{ {
Ciphers = _allCiphers.Where(c => c.Type == Type.Value).ToList(); Filter = c => c.Type == Type.Value;
} }
else if(FolderId != null) else if(FolderId != null)
{ {
@ -286,7 +289,7 @@ namespace Bit.App.Pages
{ {
PageTitle = AppResources.FolderNone; PageTitle = AppResources.FolderNone;
} }
Ciphers = _allCiphers.Where(c => c.FolderId == folderId).ToList(); Filter = c => c.FolderId == folderId;
} }
else if(CollectionId != null) else if(CollectionId != null)
{ {
@ -297,13 +300,13 @@ namespace Bit.App.Pages
{ {
PageTitle = collectionNode.Node.Name; PageTitle = collectionNode.Node.Name;
} }
Ciphers = _allCiphers.Where(c => c.CollectionIds?.Contains(CollectionId) ?? false).ToList(); Filter = c => c.CollectionIds?.Contains(CollectionId) ?? false;
} }
else else
{ {
PageTitle = AppResources.AllItems; PageTitle = AppResources.AllItems;
Ciphers = _allCiphers;
} }
Ciphers = Filter != null ? _allCiphers.Where(Filter).ToList() : _allCiphers;
} }
foreach(var c in _allCiphers) foreach(var c in _allCiphers)

View File

@ -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> /// <summary>
/// Looks up a localized string similar to No passwords to list.. /// Looks up a localized string similar to No passwords to list..
/// </summary> /// </summary>
@ -2851,7 +2860,34 @@ namespace Bit.App.Resources {
} }
/// <summary> /// <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> /// </summary>
public static string SearchVault { public static string SearchVault {
get { get {

View File

@ -656,7 +656,7 @@
<value>Re-type Master Password</value> <value>Re-type Master Password</value>
</data> </data>
<data name="SearchVault" xml:space="preserve"> <data name="SearchVault" xml:space="preserve">
<value>Search Vault</value> <value>Search vault</value>
</data> </data>
<data name="Security" xml:space="preserve"> <data name="Security" xml:space="preserve">
<value>Security</value> <value>Security</value>
@ -1396,4 +1396,16 @@
<data name="NoPasswordsToList" xml:space="preserve"> <data name="NoPasswordsToList" xml:space="preserve">
<value>No passwords to list.</value> <value>No passwords to list.</value>
</data> </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> </root>

View File

@ -48,16 +48,6 @@
<Setter Property="FontAttributes" <Setter Property="FontAttributes"
Value="Bold" /> Value="Bold" />
</Style> </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 --> <!-- Buttons -->
<Style TargetType="Button" <Style TargetType="Button"
@ -79,6 +69,31 @@
Value="{StaticResource DisabledIconColor}" /> Value="{StaticResource DisabledIconColor}" />
</Style> </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 --> <!-- List -->
<Style TargetType="ListView" <Style TargetType="ListView"

View File

@ -18,6 +18,7 @@
<Color x:Key="BoxBorderColor">#f0f0f0</Color> <Color x:Key="BoxBorderColor">#f0f0f0</Color>
<Color x:Key="BoxHeaderTextColor">#3c8dbc</Color> <Color x:Key="BoxHeaderTextColor">#3c8dbc</Color>
<Color x:Key="HeaderTextColor">#ffffff</Color>
<Color x:Key="HeaderEntryTextColor">#ffffff</Color> <Color x:Key="HeaderEntryTextColor">#ffffff</Color>
<Color x:Key="HeaderEntryPlaceholderColor">#707070</Color> <Color x:Key="HeaderEntryPlaceholderColor">#707070</Color>

View File

@ -18,8 +18,9 @@
<Color x:Key="BoxBorderColor">#dddddd</Color> <Color x:Key="BoxBorderColor">#dddddd</Color>
<Color x:Key="BoxHeaderTextColor">#3c8dbc</Color> <Color x:Key="BoxHeaderTextColor">#3c8dbc</Color>
<Color x:Key="HeaderEntryTextColor">#ffffff</Color> <Color x:Key="TitleTextColor">#ffffff</Color>
<Color x:Key="HeaderEntryPlaceholderColor">#c0dbeb</Color> <Color x:Key="TitleEntryTextColor">#ffffff</Color>
<Color x:Key="TitleEntryPlaceholderColor">#c0dbeb</Color>
<Color x:Key="ListItemBorderColor">#f0f0f0</Color> <Color x:Key="ListItemBorderColor">#f0f0f0</Color>
<Color x:Key="ListHeaderTextColor">#3c8dbc</Color> <Color x:Key="ListHeaderTextColor">#3c8dbc</Color>

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Models.View; using Bit.Core.Models.View;
@ -11,7 +12,8 @@ namespace Bit.Core.Abstractions
Task IndexCiphersAsync(); Task IndexCiphersAsync();
bool IsSearchable(string query); bool IsSearchable(string query);
Task<List<CipherView>> SearchCiphersAsync(string query, Func<CipherView, bool> filter = null, Task<List<CipherView>> SearchCiphersAsync(string query, Func<CipherView, bool> filter = null,
List<CipherView> ciphers = null); List<CipherView> ciphers = null, CancellationToken ct = default(CancellationToken));
List<CipherView> SearchCiphersBasic(List<CipherView> ciphers, string query); List<CipherView> SearchCiphersBasic(List<CipherView> ciphers, string query,
CancellationToken ct = default(CancellationToken));
} }
} }

View File

@ -3,6 +3,7 @@ using Bit.Core.Models.View;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Bit.Core.Services namespace Bit.Core.Services
@ -24,7 +25,7 @@ namespace Bit.Core.Services
public bool IsSearchable(string query) public bool IsSearchable(string query)
{ {
return true; return (query?.Length ?? 0) > 1;
} }
public Task IndexCiphersAsync() public Task IndexCiphersAsync()
@ -34,7 +35,7 @@ namespace Bit.Core.Services
} }
public async Task<List<CipherView>> SearchCiphersAsync(string query, Func<CipherView, bool> filter = null, 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>(); var results = new List<CipherView>();
if(query != null) if(query != null)
@ -49,10 +50,14 @@ namespace Bit.Core.Services
{ {
ciphers = await _cipherService.GetAllDecryptedAsync(); ciphers = await _cipherService.GetAllDecryptedAsync();
} }
ct.ThrowIfCancellationRequested();
if(filter != null) if(filter != null)
{ {
ciphers = ciphers.Where(filter).ToList(); ciphers = ciphers.Where(filter).ToList();
} }
ct.ThrowIfCancellationRequested();
if(!IsSearchable(query)) if(!IsSearchable(query))
{ {
return ciphers; return ciphers;
@ -62,11 +67,14 @@ namespace Bit.Core.Services
// TODO: advanced searching with index // 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(); query = query.Trim().ToLower();
return ciphers.Where(c => return ciphers.Where(c =>
{ {
ct.ThrowIfCancellationRequested();
if(c.Name?.ToLower().Contains(query) ?? false) if(c.Name?.ToLower().Contains(query) ?? false)
{ {
return true; return true;