diff --git a/frontend/lib/identification/qr_code_scanner/qr_code_scanner.dart b/frontend/lib/identification/qr_code_scanner/qr_code_scanner.dart index b2f08bd5b..ae692cb9d 100644 --- a/frontend/lib/identification/qr_code_scanner/qr_code_scanner.dart +++ b/frontend/lib/identification/qr_code_scanner/qr_code_scanner.dart @@ -3,11 +3,14 @@ import 'dart:typed_data'; import 'package:ehrenamtskarte/identification/qr_code_scanner/qr_code_scanner_controls.dart'; import 'package:ehrenamtskarte/identification/qr_code_scanner/qr_overlay_shape.dart'; +import 'package:ehrenamtskarte/widgets/error_message.dart'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:ehrenamtskarte/l10n/translations.g.dart'; +import 'package:ehrenamtskarte/util/android_utils.dart'; + typedef OnCodeScannedCallback = Future Function(Uint8List code); class QrCodeScanner extends StatefulWidget { @@ -20,12 +23,25 @@ class QrCodeScanner extends StatefulWidget { } class _QRViewState extends State { + late Future _hasCameraIssues; + + @override + void initState() { + super.initState(); + // Workaround for https://github.com/juliansteenbakker/mobile_scanner/issues/698 + // Check once the qr code scanner was initialized if the device has camera issues + // Depending on that set a controller with predefined camera solution to fix that qr code reading issues + _hasCameraIssues = isDeviceWithCameraIssues(); + } + final MobileScannerController _controller = MobileScannerController( - torchEnabled: false, - detectionSpeed: DetectionSpeed.normal, - formats: [BarcodeFormat.qrCode], - returnImage: false, - ); + torchEnabled: false, detectionSpeed: DetectionSpeed.normal, formats: [BarcodeFormat.qrCode], returnImage: false); + final MobileScannerController _controllerPredefinedCameraResolution = MobileScannerController( + torchEnabled: false, + detectionSpeed: DetectionSpeed.normal, + formats: [BarcodeFormat.qrCode], + returnImage: false, + cameraResolution: const Size(640, 480)); final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); // Determines whether a code is currently processed by the onCodeScanned callback @@ -36,66 +52,76 @@ class _QRViewState extends State { @override Widget build(BuildContext context) { - final t = context.t; - final controller = _controller; - return Stack( - children: [ - Column( - children: [ - Expanded( - flex: 4, - child: Stack( - fit: StackFit.expand, - children: [ - MobileScanner( - key: qrKey, - placeholderBuilder: (context, _) => Align( - alignment: Alignment.center, - child: Icon(Icons.camera_alt_outlined, size: 128, color: Colors.grey), + return FutureBuilder( + future: _hasCameraIssues, + builder: (context, AsyncSnapshot snapshot) { + final hasCameraIssues = snapshot.data; + if (snapshot.hasError && snapshot.error != null) { + return ErrorMessage(snapshot.error.toString()); + } else if (hasCameraIssues == null) { + return const Center(); + } + final controller = hasCameraIssues ? _controllerPredefinedCameraResolution : _controller; + final t = context.t; + return Stack( + children: [ + Column( + children: [ + Expanded( + flex: 4, + child: Stack( + fit: StackFit.expand, + children: [ + MobileScanner( + key: qrKey, + placeholderBuilder: (context, _) => Align( + alignment: Alignment.center, + child: Icon(Icons.camera_alt_outlined, size: 128, color: Colors.grey), + ), + onDetect: (barcodes) => _onCodeScanned(barcodes), + controller: controller, + ), + DecoratedBox( + decoration: ShapeDecoration( + shape: QrScannerOverlayShape( + borderRadius: 10, + borderColor: Theme.of(context).colorScheme.secondary, + borderLength: 30, + borderWidth: 10, + cutOutSize: _calculateScanArea(context), + ), + ), + ), + ], ), - onDetect: (barcodes) => _onCodeScanned(barcodes), - controller: controller, ), - DecoratedBox( - decoration: ShapeDecoration( - shape: QrScannerOverlayShape( - borderRadius: 10, - borderColor: Theme.of(context).colorScheme.secondary, - borderLength: 30, - borderWidth: 10, - cutOutSize: _calculateScanArea(context), + Expanded( + flex: 1, + child: FittedBox( + fit: BoxFit.contain, + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(8), + child: Text(t.identification.scanQRCode), + ), + QrCodeScannerControls(controller: controller) + ], ), ), - ), + ) ], ), - ), - Expanded( - flex: 1, - child: FittedBox( - fit: BoxFit.contain, - child: Column( - children: [ - Container( - margin: const EdgeInsets.all(8), - child: Text(t.identification.scanQRCode), - ), - QrCodeScannerControls(controller: controller) - ], - ), - ), - ) - ], - ), - if (showWaiting) - Center( - child: Card( - child: Padding( - padding: const EdgeInsets.all(32), - child: CircularProgressIndicator(), - ))), - ], - ); + if (showWaiting) + Center( + child: Card( + child: Padding( + padding: const EdgeInsets.all(32), + child: CircularProgressIndicator(), + ))), + ], + ); + }); } double _calculateScanArea(BuildContext context) { diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index fa97484e0..2474a2e44 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -5,7 +5,7 @@ import 'package:ehrenamtskarte/configuration/definitions.dart'; import 'package:ehrenamtskarte/l10n/translations.g.dart'; import 'package:ehrenamtskarte/sentry.dart'; import 'package:ehrenamtskarte/settings_provider.dart'; -import 'package:ehrenamtskarte/util/android_certificate.dart'; +import 'package:ehrenamtskarte/util/android_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:slang/builder/model/enums.dart'; diff --git a/frontend/lib/util/android_certificate.dart b/frontend/lib/util/android_certificate.dart deleted file mode 100644 index 0ebba8e09..000000000 --- a/frontend/lib/util/android_certificate.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:io'; -import 'package:flutter/services.dart'; -import 'package:device_info_plus/device_info_plus.dart'; - -Future loadCertificate() async { - ByteData? data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem'); - SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List()); -} - -Future certificateIsRequired() async { - DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - return androidInfo.version.sdkInt < 25; -} diff --git a/frontend/lib/util/android_utils.dart b/frontend/lib/util/android_utils.dart new file mode 100644 index 000000000..e41d0a34d --- /dev/null +++ b/frontend/lib/util/android_utils.dart @@ -0,0 +1,39 @@ +import 'dart:io'; +import 'package:flutter/services.dart'; +import 'package:device_info_plus/device_info_plus.dart'; + +Future loadCertificate() async { + ByteData? data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem'); + SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List()); +} + +Future certificateIsRequired() async { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + return androidInfo.version.sdkInt < 25; +} + +Future isDeviceWithCameraIssues() async { + if (!Platform.isAndroid) { + return false; + } + // All models of Galaxy A23 + List devicesWithoutQRCodeDetection = [ + 'SM-A235F', + 'SM-A235M', + 'SM-A235N', + 'SM-A233C', + 'SM-A2360', + 'SM-A236B', + 'SM-A236E', + 'SM-A236M', + 'SM-A236U', + 'SM-A236U1', + 'SM-S236DL', + 'SM-S237VL', + 'SM-A236V' + ]; + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + return devicesWithoutQRCodeDetection.contains(androidInfo.model); +}