part of '../../app/app.dart'; class BarcodeScannerScreen extends StatefulWidget { const BarcodeScannerScreen({super.key}); @override State createState() => _BarcodeScannerScreenState(); } class _BarcodeScannerScreenState extends State { final MobileScannerController _controller = MobileScannerController(); final _manualCode = TextEditingController(); bool _handled = false; @override void dispose() { _manualCode.dispose(); _controller.dispose(); super.dispose(); } void _handleDetect(BarcodeCapture capture) { if (_handled) { return; } final value = capture.barcodes .map((barcode) => barcode.rawValue?.trim() ?? '') .firstWhere((text) => text.isNotEmpty, orElse: () => ''); if (value.isEmpty) { return; } _handled = true; Navigator.of(context).pop(value); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Scan Barcode Sampel'), actions: [ IconButton( onPressed: () => _controller.toggleTorch(), icon: const Icon(Icons.flashlight_on_outlined), tooltip: 'Lampu', ), ], ), body: Stack( children: [ MobileScanner( controller: _controller, onDetect: _handleDetect, errorBuilder: (context, error) => _ScannerFallback( controller: _manualCode, message: 'Scanner belum tersedia. Tutup aplikasi lalu jalankan ulang full rebuild, atau masukkan barcode manual.', onSubmit: _submitManualCode, ), placeholderBuilder: (context) => const Center(child: CircularProgressIndicator()), ), Center( child: Container( width: 260, height: 180, decoration: BoxDecoration( border: Border.all(color: Colors.white, width: 3), borderRadius: BorderRadius.circular(16), ), ), ), Positioned( left: 18, right: 18, bottom: 24, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.62), borderRadius: BorderRadius.circular(12), ), child: const Text( 'Arahkan kamera ke barcode nomor sampel.', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, fontWeight: FontWeight.w700, ), ), ), ), ], ), ); } void _submitManualCode() { final code = _manualCode.text.trim(); if (code.isEmpty) { return; } Navigator.of(context).pop(code); } } class _ScannerFallback extends StatelessWidget { const _ScannerFallback({ required this.controller, required this.message, required this.onSubmit, }); final TextEditingController controller; final String message; final VoidCallback onSubmit; @override Widget build(BuildContext context) { return Container( color: const Color(0xFFF7FAF9), padding: const EdgeInsets.all(18), child: Center( child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Icon( Icons.qr_code_scanner_rounded, size: 42, color: Color(0xFF0F766E), ), const SizedBox(height: 10), Text( message, textAlign: TextAlign.center, style: const TextStyle(color: Colors.black54), ), const SizedBox(height: 14), TextField( controller: controller, autofocus: true, textInputAction: TextInputAction.search, onSubmitted: (_) => onSubmit(), decoration: const InputDecoration( labelText: 'Barcode / No. Sampel', border: OutlineInputBorder(), ), ), const SizedBox(height: 12), FilledButton.icon( onPressed: onSubmit, icon: const Icon(Icons.manage_search_rounded), label: const Text('Gunakan Kode'), ), ], ), ), ), ), ); } }