import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import '../providers/scanner_provider.dart'; /// Widget that provides barcode scanning functionality using device camera class BarcodeScannerWidget extends ConsumerStatefulWidget { const BarcodeScannerWidget({super.key}); @override ConsumerState createState() => _BarcodeScannerWidgetState(); } class _BarcodeScannerWidgetState extends ConsumerState with WidgetsBindingObserver { late MobileScannerController _controller; bool _isStarted = false; String? _lastScannedCode; DateTime? _lastScanTime; bool _isTorchOn = false; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _controller = MobileScannerController( formats: [ BarcodeFormat.code128, ], facing: CameraFacing.back, torchEnabled: false, ); _startScanner(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _controller.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.paused: _stopScanner(); break; case AppLifecycleState.resumed: _startScanner(); break; case AppLifecycleState.detached: case AppLifecycleState.inactive: case AppLifecycleState.hidden: break; } } Future _startScanner() async { if (!_isStarted && mounted) { try { await _controller.start(); setState(() { _isStarted = true; }); } catch (e) { debugPrint('Failed to start scanner: $e'); } } } Future _stopScanner() async { if (_isStarted) { try { await _controller.stop(); setState(() { _isStarted = false; }); } catch (e) { debugPrint('Failed to stop scanner: $e'); } } } void _onBarcodeDetected(BarcodeCapture capture) { final List barcodes = capture.barcodes; if (barcodes.isNotEmpty) { final barcode = barcodes.first; final code = barcode.rawValue; if (code != null && code.isNotEmpty) { // Prevent duplicate scans within 2 seconds final now = DateTime.now(); if (_lastScannedCode == code && _lastScanTime != null && now.difference(_lastScanTime!).inSeconds < 2) { return; } _lastScannedCode = code; _lastScanTime = now; // Update scanner provider with new barcode ref.read(scannerProvider.notifier).updateBarcode(code); // Provide haptic feedback _provideHapticFeedback(); } } } void _provideHapticFeedback() { // Haptic feedback is handled by the system // You can add custom vibration here if needed } @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(0), ), child: Stack( children: [ // Camera View ClipRRect( borderRadius: BorderRadius.circular(0), child: MobileScanner( controller: _controller, onDetect: _onBarcodeDetected, ), ), // Overlay with scanner frame _buildScannerOverlay(context), // Control buttons _buildControlButtons(context), ], ), ); } /// Build scanner overlay with frame and guidance Widget _buildScannerOverlay(BuildContext context) { return Container( decoration: const BoxDecoration( color: Colors.transparent, ), child: Stack( children: [ // Dark overlay with cutout Container( color: Colors.black.withOpacity(0.5), child: Center( child: Container( width: 250, height: 150, decoration: BoxDecoration( border: Border.all( color: Theme.of(context).colorScheme.primary, width: 2, ), borderRadius: BorderRadius.circular(12), ), child: ClipRRect( borderRadius: BorderRadius.circular(10), child: Container( color: Colors.transparent, ), ), ), ), ), // Instructions Positioned( bottom: 60, left: 0, right: 0, child: Container( padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), child: Text( 'Position barcode within the frame', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.white, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ), ), ], ), ); } /// Build control buttons (torch, camera switch) Widget _buildControlButtons(BuildContext context) { return Positioned( top: 16, right: 16, child: Column( children: [ // Torch Toggle Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), shape: BoxShape.circle, ), child: IconButton( icon: Icon( _isTorchOn ? Icons.flash_on : Icons.flash_off, color: Colors.white, ), onPressed: _toggleTorch, ), ), const SizedBox(height: 12), // Camera Switch Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), shape: BoxShape.circle, ), child: IconButton( icon: const Icon( Icons.cameraswitch, color: Colors.white, ), onPressed: _switchCamera, ), ), ], ), ); } /// Build error widget when camera fails Widget _buildErrorWidget(MobileScannerException error) { return Container( color: Colors.black, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.camera_alt_outlined, size: 64, color: Theme.of(context).colorScheme.error, ), const SizedBox(height: 16), Text( 'Camera Error', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Colors.white, ), ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Text( _getErrorMessage(error), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.white70, ), textAlign: TextAlign.center, ), ), const SizedBox(height: 16), ElevatedButton( onPressed: _restartScanner, child: const Text('Retry'), ), ], ), ), ); } /// Build placeholder while camera is loading Widget _buildPlaceholderWidget() { return Container( color: Colors.black, child: const Center( child: CircularProgressIndicator( color: Colors.white, ), ), ); } /// Get user-friendly error message String _getErrorMessage(MobileScannerException error) { switch (error.errorCode) { case MobileScannerErrorCode.permissionDenied: return 'Camera permission is required to scan barcodes. Please enable camera access in settings.'; case MobileScannerErrorCode.unsupported: return 'Your device does not support barcode scanning.'; default: return 'Unable to access camera. Please check your device settings and try again.'; } } /// Toggle torch/flashlight void _toggleTorch() async { try { await _controller.toggleTorch(); setState(() { _isTorchOn = !_isTorchOn; }); } catch (e) { debugPrint('Failed to toggle torch: $e'); } } /// Switch between front and back camera void _switchCamera() async { try { await _controller.switchCamera(); } catch (e) { debugPrint('Failed to switch camera: $e'); } } /// Restart scanner after error void _restartScanner() async { try { await _controller.stop(); await _controller.start(); setState(() { _isStarted = true; }); } catch (e) { debugPrint('Failed to restart scanner: $e'); } } }