import 'dart:io'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class ScanQRPage extends StatefulWidget { const ScanQRPage({super.key}); @override State createState() => _ScanQRPageState(); } class _ScanQRPageState extends State { MobileScannerController? cameraController; bool _isScanComplete = false; String? _scannedData; bool _hasError = false; String? _errorMessage; bool get isMobile => !kIsWeb && (Platform.isAndroid || Platform.isIOS); @override void initState() { super.initState(); _initializeController(); } void _initializeController() { // if (!isMobile) { // setState(() { // _hasError = true; // _errorMessage = // "Le scanner QR n'est pas disponible sur cette plateforme."; // }); // return; // } try { cameraController = MobileScannerController( detectionSpeed: DetectionSpeed.noDuplicates, facing: CameraFacing.back, torchEnabled: false, ); setState(() { _hasError = false; _errorMessage = null; }); } catch (e) { setState(() { _hasError = true; _errorMessage = 'Erreur d\'initialisation de la caméra: $e'; }); } } @override void dispose() { cameraController?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Scanner QR Code'), actions: _hasError ? [] : [ if (cameraController != null) ...[ IconButton( color: Colors.white, icon: const Icon(Icons.flash_on, color: Colors.white), iconSize: 32.0, onPressed: () => cameraController!.toggleTorch(), ), IconButton( color: Colors.white, icon: const Icon(Icons.flip_camera_ios, color: Colors.white), iconSize: 32.0, onPressed: () => cameraController!.switchCamera(), ), ], ], ), body: _hasError ? _buildErrorWidget() : _buildScannerWidget(), ); } Widget _buildErrorWidget() { return Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, size: 64, color: Colors.red, ), const SizedBox(height: 16), const Text( 'Erreur de caméra', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( _errorMessage ?? 'Une erreur s\'est produite', textAlign: TextAlign.center, style: const TextStyle(fontSize: 16), ), const SizedBox(height: 24), ElevatedButton( onPressed: () { _initializeController(); }, child: const Text('Réessayer'), ), const SizedBox(height: 16), const Text( 'Vérifiez que:\n• Le plugin mobile_scanner est installé\n• Les permissions de caméra sont accordées\n• Votre appareil a une caméra fonctionnelle', textAlign: TextAlign.center, style: TextStyle(fontSize: 14, color: Colors.grey), ), ], ), ), ); } Widget _buildScannerWidget() { if (cameraController == null) { return const Center(child: CircularProgressIndicator()); } return Stack( children: [ MobileScanner( controller: cameraController!, onDetect: (capture) { final List barcodes = capture.barcodes; for (final barcode in barcodes) { if (!_isScanComplete && barcode.rawValue != null) { _isScanComplete = true; _scannedData = barcode.rawValue; _showScanResult(context, _scannedData!); } } }, errorBuilder: (context, error, child) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error, size: 64, color: Colors.red), const SizedBox(height: 16), Text( 'Erreur: ${error.errorDetails?.message ?? 'Erreur inconnue'}'), const SizedBox(height: 16), ElevatedButton( onPressed: () => _initializeController(), child: const Text('Réessayer'), ), ], ), ); }, ), CustomPaint( painter: QrScannerOverlay( borderColor: Colors.blue.shade800, ), ), ], ); } void _showScanResult(BuildContext context, String data) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Résultat du scan'), content: SelectableText(data), // Permet de sélectionner le texte actions: [ TextButton( onPressed: () { Navigator.pop(context); setState(() { _isScanComplete = false; }); }, child: const Text('Fermer'), ), TextButton( onPressed: () { Navigator.pop(context); Navigator.pop(context, data); // Retourner la donnée scannée }, child: const Text('Utiliser'), ), ], ), ).then((_) { setState(() { _isScanComplete = false; }); }); } } class QrScannerOverlay extends CustomPainter { final Color borderColor; QrScannerOverlay({required this.borderColor}); @override void paint(Canvas canvas, Size size) { final double width = size.width; final double height = size.height; final double borderWidth = 2.0; final double borderLength = 30.0; final double areaSize = width * 0.7; final Paint backgroundPaint = Paint() ..color = Colors.black.withOpacity(0.4); canvas.drawRect(Rect.fromLTRB(0, 0, width, height), backgroundPaint); final Paint transparentPaint = Paint() ..color = Colors.transparent ..blendMode = BlendMode.clear; final double areaLeft = (width - areaSize) / 2; final double areaTop = (height - areaSize) / 2; canvas.drawRect( Rect.fromLTRB(areaLeft, areaTop, areaLeft + areaSize, areaTop + areaSize), transparentPaint, ); final Paint borderPaint = Paint() ..color = borderColor ..strokeWidth = borderWidth ..style = PaintingStyle.stroke; // Coins du scanner _drawCorner( canvas, borderPaint, areaLeft, areaTop, borderLength, true, true); _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop, borderLength, false, true); _drawCorner(canvas, borderPaint, areaLeft, areaTop + areaSize, borderLength, true, false); _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop + areaSize, borderLength, false, false); } void _drawCorner(Canvas canvas, Paint paint, double x, double y, double length, bool isLeft, bool isTop) { final double horizontalStart = isLeft ? x : x - length; final double horizontalEnd = isLeft ? x + length : x; final double verticalStart = isTop ? y : y - length; final double verticalEnd = isTop ? y + length : y; canvas.drawLine( Offset(horizontalStart, y), Offset(horizontalEnd, y), paint); canvas.drawLine(Offset(x, verticalStart), Offset(x, verticalEnd), paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }