import 'dart:async'; import 'package:cb_prestige_qr/core/presentation/widgets/start_loading_overlay.dart'; import 'package:cb_prestige_qr/features/scan/presentation/viewmodels/scan_controller.dart'; import 'package:cb_prestige_qr/features/scan/presentation/views/scan_result_page.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class ScanPage extends ConsumerStatefulWidget { const ScanPage({super.key}); @override ConsumerState createState() => _ScanPageState(); } class _ScanPageState extends ConsumerState { late final MobileScannerController _scannerController; String? _openedResultValue; @override void initState() { super.initState(); _scannerController = MobileScannerController(); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; ref.read(scanControllerProvider.notifier).resumeScanning(); unawaited(_scannerController.start()); }); } @override void dispose() { _scannerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { ref.listen(scanControllerProvider.select((s) => s.isScanning), ( previous, next, ) { if (previous == next) return; if (next) { unawaited(_scannerController.start()); } else { unawaited(_scannerController.stop()); } }); ref.listen(scanControllerProvider.select((s) => s.scannedValue), ( previous, next, ) { if (next == null || next == _openedResultValue) return; _openedResultValue = next; WidgetsBinding.instance.addPostFrameCallback((_) async { if (!mounted) return; await Navigator.push( context, MaterialPageRoute(builder: (_) => ScanResultPage(rawValue: next)), ); _openedResultValue = null; }); }); final scanState = ref.watch(scanControllerProvider); void onDetect(BarcodeCapture capture) { if (!scanState.isScanning) return; final value = capture.barcodes.isEmpty ? null : capture.barcodes.first.rawValue; if (value == null || value.isEmpty) return; unawaited( ref.read(scanControllerProvider.notifier).onBarcodeDetected(value), ); } return StartLoadingOverlay( child: Scaffold( appBar: AppBar(title: const Text('Scan QR')), body: Stack( children: [ MobileScanner(controller: _scannerController, onDetect: onDetect), Align( alignment: Alignment.center, child: Container( width: 250, height: 250, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 3), borderRadius: BorderRadius.circular(12), ), ), ), if (scanState.isProcessing) const Positioned.fill( child: ColoredBox( color: Color(0x66000000), child: Center(child: CircularProgressIndicator()), ), ), if (!scanState.isProcessing && scanState.errorMessage != null) Positioned.fill( child: ColoredBox( color: const Color(0x66000000), child: SafeArea( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: Padding( padding: const EdgeInsets.all(16), child: Material( borderRadius: BorderRadius.circular(16), clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( scanState.errorMessage == null ? 'Scanned Result' : 'Scan Error', style: Theme.of( context, ).textTheme.titleLarge, ), const SizedBox(height: 12), SelectableText(scanState.errorMessage ?? ''), const SizedBox(height: 16), Row( children: [ Expanded( child: OutlinedButton( onPressed: () { ref .read( scanControllerProvider .notifier, ) .resumeScanning(); }, child: const Text('Scan Again'), ), ), const SizedBox(width: 12), Expanded( child: FilledButton( onPressed: () { Navigator.pop(context); }, child: const Text('Back'), ), ), ], ), ], ), ), ), ), ), ), ), ), ), ], ), ), ); } }