import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:em2rp/utils/colors.dart'; /// Service pour la génération de QR codes optimisée class QRCodeService { // Cache pour éviter de régénérer les mêmes QR codes static final Map _qrCache = {}; static ui.Image? _cachedLogoImage; /// Génère un QR code simple sans logo static Future generateQRCode( String data, { double size = 512, bool useCache = true, }) async { // Vérifier le cache if (useCache && _qrCache.containsKey(data)) { return _qrCache[data]!; } final qrValidationResult = QrValidator.validate( data: data, version: QrVersions.auto, errorCorrectionLevel: QrErrorCorrectLevel.L, ); if (qrValidationResult.status != QrValidationStatus.valid) { throw Exception('QR code validation failed for data: $data'); } final qrCode = qrValidationResult.qrCode!; final painter = QrPainter.withQr( qr: qrCode, eyeStyle: const QrEyeStyle( eyeShape: QrEyeShape.square, color: AppColors.noir, ), gapless: true, ); final picData = await painter.toImageData(size, format: ui.ImageByteFormat.png); final bytes = picData!.buffer.asUint8List(); // Mettre en cache if (useCache) { _qrCache[data] = bytes; } return bytes; } /// Génère un QR code avec logo embarqué static Future generateQRCodeWithLogo( String data, { double size = 512, bool useCache = true, }) async { final cacheKey = '${data}_logo'; // Vérifier le cache if (useCache && _qrCache.containsKey(cacheKey)) { return _qrCache[cacheKey]!; } final qrValidationResult = QrValidator.validate( data: data, version: QrVersions.auto, errorCorrectionLevel: QrErrorCorrectLevel.L, ); if (qrValidationResult.status != QrValidationStatus.valid) { throw Exception('QR code validation failed for data: $data'); } final qrCode = qrValidationResult.qrCode!; final embedded = await _loadLogoImage(); final painter = QrPainter.withQr( qr: qrCode, embeddedImage: embedded, embeddedImageStyle: const QrEmbeddedImageStyle( size: Size(80, 80), ), gapless: true, ); final picData = await painter.toImageData(size, format: ui.ImageByteFormat.png); final bytes = picData!.buffer.asUint8List(); // Mettre en cache if (useCache) { _qrCache[cacheKey] = bytes; } return bytes; } /// Charge le logo depuis les assets (avec cache) static Future _loadLogoImage() async { if (_cachedLogoImage != null) { return _cachedLogoImage!; } final data = await rootBundle.load('assets/logos/SquareLogoBlack.png'); final codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); final frame = await codec.getNextFrame(); _cachedLogoImage = frame.image; return _cachedLogoImage!; } /// Génère plusieurs QR codes en parallèle par batches optimisés (réduit les lags) static Future> generateBulkQRCodes( List dataList, { double size = 512, bool withLogo = false, bool useCache = true, Function(int, int)? onProgress, // Callback de progression }) async { if (dataList.isEmpty) return []; // Si tout est en cache, retourner immédiatement if (useCache) { final allCached = dataList.every((data) { final key = withLogo ? '${data}_logo' : data; return _qrCache.containsKey(key); }); if (allCached) { return dataList.map((data) { final key = withLogo ? '${data}_logo' : data; return _qrCache[key]!; }).toList(); } } // Batching adaptatif ultra-optimisé int batchSize; if (dataList.length <= 10) { // Pour petites quantités, tout en une fois batchSize = dataList.length; } else if (size <= 200) { batchSize = 20; // Petits QR : lots de 20 (réduit de 100) } else if (size <= 300) { batchSize = 10; // Moyens QR : lots de 10 (réduit de 50) } else if (size <= 400) { batchSize = 5; // Grands QR : lots de 5 (réduit de 20) } else { batchSize = 3; // Très grands : lots de 3 (réduit de 10) } final List results = []; int processed = 0; for (int i = 0; i < dataList.length; i += batchSize) { final batch = dataList.skip(i).take(batchSize).toList(); // Générer le batch final batchResults = await Future.wait( batch.map((data) => withLogo ? generateQRCodeWithLogo(data, size: size, useCache: useCache) : generateQRCode(data, size: size, useCache: useCache)), ); results.addAll(batchResults); processed += batch.length; // Callback de progression if (onProgress != null) { onProgress(processed, dataList.length); } // Pause micro pour éviter de bloquer l'UI (seulement pour gros batches) if (batchSize >= 5 && i + batchSize < dataList.length) { await Future.delayed(const Duration(milliseconds: 10)); } } return results; } /// Vide le cache des QR codes static void clearCache() { _qrCache.clear(); } /// Obtient la taille du cache static int getCacheSize() { return _qrCache.length; } }