PM-3349 PRM-3350 Replaced XZing with Camera.MAUI for QRCodes

This commit is contained in:
Dinis Vieira 2023-10-23 15:17:14 +01:00
parent 62213c0aaf
commit f1d59210f9
No known key found for this signature in database
GPG key ID: 9389160FF6C295F3
7 changed files with 134 additions and 159 deletions

View file

@ -71,6 +71,7 @@
<AndroidNativeLibrary Include="Platforms\Android\lib\x86_64\libargon2.so" /> <AndroidNativeLibrary Include="Platforms\Android\lib\x86_64\libargon2.so" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Camera.MAUI" Version="1.4.4" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" /> <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
@ -88,9 +89,7 @@
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" /> <PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" /> <PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" /> <PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
<PackageReference Include="ZXing.Net.Maui" Version="0.3.0-preview.1" />
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" /> <PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
<PackageReference Include="ZXing.Net.Maui.Controls" Version="0.3.0-preview.1" />
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" /> <PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="PCLCrypto" Version="2.0.147" /> <PackageReference Include="PCLCrypto" Version="2.0.147" />

View file

@ -45,11 +45,11 @@
<EmbeddedResource Include="Resources\public_suffix_list.dat" /> <EmbeddedResource Include="Resources\public_suffix_list.dat" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Camera.MAUI" Version="1.4.4" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" /> <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="ZXing.Net.Maui.Controls" Version="0.3.0-preview.1" />
<PackageReference Include="MessagePack" Version="2.5.124" /> <PackageReference Include="MessagePack" Version="2.5.124" />
<PackageReference Include="CsvHelper" Version="30.0.1" /> <PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="LiteDB" Version="5.0.17" /> <PackageReference Include="LiteDB" Version="5.0.17" />
@ -70,16 +70,12 @@
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" /> <PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'"> <ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="ZXing.Net.Maui" Version="0.3.0-preview.1" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" /> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
<PackageReference Include="Plugin.CurrentActivity" Version="2.1.0.4" /> <PackageReference Include="Plugin.CurrentActivity" Version="2.1.0.4" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.Firebase.Messaging" Version="123.1.2.2" /> <PackageReference Include="Xamarin.Firebase.Messaging" Version="123.1.2.2" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" /> <PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<PackageReference Include="ZXing.Net.Maui" Version="0.3.0-preview.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Services\Logging\" /> <Folder Include="Services\Logging\" />
<Folder Include="Attributes\" /> <Folder Include="Attributes\" />

View file

