import 'dart:async'; import 'package:e_scan/components/components.dart'; import 'package:e_scan/pages/operation/reception/reception_scan_page_model.dart'; import 'package:e_scan/themes/app_theme.dart'; import 'package:e_scan/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class ReceptionScanPage extends ConsumerStatefulWidget { const ReceptionScanPage({super.key, required this.receptionId}); final int receptionId; @override ConsumerState createState() => _ReceptionScanPageState(); } class _ReceptionScanPageState extends ConsumerState with WidgetsBindingObserver { final MobileScannerController mobileScannerController = MobileScannerController( // required options for the scanner ); StreamSubscription? _subscription; bool qrcodeFound = false; @override void initState() { super.initState(); // Start listening to lifecycle changes. WidgetsBinding.instance.addObserver(this); SchedulerBinding.instance.addPostFrameCallback((_) { ref .read(receptionScanPageModelProvider.notifier) .getReceptionById(id: widget.receptionId); }); // Start listening to the barcode events. _subscription = mobileScannerController.barcodes.listen(_handleBarcode); // Finally, start the scanner itself. unawaited(mobileScannerController.start()); } @override void dispose() { // Stop listening to lifecycle changes. WidgetsBinding.instance.removeObserver(this); // Stop listening to the barcode events. unawaited(_subscription?.cancel()); _subscription = null; // Dispose the widget itself. // Finally, dispose of the controller. mobileScannerController.dispose(); super.dispose(); } @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 (!mobileScannerController.value.hasCameraPermission) { 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 = mobileScannerController.barcodes.listen(_handleBarcode); unawaited(mobileScannerController.start()); case AppLifecycleState.inactive: // Stop the scanner when the app is paused. // Also stop the barcode events subscription. unawaited(_subscription?.cancel()); _subscription = null; unawaited(mobileScannerController.stop()); } } Future _handleBarcode(BarcodeCapture barcodeCapture) async { if (qrcodeFound) return; if (barcodeCapture.barcodes.isNotEmpty) { qrcodeFound = true; mobileScannerController.stop(); final code = barcodeCapture.barcodes.first; final qrcodeValue = code.displayValue; debugPrint("Code détecté : ${code.displayValue}"); final model = ref.read(receptionScanPageModelProvider.notifier); if (qrcodeValue == null) { debugPrint("Produit non attendu dans cette réception"); Toast.showError('Produit non attendu dans cette réception'); return; } // find product in local database final product = model.getProduct( receptionId: widget.receptionId, barcode: qrcodeValue, ); if (product != null) { model.incrementMoveLineQuantity( barcode: qrcodeValue, receptionId: widget.receptionId, onError: () { Toast.showError('Aucun produit trouvé.'); }, ); //show dialog await showDialog( barrierDismissible: false, context: context, builder: (dialogContext) { return Dialog( elevation: 0, insetPadding: EdgeInsets.zero, backgroundColor: Colors.transparent, alignment: AlignmentDirectional( 0.0, 0.0, ).resolve(Directionality.of(context)), child: GestureDetector( onTap: () { FocusScope.of(dialogContext).unfocus(); FocusManager.instance.primaryFocus?.unfocus(); }, child: SizedBox( width: MediaQuery.sizeOf(context).width * 0.9, child: ProductScannedComponent( productStruct: product, onRescan: () async { Navigator.of(context).pop(); qrcodeFound = false; mobileScannerController.start(); }, onDetails: () { unawaited(_subscription?.cancel()); _subscription = null; unawaited(mobileScannerController.stop()); Navigator.of(context).pop(); Navigator.of(context).pop(); }, ), ), ), ); }, ); } else { Toast.showError('Produit non attendu dans cette réception'); debugPrint('Produit non attendu dans cette réception'); } } } Future _fakeBarcode() async { final qrcodeValue = "AIRF0001"; qrcodeFound = true; final model = ref.read(receptionScanPageModelProvider.notifier); mobileScannerController.stop(); // find product in local database final product = model.getProduct( receptionId: widget.receptionId, barcode: qrcodeValue, ); if (product != null) { model.incrementMoveLineQuantity( barcode: qrcodeValue, receptionId: widget.receptionId, onError: () { Toast.showError('Aucun produit trouvé.'); }, ); //show dialog await showDialog( barrierDismissible: false, context: context, builder: (dialogContext) { return Dialog( elevation: 0, insetPadding: EdgeInsets.zero, backgroundColor: Colors.transparent, alignment: AlignmentDirectional( 0.0, 0.0, ).resolve(Directionality.of(context)), child: GestureDetector( onTap: () { FocusScope.of(dialogContext).unfocus(); FocusManager.instance.primaryFocus?.unfocus(); }, child: SizedBox( width: MediaQuery.sizeOf(context).width * 0.9, child: ProductScannedComponent( productStruct: product, onRescan: () async { qrcodeFound = false; mobileScannerController.start(); Navigator.of(context).pop(); }, onDetails: () { unawaited(_subscription?.cancel()); _subscription = null; unawaited(mobileScannerController.stop()); Navigator.of(context).pop(); Navigator.of(context).pop(); }, ), ), ), ); }, ); } else { Toast.showError('Aucun produit trouvé.'); debugPrint('Aucun produit trouvé.'); } } @override Widget build(BuildContext context) { final state = ref.watch(receptionScanPageModelProvider); final reception = state.reception; return Scaffold( backgroundColor: AppTheme.of(context).primaryBackground, appBar: AppBar( backgroundColor: AppTheme.of(context).primaryBackground, automaticallyImplyLeading: false, leading: IconButton( icon: Icon( Icons.arrow_back_ios, color: AppTheme.of(context).primaryText, size: 24.0, ), onPressed: () async { Navigator.of(context).pop(); }, ), title: Text( 'Réception ${reception?.name}', style: AppTheme.of(context).titleLarge, ), actions: [ Padding( padding: EdgeInsetsDirectional.fromSTEB(8.0, 0.0, 8.0, 0.0), child: IconButton( icon: Icon(Icons.help_outline, color: Colors.white, size: 24.0), onPressed: () { debugPrint('IconButton pressed ...'); }, ), ), ], centerTitle: true, elevation: 0.0, ), body: state.loading ? Center(child: LoadingProgressComponent()) : SizedBox( width: double.infinity, height: double.infinity, child: Stack( children: [ SizedBox( width: double.infinity, height: double.infinity, child: MobileScanner( controller: mobileScannerController, onDetectError: (error, stackTrace) { debugPrint("===========> $error"); }, errorBuilder: (c, p2) { return Center(child: Icon(Icons.error)); }, ), ), Container( width: double.infinity, height: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppTheme.of(context).primaryBackground, Colors.transparent, AppTheme.of(context).primaryBackground, ], stops: [0.0, 0.5, 1.0], begin: AlignmentDirectional(0.0, -1.0), end: AlignmentDirectional(0, 1.0), ), ), child: Padding( padding: EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _InfoTextScan(), _ZoneScanBox( onTap: () { // _fakeBarcode(); qrcodeFound = false; mobileScannerController.start(); }, ), FlashButtonComponent( mobileScannerController: mobileScannerController, ), ], ), ), ), ], ), ), ); } } class _ZoneScanBox extends StatelessWidget { const _ZoneScanBox({this.onTap}); final VoidCallback? onTap; @override Widget build(BuildContext context) { return Align( alignment: AlignmentDirectional(0.0, 0.0), child: Container( width: 280.0, height: 280.0, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.0), border: Border.all(color: Colors.white, width: 3.0), ), child: GestureDetector( onTap: onTap, child: Container( width: 260.0, height: 260.0, decoration: BoxDecoration( color: Color(0x33FFFFFF), borderRadius: BorderRadius.circular(12.0), ), child: Padding( padding: EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.qr_code_scanner, color: Colors.white.withValues(alpha: .5), size: 64.0, ), ], ), ), ), ), ), ); } } class _InfoTextScan extends StatelessWidget { const _InfoTextScan(); @override Widget build(BuildContext context) { return Padding( padding: EdgeInsetsDirectional.fromSTEB(24.0, 40.0, 24.0, 0.0), child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( 'Scanner le code', textAlign: TextAlign.center, style: TextStyle(color: AppTheme.of(context).primaryText), ), Padding( padding: EdgeInsetsDirectional.fromSTEB(0.0, 8.0, 0.0, 0.0), child: Text( 'Positionnez le code-barres ou QR code dans le cadre', textAlign: TextAlign.center, style: TextStyle(color: AppTheme.of(context).primaryText), ), ), ], ), ); } }