mirror of
https://github.com/bitwarden/mobile.git
synced 2025-02-13 00:41:25 +01:00
Added busy indicator for sync operations. Optimized vault list loading. Customized search bar appearance on iOS.
This commit is contained in:
parent
635b09de9b
commit
f2893e788d
@ -4,6 +4,7 @@ namespace Bit.App.Abstractions
|
|||||||
{
|
{
|
||||||
public interface ISyncService
|
public interface ISyncService
|
||||||
{
|
{
|
||||||
|
bool SyncInProgress { get; }
|
||||||
Task<bool> SyncAsync(string id);
|
Task<bool> SyncAsync(string id);
|
||||||
Task<bool> SyncDeleteFolderAsync(string id);
|
Task<bool> SyncDeleteFolderAsync(string id);
|
||||||
Task<bool> SyncDeleteSiteAsync(string id);
|
Task<bool> SyncDeleteSiteAsync(string id);
|
||||||
|
@ -290,6 +290,15 @@ namespace Bit.App
|
|||||||
new Setter { Property = ListView.SeparatorColorProperty, Value = grayLighter }
|
new Setter { Property = ListView.SeparatorColorProperty, Value = grayLighter }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Search Bar
|
||||||
|
|
||||||
|
Resources.Add(new Style(typeof(SearchBar))
|
||||||
|
{
|
||||||
|
Setters = {
|
||||||
|
new Setter { Property = SearchBar.CancelButtonColorProperty, Value = primaryColor }
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,30 @@
|
|||||||
using System;
|
using Bit.App.Abstractions;
|
||||||
|
using System;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
|
||||||
namespace Bit.App.Controls
|
namespace Bit.App.Controls
|
||||||
{
|
{
|
||||||
public class ExtendedContentPage : ContentPage
|
public class ExtendedContentPage : ContentPage
|
||||||
{
|
{
|
||||||
|
private ISyncService _syncService;
|
||||||
|
|
||||||
public ExtendedContentPage()
|
public ExtendedContentPage()
|
||||||
{
|
{
|
||||||
|
_syncService = Resolver.Resolve<ISyncService>();
|
||||||
|
|
||||||
BackgroundColor = Color.FromHex("efeff4");
|
BackgroundColor = Color.FromHex("efeff4");
|
||||||
|
IsBusy = _syncService.SyncInProgress;
|
||||||
|
|
||||||
|
MessagingCenter.Subscribe<Application, bool>(Application.Current, "SyncCompleted", (sender, success) =>
|
||||||
|
{
|
||||||
|
IsBusy = _syncService.SyncInProgress;
|
||||||
|
});
|
||||||
|
|
||||||
|
MessagingCenter.Subscribe<Application>(Application.Current, "SyncStarted", (sender) =>
|
||||||
|
{
|
||||||
|
IsBusy = _syncService.SyncInProgress;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ using PushNotification.Plugin.Abstractions;
|
|||||||
using Plugin.Settings.Abstractions;
|
using Plugin.Settings.Abstractions;
|
||||||
using Plugin.Connectivity.Abstractions;
|
using Plugin.Connectivity.Abstractions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Bit.App.Models;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@ -25,6 +24,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IUserDialogs _userDialogs;
|
private readonly IUserDialogs _userDialogs;
|
||||||
private readonly IConnectivity _connectivity;
|
private readonly IConnectivity _connectivity;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
private readonly IPushNotification _pushNotification;
|
private readonly IPushNotification _pushNotification;
|
||||||
private readonly ISettings _settings;
|
private readonly ISettings _settings;
|
||||||
private readonly bool _favorites;
|
private readonly bool _favorites;
|
||||||
@ -37,6 +37,7 @@ namespace Bit.App.Pages
|
|||||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||||
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
||||||
_clipboardService = Resolver.Resolve<IClipboardService>();
|
_clipboardService = Resolver.Resolve<IClipboardService>();
|
||||||
|
_syncService = Resolver.Resolve<ISyncService>();
|
||||||
_pushNotification = Resolver.Resolve<IPushNotification>();
|
_pushNotification = Resolver.Resolve<IPushNotification>();
|
||||||
_settings = Resolver.Resolve<ISettings>();
|
_settings = Resolver.Resolve<ISettings>();
|
||||||
|
|
||||||
@ -52,9 +53,12 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private void Init()
|
private void Init()
|
||||||
{
|
{
|
||||||
MessagingCenter.Subscribe<Application>(Application.Current, "SyncCompleted", async (sender) =>
|
MessagingCenter.Subscribe<Application, bool>(Application.Current, "SyncCompleted", async (sender, success) =>
|
||||||
|
{
|
||||||
|
if(success)
|
||||||
{
|
{
|
||||||
await FetchAndLoadVaultAsync();
|
await FetchAndLoadVaultAsync();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!_favorites)
|
if(!_favorites)
|
||||||
@ -80,8 +84,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Search = new SearchBar
|
Search = new SearchBar
|
||||||
{
|
{
|
||||||
Placeholder = "Search vault...",
|
Placeholder = "Search vault",
|
||||||
BackgroundColor = Color.FromHex("efeff4")
|
BackgroundColor = Color.FromHex("E8E8ED")
|
||||||
};
|
};
|
||||||
Search.TextChanged += SearchBar_TextChanged;
|
Search.TextChanged += SearchBar_TextChanged;
|
||||||
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
|
Search.SearchButtonPressed += SearchBar_SearchButtonPressed;
|
||||||
@ -96,7 +100,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
|
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
FilterResults(((SearchBar)sender).Text);
|
FilterResultsBackground(((SearchBar)sender).Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
|
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
@ -108,19 +112,28 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterResults(e.NewTextValue);
|
FilterResultsBackground(e.NewTextValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FilterResults(string searchFilter)
|
private void FilterResultsBackground(string searchFilter)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
if(!string.IsNullOrWhiteSpace(searchFilter))
|
||||||
{
|
{
|
||||||
await Task.Delay(300);
|
await Task.Delay(300);
|
||||||
if(searchFilter != Search.Text)
|
if(searchFilter != Search.Text)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterResults(searchFilter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FilterResults(string searchFilter)
|
||||||
|
{
|
||||||
if(string.IsNullOrWhiteSpace(searchFilter))
|
if(string.IsNullOrWhiteSpace(searchFilter))
|
||||||
{
|
{
|
||||||
LoadFolders(Sites);
|
LoadFolders(Sites);
|
||||||
@ -131,7 +144,6 @@ namespace Bit.App.Pages
|
|||||||
var filteredSites = Sites.Where(s => s.Name.ToLower().Contains(searchFilter) || s.Username.ToLower().Contains(searchFilter));
|
var filteredSites = Sites.Where(s => s.Name.ToLower().Contains(searchFilter) || s.Username.ToLower().Contains(searchFilter));
|
||||||
LoadFolders(filteredSites);
|
LoadFolders(filteredSites);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async override void OnAppearing()
|
protected async override void OnAppearing()
|
||||||
@ -161,6 +173,11 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task FetchAndLoadVaultAsync()
|
private async Task FetchAndLoadVaultAsync()
|
||||||
{
|
{
|
||||||
|
if(PresentationFolders.Count > 0 && _syncService.SyncInProgress)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await Task.Run(async () =>
|
await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var foldersTask = _folderService.GetAllAsync();
|
var foldersTask = _folderService.GetAllAsync();
|
||||||
@ -173,7 +190,7 @@ namespace Bit.App.Pages
|
|||||||
Folders = folders.Select(f => new VaultListPageModel.Folder(f));
|
Folders = folders.Select(f => new VaultListPageModel.Folder(f));
|
||||||
Sites = sites.Select(s => new VaultListPageModel.Site(s));
|
Sites = sites.Select(s => new VaultListPageModel.Site(s));
|
||||||
|
|
||||||
LoadFolders(Sites);
|
FilterResults(Search.Text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ namespace Bit.App.Services
|
|||||||
public class SyncService : ISyncService
|
public class SyncService : ISyncService
|
||||||
{
|
{
|
||||||
private const string LastSyncKey = "lastSync";
|
private const string LastSyncKey = "lastSync";
|
||||||
|
private int _syncsInProgress = 0;
|
||||||
|
|
||||||
private readonly ICipherApiRepository _cipherApiRepository;
|
private readonly ICipherApiRepository _cipherApiRepository;
|
||||||
private readonly IFolderApiRepository _folderApiRepository;
|
private readonly IFolderApiRepository _folderApiRepository;
|
||||||
@ -40,6 +41,8 @@ namespace Bit.App.Services
|
|||||||
_settings = settings;
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SyncInProgress => _syncsInProgress > 0;
|
||||||
|
|
||||||
public async Task<bool> SyncAsync(string id)
|
public async Task<bool> SyncAsync(string id)
|
||||||
{
|
{
|
||||||
if(!_authService.IsAuthenticated)
|
if(!_authService.IsAuthenticated)
|
||||||
@ -47,9 +50,12 @@ namespace Bit.App.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncStarted();
|
||||||
|
|
||||||
var cipher = await _cipherApiRepository.GetByIdAsync(id);
|
var cipher = await _cipherApiRepository.GetByIdAsync(id);
|
||||||
if(!cipher.Succeeded)
|
if(!cipher.Succeeded)
|
||||||
{
|
{
|
||||||
|
SyncCompleted(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,10 +86,11 @@ namespace Bit.App.Services
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
SyncCompleted(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BroadcastSyncCompleted();
|
SyncCompleted(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +101,10 @@ namespace Bit.App.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncStarted();
|
||||||
|
|
||||||
await _folderRepository.DeleteAsync(id);
|
await _folderRepository.DeleteAsync(id);
|
||||||
BroadcastSyncCompleted();
|
SyncCompleted(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,8 +115,10 @@ namespace Bit.App.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncStarted();
|
||||||
|
|
||||||
await _siteRepository.DeleteAsync(id);
|
await _siteRepository.DeleteAsync(id);
|
||||||
BroadcastSyncCompleted();
|
SyncCompleted(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,10 +129,13 @@ namespace Bit.App.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncStarted();
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var ciphers = await _cipherApiRepository.GetAsync();
|
var ciphers = await _cipherApiRepository.GetAsync();
|
||||||
if(!ciphers.Succeeded)
|
if(!ciphers.Succeeded)
|
||||||
{
|
{
|
||||||
|
SyncCompleted(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,11 +145,12 @@ namespace Bit.App.Services
|
|||||||
|
|
||||||
if(folderTask.Exception != null || siteTask.Exception != null)
|
if(folderTask.Exception != null || siteTask.Exception != null)
|
||||||
{
|
{
|
||||||
|
SyncCompleted(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_settings.AddOrUpdateValue(LastSyncKey, now);
|
_settings.AddOrUpdateValue(LastSyncKey, now);
|
||||||
BroadcastSyncCompleted();
|
SyncCompleted(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,9 +168,12 @@ namespace Bit.App.Services
|
|||||||
return await FullSyncAsync();
|
return await FullSyncAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncStarted();
|
||||||
|
|
||||||
var ciphers = await _cipherApiRepository.GetByRevisionDateWithHistoryAsync(lastSync.Value);
|
var ciphers = await _cipherApiRepository.GetByRevisionDateWithHistoryAsync(lastSync.Value);
|
||||||
if(!ciphers.Succeeded)
|
if(!ciphers.Succeeded)
|
||||||
{
|
{
|
||||||
|
SyncCompleted(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,11 +184,12 @@ namespace Bit.App.Services
|
|||||||
await Task.WhenAll(siteTask, folderTask, deleteTask);
|
await Task.WhenAll(siteTask, folderTask, deleteTask);
|
||||||
if(folderTask.Exception != null || siteTask.Exception != null || deleteTask.Exception != null)
|
if(folderTask.Exception != null || siteTask.Exception != null || deleteTask.Exception != null)
|
||||||
{
|
{
|
||||||
|
SyncCompleted(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_settings.AddOrUpdateValue(LastSyncKey, now);
|
_settings.AddOrUpdateValue(LastSyncKey, now);
|
||||||
BroadcastSyncCompleted();
|
SyncCompleted(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,9 +264,16 @@ namespace Bit.App.Services
|
|||||||
await Task.WhenAll(tasks);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,13 @@ namespace Bit.iOS
|
|||||||
Resolver.Resolve<IFingerprint>(),
|
Resolver.Resolve<IFingerprint>(),
|
||||||
Resolver.Resolve<ISettings>()));
|
Resolver.Resolve<ISettings>()));
|
||||||
|
|
||||||
|
// 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.ShadowImage = new UIImage();
|
||||||
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
||||||
|
UIBarButtonItem.AppearanceWhenContainedIn(new Type[] { typeof(UISearchBar) }).TintColor = primaryColor;
|
||||||
|
|
||||||
MessagingCenter.Subscribe<Xamarin.Forms.Application, ToolsExtensionPage>(Xamarin.Forms.Application.Current, "ShowAppExtension", (sender, page) =>
|
MessagingCenter.Subscribe<Xamarin.Forms.Application, ToolsExtensionPage>(Xamarin.Forms.Application.Current, "ShowAppExtension", (sender, page) =>
|
||||||
{
|
{
|
||||||
|
33
src/iOS/Controls/CustomSearchBarRenderer.cs
Normal file
33
src/iOS/Controls/CustomSearchBarRenderer.cs
Normal file
@ -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<SearchBar> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -104,6 +104,7 @@
|
|||||||
<Compile Include="Controls\CustomButtonRenderer.cs" />
|
<Compile Include="Controls\CustomButtonRenderer.cs" />
|
||||||
<Compile Include="Controls\CustomLabelRenderer.cs" />
|
<Compile Include="Controls\CustomLabelRenderer.cs" />
|
||||||
<Compile Include="Controls\ExtendedEditorRenderer.cs" />
|
<Compile Include="Controls\ExtendedEditorRenderer.cs" />
|
||||||
|
<Compile Include="Controls\CustomSearchBarRenderer.cs" />
|
||||||
<Compile Include="Controls\ExtendedSwitchCellRenderer.cs" />
|
<Compile Include="Controls\ExtendedSwitchCellRenderer.cs" />
|
||||||
<Compile Include="Controls\ListViewRenderer.cs" />
|
<Compile Include="Controls\ListViewRenderer.cs" />
|
||||||
<Compile Include="Controls\ExtendedViewCellRenderer.cs" />
|
<Compile Include="Controls\ExtendedViewCellRenderer.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user