@ -1,9 +1,9 @@
using CommunityToolkit.Maui; using Camera.MAUI;
using CommunityToolkit.Maui;
using FFImageLoading.Maui; using FFImageLoading.Maui;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Compatibility.Hosting; using Microsoft.Maui.Controls.Compatibility.Hosting;
using SkiaSharp.Views.Maui.Controls.Hosting; using SkiaSharp.Views.Maui.Controls.Hosting;
using ZXing.Net.Maui.Controls;
using AppEffects = Bit.App.Effects; using AppEffects = Bit.App.Effects;
namespace Bit.Core; namespace Bit.Core;
@ -20,7 +20,7 @@ public static class MauiProgram
builder builder
.UseMauiCommunityToolkit() .UseMauiCommunityToolkit()
.UseMauiCompatibility() .UseMauiCompatibility()
.UseBarcodeReader() .UseMauiCameraView()
.UseSkiaSharp() .UseSkiaSharp()
.UseFFImageLoading() .UseFFImageLoading()
.ConfigureEffects(effects => .ConfigureEffects(effects =>

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; using Bit.App.Abstractions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
@ -9,8 +7,6 @@ using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -62,14 +58,12 @@ namespace Bit.App.Pages
_vm.CipherDetailsPage = cipherDetailsPage; _vm.CipherDetailsPage = cipherDetailsPage;
_vm.Init(); _vm.Init();
SetActivityIndicator(); SetActivityIndicator();
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (_vm.EditMode && !_vm.CloneMode && DeviceInfo.Platform == DevicePlatform.Android)
if (_vm.EditMode && !_vm.CloneMode && Device.RuntimePlatform == Device.Android)
{ {
ToolbarItems.Add(_attachmentsItem); ToolbarItems.Add(_attachmentsItem);
ToolbarItems.Add(_deleteItem); ToolbarItems.Add(_deleteItem);
} }
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.iOS)
if (Device.RuntimePlatform == Device.iOS)
{ {
ToolbarItems.Add(_closeItem); ToolbarItems.Add(_closeItem);
if (_vm.EditMode && !_vm.CloneMode) if (_vm.EditMode && !_vm.CloneMode)
@ -267,7 +261,7 @@ namespace Bit.App.Pages
{ {
var page = new ScanPage(key => var page = new ScanPage(key =>
{ {
Device.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(async () =>
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
await _vm.UpdateTotpKeyAsync(key); await _vm.UpdateTotpKeyAsync(key);
@ -335,8 +329,7 @@ namespace Bit.App.Pages
if (_vm.Cipher.Type == CipherType.Login && !_fromAutofill && !addLoginShown.GetValueOrDefault()) if (_vm.Cipher.Type == CipherType.Login && !_fromAutofill && !addLoginShown.GetValueOrDefault())
{ {
await _stateService.SetAddSitePromptShownAsync(true); await _stateService.SetAddSitePromptShownAsync(true);
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.iOS)
if (Device.RuntimePlatform == Device.iOS)
{ {
if (_deviceActionService.SystemMajorVersion() < 12) if (_deviceActionService.SystemMajorVersion() < 12)
{ {
@ -349,9 +342,9 @@ namespace Bit.App.Pages
AppResources.BitwardenAutofillAlert2, AppResources.Ok); AppResources.BitwardenAutofillAlert2, AppResources.Ok);
} }
} }
else if (Device.RuntimePlatform == Device.Android && else if (DeviceInfo.Platform == DevicePlatform.Android &&
!_autofillHandler.AutofillAccessibilityServiceRunning() && !_autofillHandler.AutofillAccessibilityServiceRunning() &&
!_autofillHandler.AutofillServiceEnabled()) !_autofillHandler.AutofillServiceEnabled())
{ {
await DisplayAlert(AppResources.BitwardenAutofillService, await DisplayAlert(AppResources.BitwardenAutofillService,
AppResources.BitwardenAutofillServiceAlert2, AppResources.Ok); AppResources.BitwardenAutofillServiceAlert2, AppResources.Ok);
@ -362,8 +355,7 @@ namespace Bit.App.Pages
private void AdjustToolbar() private void AdjustToolbar()
{ {
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if ((_vm.EditMode || _vm.CloneMode) && DeviceInfo.Platform == DevicePlatform.Android)
if ((_vm.EditMode || _vm.CloneMode) && Device.RuntimePlatform == Device.Android)
{ {
if (_vm.Cipher == null) if (_vm.Cipher == null)
{ {

View file

@ -8,7 +8,7 @@
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:skia="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls" xmlns:skia="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
xmlns:core="clr-namespace:Bit.Core" xmlns:core="clr-namespace:Bit.Core"
xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI.Controls" xmlns:maui="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"
x:Name="_page" x:Name="_page"
Title="{Binding ScanQrPageTitle}"> Title="{Binding ScanQrPageTitle}">
@ -34,18 +34,21 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- TODO: [MAUI-Migration] <maui:CameraView x:Name="_cameraView"
OnScanResult="OnScanResult"--> AutomationId="zxingScannerView"
<zxing:CameraBarcodeReaderView HorizontalOptions="Fill"
x:Name="_zxing" VerticalOptions="Fill"
HorizontalOptions="FillAndExpand" ControlBarcodeResultDuplicate="True"
VerticalOptions="FillAndExpand" BarCodeDetectionEnabled="True"
AutomationId="zxingScannerView" BarcodeDetected="CameraViewOnBarcodeDetected"
IsVisible="{Binding ShowScanner}" BarCodeOptions="{Binding BarCodeOptions}"
Grid.Column="0" Cameras="{Binding Cameras, Mode=OneWayToSource}"
Grid.Row="0" Camera="{Binding Camera}"
Grid.RowSpan="3" AutoStartPreview="{Binding AutoStartPreview}"
BarcodesDetected="_zxing_BarcodesDetected"/> NumCamerasDetected="{Binding NumCameras, Mode=OneWayToSource}"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="3" />
<StackLayout <StackLayout
VerticalOptions="Center" VerticalOptions="Center"
HorizontalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"

View file

@ -2,9 +2,9 @@
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Camera.MAUI.ZXingHelper;
using SkiaSharp; using SkiaSharp;
using SkiaSharp.Views.Maui; using SkiaSharp.Views.Maui;
using ZXing.Net.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -12,8 +12,6 @@ namespace Bit.App.Pages
{ {
private ScanPageViewModel ViewModel => BindingContext as ScanPageViewModel; private ScanPageViewModel ViewModel => BindingContext as ScanPageViewModel;
private readonly Action<string> _callback; private readonly Action<string> _callback;
private CancellationTokenSource _autofocusCts;
private Task _continuousAutofocusTask;
private readonly Color _greenColor; private readonly Color _greenColor;
private readonly SKColor _blueSKColor; private readonly SKColor _blueSKColor;
private readonly SKColor _greenSKColor; private readonly SKColor _greenSKColor;
@ -27,10 +25,8 @@ namespace Bit.App.Pages
{ {
InitializeComponent(); InitializeComponent();
_callback = callback; _callback = callback;
ViewModel.InitScannerCommand = new Command(() => InitScanner());
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes if (DeviceInfo.Platform == DevicePlatform.Android)
if (Device.RuntimePlatform == Device.Android)
{ {
ToolbarItems.RemoveAt(0); ToolbarItems.RemoveAt(0);
} }
@ -54,125 +50,47 @@ namespace Bit.App.Pages
base.OnDisappearing(); 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 BarcodeReaderOptions
{
//UseNativeScanning = true,
//PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE },
Formats = BarcodeFormat.QrCode,
AutoRotate = false,
TryInverted = true,
//DelayBetweenAnalyzingFrames = 5,
//DelayBetweenContinuousScans = 5
};
//_scannerContainer.Content = _zxing;
StartScanner();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
private void StartScanner() private void StartScanner()
{ {
if (_zxing == null) if (_cameraView == null) { return; }
{
return;
}
//_zxing.OnScanResult -= OnScanResult; ViewModel.StartCameraCommand?.Execute(this);
//_zxing.OnScanResult += OnScanResult;
// TODO: [MAUI-Migration] [Critical]
//_zxing.IsScanning = true;
// Fix for Autofocus, now it's done every 2 seconds so that the user does't have to do it
// https://github.com/Redth/ZXing.Net.Mobile/issues/414
_autofocusCts?.Cancel();
_autofocusCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
var autofocusCts = _autofocusCts;
// this task is needed to be awaited OnDisappearing to avoid some crashes
// when changing the value of _zxing.IsScanning
_continuousAutofocusTask = Task.Run(async () =>
{
try
{
while (!autofocusCts.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(2), autofocusCts.Token);
await Device.InvokeOnMainThreadAsync(() =>
{
if (!autofocusCts.IsCancellationRequested)
{
try
{
_zxing.AutoFocus();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
});
}
}
catch (TaskCanceledException) { }
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}, autofocusCts.Token);
_pageIsActive = true; _pageIsActive = true;
AnimationLoopAsync(); AnimationLoopAsync();
} }
private async Task StopScanner() private async Task StopScanner()
{ {
if (_zxing == null) if (_cameraView == null)
{ {
return; return;
} }
_autofocusCts?.Cancel(); _cameraView.BarCodeDetectionEnabled = false;
if (_continuousAutofocusTask != null)
{ await _cameraView.StopCameraAsync();
await _continuousAutofocusTask;
}
// TODO: [MAUI-Migration] [Critical]
//_zxing.IsScanning = false;
//_zxing.OnScanResult -= OnScanResult;
_pageIsActive = false; _pageIsActive = false;
} }
// TODO: [MAUI-Migration] [Critical] private async void CameraViewOnBarcodeDetected(object sender, BarcodeEventArgs e)
private async void _zxing_BarcodesDetected(System.Object sender, ZXing.Net.Maui.BarcodeDetectionEventArgs e)
{ {
try try
{ {
if (!e.Results.Any()) if (!e.Result.Any())
{ {
return; return;
} }
var result = e.Results[0]; var result = e.Result[0];
// Stop analysis until we navigate away so we don't keep reading barcodes // Stop analysis until we navigate away so we don't keep reading barcodes
// TODO: [MAUI-Migration] [Critical] _cameraView.BarCodeDetectionEnabled = false;
//_zxing.IsAnalyzing = false;
var text = result?.Value; var text = result?.Text;
if (!string.IsNullOrWhiteSpace(text)) if (!string.IsNullOrWhiteSpace(text))
{ {
if (text.StartsWith("otpauth://totp")) if (text.StartsWith("otpauth://totp"))
{ {
if (_qrcodeFound) { return; } //To avoid duplicate barcode detected events
await QrCodeFoundAsync(); await QrCodeFoundAsync();
_callback(text); _callback(text);
return; return;
@ -185,6 +103,7 @@ namespace Bit.App.Pages
{ {
if (part.StartsWith("secret=")) if (part.StartsWith("secret="))
{ {
if (_qrcodeFound) { return; } //To avoid duplicate barcode detected events
await QrCodeFoundAsync(); await QrCodeFoundAsync();
var subResult = part.Substring(7); var subResult = part.Substring(7);
if (!string.IsNullOrEmpty(subResult)) if (!string.IsNullOrEmpty(subResult))
@ -196,7 +115,11 @@ namespace Bit.App.Pages
} }
} }
} }
_callback(null);
if (!_qrcodeFound)
{
_callback(null);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -209,8 +132,6 @@ namespace Bit.App.Pages
_qrcodeFound = true; _qrcodeFound = true;
Vibration.Vibrate(); Vibration.Vibrate();
await Task.Delay(1000); await Task.Delay(1000);
// TODO: [MAUI-Migration] [Critical]
//_zxing.IsScanning = false;
} }
private async void Close_Clicked(object sender, System.EventArgs e) private async void Close_Clicked(object sender, System.EventArgs e)

View file

@ -1,16 +1,12 @@
using System; using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
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 Bit.Core.Utilities;
using Camera.MAUI;
using Microsoft.Maui.ApplicationModel; using Camera.MAUI.ZXingHelper;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -25,6 +21,7 @@ namespace Bit.App.Pages
public ScanPageViewModel() public ScanPageViewModel()
{ {
ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException); ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException);
StartCameraCommand = new Command(StartCamera);
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
@ -35,29 +32,80 @@ namespace Bit.App.Pages
{ {
try try
{ {
await Device.InvokeOnMainThreadAsync(async () => await MainThread.InvokeOnMainThreadAsync(async () =>
{ {
var hasCameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera()); var hasCameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
HasCameraPermission = hasCameraPermission == PermissionStatus.Granted; HasCameraPermission = hasCameraPermission == PermissionStatus.Granted;
ShowScanner = hasCameraPermission == PermissionStatus.Granted; ShowScanner = hasCameraPermission == PermissionStatus.Granted;
}); });
if (!HasCameraPermission) BarCodeOptions = new BarcodeDecodeOptions
{ {
return; AutoRotate = false, //shouldn't be needed for QRCodes
} PossibleFormats = { ZXing.BarcodeFormat.QR_CODE },
InitScannerCommand.Execute(null); ReadMultipleCodes = false, //runs slower when true and we only need one
TryHarder = false, //runs slower when true
TryInverted = true
};
TriggerPropertyChanged(nameof(BarCodeOptions));
} }
catch (System.Exception ex) catch (Exception ex)
{ {
HandleException(ex); HandleException(ex);
} }
} }
public ICommand ToggleScanModeCommand { get; set; } private CameraInfo _camera = null;
public ICommand InitScannerCommand { get; set; } public CameraInfo Camera
{
get => _camera;
set
{
_camera = value;
TriggerPropertyChanged(nameof(Camera));
StartCameraCommand?.Execute(this);
}
}
private ObservableCollection<CameraInfo> _cameras = new();
public ObservableCollection<CameraInfo> Cameras
{
get => _cameras;
set
{
_cameras = value;
TriggerPropertyChanged(nameof(Cameras));
}
}
public int NumCameras
{
set
{
if (value > 0)
{
Camera = Cameras.First();
}
}
}
public BarcodeDecodeOptions BarCodeOptions { get; set; }
public bool AutoStartPreview { get; set; } = false;
public ICommand StartCameraCommand { get; set; }
public ICommand ToggleScanModeCommand { get; set; }
private bool _hasCameraPermission = false;
public bool HasCameraPermission
{
get => _hasCameraPermission;
set
{
_hasCameraPermission = value;
StartCameraCommand?.Execute(this);
}
}
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
@ -81,6 +129,23 @@ namespace Bit.App.Pages
}); });
} }
private void StartCamera()
{
if (HasCameraPermission && Camera != null)
{
// Note: If we need to improve performance on Android we can use _cameraView.StartCamera() directly on ScanPage.xaml.cs
// this allows us to set a specific smaller resolution that should help performance and time to scan.
// (The supported resolutions are available in the Camera object)
// This solution would likely replace the "AutoStartPreview" logic in this Command.
//Setting AutoStartPreview to false and then true should trigger the CameraView to start
AutoStartPreview = false;
TriggerPropertyChanged(nameof(AutoStartPreview));
AutoStartPreview = true;
TriggerPropertyChanged(nameof(AutoStartPreview));
}
}
private async Task ToggleScanMode() private async Task ToggleScanMode()
{ {
var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera()); var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
@ -95,7 +160,6 @@ namespace Bit.App.Pages
return; return;
} }
ShowScanner = !ShowScanner; ShowScanner = !ShowScanner;
InitScannerCommand.Execute(null);
} }
public FormattedString ToggleScanModeLabel public FormattedString ToggleScanModeLabel
@ -119,7 +183,7 @@ namespace Bit.App.Pages
private void HandleException(Exception ex) private void HandleException(Exception ex)
{ {
Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () => MainThread.InvokeOnMainThreadAsync(async () =>
{ {
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage); await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);