account switching in autofill UI (Android) (#1882)

This commit is contained in:
mp-bw 2022-04-19 20:38:17 -04:00 committed by GitHub
parent 14d4b2ee29
commit cfbbea59e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 10 deletions

View file

@ -208,7 +208,10 @@ namespace Bit.App
{ {
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
} }
SetTabsPageFromAutofill(isLocked); if (!SetTabsPageFromAutofill(isLocked))
{
ClearAutofillUri();
}
await SleptAsync(); await SleptAsync();
} }
} }
@ -365,7 +368,15 @@ namespace Bit.App
} }
} }
private void SetTabsPageFromAutofill(bool isLocked) private void ClearAutofillUri()
{
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
{
Options.Uri = null;
}
}
private bool SetTabsPageFromAutofill(bool isLocked)
{ {
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) && if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
!Options.FromAutofillFramework) !Options.FromAutofillFramework)
@ -385,7 +396,9 @@ namespace Bit.App
} }
}); });
}); });
return true;
} }
return false;
} }
private void Prime() private void Prime()

View file

@ -15,7 +15,18 @@
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<ToolbarItem Icon="search.png" Clicked="Search_Clicked" /> <controls:ExtendedToolbarItem
x:Name="_accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" />
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<ContentPage.Resources> <ContentPage.Resources>
@ -58,7 +69,7 @@
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Padding="20, 0" Padding="20, 0"
Spacing="20" Spacing="20"
IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}"> IsVisible="{Binding ShowNoData}">
<Label <Label
Text="{Binding NoDataText}" Text="{Binding NoDataText}"
HorizontalTextAlignment="Center"></Label> HorizontalTextAlignment="Center"></Label>
@ -88,6 +99,8 @@
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"> AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView> </ContentView>
<!-- Android FAB -->
<Button <Button
x:Name="_fab" x:Name="_fab"
Image="plus.png" Image="plus.png"
@ -99,6 +112,14 @@
<effects:FabShadowEffect /> <effects:FabShadowEffect />
</Button.Effects> </Button.Effects>
</Button> </Button>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
MainPage="{Binding Source={x:Reference _page}}"
MainFab="{Binding Source={x:Reference _fab}, Path=.}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout> </AbsoluteLayout>
</pages:BaseContentPage> </pages:BaseContentPage>

View file

@ -1,5 +1,4 @@
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -15,7 +14,8 @@ namespace Bit.App.Pages
public partial class AutofillCiphersPage : BaseContentPage public partial class AutofillCiphersPage : BaseContentPage
{ {
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private AutofillCiphersPageViewModel _vm; private AutofillCiphersPageViewModel _vm;
@ -24,17 +24,23 @@ namespace Bit.App.Pages
{ {
_appOptions = appOptions; _appOptions = appOptions;
InitializeComponent(); InitializeComponent();
SetActivityIndicator(_mainContent);
_vm = BindingContext as AutofillCiphersPageViewModel; _vm = BindingContext as AutofillCiphersPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.Init(appOptions); _vm.Init(appOptions);
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
} }
protected async override void OnAppearing() protected async override void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
if (_syncService.SyncInProgress)
{
IsBusy = true;
}
if (!await AppHelpers.IsVaultTimeoutImmediateAsync()) if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
{ {
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
@ -43,6 +49,30 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
});
await LoadOnAppearedAsync(_mainLayout, false, async () => await LoadOnAppearedAsync(_mainLayout, false, async () =>
{ {
try try
@ -59,6 +89,11 @@ namespace Bit.App.Pages
protected override bool OnBackButtonPressed() protected override bool OnBackButtonPressed()
{ {
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
_appOptions.Uri = null; _appOptions.Uri = null;
@ -66,6 +101,13 @@ namespace Bit.App.Pages
return base.OnBackButtonPressed(); return base.OnBackButtonPressed();
} }
protected override void OnDisappearing()
{
base.OnDisappearing();
IsBusy = false;
_accountAvatar?.OnDisappearing();
}
private async void RowSelected(object sender, SelectionChangedEventArgs e) private async void RowSelected(object sender, SelectionChangedEventArgs e)
{ {
((ExtendedCollectionView)sender).SelectedItem = null; ((ExtendedCollectionView)sender).SelectedItem = null;

View file

@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Bit.App.Controls;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@ -23,8 +24,10 @@ namespace Bit.App.Pages
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService; private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IMessagingService _messagingService;
private readonly ILogger _logger;
private AppOptions _appOptions; private bool _showNoData;
private bool _showList; private bool _showList;
private string _noDataText; private string _noDataText;
private bool _websiteIconsEnabled; private bool _websiteIconsEnabled;
@ -36,15 +39,30 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"); _passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>(); GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync); CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = false
};
} }
public string Name { get; set; } public string Name { get; set; }
public string Uri { get; set; } public string Uri { get; set; }
public Command CipherOptionsCommand { get; set; } public Command CipherOptionsCommand { get; set; }
public bool LoadedOnce { get; set; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; } public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public bool ShowNoData
{
get => _showNoData;
set => SetProperty(ref _showNoData, value);
}
public bool ShowList public bool ShowList
{ {
@ -65,7 +83,6 @@ namespace Bit.App.Pages
public void Init(AppOptions appOptions) public void Init(AppOptions appOptions)
{ {
_appOptions = appOptions;
Uri = appOptions?.Uri; Uri = appOptions?.Uri;
string name = null; string name = null;
if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false) if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false)
@ -87,8 +104,10 @@ namespace Bit.App.Pages
public async Task LoadAsync() public async Task LoadAsync()
{ {
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); LoadedOnce = true;
ShowList = false; ShowList = false;
ShowNoData = false;
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var groupedItems = new List<GroupingsPageListGroup>(); var groupedItems = new List<GroupingsPageListGroup>();
var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null); var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null);
var matching = ciphers.Item1?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList(); var matching = ciphers.Item1?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
@ -150,6 +169,7 @@ namespace Bit.App.Pages
} }
} }
ShowList = groupedItems.Any(); ShowList = groupedItems.Any();
ShowNoData = !ShowList;
} }
public async Task SelectCipherAsync(CipherView cipher, bool fuzzy) public async Task SelectCipherAsync(CipherView cipher, bool fuzzy)