diff --git a/src/App/Abstractions/Repositories/ICipherCollectionRepository.cs b/src/App/Abstractions/Repositories/ICipherCollectionRepository.cs index 559e48d1c..1ea52be49 100644 --- a/src/App/Abstractions/Repositories/ICipherCollectionRepository.cs +++ b/src/App/Abstractions/Repositories/ICipherCollectionRepository.cs @@ -7,6 +7,7 @@ namespace Bit.App.Abstractions public interface ICipherCollectionRepository { Task> GetAllByUserIdAsync(string userId); + Task> GetAllByUserIdCollectionAsync(string userId, string collectionId); Task InsertAsync(CipherCollectionData obj); Task DeleteAsync(CipherCollectionData obj); Task DeleteByUserIdAsync(string userId); diff --git a/src/App/Abstractions/Services/ICipherService.cs b/src/App/Abstractions/Services/ICipherService.cs index 53afa3a7a..c9c2343b1 100644 --- a/src/App/Abstractions/Services/ICipherService.cs +++ b/src/App/Abstractions/Services/ICipherService.cs @@ -12,6 +12,8 @@ namespace Bit.App.Abstractions Task GetByIdAsync(string id); Task> GetAllAsync(); Task> GetAllAsync(bool favorites); + Task> GetAllByFolderAsync(string folderId); + Task> GetAllByCollectionAsync(string collectionId); Task, IEnumerable, IEnumerable>> GetAllAsync(string uriString); Task> SaveAsync(Cipher cipher); Task UpsertDataAsync(CipherData cipher); diff --git a/src/App/Pages/MainPage.cs b/src/App/Pages/MainPage.cs index 76169c95b..e61ca9b48 100644 --- a/src/App/Pages/MainPage.cs +++ b/src/App/Pages/MainPage.cs @@ -1,6 +1,5 @@ using System; using Bit.App.Controls; -using Bit.App.Resources; using Xamarin.Forms; namespace Bit.App.Pages @@ -12,7 +11,7 @@ namespace Bit.App.Pages TintColor = Color.FromHex("3c8dbc"); var settingsNavigation = new ExtendedNavigationPage(new SettingsPage()); - var favoritesNavigation = new ExtendedNavigationPage(new VaultListCiphersPage(true)); + var favoritesNavigation = new ExtendedNavigationPage(new VaultSearchCiphersPage(favorites: true)); var vaultNavigation = new ExtendedNavigationPage(new VaultListGroupingsPage()); var toolsNavigation = new ExtendedNavigationPage(new ToolsPage()); diff --git a/src/App/Pages/Vault/VaultListGroupingsPage.cs b/src/App/Pages/Vault/VaultListGroupingsPage.cs index cbb9b6383..e6d7837e2 100644 --- a/src/App/Pages/Vault/VaultListGroupingsPage.cs +++ b/src/App/Pages/Vault/VaultListGroupingsPage.cs @@ -214,7 +214,7 @@ namespace Bit.App.Pages return cts; } - private void GroupingSelected(object sender, SelectedItemChangedEventArgs e) + private async void GroupingSelected(object sender, SelectedItemChangedEventArgs e) { var grouping = e.SelectedItem as Grouping; if(grouping == null) @@ -222,6 +222,17 @@ namespace Bit.App.Pages return; } + Page page; + if(grouping.Folder) + { + page = new VaultSearchCiphersPage(folder: true, folderId: grouping.Id, groupingName: grouping.Name); + } + else + { + page = new VaultSearchCiphersPage(collectionId: grouping.Id, groupingName: grouping.Name); + } + + await Navigation.PushAsync(page); ((ListView)sender).SelectedItem = null; } diff --git a/src/App/Pages/Vault/VaultSearchCiphersPage.cs b/src/App/Pages/Vault/VaultSearchCiphersPage.cs index 11826b3e5..457655f6d 100644 --- a/src/App/Pages/Vault/VaultSearchCiphersPage.cs +++ b/src/App/Pages/Vault/VaultSearchCiphersPage.cs @@ -11,6 +11,7 @@ using Plugin.Settings.Abstractions; using Plugin.Connectivity.Abstractions; using System.Threading; using static Bit.App.Models.Page.VaultListPageModel; +using System.Collections.Generic; namespace Bit.App.Pages { @@ -24,10 +25,22 @@ namespace Bit.App.Pages private readonly IAppSettingsService _appSettingsService; private readonly IGoogleAnalyticsService _googleAnalyticsService; private CancellationTokenSource _filterResultsCancellationTokenSource; + private readonly bool _favorites = false; + private readonly bool _folder = false; + private readonly string _folderId = null; + private readonly string _collectionId = null; + private readonly string _groupingName = null; - public VaultSearchCiphersPage() + public VaultSearchCiphersPage(bool folder = false, string folderId = null, + string collectionId = null, string groupingName = null, bool favorites = false) : base(true) { + _folder = folder; + _folderId = folderId; + _collectionId = collectionId; + _favorites = favorites; + _groupingName = groupingName; + _cipherService = Resolver.Resolve(); _connectivity = Resolver.Resolve(); _syncService = Resolver.Resolve(); @@ -83,7 +96,19 @@ namespace Bit.App.Pages Spacing = 0 }; - Title = AppResources.SearchVault; + if(!string.IsNullOrWhiteSpace(_groupingName)) + { + Title = _groupingName; + } + else if(_favorites) + { + Title = AppResources.Favorites; + } + else + { + Title = AppResources.SearchVault; + } + Content = new ActivityIndicator { IsRunning = true, @@ -177,7 +202,11 @@ namespace Bit.App.Pages Search.TextChanged += SearchBar_TextChanged; Search.SearchButtonPressed += SearchBar_SearchButtonPressed; _filterResultsCancellationTokenSource = FetchAndLoadVault(); - Search.FocusWithDelay(); + + if(!_folder && string.IsNullOrWhiteSpace(_folderId) && string.IsNullOrWhiteSpace(_collectionId) && !_favorites) + { + Search.FocusWithDelay(); + } } protected override void OnDisappearing() @@ -202,7 +231,23 @@ namespace Bit.App.Pages Task.Run(async () => { - var ciphers = await _cipherService.GetAllAsync(); + IEnumerable ciphers; + if(_folder || !string.IsNullOrWhiteSpace(_folderId)) + { + ciphers = await _cipherService.GetAllByFolderAsync(_folderId); + } + else if(!string.IsNullOrWhiteSpace(_collectionId)) + { + ciphers = await _cipherService.GetAllByCollectionAsync(_collectionId); + } + else if(_favorites) + { + ciphers = await _cipherService.GetAllAsync(true); + } + else + { + ciphers = await _cipherService.GetAllAsync(); + } Ciphers = ciphers .Select(s => new Cipher(s, _appSettingsService)) diff --git a/src/App/Repositories/CipherCollectionRepository.cs b/src/App/Repositories/CipherCollectionRepository.cs index c844a6b99..c2bcd8d89 100644 --- a/src/App/Repositories/CipherCollectionRepository.cs +++ b/src/App/Repositories/CipherCollectionRepository.cs @@ -19,6 +19,13 @@ namespace Bit.App.Repositories return Task.FromResult(cipherCollections); } + public Task> GetAllByUserIdCollectionAsync(string userId, string collectionId) + { + var cipherCollections = Connection.Table().Where( + f => f.UserId == userId && f.CollectionId == collectionId).Cast(); + return Task.FromResult(cipherCollections); + } + public virtual Task InsertAsync(CipherCollectionData obj) { Connection.Insert(obj); diff --git a/src/App/Services/CipherService.cs b/src/App/Services/CipherService.cs index 47ce79dd0..ad6a6fa8e 100644 --- a/src/App/Services/CipherService.cs +++ b/src/App/Services/CipherService.cs @@ -16,6 +16,7 @@ namespace Bit.App.Services private readonly string[] _ignoredSearchTerms = new string[] { "com", "net", "org", "android", "io", "co", "uk", "au", "nz", "fr", "de", "tv", "info", "app", "apps", "eu", "me", "dev", "jp", "mobile" }; private readonly ICipherRepository _cipherRepository; + private readonly ICipherCollectionRepository _cipherCollectionRepository; private readonly IAttachmentRepository _attachmentRepository; private readonly IAuthService _authService; private readonly ICipherApiRepository _cipherApiRepository; @@ -26,6 +27,7 @@ namespace Bit.App.Services public CipherService( ICipherRepository cipherRepository, + ICipherCollectionRepository cipherCollectionRepository, IAttachmentRepository attachmentRepository, IAuthService authService, ICipherApiRepository cipherApiRepository, @@ -33,6 +35,7 @@ namespace Bit.App.Services ICryptoService cryptoService) { _cipherRepository = cipherRepository; + _cipherCollectionRepository = cipherCollectionRepository; _attachmentRepository = attachmentRepository; _authService = authService; _cipherApiRepository = cipherApiRepository; @@ -71,11 +74,22 @@ namespace Bit.App.Services public async Task> GetAllAsync(bool favorites) { - var attachmentData = await _attachmentRepository.GetAllByUserIdAsync(_authService.UserId); - var attachmentDict = attachmentData.GroupBy(a => a.LoginId).ToDictionary(g => g.Key, g => g.ToList()); - var data = await _cipherRepository.GetAllByUserIdAsync(_authService.UserId, favorites); - var cipher = data.Select(f => new Cipher(f, attachmentDict.ContainsKey(f.Id) ? attachmentDict[f.Id] : null)); - return cipher; + var ciphers = await GetAllAsync(); + return ciphers.Where(c => c.Favorite == favorites); + } + + public async Task> GetAllByFolderAsync(string folderId) + { + var ciphers = await GetAllAsync(); + return ciphers.Where(c => c.FolderId == folderId); + } + + public async Task> GetAllByCollectionAsync(string collectionId) + { + var assoc = await _cipherCollectionRepository.GetAllByUserIdCollectionAsync(_authService.UserId, collectionId); + var cipherIds = new HashSet(assoc.Select(c => c.CipherId)); + var ciphers = await GetAllAsync(); + return ciphers.Where(c => cipherIds.Contains(c.Id)); } public async Task, IEnumerable, IEnumerable>> GetAllAsync(string uriString)