diff --git a/src/App/Abstractions/Services/ISyncService.cs b/src/App/Abstractions/Services/ISyncService.cs index b77107ddc..15f2abe81 100644 --- a/src/App/Abstractions/Services/ISyncService.cs +++ b/src/App/Abstractions/Services/ISyncService.cs @@ -4,6 +4,7 @@ namespace Bit.App.Abstractions { public interface ISyncService { + bool SyncInProgress { get; } Task SyncAsync(string id); Task SyncDeleteFolderAsync(string id); Task SyncDeleteSiteAsync(string id); diff --git a/src/App/App.cs b/src/App/App.cs index 744f85e4d..4f77aeefa 100644 --- a/src/App/App.cs +++ b/src/App/App.cs @@ -290,6 +290,15 @@ namespace Bit.App new Setter { Property = ListView.SeparatorColorProperty, Value = grayLighter } } }); + + // Search Bar + + Resources.Add(new Style(typeof(SearchBar)) + { + Setters = { + new Setter { Property = SearchBar.CancelButtonColorProperty, Value = primaryColor } + } + }); } } } diff --git a/src/App/Controls/ExtendedContentPage.cs b/src/App/Controls/ExtendedContentPage.cs index 7e3c003d8..fd5e0cb1a 100644 --- a/src/App/Controls/ExtendedContentPage.cs +++ b/src/App/Controls/ExtendedContentPage.cs @@ -1,13 +1,30 @@ -using System; +using Bit.App.Abstractions; +using System; using Xamarin.Forms; +using XLabs.Ioc; namespace Bit.App.Controls { public class ExtendedContentPage : ContentPage { + private ISyncService _syncService; + public ExtendedContentPage() { + _syncService = Resolver.Resolve(); + BackgroundColor = Color.FromHex("efeff4"); + IsBusy = _syncService.SyncInProgress; + + MessagingCenter.Subscribe(Application.Current, "SyncCompleted", (sender, success) => + { + IsBusy = _syncService.SyncInProgress; + }); + + MessagingCenter.Subscribe(Application.Current, "SyncStarted", (sender) => + { + IsBusy = _syncService.SyncInProgress; + }); } } } diff --git a/src/App/Pages/Vault/VaultListSitesPage.cs b/src/App/Pages/Vault/VaultListSitesPage.cs index 1b11be847..d65a5648c 100644 --- a/src/App/Pages/Vault/VaultListSitesPage.cs +++ b/src/App/Pages/Vault/VaultListSitesPage.cs @@ -14,7 +14,6 @@ using PushNotification.Plugin.Abstractions; using Plugin.Settings.Abstractions; using Plugin.Connectivity.Abstractions; using System.Collections.Generic; -using Bit.App.Models; namespace Bit.App.Pages { @@ -25,6 +24,7 @@ namespace Bit.App.Pages private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; private readonly IClipboardService _clipboardService; + private readonly ISyncService _syncService; private readonly IPushNotification _pushNotification; private readonly ISettings _settings; private readonly bool _favorites; @@ -37,6 +37,7 @@ namespace Bit.App.Pages _connectivity = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _clipboardService = Resolver.Resolve(); + _syncService = Resolver.Resolve(); _pushNotification = Resolver.Resolve(); _settings = Resolver.Resolve(); @@ -52,9 +53,12 @@ namespace Bit.App.Pages private void Init() { - MessagingCenter.Subscribe(Application.Current, "SyncCompleted", async (sender) => + MessagingCenter.Subscribe(Application.Current, "SyncCompleted", async (sender, success) => { - await FetchAndLoadVaultAsync(); + if(success) + { + await FetchAndLoadVaultAsync(); + } }); if(!_favorites) @@ -80,8 +84,8 @@ namespace Bit.App.Pages Search = new SearchBar { - Placeholder = "Search vault...", - BackgroundColor = Color.FromHex("efeff4") + Placeholder = "Search vault", + BackgroundColor = Color.FromHex("E8E8ED") }; Search.TextChanged += SearchBar_TextChanged; Search.SearchButtonPressed += SearchBar_SearchButtonPressed; @@ -96,7 +100,7 @@ namespace Bit.App.Pages private void SearchBar_SearchButtonPressed(object sender, EventArgs e) { - FilterResults(((SearchBar)sender).Text); + FilterResultsBackground(((SearchBar)sender).Text); } private void SearchBar_TextChanged(object sender, TextChangedEventArgs e) @@ -108,30 +112,38 @@ namespace Bit.App.Pages return; } - FilterResults(e.NewTextValue); + FilterResultsBackground(e.NewTextValue); + } + + private void FilterResultsBackground(string searchFilter) + { + Task.Run(async () => + { + if(!string.IsNullOrWhiteSpace(searchFilter)) + { + await Task.Delay(300); + if(searchFilter != Search.Text) + { + return; + } + } + + FilterResults(searchFilter); + }); } private void FilterResults(string searchFilter) { - Task.Run(async () => + if(string.IsNullOrWhiteSpace(searchFilter)) { - await Task.Delay(300); - if(searchFilter != Search.Text) - { - return; - } - - if(string.IsNullOrWhiteSpace(searchFilter)) - { - LoadFolders(Sites); - } - else - { - searchFilter = searchFilter.ToLower(); - var filteredSites = Sites.Where(s => s.Name.ToLower().Contains(searchFilter) || s.Username.ToLower().Contains(searchFilter)); - LoadFolders(filteredSites); - } - }); + LoadFolders(Sites); + } + else + { + searchFilter = searchFilter.ToLower(); + var filteredSites = Sites.Where(s => s.Name.ToLower().Contains(searchFilter) || s.Username.ToLower().Contains(searchFilter)); + LoadFolders(filteredSites); + } } protected async override void OnAppearing() @@ -161,6 +173,11 @@ namespace Bit.App.Pages private async Task FetchAndLoadVaultAsync() { + if(PresentationFolders.Count > 0 && _syncService.SyncInProgress) + { + return; + } + await Task.Run(async () => { var foldersTask = _folderService.GetAllAsync(); @@ -173,7 +190,7 @@ namespace Bit.App.Pages Folders = folders.Select(f => new VaultListPageModel.Folder(f)); Sites = sites.Select(s => new VaultListPageModel.Site(s)); - LoadFolders(Sites); + FilterResults(Search.Text); }); } diff --git a/src/App/Services/SyncService.cs b/src/App/Services/SyncService.cs index 1cb70d721..0ba8ebb17 100644 --- a/src/App/Services/SyncService.cs +++ b/src/App/Services/SyncService.cs @@ -13,6 +13,7 @@ namespace Bit.App.Services public class SyncService : ISyncService { private const string LastSyncKey = "lastSync"; + private int _syncsInProgress = 0; private readonly ICipherApiRepository _cipherApiRepository; private readonly IFolderApiRepository _folderApiRepository; @@ -40,6 +41,8 @@ namespace Bit.App.Services _settings = settings; } + public bool SyncInProgress => _syncsInProgress > 0; + public async Task SyncAsync(string id) { if(!_authService.IsAuthenticated) @@ -47,9 +50,12 @@ namespace Bit.App.Services return false; } + SyncStarted(); + var cipher = await _cipherApiRepository.GetByIdAsync(id); if(!cipher.Succeeded) { + SyncCompleted(false); return false; } @@ -80,10 +86,11 @@ namespace Bit.App.Services } break; default: + SyncCompleted(false); return false; } - BroadcastSyncCompleted(); + SyncCompleted(true); return true; } @@ -94,8 +101,10 @@ namespace Bit.App.Services return false; } + SyncStarted(); + await _folderRepository.DeleteAsync(id); - BroadcastSyncCompleted(); + SyncCompleted(true); return true; } @@ -106,8 +115,10 @@ namespace Bit.App.Services return false; } + SyncStarted(); + await _siteRepository.DeleteAsync(id); - BroadcastSyncCompleted(); + SyncCompleted(true); return true; } @@ -118,10 +129,13 @@ namespace Bit.App.Services return false; } + SyncStarted(); + var now = DateTime.UtcNow; var ciphers = await _cipherApiRepository.GetAsync(); if(!ciphers.Succeeded) { + SyncCompleted(false); return false; } @@ -131,11 +145,12 @@ namespace Bit.App.Services if(folderTask.Exception != null || siteTask.Exception != null) { + SyncCompleted(false); return false; } _settings.AddOrUpdateValue(LastSyncKey, now); - BroadcastSyncCompleted(); + SyncCompleted(true); return true; } @@ -153,9 +168,12 @@ namespace Bit.App.Services return await FullSyncAsync(); } + SyncStarted(); + var ciphers = await _cipherApiRepository.GetByRevisionDateWithHistoryAsync(lastSync.Value); if(!ciphers.Succeeded) { + SyncCompleted(false); return false; } @@ -166,11 +184,12 @@ namespace Bit.App.Services await Task.WhenAll(siteTask, folderTask, deleteTask); if(folderTask.Exception != null || siteTask.Exception != null || deleteTask.Exception != null) { + SyncCompleted(false); return false; } _settings.AddOrUpdateValue(LastSyncKey, now); - BroadcastSyncCompleted(); + SyncCompleted(true); return true; } @@ -245,9 +264,16 @@ namespace Bit.App.Services await Task.WhenAll(tasks); } - private void BroadcastSyncCompleted() + private void SyncStarted() { - MessagingCenter.Send(Application.Current, "SyncCompleted"); + _syncsInProgress++; + MessagingCenter.Send(Application.Current, "SyncStarted"); + } + + private void SyncCompleted(bool successfully) + { + _syncsInProgress--; + MessagingCenter.Send(Application.Current, "SyncCompleted", successfully); } } } diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index 9413ae2c9..18c3c602a 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -50,8 +50,13 @@ namespace Bit.iOS Resolver.Resolve(), Resolver.Resolve())); + // Appearance stuff + + var primaryColor = new UIColor(red: 0.24f, green: 0.55f, blue: 0.74f, alpha: 1.0f); + UINavigationBar.Appearance.ShadowImage = new UIImage(); UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default); + UIBarButtonItem.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).TintColor = primaryColor; MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, "ShowAppExtension", (sender, page) => { diff --git a/src/iOS/Controls/CustomSearchBarRenderer.cs b/src/iOS/Controls/CustomSearchBarRenderer.cs new file mode 100644 index 000000000..05c0783d2 --- /dev/null +++ b/src/iOS/Controls/CustomSearchBarRenderer.cs @@ -0,0 +1,33 @@ +using Bit.App.Controls; +using Bit.iOS.Controls; +using System.ComponentModel; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))] +namespace Bit.iOS.Controls +{ + public class CustomSearchBarRenderer : SearchBarRenderer + { + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + var view = e.NewElement; + if(view != null) + { + Control.SearchBarStyle = UISearchBarStyle.Minimal; + Control.BarStyle = UIBarStyle.BlackTranslucent; + Control.ShowsCancelButton = Control.IsFirstResponder; + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + var view = Element; + Control.ShowsCancelButton = Control.IsFirstResponder; + } + } +} diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj index b70699b75..919d97e30 100644 --- a/src/iOS/iOS.csproj +++ b/src/iOS/iOS.csproj @@ -104,6 +104,7 @@ +