Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

655: replace qr_code_scanner with mobile_scanner #679

Merged
merged 7 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"args": [
"flutter",
"run",
"--dart-define=environment=local"
"--dart-define=environment=local",
"--flavor=Bayern"
],
"problemMatcher": [
"$gradle"
Expand Down
2 changes: 1 addition & 1 deletion frontend/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ android {
}

defaultConfig {
minSdkVersion 20
minSdkVersion 21
targetSdkVersion 33
multiDexEnabled true
versionCode flutterVersionCode.toInteger()
Expand Down
99 changes: 46 additions & 53 deletions frontend/lib/qr_code_scanner/qr_code_scanner.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'dart:math';

import 'package:ehrenamtskarte/qr_code_scanner/qr_code_scanner_controls.dart';
import 'package:ehrenamtskarte/qr_code_scanner/qr_overlay_shape.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;

Expand All @@ -18,7 +19,10 @@ class QrCodeScanner extends StatefulWidget {
}

class _QRViewState extends State<QrCodeScanner> {
QRViewController? _controller;
final MobileScannerController _controller = MobileScannerController(
torchEnabled: false,
formats: [BarcodeFormat.qrCode],
);
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
bool isProcessingCode = false;

Expand All @@ -29,35 +33,47 @@ class _QRViewState extends State<QrCodeScanner> {
children: <Widget>[
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: <Widget>[
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: Container(
decoration: ShapeDecoration(
shape: QrScannerOverlayShape(
borderRadius: 10,
borderColor: Theme.of(context).colorScheme.secondary,
borderLength: 30,
borderWidth: 10,
cutOutSize: _calculateScanArea(context),
),
),
QrCodeScannerControls(controller: controller)
],
),
),
],
),
),
Expanded(
flex: 1,
child: FittedBox(
fit: BoxFit.contain,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: const Text('Halten Sie die Kamera auf den QR Code.'),
),
QrCodeScannerControls(controller: controller)
],
),
)
),
)
],
);
}
Expand All @@ -76,37 +92,14 @@ class _QRViewState extends State<QrCodeScanner> {
return scanArea;
}

void _onQrViewCreated(QRViewController controller) {
setState(() {
_controller = controller;
});
controller.scannedDataStream.listen(_onCodeScanned);
}

Future<void> _onCodeScanned(Barcode scanData) async {
final controller = _controller;
final code = scanData.code;
if (controller == null || code == null) {
return;
}

// needed because this method gets called multiple times in a row after one
// qr code gets detected, therefore we need to protect it
if (isProcessingCode) {
final code = scanData.rawValue;
if (code == null) {
return;
}
isProcessingCode = true;
controller.pauseCamera();

await widget.onCodeScanned(code);

// give the user time to move the camara away from the qr code
await Future.delayed(const Duration(milliseconds: scanDelayAfterErrorMs));

if (mounted) {
controller.resumeCamera();
}
isProcessingCode = false;
}

@override
Expand Down
106 changes: 33 additions & 73 deletions frontend/lib/qr_code_scanner/qr_code_scanner_controls.dart
Original file line number Diff line number Diff line change
@@ -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<bool> flashStreamController = StreamController();
final StreamController<CameraFacing> cameraStreamController = StreamController<CameraFacing>();

QrCodeScannerControls({super.key, required this.controller}) {
updateFlashStream();
updateCameraStream();
}

Future<void> updateFlashStream() =>
controller.getFlashStatus().then((flashStatus) => flashStreamController.add(flashStatus ?? false));
final MobileScannerController controller;

Future<void> updateCameraStream() => controller.getCameraInfo().then(cameraStreamController.add);
const QrCodeScannerControls({super.key, required this.controller});

@override
Widget build(BuildContext context) {
return FutureBuilder<SystemFeatures>(
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: <Widget>[
if (systemFeatures.hasFlash)
Container(
margin: const EdgeInsets.all(8),
child: OutlinedButton(
onPressed: () => controller.toggleFlash().whenComplete(updateFlashStream),
child: StreamBuilder<bool>(
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: <Widget>[
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 aus" : "Blitz an",
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<CameraFacing>(
stream: cameraStreamController.stream,
builder: (ctx, snapshot) => Text(
snapshot.data == CameraFacing.back ? "Frontkamera" : "Standard-Kamera",
style: const TextStyle(fontSize: 16),
),
),
),
)
],
);
},
),
),
)
],
);
}

Future<SystemFeatures> _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);
}
}
Loading