You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
270 lines
8.1 KiB
270 lines
8.1 KiB
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<ScanQRPage> createState() => _ScanQRPageState();
|
|
}
|
|
|
|
class _ScanQRPageState extends State<ScanQRPage> {
|
|
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<Barcode> 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;
|
|
}
|
|
}
|
|
|