mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 18:38:27 +03:00
account switching in autofill UI (Android) (#1882)
This commit is contained in:
parent
14d4b2ee29
commit
cfbbea59e9
4 changed files with 106 additions and 10 deletions
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue