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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Camera.MAUI" Version="1.4.4" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
@ -88,9 +89,7 @@
<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.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="ZXing.Net.Maui.Controls" Version="0.3.0-preview.1" />
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="PCLCrypto" Version="2.0.147" />

View file

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

View file

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

View file

@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities;
@ -9,8 +7,6 @@ using Bit.Core.Enums;
using Bit.Core.Utilities;
using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages
{
@ -62,14 +58,12 @@ namespace Bit.App.Pages
_vm.CipherDetailsPage = cipherDetailsPage;
_vm.Init();
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 && Device.RuntimePlatform == Device.Android)
if (_vm.EditMode && !_vm.CloneMode && DeviceInfo.Platform == DevicePlatform.Android)
{
ToolbarItems.Add(_attachmentsItem);
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 (Device.RuntimePlatform == Device.iOS)
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
ToolbarItems.Add(_closeItem);
if (_vm.EditMode && !_vm.CloneMode)
@ -267,7 +261,7 @@ namespace Bit.App.Pages
{
var page = new ScanPage(key =>
{
Device.BeginInvokeOnMainThread(async () =>
MainThread.BeginInvokeOnMainThread(async () =>
{
await Navigation.PopModalAsync();
await _vm.UpdateTotpKeyAsync(key);
@ -335,8 +329,7 @@ namespace Bit.App.Pages
if (_vm.Cipher.Type == CipherType.Login && !_fromAutofill && !addLoginShown.GetValueOrDefault())
{
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 (Device.RuntimePlatform == Device.iOS)
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
if (_deviceActionService.SystemMajorVersion() < 12)
{
@ -349,9 +342,9 @@ namespace Bit.App.Pages
AppResources.BitwardenAutofillAlert2, AppResources.Ok);
}
}
else if (Device.RuntimePlatform == Device.Android &&
!_autofillHandler.AutofillAccessibilityServiceRunning() &&
!_autofillHandler.AutofillServiceEnabled())
else if (DeviceInfo.Platform == DevicePlatform.Android &&
!_autofillHandler.AutofillAccessibilityServiceRunning() &&
!_autofillHandler.AutofillServiceEnabled())
{
await DisplayAlert(AppResources.BitwardenAutofillService,
AppResources.BitwardenAutofillServiceAlert2, AppResources.Ok);
@ -362,8 +355,7 @@ namespace Bit.App.Pages
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) && Device.RuntimePlatform == Device.Android)
if ((_vm.EditMode || _vm.CloneMode) && DeviceInfo.Platform == DevicePlatform.Android)
{
if (_vm.Cipher == null)
{

View file

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

View file

@ -2,9 +2,9 @@
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Camera.MAUI.ZXingHelper;
using SkiaSharp;
using SkiaSharp.Views.Maui;
using ZXing.Net.Maui;
namespace Bit.App.Pages
{
@ -12,8 +12,6 @@ namespace Bit.App.Pages
{
private ScanPageViewModel ViewModel => BindingContext as ScanPageViewModel;
private readonly Action<string> _callback;
private CancellationTokenSource _autofocusCts;
private Task _continuousAutofocusTask;
private readonly Color _greenColor;
private readonly SKColor _blueSKColor;
private readonly SKColor _greenSKColor;
@ -27,10 +25,8 @@ namespace Bit.App.Pages
{
InitializeComponent();
_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 (Device.RuntimePlatform == Device.Android)
if (DeviceInfo.Platform == DevicePlatform.Android)
{
ToolbarItems.RemoveAt(0);
}
@ -54,125 +50,47 @@ namespace Bit.App.Pages
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()
{
if (_zxing == null)
{
return;
}
if (_cameraView == null) { return; }
//_zxing.OnScanResult -= OnScanResult;
//_zxing.OnScanResult += OnScanResult;
// TODO: [MAUI-Migration] [Critical]
//_zxing.IsScanning = true;
ViewModel.StartCameraCommand?.Execute(this);
// 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;
AnimationLoopAsync();
}
private async Task StopScanner()
{
if (_zxing == null)
if (_cameraView == null)
{
return;
}
_autofocusCts?.Cancel();
if (_continuousAutofocusTask != null)
{
await _continuousAutofocusTask;
}
// TODO: [MAUI-Migration] [Critical]
//_zxing.IsScanning = false;
//_zxing.OnScanResult -= OnScanResult;
_cameraView.BarCodeDetectionEnabled = false;
await _cameraView.StopCameraAsync();
_pageIsActive = false;
}
// TODO: [MAUI-Migration] [Critical]
private async void _zxing_BarcodesDetected(System.Object sender, ZXing.Net.Maui.BarcodeDetectionEventArgs e)
private async void CameraViewOnBarcodeDetected(object sender, BarcodeEventArgs e)
{
try
{
if (!e.Results.Any())
if (!e.Result.Any())
{
return;
}
var result = e.Results[0];
var result = e.Result[0];
// Stop analysis until we navigate away so we don't keep reading barcodes
// TODO: [MAUI-Migration] [Critical]
//_zxing.IsAnalyzing = false;
var text = result?.Value;
_cameraView.BarCodeDetectionEnabled = false;
var text = result?.Text;
if (!string.IsNullOrWhiteSpace(text))
{
if (text.StartsWith("otpauth://totp"))
{
if (_qrcodeFound) { return; } //To avoid duplicate barcode detected events
await QrCodeFoundAsync();
_callback(text);
return;
@ -185,6 +103,7 @@ namespace Bit.App.Pages
{
if (part.StartsWith("secret="))
{
if (_qrcodeFound) { return; } //To avoid duplicate barcode detected events
await QrCodeFoundAsync();
var subResult = part.Substring(7);
if (!string.IsNullOrEmpty(subResult))
@ -196,7 +115,11 @@ namespace Bit.App.Pages
}
}
}
_callback(null);
if (!_qrcodeFound)
{
_callback(null);
}
}
catch (Exception ex)
{
@ -209,8 +132,6 @@ namespace Bit.App.Pages
_qrcodeFound = true;
Vibration.Vibrate();
await Task.Delay(1000);
// TODO: [MAUI-Migration] [Critical]
//_zxing.IsScanning = false;
}
private async void Close_Clicked(object sender, System.EventArgs e)

View file

@ -1,16 +1,12 @@
using System;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Camera.MAUI;
using Camera.MAUI.ZXingHelper;
namespace Bit.App.Pages
{
@ -25,6 +21,7 @@ namespace Bit.App.Pages
public ScanPageViewModel()
{
ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException);
StartCameraCommand = new Command(StartCamera);
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_logger = ServiceContainer.Resolve<ILogger>();
@ -35,29 +32,80 @@ namespace Bit.App.Pages
{
try
{
await Device.InvokeOnMainThreadAsync(async () =>
await MainThread.InvokeOnMainThreadAsync(async () =>
{
var hasCameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
HasCameraPermission = hasCameraPermission == PermissionStatus.Granted;
ShowScanner = hasCameraPermission == PermissionStatus.Granted;
});
if (!HasCameraPermission)
BarCodeOptions = new BarcodeDecodeOptions
{
return;
}
InitScannerCommand.Execute(null);
AutoRotate = false, //shouldn't be needed for QRCodes
PossibleFormats = { ZXing.BarcodeFormat.QR_CODE },
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);
}
}
public ICommand ToggleScanModeCommand { get; set; }
public ICommand InitScannerCommand { get; set; }
private CameraInfo _camera = null;
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 CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered;
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()
{
var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
@ -95,7 +160,6 @@ namespace Bit.App.Pages
return;
}
ShowScanner = !ShowScanner;
InitScannerCommand.Execute(null);
}
public FormattedString ToggleScanModeLabel
@ -119,7 +183,7 @@ namespace Bit.App.Pages
private void HandleException(Exception ex)
{
Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () =>
MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);