Files
EM2_ERP/em2rp/lib/services/qr_code_service.dart

195 lines
5.4 KiB
Dart

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<String, Uint8List> _qrCache = {};
static ui.Image? _cachedLogoImage;
/// Génère un QR code simple sans logo
static Future<Uint8List> 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<Uint8List> 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<ui.Image> _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<List<Uint8List>> generateBulkQRCodes(
List<String> 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<Uint8List> 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;
}
}