diff --git a/src/App/Pages/Vault/CipherAddEditPage.xaml.cs b/src/App/Pages/Vault/CipherAddEditPage.xaml.cs
index 60e6b667d..59a246529 100644
--- a/src/App/Pages/Vault/CipherAddEditPage.xaml.cs
+++ b/src/App/Pages/Vault/CipherAddEditPage.xaml.cs
@@ -263,12 +263,6 @@ namespace Bit.App.Pages
{
if (DoOnce())
{
- var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
- if (cameraPermission != PermissionStatus.Granted)
- {
- return;
- }
-
var page = new ScanPage(key =>
{
Device.BeginInvokeOnMainThread(async () =>
@@ -277,6 +271,7 @@ namespace Bit.App.Pages
await _vm.UpdateTotpKeyAsync(key);
});
});
+
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
}
diff --git a/src/App/Pages/Vault/ScanPage.xaml b/src/App/Pages/Vault/ScanPage.xaml
index 9c1b31346..c1ac0e7b7 100644
--- a/src/App/Pages/Vault/ScanPage.xaml
+++ b/src/App/Pages/Vault/ScanPage.xaml
@@ -34,16 +34,13 @@
-
+ Grid.RowSpan="3"/>
_logger = new LazyResolve("logger");
public ScanPage(Action callback)
{
- _callback = callback;
InitializeComponent();
- _zxing.Options = new ZXing.Mobile.MobileBarcodeScanningOptions
- {
- UseNativeScanning = true,
- PossibleFormats = new List { ZXing.BarcodeFormat.QR_CODE },
- AutoRotate = false,
- TryInverted = true
- };
+ _callback = callback;
+ ViewModel.InitScannerCommand = new Command(() => InitScanner());
+
if (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
@@ -55,6 +52,53 @@ namespace Bit.App.Pages
protected override void 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.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;
// 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();
}
- protected override async void OnDisappearing()
+ private async Task StopScanner()
{
+ if (_zxing == null)
+ {
+ return;
+ }
+
_autofocusCts?.Cancel();
if (_continuousAutofocusTask != null)
{
await _continuousAutofocusTask;
}
_zxing.IsScanning = false;
+ _zxing.OnScanResult -= OnScanResult;
_pageIsActive = false;
- base.OnDisappearing();
}
private async void OnScanResult(ZXing.Result result)
diff --git a/src/App/Pages/Vault/ScanPageViewModel.cs b/src/App/Pages/Vault/ScanPageViewModel.cs
index 00405756b..74b2d1a4c 100644
--- a/src/App/Pages/Vault/ScanPageViewModel.cs
+++ b/src/App/Pages/Vault/ScanPageViewModel.cs
@@ -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.Core.Abstractions;
+using Bit.Core.Services;
+using Bit.Core.Utilities;
+using Xamarin.CommunityToolkit.ObjectModel;
+using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -9,13 +17,46 @@ namespace Bit.App.Pages
{
private bool _showScanner = true;
private string _totpAuthenticationKey;
+ private IPlatformUtilsService _platformUtilsService;
+ private IDeviceActionService _deviceActionService;
+ private ILogger _logger;
public ScanPageViewModel()
{
- ToggleScanModeCommand = new Command(() => ShowScanner = !ShowScanner);
+ ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException);
+ _platformUtilsService = ServiceContainer.Resolve("platformUtilsService");
+ _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _logger = ServiceContainer.Resolve();
+ 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 CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered;
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
{
get
@@ -57,5 +115,15 @@ namespace Bit.App.Pages
return fs;
}
}
+
+ private void HandleException(Exception ex)
+ {
+ Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
+ {
+ await _deviceActionService.HideLoadingAsync();
+ await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
+ }).FireAndForget();
+ _logger.Exception(ex);
+ }
}
}
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index ff83de8a7..ca0e23b08 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -2128,6 +2128,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Enable camera permission to use the scanner.
+ ///
+ public static string EnableCamerPermissionToUseTheScanner {
+ get {
+ return ResourceManager.GetString("EnableCamerPermissionToUseTheScanner", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Enabled.
///
@@ -3544,7 +3553,7 @@ namespace Bit.App.Resources {
}
///
- /// Looks up a localized string similar to Enterprise Single Sign-On.
+ /// Looks up a localized string similar to Enterprise single sign-on.
///
public static string LogInSso {
get {
@@ -3589,7 +3598,7 @@ namespace Bit.App.Resources {
}
///
- /// Looks up a localized string similar to Log In with master password.
+ /// Looks up a localized string similar to Log in with master password.
///
public static string LogInWithMasterPassword {
get {
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index 3a651bb62..82ed75f6b 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -2512,4 +2512,7 @@ Do you want to switch to this account?
This request is no longer valid
+
+ Enable camera permission to use the scanner
+