mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 19:08:32 +03:00
[SG-601] Enhance experience when user denies camera access in Authenticator (#2205)
* [SG-601] Handle camera denied permissions * [SG-601] Code format * [SG-601] PR Fixes * [SG-601] Change resource text to singular * [SG-601] Remove horizontal and vertical options * [SG-601] Add start and stop scanner methods * [SG-601] Remove parameter from ScanPage * [SG-601] Move initialization to viewmodel * [SG-601] Fix zxing scanning bug * [SG-601] Move RunUIThread inside of method
This commit is contained in:
parent
eaa4f193ce
commit
2a60ff62d8
6 changed files with 149 additions and 28 deletions
|
@ -263,12 +263,6 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
|
|
||||||
if (cameraPermission != PermissionStatus.Granted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var page = new ScanPage(key =>
|
var page = new ScanPage(key =>
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
|
@ -277,6 +271,7 @@ namespace Bit.App.Pages
|
||||||
await _vm.UpdateTotpKeyAsync(key);
|
await _vm.UpdateTotpKeyAsync(key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
|
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,16 +34,13 @@
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<zxing:ZXingScannerView
|
<ContentView
|
||||||
x:Name="_zxing"
|
x:Name="_scannerContainer"
|
||||||
HorizontalOptions="FillAndExpand"
|
|
||||||
VerticalOptions="FillAndExpand"
|
|
||||||
AutomationId="zxingScannerView"
|
AutomationId="zxingScannerView"
|
||||||
IsVisible="{Binding ShowScanner}"
|
IsVisible="{Binding ShowScanner}"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.RowSpan="3"
|
Grid.RowSpan="3"/>
|
||||||
OnScanResult="OnScanResult"/>
|
|
||||||
<StackLayout
|
<StackLayout
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
|
|
|
@ -8,8 +8,10 @@ using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using SkiaSharp.Views.Forms;
|
using SkiaSharp.Views.Forms;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
using ZXing.Net.Mobile.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
|
@ -26,20 +28,15 @@ namespace Bit.App.Pages
|
||||||
private bool _pageIsActive;
|
private bool _pageIsActive;
|
||||||
private bool _qrcodeFound;
|
private bool _qrcodeFound;
|
||||||
private float _scale;
|
private float _scale;
|
||||||
|
private ZXingScannerView _zxing;
|
||||||
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
public ScanPage(Action<string> callback)
|
public ScanPage(Action<string> callback)
|
||||||
{
|
{
|
||||||
_callback = callback;
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_zxing.Options = new ZXing.Mobile.MobileBarcodeScanningOptions
|
_callback = callback;
|
||||||
{
|
ViewModel.InitScannerCommand = new Command(() => InitScanner());
|
||||||
UseNativeScanning = true,
|
|
||||||
PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE },
|
|
||||||
AutoRotate = false,
|
|
||||||
TryInverted = true
|
|
||||||
};
|
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
ToolbarItems.RemoveAt(0);
|
ToolbarItems.RemoveAt(0);
|
||||||
|
@ -55,6 +52,53 @@ namespace Bit.App.Pages
|
||||||
protected override void OnAppearing()
|
protected override void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
StartScanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
StopScanner().FireAndForget();
|
||||||
|
base.OnDisappearing();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix known bug with DelayBetweenAnalyzingFrames & DelayBetweenContinuousScans: https://github.com/Redth/ZXing.Net.Mobile/issues/721
|
||||||
|
private void InitScanner()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!ViewModel.HasCameraPermission || !ViewModel.ShowScanner || _zxing != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_zxing = new ZXingScannerView();
|
||||||
|
_zxing.Options = new ZXing.Mobile.MobileBarcodeScanningOptions
|
||||||
|
{
|
||||||
|
UseNativeScanning = true,
|
||||||
|
PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE },
|
||||||
|
AutoRotate = false,
|
||||||
|
TryInverted = true,
|
||||||
|
DelayBetweenAnalyzingFrames = 5,
|
||||||
|
DelayBetweenContinuousScans = 5
|
||||||
|
};
|
||||||
|
_scannerContainer.Content = _zxing;
|
||||||
|
StartScanner();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartScanner()
|
||||||
|
{
|
||||||
|
if (_zxing == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_zxing.OnScanResult -= OnScanResult;
|
||||||
|
_zxing.OnScanResult += OnScanResult;
|
||||||
_zxing.IsScanning = true;
|
_zxing.IsScanning = true;
|
||||||
|
|
||||||
// Fix for Autofocus, now it's done every 2 seconds so that the user does't have to do it
|
// Fix for Autofocus, now it's done every 2 seconds so that the user does't have to do it
|
||||||
|
@ -98,16 +142,21 @@ namespace Bit.App.Pages
|
||||||
AnimationLoopAsync();
|
AnimationLoopAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async void OnDisappearing()
|
private async Task StopScanner()
|
||||||
{
|
{
|
||||||
|
if (_zxing == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_autofocusCts?.Cancel();
|
_autofocusCts?.Cancel();
|
||||||
if (_continuousAutofocusTask != null)
|
if (_continuousAutofocusTask != null)
|
||||||
{
|
{
|
||||||
await _continuousAutofocusTask;
|
await _continuousAutofocusTask;
|
||||||
}
|
}
|
||||||
_zxing.IsScanning = false;
|
_zxing.IsScanning = false;
|
||||||
|
_zxing.OnScanResult -= OnScanResult;
|
||||||
_pageIsActive = false;
|
_pageIsActive = false;
|
||||||
base.OnDisappearing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnScanResult(ZXing.Result result)
|
private async void OnScanResult(ZXing.Result result)
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
using Bit.App.Resources;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
|
@ -9,13 +17,46 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
private bool _showScanner = true;
|
private bool _showScanner = true;
|
||||||
private string _totpAuthenticationKey;
|
private string _totpAuthenticationKey;
|
||||||
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
|
private IDeviceActionService _deviceActionService;
|
||||||
|
private ILogger _logger;
|
||||||
|
|
||||||
public ScanPageViewModel()
|
public ScanPageViewModel()
|
||||||
{
|
{
|
||||||
ToggleScanModeCommand = new Command(() => ShowScanner = !ShowScanner);
|
ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException);
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
InitAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command ToggleScanModeCommand { get; set; }
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Device.InvokeOnMainThreadAsync(async () =>
|
||||||
|
{
|
||||||
|
var hasCameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
|
||||||
|
HasCameraPermission = hasCameraPermission == PermissionStatus.Granted;
|
||||||
|
ShowScanner = hasCameraPermission == PermissionStatus.Granted;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!HasCameraPermission)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InitScannerCommand.Execute(null);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand ToggleScanModeCommand { get; set; }
|
||||||
|
public ICommand InitScannerCommand { get; set; }
|
||||||
|
|
||||||
|
public bool HasCameraPermission { get; set; }
|
||||||
public string ScanQrPageTitle => ShowScanner ? AppResources.ScanQrTitle : AppResources.AuthenticatorKeyScanner;
|
public string ScanQrPageTitle => ShowScanner ? AppResources.ScanQrTitle : AppResources.AuthenticatorKeyScanner;
|
||||||
public string CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered;
|
public string CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered;
|
||||||
public string TotpAuthenticationKey
|
public string TotpAuthenticationKey
|
||||||
|
@ -39,6 +80,23 @@ namespace Bit.App.Pages
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ToggleScanMode()
|
||||||
|
{
|
||||||
|
var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
|
||||||
|
HasCameraPermission = cameraPermission == PermissionStatus.Granted;
|
||||||
|
if (!HasCameraPermission)
|
||||||
|
{
|
||||||
|
var openAppSettingsResult = await _platformUtilsService.ShowDialogAsync(AppResources.EnableCamerPermissionToUseTheScanner, title: string.Empty, confirmText: AppResources.Settings, cancelText: AppResources.NoThanks);
|
||||||
|
if (openAppSettingsResult)
|
||||||
|
{
|
||||||
|
_deviceActionService.OpenAppSettings();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ShowScanner = !ShowScanner;
|
||||||
|
InitScannerCommand.Execute(null);
|
||||||
|
}
|
||||||
|
|
||||||
public FormattedString ToggleScanModeLabel
|
public FormattedString ToggleScanModeLabel
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -57,5 +115,15 @@ namespace Bit.App.Pages
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleException(Exception ex)
|
||||||
|
{
|
||||||
|
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
|
||||||
|
}).FireAndForget();
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
src/App/Resources/AppResources.Designer.cs
generated
13
src/App/Resources/AppResources.Designer.cs
generated
|
@ -2128,6 +2128,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Enable camera permission to use the scanner.
|
||||||
|
/// </summary>
|
||||||
|
public static string EnableCamerPermissionToUseTheScanner {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnableCamerPermissionToUseTheScanner", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Enabled.
|
/// Looks up a localized string similar to Enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3544,7 +3553,7 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Enterprise Single Sign-On.
|
/// Looks up a localized string similar to Enterprise single sign-on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string LogInSso {
|
public static string LogInSso {
|
||||||
get {
|
get {
|
||||||
|
@ -3589,7 +3598,7 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Log In with master password.
|
/// Looks up a localized string similar to Log in with master password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string LogInWithMasterPassword {
|
public static string LogInWithMasterPassword {
|
||||||
get {
|
get {
|
||||||
|
|
|
@ -2512,4 +2512,7 @@ Do you want to switch to this account?</value>
|
||||||
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
|
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
|
||||||
<value>This request is no longer valid</value>
|
<value>This request is no longer valid</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
|
||||||
|
<value>Enable camera permission to use the scanner</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
Loading…
Reference in a new issue