import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import '../services/client_repository.dart'; import '../theme.dart'; import 'client_detail_screen.dart'; /// Onglet 2 : scan du QR code d'un client → ouverture de sa fiche. /// /// V1 : on utilise la caméra de la tablette. Quand la scanette (lecteur externe) /// sera branchée, elle se comporte comme un clavier : il suffira d'ajouter un /// champ caché qui reçoit le code et appelle [_ouvrirParCode]. La logique de /// recherche du client est déjà factorisée pour ça. class ScanScreen extends StatefulWidget { final bool active; const ScanScreen({super.key, required this.active}); @override State createState() => _ScanScreenState(); } class _ScanScreenState extends State { final _controller = MobileScannerController( detectionSpeed: DetectionSpeed.noDuplicates, formats: const [BarcodeFormat.qrCode], ); final _repo = ClientRepository.instance; bool _traite = false; @override void didUpdateWidget(covariant ScanScreen old) { super.didUpdateWidget(old); // En revenant sur l'onglet Scanner, on réautorise la détection. if (widget.active && !old.active) _traite = false; } @override void dispose() { _controller.dispose(); super.dispose(); } Future _onDetect(BarcodeCapture capture) async { if (_traite || !widget.active) return; final code = capture.barcodes.firstOrNull?.rawValue; if (code == null || code.isEmpty) return; setState(() => _traite = true); await _ouvrirParCode(code.trim()); } /// Retrouve le client par son code et ouvre sa fiche (ou signale l'échec). Future _ouvrirParCode(String code) async { final client = _repo.parCode(code); if (!mounted) return; if (client == null) { await _popupInconnu(code); if (mounted) setState(() => _traite = false); return; } await Navigator.of(context).push( MaterialPageRoute(builder: (_) => ClientDetailScreen(clientId: client.id)), ); if (mounted) setState(() => _traite = false); } Future _popupInconnu(String code) async { await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('QR code non reconnu'), content: Text( 'Aucun client ne correspond au code « $code ».\n\n' 'Vérifiez qu\'il s\'agit bien d\'une carte de fidélité de ce magasin.'), actions: [ FilledButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('OK'), ), ], ), ); } @override Widget build(BuildContext context) { if (!widget.active) { return const ColoredBox(color: Colors.black); } return Scaffold( backgroundColor: Colors.black, body: Stack( fit: StackFit.expand, children: [ MobileScanner( controller: _controller, onDetect: _onDetect, errorBuilder: (context, error) => _erreurCamera(error), ), _cadre(), _bandeauHaut(), if (_traite) Container( color: Colors.black54, child: const Center( child: CircularProgressIndicator(color: Colors.white), ), ), ], ), ); } Widget _bandeauHaut() { return SafeArea( child: Align( alignment: Alignment.topCenter, child: Container( margin: const EdgeInsets.only(top: 24), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.55), borderRadius: BorderRadius.circular(30), ), child: const Text( 'Scannez le QR code de fidélité du client', style: TextStyle( color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600), ), ), ), ); } Widget _cadre() { return Center( child: Container( width: 240, height: 240, decoration: BoxDecoration( border: Border.all(color: AppTheme.accent, width: 3), borderRadius: BorderRadius.circular(20), ), ), ); } Widget _erreurCamera(MobileScannerException error) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.no_photography_outlined, color: Colors.white70, size: 56), const SizedBox(height: 16), const Text( 'Accès à la caméra impossible.\nAutorisez la caméra dans les réglages.', textAlign: TextAlign.center, style: TextStyle(color: Colors.white70, fontSize: 15), ), const SizedBox(height: 20), FilledButton( onPressed: () => _controller.start(), child: const Text('Réessayer'), ), ], ), ), ); } }