scan_page.dart 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:go_router/go_router.dart';
  4. import 'package:mobile_scanner/mobile_scanner.dart';
  5. import 'package:flutter_gen/gen_l10n/app_localizations.dart';
  6. class ScanPage extends StatelessWidget {
  7. const ScanPage({super.key, required this.title});
  8. final String title;
  9. @override
  10. Widget build(BuildContext context) {
  11. return Scaffold(
  12. appBar: AppBar(title: Text(title)),
  13. body: ScanView(
  14. resultHandler: (capture, resumeScanner) {
  15. context.pop(capture.barcodes.first.rawValue);
  16. },
  17. ),
  18. );
  19. }
  20. }
  21. typedef ScanViewResultHandler = void Function(
  22. BarcodeCapture capture,
  23. void Function() resumeScanner,
  24. );
  25. class ScanView extends StatefulWidget {
  26. const ScanView({super.key, required this.resultHandler});
  27. /// handle the QR code result.
  28. ///
  29. /// The scanner will stop each time the result is complete.
  30. /// Process the result here and then resume the scanner by calling `resumeScanner`.
  31. final ScanViewResultHandler resultHandler;
  32. @override
  33. State<ScanView> createState() => _ScanViewState();
  34. }
  35. class _ScanViewState extends State<ScanView> with WidgetsBindingObserver {
  36. final scannerController = MobileScannerController(
  37. formats: [BarcodeFormat.qrCode],
  38. );
  39. StreamSubscription? subscription;
  40. bool hasResult = false;
  41. void resumeScanner() {
  42. hasResult = false;
  43. scannerController.start();
  44. }
  45. void handleQRcode(BarcodeCapture capture) {
  46. if (hasResult) return;
  47. hasResult = true;
  48. scannerController.stop().then((_) {
  49. widget.resultHandler(capture, resumeScanner);
  50. });
  51. }
  52. @override
  53. void initState() {
  54. super.initState();
  55. // Start listening to lifecycle changes.
  56. WidgetsBinding.instance.addObserver(this);
  57. // Start listening to the barcode events.
  58. subscription = scannerController.barcodes.listen(handleQRcode);
  59. // Finally, start the scanner itself.
  60. scannerController.start();
  61. }
  62. @override
  63. void didChangeAppLifecycleState(AppLifecycleState state) {
  64. // If the controller is not ready, do not try to start or stop it.
  65. // Permission dialogs can trigger lifecycle changes before the controller is ready.
  66. if (!scannerController.value.isInitialized) return;
  67. switch (state) {
  68. case AppLifecycleState.detached:
  69. case AppLifecycleState.hidden:
  70. case AppLifecycleState.paused:
  71. return;
  72. case AppLifecycleState.resumed:
  73. // Restart the scanner when the app is resumed.
  74. // Don't forget to resume listening to the barcode events.
  75. subscription = scannerController.barcodes.listen(handleQRcode);
  76. scannerController.start();
  77. break;
  78. case AppLifecycleState.inactive:
  79. // Stop the scanner when the app is paused.
  80. // Also stop the barcode events subscription.
  81. subscription?.cancel();
  82. subscription = null;
  83. scannerController.stop();
  84. break;
  85. }
  86. }
  87. @override
  88. Widget build(BuildContext context) {
  89. return MobileScanner(
  90. controller: scannerController,
  91. errorBuilder: (context, error, _) {
  92. final scheme = Theme.of(context).colorScheme;
  93. return ColoredBox(
  94. color: scheme.surface,
  95. child: Center(
  96. child: switch (error.errorCode) {
  97. MobileScannerErrorCode.controllerAlreadyInitialized ||
  98. MobileScannerErrorCode.controllerDisposed ||
  99. MobileScannerErrorCode.controllerUninitialized ||
  100. MobileScannerErrorCode.genericError ||
  101. MobileScannerErrorCode.unsupported =>
  102. Icon(Icons.error, color: scheme.error),
  103. MobileScannerErrorCode.permissionDenied => Text(
  104. AppLocalizations.of(context)!.scanPagePermissionDeniedMsg,
  105. textAlign: TextAlign.center,
  106. ),
  107. },
  108. ),
  109. );
  110. },
  111. );
  112. }
  113. @override
  114. void dispose() async {
  115. // Stop listening to lifecycle changes.
  116. WidgetsBinding.instance.removeObserver(this);
  117. // Stop listening to the barcode events.
  118. subscription?.cancel();
  119. subscription = null;
  120. super.dispose();
  121. // Finally, dispose the controller.
  122. scannerController.dispose();
  123. }
  124. }