diff --git a/src/App/App.csproj b/src/App/App.csproj
index ea6f7c8d8..77ac609dc 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -28,6 +28,9 @@
GeneratorPage.xaml
+
+ CiphersPage.xaml
+
ViewPage.xaml
diff --git a/src/App/Pages/Vault/CiphersPage.xaml b/src/App/Pages/Vault/CiphersPage.xaml
new file mode 100644
index 000000000..bf8083c67
--- /dev/null
+++ b/src/App/Pages/Vault/CiphersPage.xaml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Pages/Vault/CiphersPage.xaml.cs b/src/App/Pages/Vault/CiphersPage.xaml.cs
new file mode 100644
index 000000000..711cd4101
--- /dev/null
+++ b/src/App/Pages/Vault/CiphersPage.xaml.cs
@@ -0,0 +1,54 @@
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace Bit.App.Pages
+{
+ public partial class ViewPage : ContentPage
+ {
+ private readonly IBroadcasterService _broadcasterService;
+ private ViewPageViewModel _vm;
+
+ public ViewPage(string cipherId)
+ {
+ InitializeComponent();
+ _broadcasterService = ServiceContainer.Resolve("broadcasterService");
+ _vm = BindingContext as ViewPageViewModel;
+ _vm.Page = this;
+ _vm.CipherId = cipherId;
+ }
+
+ protected async override void OnAppearing()
+ {
+ base.OnAppearing();
+ _broadcasterService.Subscribe(nameof(ViewPage), async (message) =>
+ {
+ if(message.Command == "syncCompleted")
+ {
+ var data = message.Data as Dictionary;
+ if(data.ContainsKey("successfully"))
+ {
+ var success = data["successfully"] as bool?;
+ if(success.HasValue && success.Value)
+ {
+ await _vm.LoadAsync();
+ }
+ }
+ }
+ });
+ await _vm.LoadAsync();
+ }
+
+ protected override void OnDisappearing()
+ {
+ base.OnDisappearing();
+ _broadcasterService.Unsubscribe(nameof(ViewPage));
+ }
+ }
+}
diff --git a/src/App/Pages/Vault/CiphersPageViewModel.cs b/src/App/Pages/Vault/CiphersPageViewModel.cs
new file mode 100644
index 000000000..18c59a216
--- /dev/null
+++ b/src/App/Pages/Vault/CiphersPageViewModel.cs
@@ -0,0 +1,50 @@
+using Bit.App.Abstractions;
+using Bit.App.Resources;
+using Bit.Core.Abstractions;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+using System.Threading.Tasks;
+
+namespace Bit.App.Pages
+{
+ public class CiphersPageViewModel : BaseViewModel
+ {
+ private readonly IDeviceActionService _deviceActionService;
+ private readonly ICipherService _cipherService;
+ private readonly IUserService _userService;
+ private CipherView _cipher;
+ private bool _canAccessPremium;
+
+ public CiphersPageViewModel()
+ {
+ _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _cipherService = ServiceContainer.Resolve("cipherService");
+ _userService = ServiceContainer.Resolve("userService");
+
+ PageTitle = AppResources.ViewItem;
+ }
+
+ public string CipherId { get; set; }
+ public CipherView Cipher
+ {
+ get => _cipher;
+ set => SetProperty(ref _cipher, value);
+ }
+ public bool CanAccessPremium
+ {
+ get => _canAccessPremium;
+ set => SetProperty(ref _canAccessPremium, value);
+ }
+
+ public async Task LoadAsync()
+ {
+ // TODO: Cleanup
+
+ var cipher = await _cipherService.GetAsync(CipherId);
+ Cipher = await cipher.DecryptAsync();
+ CanAccessPremium = await _userService.CanAccessPremiumAsync();
+
+ // TODO: Totp
+ }
+ }
+}
diff --git a/src/Core/Abstractions/ISearchService.cs b/src/Core/Abstractions/ISearchService.cs
new file mode 100644
index 000000000..49c1729b1
--- /dev/null
+++ b/src/Core/Abstractions/ISearchService.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.Core.Models.View;
+
+namespace Bit.Core.Abstractions
+{
+ public interface ISearchService
+ {
+ void ClearIndex();
+ Task IndexCiphersAsync();
+ bool IsSearchable(string query);
+ Task> SearchCiphersAsync(string query, Func filter = null,
+ List ciphers = null);
+ List SearchCiphersBasic(List ciphers, string query);
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs
index f62ccc603..e8a98601b 100644
--- a/src/Core/Services/CipherService.cs
+++ b/src/Core/Services/CipherService.cs
@@ -32,6 +32,7 @@ namespace Bit.Core.Services
private readonly IApiService _apiService;
private readonly IStorageService _storageService;
private readonly II18nService _i18nService;
+ private readonly Func _searchService;
private Dictionary> _domainMatchBlacklist = new Dictionary>
{
["google.com"] = new HashSet { "script.google.com" }
@@ -45,7 +46,8 @@ namespace Bit.Core.Services
ISettingsService settingsService,
IApiService apiService,
IStorageService storageService,
- II18nService i18nService)
+ II18nService i18nService,
+ Func searchService)
{
_cryptoService = cryptoService;
_userService = userService;
@@ -53,6 +55,7 @@ namespace Bit.Core.Services
_apiService = apiService;
_storageService = storageService;
_i18nService = i18nService;
+ _searchService = searchService;
}
private List DecryptedCipherCache
@@ -65,7 +68,17 @@ namespace Bit.Core.Services
_decryptedCipherCache.Clear();
}
_decryptedCipherCache = value;
- // TODO: update search index
+ if(_searchService != null)
+ {
+ if(value == null)
+ {
+ _searchService().ClearIndex();
+ }
+ else
+ {
+ _searchService().IndexCiphersAsync();
+ }
+ }
}
}
diff --git a/src/Core/Services/SearchService.cs b/src/Core/Services/SearchService.cs
new file mode 100644
index 000000000..bfb2af85e
--- /dev/null
+++ b/src/Core/Services/SearchService.cs
@@ -0,0 +1,90 @@
+using Bit.Core.Abstractions;
+using Bit.Core.Models.View;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Bit.Core.Services
+{
+ public class SearchService : ISearchService
+ {
+ private readonly ICipherService _cipherService;
+
+ public SearchService(
+ ICipherService cipherService)
+ {
+ _cipherService = cipherService;
+ }
+
+ public void ClearIndex()
+ {
+ // TODO
+ }
+
+ public bool IsSearchable(string query)
+ {
+ return true;
+ }
+
+ public Task IndexCiphersAsync()
+ {
+ // TODO
+ return Task.FromResult(0);
+ }
+
+ public async Task> SearchCiphersAsync(string query, Func filter = null,
+ List ciphers = null)
+ {
+ var results = new List();
+ if(query != null)
+ {
+ query = query.Trim().ToLower();
+ }
+ if(query == string.Empty)
+ {
+ query = null;
+ }
+ if(ciphers == null)
+ {
+ ciphers = await _cipherService.GetAllDecryptedAsync();
+ }
+ if(filter != null)
+ {
+ ciphers = ciphers.Where(filter).ToList();
+ }
+ if(!IsSearchable(query))
+ {
+ return ciphers;
+ }
+
+ return SearchCiphersBasic(ciphers, query);
+ // TODO: advanced searching with index
+ }
+
+ public List SearchCiphersBasic(List ciphers, string query)
+ {
+ query = query.Trim().ToLower();
+ return ciphers.Where(c =>
+ {
+ if(c.Name?.ToLower().Contains(query) ?? false)
+ {
+ return true;
+ }
+ if(query.Length >= 8 && c.Id.StartsWith(query))
+ {
+ return true;
+ }
+ if(c.SubTitle?.ToLower().Contains(query) ?? false)
+ {
+ return true;
+ }
+ if(c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
+ {
+ return true;
+ }
+ return false;
+ }).ToList();
+ }
+ }
+}
diff --git a/src/Core/Utilities/ServiceContainer.cs b/src/Core/Utilities/ServiceContainer.cs
index e5de3c099..17498fb70 100644
--- a/src/Core/Utilities/ServiceContainer.cs
+++ b/src/Core/Utilities/ServiceContainer.cs
@@ -25,6 +25,7 @@ namespace Bit.Core.Utilities
var cryptoPrimitiveService = Resolve("cryptoPrimitiveService");
var i18nService = Resolve("i18nService");
var messagingService = Resolve("messagingService");
+ ISearchService searchService = null;
var stateService = new StateService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
@@ -35,10 +36,11 @@ namespace Bit.Core.Utilities
var userService = new UserService(storageService, tokenService);
var settingsService = new SettingsService(userService, storageService);
var cipherService = new CipherService(cryptoService, userService, settingsService, apiService,
- storageService, i18nService);
+ storageService, i18nService, () => searchService);
var folderService = new FolderService(cryptoService, userService, apiService, storageService,
i18nService, cipherService);
var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
+ searchService = new SearchService(cipherService);
// TODO: lock service
var syncService = new SyncService(userService, apiService, settingsService, folderService,
cipherService, cryptoService, collectionService, storageService, messagingService);