feat: Gestion complète des containers et refactorisation du matériel
Ajout de la gestion des containers (création, édition, suppression, affichage des détails).
Introduction d'un système de génération de QR codes unifié et d'un mode de sélection multiple.
**Features:**
- **Gestion des Containers :**
    - Nouvelle page de gestion des containers (`container_management_page.dart`) avec recherche et filtres.
    - Formulaire de création/édition de containers (`container_form_page.dart`) avec génération d'ID automatique.
    - Page de détails d'un container (`container_detail_page.dart`) affichant son contenu et ses caractéristiques.
    - Ajout des routes et du provider (`ContainerProvider`) nécessaires.
- **Modèle de Données :**
    - Ajout du `ContainerModel` pour représenter les boîtes, flight cases, etc.
    - Le modèle `EquipmentModel` a été enrichi avec des caractéristiques physiques (poids, dimensions).
- **QR Codes :**
    - Nouveau service unifié (`UnifiedPDFGeneratorService`) pour générer des PDFs de QR codes pour n'importe quelle entité.
    - Services `PDFGeneratorService` et `ContainerPDFGeneratorService` transformés en wrappers pour maintenir la compatibilité.
    - Amélioration de la performance de la génération de QR codes en masse.
- **Interface Utilisateur (UI/UX) :**
    - Nouvelle page de détails pour le matériel (`equipment_detail_page.dart`).
    - Ajout d'un `SelectionModeMixin` pour gérer la sélection multiple dans les pages de gestion.
    - Dialogues réutilisables pour l'affichage de QR codes (`QRCodeDialog`) et la sélection de format d'impression (`QRCodeFormatSelectorDialog`).
    - Ajout d'un bouton "Gérer les boîtes" sur la page de gestion du matériel.
**Refactorisation :**
- L' `IdGenerator` a été déplacé dans le répertoire `utils` et étendu pour gérer les containers.
- Mise à jour de nombreuses dépendances `pubspec.yaml` vers des versions plus récentes.
- Séparation de la logique d'affichage des containers et du matériel dans des widgets dédiés (`ContainerHeaderCard`, `EquipmentParentContainers`, etc.).
			
			
This commit is contained in:
		
							
								
								
									
										173
									
								
								em2rp/lib/services/qr_code_service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								em2rp/lib/services/qr_code_service.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| 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 (optimisé) | ||||
|   static Future<List<Uint8List>> generateBulkQRCodes( | ||||
|     List<String> dataList, { | ||||
|     double size = 512, | ||||
|     bool withLogo = false, | ||||
|     bool useCache = true, | ||||
|   }) async { | ||||
|     // 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 optimisé selon la taille et le nombre | ||||
|     int batchSize; | ||||
|     if (size <= 200) { | ||||
|       batchSize = 100; // Petits QR : lots de 100 | ||||
|     } else if (size <= 300) { | ||||
|       batchSize = 50;  // Moyens QR : lots de 50 | ||||
|     } else if (size <= 500) { | ||||
|       batchSize = 20;  // Grands QR : lots de 20 | ||||
|     } else { | ||||
|       batchSize = 10;  // Très grands : lots de 10 | ||||
|     } | ||||
|  | ||||
|     final List<Uint8List> results = []; | ||||
|  | ||||
|     for (int i = 0; i < dataList.length; i += batchSize) { | ||||
|       final batch = dataList.skip(i).take(batchSize).toList(); | ||||
|       final batchResults = await Future.wait( | ||||
|         batch.map((data) => withLogo | ||||
|             ? generateQRCodeWithLogo(data, size: size, useCache: useCache) | ||||
|             : generateQRCode(data, size: size, useCache: useCache)), | ||||
|       ); | ||||
|       results.addAll(batchResults); | ||||
|     } | ||||
|  | ||||
|     return results; | ||||
|   } | ||||
|  | ||||
|   /// Vide le cache des QR codes | ||||
|   static void clearCache() { | ||||
|     _qrCache.clear(); | ||||
|   } | ||||
|  | ||||
|   /// Obtient la taille du cache | ||||
|   static int getCacheSize() { | ||||
|     return _qrCache.length; | ||||
|   } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 ElPoyo
					ElPoyo