[PM-3340] Update timeout action for users without master password. (#2818)

* [PM-3340] Update timeout action for users without master password.

* [PM-3340] PR fixes and refactor

* [PM-3340] Raise command can execute.

* [PM-3340] Fix converter name

* [PM-3340] Fix variable naming

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
This commit is contained in:
André Bispo 2023-11-06 15:28:54 +00:00 committed by GitHub
parent d0ce89fedb
commit 7a65bf7fd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 10 deletions

View file

@ -1,14 +1,16 @@
using Xamarin.Forms; using System.Runtime.CompilerServices;
using Bit.App.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
public class BaseSettingItemView : ContentView public class BaseSettingItemView : ContentView
{ {
public static readonly BindableProperty TitleProperty = BindableProperty.Create( public static readonly BindableProperty TitleProperty = BindableProperty.Create(
nameof(Title), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay); nameof(Title), typeof(string), typeof(SwitchItemView), null);
public static readonly BindableProperty SubtitleProperty = BindableProperty.Create( public static readonly BindableProperty SubtitleProperty = BindableProperty.Create(
nameof(Subtitle), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay); nameof(Subtitle), typeof(string), typeof(SwitchItemView), null);
public string Title public string Title
{ {

View file

@ -6,7 +6,7 @@ namespace Bit.App.Controls
public partial class SettingChooserItemView : BaseSettingItemView public partial class SettingChooserItemView : BaseSettingItemView
{ {
public static readonly BindableProperty DisplayValueProperty = BindableProperty.Create( public static readonly BindableProperty DisplayValueProperty = BindableProperty.Create(
nameof(DisplayValue), typeof(string), typeof(SettingChooserItemView), null, BindingMode.OneWay); nameof(DisplayValue), typeof(string), typeof(SettingChooserItemView), null);
public static readonly BindableProperty ChooseCommandProperty = BindableProperty.Create( public static readonly BindableProperty ChooseCommandProperty = BindableProperty.Create(
nameof(ChooseCommand), typeof(ICommand), typeof(ExternalLinkItemView)); nameof(ChooseCommand), typeof(ICommand), typeof(ExternalLinkItemView));

View file

@ -110,8 +110,10 @@
<controls:SettingChooserItemView <controls:SettingChooserItemView
Title="{u:I18n SessionTimeoutAction}" Title="{u:I18n SessionTimeoutAction}"
Subtitle="{Binding SetUpUnlockMethodLabel}"
DisplayValue="{Binding VaultTimeoutActionPickerViewModel.SelectedValue}" DisplayValue="{Binding VaultTimeoutActionPickerViewModel.SelectedValue}"
ChooseCommand="{Binding VaultTimeoutActionPickerViewModel.SelectOptionCommand}" ChooseCommand="{Binding VaultTimeoutActionPickerViewModel.SelectOptionCommand}"
IsEnabled="{Binding IsVaultTimeoutActionLockAllowed}"
AutomationId="VaultTimeoutActionChooser" AutomationId="VaultTimeoutActionChooser"
StyleClass="settings-item-view" StyleClass="settings-item-view"
HorizontalOptions="FillAndExpand"/> HorizontalOptions="FillAndExpand"/>
@ -148,6 +150,7 @@
<controls:CustomLabel <controls:CustomLabel
Text="{u:I18n LockNow}" Text="{u:I18n LockNow}"
IsVisible="{Binding IsVaultTimeoutActionLockAllowed}"
StyleClass="settings-navigatable-label" StyleClass="settings-navigatable-label"
AutomationId="LockNowLabel"> AutomationId="LockNowLabel">
<Label.GestureRecognizers> <Label.GestureRecognizers>

View file

@ -76,7 +76,7 @@ namespace Bit.App.Pages
_logger, _logger,
OnVaultTimeoutActionChangingAsync, OnVaultTimeoutActionChangingAsync,
AppResources.SessionTimeoutAction, AppResources.SessionTimeoutAction,
_ => _inited && !HasVaultTimeoutActionPolicy, _ => _inited && !HasVaultTimeoutActionPolicy && IsVaultTimeoutActionLockAllowed,
ex => HandleException(ex)); ex => HandleException(ex));
ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncCommnad(ToggleUseThisDeviceToApproveLoginRequestsAsync, _ => _inited); ToggleUseThisDeviceToApproveLoginRequestsCommand = CreateDefaultAsyncCommnad(ToggleUseThisDeviceToApproveLoginRequestsAsync, _ => _inited);
@ -129,6 +129,7 @@ namespace Bit.App.Pages
get => _canUnlockWithBiometrics; get => _canUnlockWithBiometrics;
set set
{ {
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
if (SetProperty(ref _canUnlockWithBiometrics, value)) if (SetProperty(ref _canUnlockWithBiometrics, value))
{ {
((ICommand)ToggleCanUnlockWithBiometricsCommand).Execute(null); ((ICommand)ToggleCanUnlockWithBiometricsCommand).Execute(null);
@ -141,6 +142,7 @@ namespace Bit.App.Pages
get => _canUnlockWithPin; get => _canUnlockWithPin;
set set
{ {
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
if (SetProperty(ref _canUnlockWithPin, value)) if (SetProperty(ref _canUnlockWithPin, value))
{ {
((ICommand)ToggleCanUnlockWithPinCommand).Execute(null); ((ICommand)ToggleCanUnlockWithPinCommand).Execute(null);
@ -148,6 +150,10 @@ namespace Bit.App.Pages
} }
} }
public bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _canUnlockWithBiometrics || _canUnlockWithPin;
public string SetUpUnlockMethodLabel => IsVaultTimeoutActionLockAllowed ? null : AppResources.SetUpAnUnlockOptionToChangeYourVaultTimeoutAction;
public TimeSpan? CustomVaultTimeoutTime public TimeSpan? CustomVaultTimeoutTime
{ {
get => _customVaultTimeoutTime; get => _customVaultTimeoutTime;
@ -164,6 +170,7 @@ namespace Bit.App.Pages
MainThread.BeginInvokeOnMainThread(() => SetProperty(ref _customVaultTimeoutTime, oldValue)); MainThread.BeginInvokeOnMainThread(() => SetProperty(ref _customVaultTimeoutTime, oldValue));
}); });
} }
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
} }
} }
@ -203,8 +210,6 @@ namespace Bit.App.Pages
public bool ShowChangeMasterPassword { get; private set; } public bool ShowChangeMasterPassword { get; private set; }
private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _canUnlockWithBiometrics || _canUnlockWithPin;
private int? CurrentVaultTimeout => GetRawVaultTimeoutFrom(VaultTimeoutPickerViewModel.SelectedKey); private int? CurrentVaultTimeout => GetRawVaultTimeoutFrom(VaultTimeoutPickerViewModel.SelectedKey);
private bool IncludeLinksWithSubscriptionInfo => Device.RuntimePlatform != Device.iOS; private bool IncludeLinksWithSubscriptionInfo => Device.RuntimePlatform != Device.iOS;
@ -253,6 +258,7 @@ namespace Bit.App.Pages
TriggerPropertyChanged(nameof(VaultTimeoutPolicyDescription)); TriggerPropertyChanged(nameof(VaultTimeoutPolicyDescription));
TriggerPropertyChanged(nameof(ShowChangeMasterPassword)); TriggerPropertyChanged(nameof(ShowChangeMasterPassword));
TriggerUpdateCustomVaultTimeoutPicker(); TriggerUpdateCustomVaultTimeoutPicker();
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
ToggleUseThisDeviceToApproveLoginRequestsCommand.RaiseCanExecuteChanged(); ToggleUseThisDeviceToApproveLoginRequestsCommand.RaiseCanExecuteChanged();
ToggleCanUnlockWithBiometricsCommand.RaiseCanExecuteChanged(); ToggleCanUnlockWithBiometricsCommand.RaiseCanExecuteChanged();
ToggleCanUnlockWithPinCommand.RaiseCanExecuteChanged(); ToggleCanUnlockWithPinCommand.RaiseCanExecuteChanged();
@ -305,6 +311,7 @@ namespace Bit.App.Pages
{ {
_customVaultTimeoutTime = TimeSpan.FromMinutes(vaultTimeout); _customVaultTimeoutTime = TimeSpan.FromMinutes(vaultTimeout);
} }
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
} }
private async Task InitVaultTimeoutActionPickerAsync() private async Task InitVaultTimeoutActionPickerAsync()
@ -324,6 +331,7 @@ namespace Bit.App.Pages
} }
VaultTimeoutActionPickerViewModel.Init(options, timeoutAction, IsVaultTimeoutActionLockAllowed ? VaultTimeoutAction.Lock : VaultTimeoutAction.Logout); VaultTimeoutActionPickerViewModel.Init(options, timeoutAction, IsVaultTimeoutActionLockAllowed ? VaultTimeoutAction.Lock : VaultTimeoutAction.Logout);
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
} }
private async Task ToggleUseThisDeviceToApproveLoginRequestsAsync() private async Task ToggleUseThisDeviceToApproveLoginRequestsAsync()
@ -360,6 +368,7 @@ namespace Bit.App.Pages
{ {
if (!_canUnlockWithBiometrics) if (!_canUnlockWithBiometrics)
{ {
MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics)));
await UpdateVaultTimeoutActionIfNeededAsync(); await UpdateVaultTimeoutActionIfNeededAsync();
await _biometricsService.SetCanUnlockWithBiometricsAsync(CanUnlockWithBiometrics); await _biometricsService.SetCanUnlockWithBiometricsAsync(CanUnlockWithBiometrics);
return; return;
@ -375,11 +384,12 @@ namespace Bit.App.Pages
} }
await _biometricsService.SetCanUnlockWithBiometricsAsync(CanUnlockWithBiometrics); await _biometricsService.SetCanUnlockWithBiometricsAsync(CanUnlockWithBiometrics);
await InitVaultTimeoutActionPickerAsync();
} }
public async Task ToggleCanUnlockWithPinAsync() public async Task ToggleCanUnlockWithPinAsync()
{ {
if (!CanUnlockWithPin) if (!_canUnlockWithPin)
{ {
await _vaultTimeoutService.ClearAsync(); await _vaultTimeoutService.ClearAsync();
await UpdateVaultTimeoutActionIfNeededAsync(); await UpdateVaultTimeoutActionIfNeededAsync();
@ -403,10 +413,12 @@ namespace Bit.App.Pages
AppResources.No); AppResources.No);
await _userPinService.SetupPinAsync(newPin, requireMasterPasswordOnRestart); await _userPinService.SetupPinAsync(newPin, requireMasterPasswordOnRestart);
await InitVaultTimeoutActionPickerAsync();
} }
private async Task UpdateVaultTimeoutActionIfNeededAsync() private async Task UpdateVaultTimeoutActionIfNeededAsync()
{ {
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
if (IsVaultTimeoutActionLockAllowed) if (IsVaultTimeoutActionLockAllowed)
{ {
return; return;
@ -467,6 +479,16 @@ namespace Bit.App.Pages
TriggerPropertyChanged(nameof(CustomVaultTimeoutTime)); TriggerPropertyChanged(nameof(CustomVaultTimeoutTime));
} }
private void TriggerVaultTimeoutActionLockAllowedPropertyChanged()
{
MainThread.BeginInvokeOnMainThread(() =>
{
TriggerPropertyChanged(nameof(IsVaultTimeoutActionLockAllowed));
TriggerPropertyChanged(nameof(SetUpUnlockMethodLabel));
VaultTimeoutActionPickerViewModel.SelectOptionCommand.RaiseCanExecuteChanged();
});
}
private int? GetRawVaultTimeoutFrom(int vaultTimeoutPickerKey) private int? GetRawVaultTimeoutFrom(int vaultTimeoutPickerKey)
{ {
if (vaultTimeoutPickerKey == NEVER_SESSION_TIMEOUT_VALUE) if (vaultTimeoutPickerKey == NEVER_SESSION_TIMEOUT_VALUE)
@ -501,7 +523,7 @@ namespace Bit.App.Pages
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(CurrentVaultTimeout, timeoutActionKey); await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(CurrentVaultTimeout, timeoutActionKey);
_messagingService.Send(AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND); _messagingService.Send(AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND);
TriggerVaultTimeoutActionLockAllowedPropertyChanged();
return true; return true;
} }

View file

@ -6227,6 +6227,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Set up an unlock option to change your vault timeout action..
/// </summary>
public static string SetUpAnUnlockOptionToChangeYourVaultTimeoutAction {
get {
return ResourceManager.GetString("SetUpAnUnlockOptionToChangeYourVaultTimeoutAction", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Set up TOTP. /// Looks up a localized string similar to Set up TOTP.
/// </summary> /// </summary>

View file

@ -2862,4 +2862,7 @@ Do you want to switch to this account?</value>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve"> <data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value> <value>Account logged out.</value>
</data> </data>
<data name="SetUpAnUnlockOptionToChangeYourVaultTimeoutAction" xml:space="preserve">
<value>Set up an unlock option to change your vault timeout action.</value>
</data>
</root> </root>

View file

@ -6,6 +6,7 @@
x:Class="Bit.App.Styles.ControlTemplates"> x:Class="Bit.App.Styles.ControlTemplates">
<u:StringHasValueConverter x:Key="stringHasValueConverter" /> <u:StringHasValueConverter x:Key="stringHasValueConverter" />
<u:BoolEnablementToTextColorConverter x:Key="boolEnablementToTextColorConverter" />
<ControlTemplate x:Key="SettingControlTemplate"> <ControlTemplate x:Key="SettingControlTemplate">
<Grid <Grid
@ -17,6 +18,7 @@
Grid.Column="0" Grid.Column="0"
MaxLines="2" MaxLines="2"
Text="{TemplateBinding Title}" Text="{TemplateBinding Title}"
TextColor="{TemplateBinding IsEnabled, Converter={StaticResource boolEnablementToTextColorConverter}}"
HorizontalOptions="StartAndExpand" HorizontalOptions="StartAndExpand"
LineBreakMode="TailTruncation" /> LineBreakMode="TailTruncation" />

View file

@ -0,0 +1,26 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Utilities
{
public class BoolEnablementToTextColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType == typeof(Color) && value is bool valueBool)
{
return valueBool ? ThemeManager.GetResourceColor("TextColor") :
ThemeManager.GetResourceColor("MutedColor");
}
throw new InvalidOperationException("The value must be a boolean with a Color target.");
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
}