feat: Introduce PDFService for optimized PDF generation and caching in container and equipment management
This commit is contained in:
@@ -7,7 +7,7 @@ import 'package:em2rp/views/widgets/nav/custom_app_bar.dart';
|
||||
import 'package:em2rp/providers/container_provider.dart';
|
||||
import 'package:em2rp/models/container_model.dart';
|
||||
import 'package:em2rp/models/equipment_model.dart';
|
||||
import 'package:em2rp/services/container_pdf_generator_service.dart';
|
||||
import 'package:em2rp/services/pdf_service.dart';
|
||||
import 'package:em2rp/views/widgets/common/qr_code_dialog.dart';
|
||||
import 'package:em2rp/mixins/selection_mode_mixin.dart';
|
||||
import 'package:printing/printing.dart';
|
||||
@@ -26,6 +26,7 @@ class _ContainerManagementPageState extends State<ContainerManagementPage>
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
ContainerType? _selectedType;
|
||||
EquipmentStatus? _selectedStatus;
|
||||
List<ContainerModel>? _cachedContainers; // Cache pour éviter le rebuild
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -321,7 +322,13 @@ class _ContainerManagementPageState extends State<ContainerManagementPage>
|
||||
return StreamBuilder<List<ContainerModel>>(
|
||||
stream: provider.containersStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
// Utiliser les données en cache si disponibles pendant le rebuild
|
||||
if (snapshot.hasData) {
|
||||
_cachedContainers = snapshot.data;
|
||||
}
|
||||
|
||||
// Afficher le loader seulement au premier chargement
|
||||
if (snapshot.connectionState == ConnectionState.waiting && _cachedContainers == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
@@ -331,7 +338,7 @@ class _ContainerManagementPageState extends State<ContainerManagementPage>
|
||||
);
|
||||
}
|
||||
|
||||
final containers = snapshot.data ?? [];
|
||||
final containers = _cachedContainers ?? snapshot.data ?? [];
|
||||
|
||||
if (containers.isEmpty) {
|
||||
return Center(
|
||||
@@ -635,7 +642,7 @@ class _ContainerManagementPageState extends State<ContainerManagementPage>
|
||||
}
|
||||
|
||||
// Afficher le dialogue de sélection de format
|
||||
final format = await showDialog<ContainerQRLabelFormat>(
|
||||
final format = await showDialog<QRLabelFormat>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Format des étiquettes'),
|
||||
@@ -646,19 +653,19 @@ class _ContainerManagementPageState extends State<ContainerManagementPage>
|
||||
leading: const Icon(Icons.qr_code_2),
|
||||
title: const Text('Petits QR codes'),
|
||||
subtitle: const Text('2×2 cm - QR code + ID (20 par page)'),
|
||||
onTap: () => Navigator.pop(context, ContainerQRLabelFormat.small),
|
||||
onTap: () => Navigator.pop(context, QRLabelFormat.small),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.qr_code),
|
||||
title: const Text('QR codes moyens'),
|
||||
subtitle: const Text('4×4 cm - QR code + ID (6 par page)'),
|
||||
onTap: () => Navigator.pop(context, ContainerQRLabelFormat.medium),
|
||||
onTap: () => Navigator.pop(context, QRLabelFormat.medium),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.label),
|
||||
title: const Text('Grandes étiquettes'),
|
||||
subtitle: const Text('QR code + ID + Type + Contenu (6 par page)'),
|
||||
onTap: () => Navigator.pop(context, ContainerQRLabelFormat.large),
|
||||
subtitle: const Text('QR code + ID + Type + Contenu (10 par page)'),
|
||||
onTap: () => Navigator.pop(context, QRLabelFormat.large),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -673,12 +680,21 @@ class _ContainerManagementPageState extends State<ContainerManagementPage>
|
||||
|
||||
if (format == null || !mounted) return;
|
||||
|
||||
// Générer et afficher le PDF
|
||||
// Générer et afficher le PDF avec la nouvelle API optimisée
|
||||
try {
|
||||
final pdfBytes = await ContainerPDFGeneratorService.generateQRCodesPDF(
|
||||
containerList: selectedContainers,
|
||||
containerEquipmentMap: containerEquipmentMap,
|
||||
final pdfBytes = await PDFService.generatePDF<ContainerModel>(
|
||||
items: selectedContainers,
|
||||
format: format,
|
||||
getId: (c) => c.id,
|
||||
getTitle: (c) => c.name,
|
||||
getDetails: format == QRLabelFormat.large ? (ContainerModel c) {
|
||||
final equipment = containerEquipmentMap[c.id] ?? <EquipmentModel>[];
|
||||
return [
|
||||
'Contenu (${equipment.length}):',
|
||||
...equipment.take(5).map((eq) => '- ${eq.id}'),
|
||||
if (equipment.length > 5) '... +${equipment.length - 5}',
|
||||
];
|
||||
} : null,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
|
||||
@@ -25,6 +25,7 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
|
||||
with SelectionModeMixin<EquipmentManagementPage> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
EquipmentCategory? _selectedCategory;
|
||||
List<EquipmentModel>? _cachedEquipment;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -420,7 +421,13 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
|
||||
return StreamBuilder<List<EquipmentModel>>(
|
||||
stream: provider.equipmentStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
// Mettre en cache les données quand elles arrivent
|
||||
if (snapshot.hasData) {
|
||||
_cachedEquipment = snapshot.data;
|
||||
}
|
||||
|
||||
// Afficher le loader seulement si on n'a pas encore de cache
|
||||
if (snapshot.connectionState == ConnectionState.waiting && _cachedEquipment == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
@@ -430,7 +437,10 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
|
||||
);
|
||||
}
|
||||
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
// Utiliser le cache si disponible, sinon les nouvelles données
|
||||
final equipment = _cachedEquipment ?? snapshot.data ?? [];
|
||||
|
||||
if (equipment.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -452,15 +462,15 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
|
||||
);
|
||||
}
|
||||
|
||||
// Tri par nom
|
||||
final equipment = snapshot.data!;
|
||||
equipment.sort((a, b) => a.name.compareTo(b.name));
|
||||
// Créer une copie pour le tri
|
||||
final sortedEquipment = List<EquipmentModel>.from(equipment);
|
||||
sortedEquipment.sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: equipment.length,
|
||||
itemCount: sortedEquipment.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildEquipmentCard(equipment[index]);
|
||||
return _buildEquipmentCard(sortedEquipment[index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:em2rp/utils/colors.dart';
|
||||
import 'package:em2rp/models/equipment_model.dart';
|
||||
import 'package:em2rp/services/pdf_generator_service.dart';
|
||||
import 'package:em2rp/services/pdf_service.dart';
|
||||
import 'package:printing/printing.dart';
|
||||
|
||||
/// Widget réutilisable pour sélectionner le format de génération de QR codes multiples
|
||||
@@ -157,10 +157,25 @@ class QRCodeFormatSelectorDialog extends StatelessWidget {
|
||||
);
|
||||
|
||||
try {
|
||||
// Génération du PDF
|
||||
final pdfBytes = await PDFGeneratorService.generateQRCodesPDF(
|
||||
equipmentList: equipmentList,
|
||||
// Génération du PDF avec progression
|
||||
final pdfBytes = await PDFService.generatePDF<EquipmentModel>(
|
||||
items: equipmentList,
|
||||
format: format,
|
||||
getId: (eq) => eq.id,
|
||||
getTitle: (eq) => '${eq.brand ?? ''} ${eq.model ?? ''}'.trim(),
|
||||
getDetails: format == QRLabelFormat.large ? (EquipmentModel eq) {
|
||||
final details = <String>[];
|
||||
final brand = eq.brand;
|
||||
if (brand != null && brand.isNotEmpty) {
|
||||
details.add('Marque: $brand');
|
||||
}
|
||||
final model = eq.model;
|
||||
if (model != null && model.isNotEmpty) {
|
||||
details.add('Modèle: $model');
|
||||
}
|
||||
details.add('Catégorie: ${eq.category.label}');
|
||||
return details;
|
||||
} : null,
|
||||
);
|
||||
|
||||
// Fermer le dialogue de chargement
|
||||
|
||||
Reference in New Issue
Block a user