2024-09-01 22:34:05 +08:00

146 lines
4.2 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ScanPage extends StatelessWidget {
const ScanPage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: ScanView(
resultHandler: (capture, resumeScanner) {
context.pop(capture.barcodes.first.rawValue);
},
),
);
}
}
typedef ScanViewResultHandler = void Function(
BarcodeCapture capture,
void Function() resumeScanner,
);
class ScanView extends StatefulWidget {
const ScanView({super.key, required this.resultHandler});
/// handle the QR code result.
///
/// The scanner will stop each time the result is complete.
/// Process the result here and then resume the scanner by calling `resumeScanner`.
final ScanViewResultHandler resultHandler;
@override
State<ScanView> createState() => _ScanViewState();
}
class _ScanViewState extends State<ScanView> with WidgetsBindingObserver {
final scannerController = MobileScannerController(
formats: [BarcodeFormat.qrCode],
);
StreamSubscription? subscription;
bool hasResult = false;
void resumeScanner() {
hasResult = false;
scannerController.start();
}
void handleQRcode(BarcodeCapture capture) {
if (hasResult) return;
hasResult = true;
scannerController.stop().then((_) {
widget.resultHandler(capture, resumeScanner);
});
}
@override
void initState() {
super.initState();
// Start listening to lifecycle changes.
WidgetsBinding.instance.addObserver(this);
// Start listening to the barcode events.
subscription = scannerController.barcodes.listen(handleQRcode);
// Finally, start the scanner itself.
scannerController.start();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// If the controller is not ready, do not try to start or stop it.
// Permission dialogs can trigger lifecycle changes before the controller is ready.
if (!scannerController.value.isInitialized) return;
switch (state) {
case AppLifecycleState.detached:
case AppLifecycleState.hidden:
case AppLifecycleState.paused:
return;
case AppLifecycleState.resumed:
// Restart the scanner when the app is resumed.
// Don't forget to resume listening to the barcode events.
subscription = scannerController.barcodes.listen(handleQRcode);
scannerController.start();
break;
case AppLifecycleState.inactive:
// Stop the scanner when the app is paused.
// Also stop the barcode events subscription.
subscription?.cancel();
subscription = null;
scannerController.stop();
break;
}
}
@override
Widget build(BuildContext context) {
return MobileScanner(
controller: scannerController,
errorBuilder: (context, error, _) {
final scheme = Theme.of(context).colorScheme;
return ColoredBox(
color: scheme.surface,
child: Center(
child: switch (error.errorCode) {
MobileScannerErrorCode.controllerAlreadyInitialized ||
MobileScannerErrorCode.controllerDisposed ||
MobileScannerErrorCode.controllerUninitialized ||
MobileScannerErrorCode.genericError ||
MobileScannerErrorCode.unsupported =>
Icon(Icons.error, color: scheme.error),
MobileScannerErrorCode.permissionDenied => Text(
AppLocalizations.of(context)!.scanPagePermissionDeniedMsg,
textAlign: TextAlign.center,
),
},
),
);
},
);
}
@override
void dispose() async {
// Stop listening to lifecycle changes.
WidgetsBinding.instance.removeObserver(this);
// Stop listening to the barcode events.
subscription?.cancel();
subscription = null;
super.dispose();
// Finally, dispose the controller.
scannerController.dispose();
}
}