diff --git a/frontend/android/app/build.gradle b/frontend/android/app/build.gradle index 19f7a8c2a..8a41ccacd 100644 --- a/frontend/android/app/build.gradle +++ b/frontend/android/app/build.gradle @@ -48,7 +48,7 @@ android { applicationId buildConfig.applicationId - minSdkVersion 20 + minSdkVersion 21 targetSdkVersion 30 multiDexEnabled true versionCode flutterVersionCode.toInteger() diff --git a/frontend/lib/qr_code_scanner/qr_code_scanner.dart b/frontend/lib/qr_code_scanner/qr_code_scanner.dart index edeb6c895..a128f1a46 100644 --- a/frontend/lib/qr_code_scanner/qr_code_scanner.dart +++ b/frontend/lib/qr_code_scanner/qr_code_scanner.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:ehrenamtskarte/qr_code_scanner/qr_code_scanner_controls.dart'; import 'package:flutter/material.dart'; -import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; const scanDelayAfterErrorMs = 500; @@ -18,7 +18,10 @@ class QrCodeScanner extends StatefulWidget { } class _QRViewState extends State { - QRViewController? _controller; + final MobileScannerController _controller = MobileScannerController( + torchEnabled: true, + formats: [BarcodeFormat.qrCode], + ); final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); bool isProcessingCode = false; @@ -29,35 +32,46 @@ class _QRViewState extends State { children: [ Expanded( flex: 4, - child: QRView( - key: qrKey, - onQRViewCreated: _onQrViewCreated, - overlay: QrScannerOverlayShape( - borderColor: Theme.of(context).colorScheme.secondary, - borderRadius: 10, - borderLength: 30, - borderWidth: 10, - cutOutSize: _calculateScanArea(context), - ), - ), - ), - if (controller != null) - Expanded( - flex: 1, - child: FittedBox( - fit: BoxFit.contain, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - margin: const EdgeInsets.all(8), - child: const Text('Halten Sie die Kamera auf den QR Code.'), + child: Stack( + children: [ + MobileScanner( + key: qrKey, + onDetect: (barcode, args) => _onCodeScanned(barcode), + allowDuplicates: false, + controller: controller, + ), + Padding( + padding: EdgeInsets.zero, + child: DecoratedBox( + decoration: BoxDecoration( + color: Colors.transparent, + border: Border.all( + color: Colors.orange, + width: 10, + style: BorderStyle.solid, + ), ), - QrCodeScannerControls(controller: controller) - ], + ), ), + ], + ), + ), + Expanded( + flex: 1, + child: FittedBox( + fit: BoxFit.contain, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + margin: const EdgeInsets.all(8), + child: const Text('Halten Sie die Kamera auf den QR Code.'), + ), + QrCodeScannerControls(controller: controller) + ], ), - ) + ), + ) ], ); } @@ -76,17 +90,10 @@ class _QRViewState extends State { return scanArea; } - void _onQrViewCreated(QRViewController controller) { - setState(() { - _controller = controller; - }); - controller.scannedDataStream.listen(_onCodeScanned); - } - Future _onCodeScanned(Barcode scanData) async { final controller = _controller; - final code = scanData.code; - if (controller == null || code == null) { + final code = scanData.rawValue; + if (code == null) { return; } @@ -96,7 +103,7 @@ class _QRViewState extends State { return; } isProcessingCode = true; - controller.pauseCamera(); + controller.stop(); await widget.onCodeScanned(code); @@ -104,7 +111,7 @@ class _QRViewState extends State { await Future.delayed(const Duration(milliseconds: scanDelayAfterErrorMs)); if (mounted) { - controller.resumeCamera(); + controller.start(); } isProcessingCode = false; } diff --git a/frontend/lib/qr_code_scanner/qr_code_scanner_controls.dart b/frontend/lib/qr_code_scanner/qr_code_scanner_controls.dart index 21b54cbd4..2d1fe3fb8 100644 --- a/frontend/lib/qr_code_scanner/qr_code_scanner_controls.dart +++ b/frontend/lib/qr_code_scanner/qr_code_scanner_controls.dart @@ -1,84 +1,44 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; -import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; class QrCodeScannerControls extends StatelessWidget { - final QRViewController controller; - - // Not really nice. We need to update the button texts after we pressed them. - // Other possibility would be to convert this to a stateful widget without - // state and just call setState({}) to trigger redrawing of whole widget. - final StreamController flashStreamController = StreamController(); - final StreamController cameraStreamController = StreamController(); - - QrCodeScannerControls({super.key, required this.controller}) { - updateFlashStream(); - updateCameraStream(); - } - - Future updateFlashStream() => - controller.getFlashStatus().then((flashStatus) => flashStreamController.add(flashStatus ?? false)); + final MobileScannerController controller; - Future updateCameraStream() => controller.getCameraInfo().then(cameraStreamController.add); + const QrCodeScannerControls({super.key, required this.controller}); @override Widget build(BuildContext context) { - return FutureBuilder( - future: _tryToGetSystemFeatures(), - builder: (context, snapshot) { - final SystemFeatures? systemFeatures = snapshot.data; - - if (snapshot.hasData || systemFeatures == null) { - return const Center(); - } - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (systemFeatures.hasFlash) - Container( - margin: const EdgeInsets.all(8), - child: OutlinedButton( - onPressed: () => controller.toggleFlash().whenComplete(updateFlashStream), - child: StreamBuilder( - stream: flashStreamController.stream, - builder: (ctx, snapshot) => Text( - snapshot.data == null ? "Blitz aus" : "Blitz an", - style: const TextStyle(fontSize: 16), - ), - ), - ), + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.all(8), + child: OutlinedButton( + onPressed: () => controller.toggleTorch(), + child: ValueListenableBuilder( + valueListenable: controller.torchState, + builder: (ctx, state, child) => Text( + state == TorchState.on ? "Blitz an" : "Blitz aus", + style: const TextStyle(fontSize: 16), + ), + ), + ), + ), + Container( + margin: const EdgeInsets.all(8), + child: OutlinedButton( + onPressed: () => controller.switchCamera(), + child: ValueListenableBuilder( + valueListenable: controller.cameraFacingState, + builder: (ctx, state, child) => Text( + state == CameraFacing.back ? "Frontkamera" : "Standard-Kamera", + style: const TextStyle(fontSize: 16), ), - if (systemFeatures.hasBackCamera && systemFeatures.hasFrontCamera) - Container( - margin: const EdgeInsets.all(8), - child: OutlinedButton( - onPressed: () => controller.flipCamera().whenComplete(updateCameraStream), - child: StreamBuilder( - stream: cameraStreamController.stream, - builder: (ctx, snapshot) => Text( - snapshot.data == CameraFacing.back ? "Frontkamera" : "Standard-Kamera", - style: const TextStyle(fontSize: 16), - ), - ), - ), - ) - ], - ); - }, + ), + ), + ) + ], ); } - - Future _tryToGetSystemFeatures() async { - for (var i = 0; i < 10; i++) { - try { - return await controller.getSystemFeatures(); - } on CameraException { - await Future.delayed(const Duration(milliseconds: 50)); - } - } - return SystemFeatures(true, true, true); - } } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index bab0bdbf3..79a0dadcb 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -680,6 +680,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" mutex: dependency: "direct main" description: @@ -918,13 +925,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - qr_code_scanner: - dependency: "direct main" - description: - name: qr_code_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" qr_flutter: dependency: "direct main" description: diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index f7d9e4db3..ca38ee485 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: maps_launcher: ^2.0.1 flutter_svg: ^1.1.4 package_info_plus: ^1.4.3+1 # for about dialog - qr_code_scanner: ^1.0.1 + mobile_scanner: ^2.1.0 flutter_secure_storage: ^6.0.0 infinite_scroll_pagination: ^3.2.0 geolocator: ^9.0.1