import 'dart:async'; import 'package:barcode_scanner/backend/api/api_calls.dart'; import 'package:barcode_scanner/backend/objectbox/entities/product/product_entity.dart'; import 'package:barcode_scanner/backend/objectbox/objectbox_manager.dart'; import 'package:barcode_scanner/backend/schema/product/product_struct.dart'; import 'package:barcode_scanner/components/loading_progress_component.dart'; import 'package:barcode_scanner/components/product_scanned_component.dart'; import 'package:barcode_scanner/router/go_secure_router_builder.dart'; import 'package:barcode_scanner/themes/app_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class ScannerPage extends ConsumerStatefulWidget { const ScannerPage({super.key}); @override ConsumerState createState() => _ScannerPageState(); } class _ScannerPageState extends ConsumerState with WidgetsBindingObserver { final MobileScannerController mobileScannerController = MobileScannerController( // required options for the scanner ); StreamSubscription? _subscription; bool qrcodeFound = false; bool loading = false; @override void initState() { super.initState(); // Start listening to lifecycle changes. WidgetsBinding.instance.addObserver(this); // 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}"); if (qrcodeValue == null) { debugPrint("Qrcode non valide"); return; } setState(() { loading = true; }); final product = await ApiCalls.fetchProduct(code.displayValue!); if (product != null) { setState(() { loading = false; }); debugPrint('Nom du produit : ${product["product_name"]}'); debugPrint('Marque : ${product["brands"]}'); debugPrint('Image : ${product["image_url"]}'); final productStruct = ProductStruct( id: int.parse(product["id"]), image: product["image_thumb_url"], name: product["generic_name"], description: product["product_name"], quantity: product["quantity"], ); Box productStore = objectboxManager.store .box(); productStore.put(productStruct.toEntity); //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: productStruct, onRescan: () async { Navigator.of(context).pop(); qrcodeFound = false; mobileScannerController.start(); }, onDetails: () { Navigator.of(context).pop(); unawaited(_subscription?.cancel()); _subscription = null; unawaited(mobileScannerController.stop()); ProductFormRoute().push(context); }, ), ), ), ); }, ); } else { setState(() { loading = false; }); debugPrint('Aucun produit trouvé.'); } } } Future _fakeBarcode() async { final qrcodeValue = "0737628064502"; qrcodeFound = true; mobileScannerController.stop(); setState(() { loading = true; }); final product = await ApiCalls.fetchProduct(qrcodeValue); if (product != null) { setState(() { loading = false; }); debugPrint('Nom du produit : ${product["product_name"]}'); debugPrint('Marque : ${product["brands"]}'); debugPrint('Image : ${product["image_url"]}'); final productStruct = ProductStruct( id: int.parse(product["id"]), image: product["image_thumb_url"], name: product["generic_name"], description: product["product_name"], quantity: product["quantity"], ); Box productStore = objectboxManager.store .box(); productStore.put(productStruct.toEntity); //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: productStruct, onRescan: () async { Navigator.of(context).pop(); qrcodeFound = false; mobileScannerController.start(); }, onDetails: () { Navigator.of(context).pop(); unawaited(_subscription?.cancel()); _subscription = null; unawaited(mobileScannerController.stop()); ProductFormRoute().push(context); }, ), ), ), ); }, ); } else { setState(() { loading = false; }); debugPrint('Aucun produit trouvé.'); } } @override Widget build(BuildContext context) { 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('Scanner', 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: 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) { print("===========> $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: [ 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, ), ), ), ], ), ), 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: () { _fakeBarcode(); qrcodeFound = false; mobileScannerController.start(); }, 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, ), ], ), ), ), ), ), ), Padding( padding: const EdgeInsets.all(40), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( color: AppTheme.of(context).primaryText, iconSize: 2, icon: Icon( mobileScannerController.torchEnabled ? Icons.flash_off : Icons.flash_on, color: AppTheme.of(context).primaryText, size: 28.0, ), onPressed: () { mobileScannerController.toggleTorch(); }, ), ], ), ), ], ), ), ), ], ), ), ); } }