mirror of
https://github.com/bitwarden/mobile
synced 2025-02-02 11:37:14 +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
|
||||
{
|
||||
bool SyncInProgress { get; }
|
||||
Task<bool> SyncAsync(string id);
|
||||
Task<bool> SyncDeleteFolderAsync(string id);
|
||||
Task<bool> SyncDeleteSiteAsync(string id);
|
||||
|
@ -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 }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ISyncService>();
|
||||
|
||||
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.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<IConnectivity>();
|
||||
_userDialogs = Resolver.Resolve<IUserDialogs>();
|
||||
_clipboardService = Resolver.Resolve<IClipboardService>();
|
||||
_syncService = Resolver.Resolve<ISyncService>();
|
||||
_pushNotification = Resolver.Resolve<IPushNotification>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
|
||||
@ -52,9 +53,12 @@ namespace Bit.App.Pages
|
||||
|
||||
private void Init()
|
||||
{
|
||||
MessagingCenter.Subscribe<Application>(Application.Current, "SyncCompleted", async (sender) =>
|
||||
MessagingCenter.Subscribe<Application, bool>(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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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<bool> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +50,13 @@ namespace Bit.iOS
|
||||
Resolver.Resolve<IFingerprint>(),
|
||||
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.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) =>
|
||||
{
|
||||
|
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\CustomLabelRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedEditorRenderer.cs" />
|
||||
<Compile Include="Controls\CustomSearchBarRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedSwitchCellRenderer.cs" />
|
||||
<Compile Include="Controls\ListViewRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedViewCellRenderer.cs" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user