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.
 
 
 
 
 
 

1090 lines
35 KiB

import 'dart:typed_data';
import 'dart:io' show Platform, File;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
import 'package:qr_flutter/qr_flutter.dart';
// Classe pour gérer les formats d'étiquettes Niimbot
class NiimbotLabelSize {
final double width;
final double height;
final String name;
const NiimbotLabelSize(this.width, this.height, this.name);
// Formats courants Niimbot B1
static const small = NiimbotLabelSize(50, 15, "Petit (50x15mm)");
static const medium = NiimbotLabelSize(40, 30, "Moyen (40x30mm)");
static const large = NiimbotLabelSize(50, 30, "Grand (50x30mm)");
PdfPageFormat get pageFormat => PdfPageFormat(
width * PdfPageFormat.mm,
height * PdfPageFormat.mm,
marginAll: 0,
);
}
class PdfPrintService {
pw.Font? robotoRegular;
pw.Font? robotoBold;
PdfPrintService();
/// Charge les polices Unicode
Future<void> loadFonts() async {
if (robotoRegular != null && robotoBold != null) return;
try {
// Charger les polices depuis les assets ou utiliser les polices système
final regularData = await rootBundle.load('assets/fonts/Roboto-Regular.ttf');
final boldData = await rootBundle.load('assets/fonts/Roboto-Bold.ttf');
robotoRegular = pw.Font.ttf(regularData);
robotoBold = pw.Font.ttf(boldData);
} catch (e) {
print('Erreur chargement polices personnalisées: $e');
// Fallback vers les polices par défaut
try {
robotoRegular = await PdfGoogleFonts.robotoRegular();
robotoBold = await PdfGoogleFonts.robotoBold();
} catch (e2) {
print('Erreur chargement polices Google: $e2');
// Utiliser les polices système par défaut
robotoRegular = null;
robotoBold = null;
}
}
}
/// Génère une image QR optimisée
Future<Uint8List> _generateQrImage(String data, {double size = 512}) async {
final qrValidation = QrValidator.validate(
data: data,
version: QrVersions.auto,
errorCorrectionLevel: QrErrorCorrectLevel.M,
);
if (qrValidation.status != QrValidationStatus.valid) {
throw Exception('QR code invalide: ${qrValidation.error}');
}
final qrCode = qrValidation.qrCode!;
final painter = QrPainter.withQr(
qr: qrCode,
color: Colors.black,
emptyColor: Colors.white,
gapless: true,
embeddedImageStyle: null,
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Colors.black,
),
dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Colors.black,
),
);
final picData = await painter.toImageData(
size,
format: ImageByteFormat.png
);
return picData!.buffer.asUint8List();
}
/// Génère le PDF optimisé pour l'impression d'étiquettes
Future<Uint8List> generateQrPdf(String data, {
String? productName,
String? reference,
bool isLabelFormat = true,
}) async {
await loadFonts();
final qrImageData = await _generateQrImage(data, size: 300);
final qrImage = pw.MemoryImage(qrImageData);
final pdf = pw.Document();
if (isLabelFormat) {
// Format étiquette (petit format)
pdf.addPage(
pw.Page(
pageFormat: const PdfPageFormat(
2.5 * PdfPageFormat.inch,
1.5 * PdfPageFormat.inch,
marginAll: 0,
),
build: (context) => _buildLabelContent(qrImage, data, productName, reference),
),
);
} else {
// Format A4 standard
pdf.addPage(
pw.Page(
pageFormat: PdfPageFormat.a4,
margin: const pw.EdgeInsets.all(20),
build: (context) => _buildA4Content(qrImage, data, productName, reference),
),
);
}
return pdf.save();
}
/// Contenu pour format étiquette
pw.Widget _buildLabelContent(
pw.MemoryImage qrImage,
String data,
String? productName,
String? reference,
) {
return pw.Container(
width: double.infinity,
height: double.infinity,
padding: const pw.EdgeInsets.all(4),
child: pw.Center(
child: pw.Column(
mainAxisAlignment: pw.MainAxisAlignment.center,
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// QR Code principal
pw.Container(
width: 70,
height: 70,
child: pw.Center(
child: pw.Image(
qrImage,
width: 70,
height: 70,
fit: pw.BoxFit.contain,
),
),
),
pw.SizedBox(height: 4),
// Informations textuelles centrées
if (reference != null) ...[
pw.Container(
width: double.infinity,
child: pw.Text(
reference,
style: _getTextStyle(fontSize: 8, bold: true),
textAlign: pw.TextAlign.center,
maxLines: 1,
),
),
pw.SizedBox(height: 2),
],
if (productName != null && productName.isNotEmpty) ...[
pw.Container(
width: double.infinity,
child: pw.Text(
productName.length > 20 ? '${productName.substring(0, 17)}...' : productName,
style: _getTextStyle(fontSize: 6),
textAlign: pw.TextAlign.center,
maxLines: 1,
),
),
],
],
),
),
);
}
/// Contenu pour format A4
pw.Widget _buildA4Content(
pw.MemoryImage qrImage,
String data,
String? productName,
String? reference,
) {
return pw.Center(
child: pw.Column(
mainAxisAlignment: pw.MainAxisAlignment.center,
children: [
if (productName != null) ...[
pw.Text(
productName,
style: _getTextStyle(fontSize: 18, bold: true),
textAlign: pw.TextAlign.center,
),
pw.SizedBox(height: 20),
],
pw.Image(
qrImage,
width: 200,
height: 200,
fit: pw.BoxFit.contain,
),
pw.SizedBox(height: 20),
if (reference != null) ...[
pw.Text(
'Référence: $reference',
style: _getTextStyle(fontSize: 14, bold: true),
),
pw.SizedBox(height: 10),
],
pw.Text(
'URL: $data',
style: _getTextStyle(fontSize: 12),
textAlign: pw.TextAlign.center,
),
],
),
);
}
/// Obtient le style de texte approprié
pw.TextStyle _getTextStyle({double fontSize = 12, bool bold = false}) {
if (bold && robotoBold != null) {
return pw.TextStyle(font: robotoBold!, fontSize: fontSize);
} else if (!bold && robotoRegular != null) {
return pw.TextStyle(font: robotoRegular!, fontSize: fontSize);
} else {
return pw.TextStyle(fontSize: fontSize, fontWeight: bold ? pw.FontWeight.bold : pw.FontWeight.normal);
}
}
/// Imprime le QR code avec options
Future<void> printQr(
String data, {
String? productName,
String? reference,
bool isLabelFormat = true,
bool showPreview = true,
}) async {
try {
final pdfBytes = await generateQrPdf(
data,
productName: productName,
reference: reference,
isLabelFormat: isLabelFormat,
);
if (Platform.isWindows || Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
await Printing.layoutPdf(
onLayout: (format) async => pdfBytes,
name: 'QR_Code_${reference ?? 'etiquette'}',
format: isLabelFormat ?
const PdfPageFormat(
2.5 * PdfPageFormat.inch,
1.5 * PdfPageFormat.inch,
marginAll: 0,
) : PdfPageFormat.a4,
usePrinterSettings: false,
);
Get.snackbar(
'Succès',
'QR Code envoyé à l\'imprimante',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
} else {
Get.snackbar(
'Info',
'Impression non supportée sur cette plateforme',
backgroundColor: Colors.orange,
colorText: Colors.white,
);
}
} catch (e) {
print('Erreur impression: $e');
Get.snackbar(
'Erreur',
'Impossible d\'imprimer: ${e.toString()}',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
}
}
// ===== NOUVELLES MÉTHODES OPTIMISÉES POUR NIIMBOT B1 =====
Future<void> printQrNiimbotOptimized(
String data, {
String? productName,
String? reference,
double leftPadding = 1.0,
double topPadding = 0.5,
double qrSize = 11.5, // légèrement plus petit pour laisser la place au texte
double fontSize = 5.0,
NiimbotLabelSize labelSize = NiimbotLabelSize.small,
}) async {
try {
await loadFonts();
// Générer le QR code en image mémoire
final qrImageData = await _generateQrImage(data, size: 110);
final qrImage = pw.MemoryImage(qrImageData);
final pdf = pw.Document();
pdf.addPage(
pw.Page(
pageFormat: labelSize.pageFormat,
margin: pw.EdgeInsets.zero,
build: (context) {
return pw.Container(
width: double.infinity,
height: double.infinity,
padding: pw.EdgeInsets.only(
left: leftPadding * PdfPageFormat.mm,
top: topPadding * PdfPageFormat.mm,
right: 1 * PdfPageFormat.mm,
bottom: 0.3 * PdfPageFormat.mm, // bordure minimale
),
child: pw.Column(
mainAxisAlignment: pw.MainAxisAlignment.start,
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// --- QR code ---
pw.Container(
width: qrSize * PdfPageFormat.mm,
height: qrSize * PdfPageFormat.mm,
child: pw.Image(qrImage, fit: pw.BoxFit.contain),
),
// --- Référence directement sous le QR ---
if (reference != null && reference.isNotEmpty)
pw.Padding(
padding: pw.EdgeInsets.only(top: 0.3 * PdfPageFormat.mm),
child: pw.Text(
reference,
style: pw.TextStyle(
fontSize: fontSize,
fontWeight: pw.FontWeight.bold,
),
textAlign: pw.TextAlign.center,
maxLines: 1,
overflow: pw.TextOverflow.clip,
),
),
],
),
);
},
),
);
final pdfBytes = await pdf.save();
// Impression via Printing
await Printing.layoutPdf(
onLayout: (format) async => pdfBytes,
name: 'QR_${reference ?? DateTime.now().millisecondsSinceEpoch}',
format: labelSize.pageFormat,
);
Get.snackbar(
'Succès',
'QR imprimé (${labelSize.name}) avec référence visible',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
} catch (e) {
print('Erreur impression Niimbot: $e');
Get.snackbar(
'Erreur',
'Impression échouée: ${e.toString()}',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
}
}
/// Méthode de calibrage pour trouver le décalage optimal
Future<void> printNiimbotCalibration({
NiimbotLabelSize labelSize = NiimbotLabelSize.small,
}) async {
try {
final pdf = pw.Document();
pdf.addPage(
pw.Page(
pageFormat: labelSize.pageFormat,
build: (context) => pw.Container(
width: double.infinity,
height: double.infinity,
decoration: pw.BoxDecoration(
border: pw.Border.all(width: 0.5, color: PdfColors.black),
),
child: pw.Stack(
children: [
// Grille de référence
pw.Positioned(
left: 0,
top: 0,
child: pw.Container(
width: labelSize.width * PdfPageFormat.mm,
height: labelSize.height * PdfPageFormat.mm,
child: pw.CustomPaint(
painter: (PdfGraphics canvas, PdfPoint size) {
// Lignes verticales tous les 5mm
for (int i = 5; i < labelSize.width; i += 5) {
canvas.drawLine(
i * PdfPageFormat.mm,
0,
i * PdfPageFormat.mm,
labelSize.height * PdfPageFormat.mm,
);
}
// Lignes horizontales tous les 5mm
for (int i = 5; i < labelSize.height; i += 5) {
canvas.drawLine(
0,
i * PdfPageFormat.mm,
labelSize.width * PdfPageFormat.mm,
i * PdfPageFormat.mm,
);
}
},
),
),
),
// Marqueur centre
pw.Positioned(
left: (labelSize.width / 2 - 3) * PdfPageFormat.mm,
top: (labelSize.height / 2 - 3) * PdfPageFormat.mm,
child: pw.Container(
width: 6 * PdfPageFormat.mm,
height: 6 * PdfPageFormat.mm,
color: PdfColors.black,
child: pw.Center(
child: pw.Text(
'C',
style: pw.TextStyle(
color: PdfColors.white,
fontSize: 5,
fontWeight: pw.FontWeight.bold,
),
),
),
),
),
// Coin haut-gauche
pw.Positioned(
left: 1 * PdfPageFormat.mm,
top: 1 * PdfPageFormat.mm,
child: pw.Container(
width: 4 * PdfPageFormat.mm,
height: 4 * PdfPageFormat.mm,
color: PdfColors.black,
child: pw.Center(
child: pw.Text(
'1',
style: pw.TextStyle(
color: PdfColors.white,
fontSize: 3,
fontWeight: pw.FontWeight.bold,
),
),
),
),
),
// Titre
pw.Positioned(
left: 1 * PdfPageFormat.mm,
bottom: 1 * PdfPageFormat.mm,
child: pw.Text(
'CALIBRAGE ${labelSize.name}',
style: _getTextStyle(fontSize: 3, bold: true),
),
),
],
),
),
),
);
final pdfBytes = await pdf.save();
await Printing.layoutPdf(
onLayout: (format) async => pdfBytes,
name: 'Calibrage_Niimbot_${DateTime.now().millisecondsSinceEpoch}',
format: labelSize.pageFormat,
usePrinterSettings: false,
);
Get.snackbar(
'Calibrage',
'Page de calibrage envoyée (${labelSize.name})',
backgroundColor: Colors.blue,
colorText: Colors.white,
duration: const Duration(seconds: 4),
);
} catch (e) {
Get.snackbar(
'Erreur calibrage',
'Impossible d\'imprimer: $e',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
/// Méthode avec paramètres ajustables pour corriger le décalage
Future<void> printQrNiimbotWithOffset(
String data, {
String? productName,
String? reference,
double offsetX = 0.0,
double offsetY = 0.0,
double scale = 1.0,
NiimbotLabelSize labelSize = NiimbotLabelSize.small,
}) async {
try {
await loadFonts();
final qrImageData = await _generateQrImage(data, size: 120);
final qrImage = pw.MemoryImage(qrImageData);
final pdf = pw.Document();
pdf.addPage(
pw.Page(
pageFormat: labelSize.pageFormat,
build: (context) => pw.Container(
width: double.infinity,
height: double.infinity,
child: pw.Stack(
children: [
pw.Positioned(
left: (labelSize.width / 2 + offsetX) * PdfPageFormat.mm - (15 * scale) * PdfPageFormat.mm / 2,
top: (labelSize.height / 2 + offsetY) * PdfPageFormat.mm - (15 * scale) * PdfPageFormat.mm / 2,
child: pw.Transform(
transform: Matrix4.identity()..scale(scale),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
pw.Container(
width: 12 * PdfPageFormat.mm,
height: 12 * PdfPageFormat.mm,
child: pw.Image(
qrImage,
fit: pw.BoxFit.contain,
),
),
if (reference != null && reference.isNotEmpty) ...[
pw.SizedBox(height: 0.5 * PdfPageFormat.mm),
pw.Container(
width: 20 * PdfPageFormat.mm,
child: pw.Text(
reference.length > 12 ? '${reference.substring(0, 12)}...' : reference,
style: _getTextStyle(fontSize: 4, bold: true),
textAlign: pw.TextAlign.center,
maxLines: 1,
),
),
],
],
),
),
),
],
),
),
),
);
final pdfBytes = await pdf.save();
await Printing.layoutPdf(
onLayout: (format) async => pdfBytes,
name: 'QR_Offset_X${offsetX}_Y${offsetY}_S${scale}',
format: labelSize.pageFormat,
usePrinterSettings: false,
);
Get.snackbar(
'Test décalage',
'QR imprimé avec décalage X:${offsetX}mm Y:${offsetY}mm Scale:${scale}',
backgroundColor: Colors.orange,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
} catch (e) {
Get.snackbar(
'Erreur test',
'Impossible d\'imprimer: $e',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
/// Widget de dialogue pour sélectionner le format d'étiquette
void showNiimbotFormatDialog(Function(NiimbotLabelSize) onFormatSelected) {
Get.dialog(
AlertDialog(
title: Row(
children: [
Icon(Icons.straighten, color: Colors.orange[700]),
const SizedBox(width: 8),
const Text('Taille d\'étiquette'),
],
),
content: Container(
width: 300,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Sélectionnez la taille de vos étiquettes Niimbot B1 :',
style: TextStyle(fontSize: 14, color: Colors.grey.shade700),
),
const SizedBox(height: 16),
// Option Petit
_buildFormatOption(
size: NiimbotLabelSize.small,
icon: Icons.label_outline,
color: Colors.green,
onTap: () {
Get.back();
onFormatSelected(NiimbotLabelSize.small);
},
),
const SizedBox(height: 8),
// Option Moyen
_buildFormatOption(
size: NiimbotLabelSize.medium,
icon: Icons.label,
color: Colors.orange,
onTap: () {
Get.back();
onFormatSelected(NiimbotLabelSize.medium);
},
),
const SizedBox(height: 8),
// Option Grand
_buildFormatOption(
size: NiimbotLabelSize.large,
icon: Icons.label_important,
color: Colors.red,
onTap: () {
Get.back();
onFormatSelected(NiimbotLabelSize.large);
},
),
],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('Annuler'),
),
],
),
);
}
Widget _buildFormatOption({
required NiimbotLabelSize size,
required IconData icon,
required Color color,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: color.withOpacity(0.5)),
borderRadius: BorderRadius.circular(8),
color: color.withOpacity(0.1),
),
child: Row(
children: [
Icon(icon, color: color, size: 32),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
size.name,
style: TextStyle(
fontWeight: FontWeight.bold,
color: color.withOpacity(0.7),
fontSize: 14,
),
),
Text(
'${size.width.toInt()}mm x ${size.height.toInt()}mm',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
Icon(Icons.arrow_forward_ios, size: 16, color: color),
],
),
),
);
}
/// Widget de dialogue pour ajuster les paramètres d'impression
void showNiimbotSettingsDialog() {
final offsetXController = TextEditingController(text: '0.0');
final offsetYController = TextEditingController(text: '0.0');
final scaleController = TextEditingController(text: '1.0');
final qrSizeController = TextEditingController(text: '12.0');
// Variable pour stocker la taille sélectionnée
Rx<NiimbotLabelSize> selectedSize = NiimbotLabelSize.small.obs;
Get.dialog(
Obx(() => AlertDialog(
title: Row(
children: [
Icon(Icons.settings, color: Colors.blue.shade700),
const SizedBox(width: 8),
const Text('Paramètres Niimbot B1'),
],
),
content: Container(
width: 400,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Sélection de format
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Format d\'étiquette :',
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.orange.shade700),
),
const SizedBox(height: 8),
DropdownButton<NiimbotLabelSize>(
value: selectedSize.value,
isExpanded: true,
items: [
DropdownMenuItem(value: NiimbotLabelSize.small, child: Text(NiimbotLabelSize.small.name)),
DropdownMenuItem(value: NiimbotLabelSize.medium, child: Text(NiimbotLabelSize.medium.name)),
DropdownMenuItem(value: NiimbotLabelSize.large, child: Text(NiimbotLabelSize.large.name)),
],
onChanged: (value) {
if (value != null) {
selectedSize.value = value;
}
},
),
],
),
),
const SizedBox(height: 16),
// Instructions
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Conseils pour corriger le décalage :',
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue.shade700),
),
const SizedBox(height: 4),
Text('• Décalage X : + = droite, - = gauche', style: TextStyle(fontSize: 12)),
Text('• Décalage Y : + = bas, - = haut', style: TextStyle(fontSize: 12)),
Text('• Échelle : 0.9 = plus petit, 1.1 = plus grand', style: TextStyle(fontSize: 12)),
],
),
),
const SizedBox(height: 16),
// Champs de saisie
Row(
children: [
Expanded(
child: TextField(
controller: offsetXController,
decoration: const InputDecoration(
labelText: 'Décalage X (mm)',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: offsetYController,
decoration: const InputDecoration(
labelText: 'Décalage Y (mm)',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: TextField(
controller: scaleController,
decoration: const InputDecoration(
labelText: 'Échelle',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: qrSizeController,
decoration: const InputDecoration(
labelText: 'Taille QR (mm)',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
),
),
],
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('Fermer'),
),
TextButton(
onPressed: () async {
await printNiimbotCalibration(labelSize: selectedSize.value);
},
child: const Text('Calibrage'),
style: TextButton.styleFrom(foregroundColor: Colors.blue),
),
ElevatedButton(
onPressed: () async {
final offsetX = double.tryParse(offsetXController.text) ?? 0.0;
final offsetY = double.tryParse(offsetYController.text) ?? 0.0;
final scale = double.tryParse(scaleController.text) ?? 1.0;
Get.back();
await printQrNiimbotWithOffset(
'https://stock.guycom.mg/TEST_${DateTime.now().millisecondsSinceEpoch}',
reference: 'TEST-${DateTime.now().millisecond}',
offsetX: offsetX,
offsetY: offsetY,
scale: scale,
labelSize: selectedSize.value,
);
},
child: const Text('Test'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
),
),
],
)),
);
}
// ===== MÉTHODES ORIGINALES (CONSERVÉES POUR COMPATIBILITÉ) =====
/// Méthode spécialisée pour imprimantes Niimbot (ancienne version)
Future<void> printQrNiimbot(
String data, {
String? productName,
String? reference,
}) async {
try {
await loadFonts();
final qrImageData = await _generateQrImage(data, size: 200);
final qrImage = pw.MemoryImage(qrImageData);
final pdf = pw.Document();
// Format spécifique Niimbot : 50mm x 30mm
pdf.addPage(
pw.Page(
pageFormat: const PdfPageFormat(
50 * PdfPageFormat.mm,
30 * PdfPageFormat.mm,
marginAll: 0,
),
build: (context) => pw.Container(
width: double.infinity,
height: double.infinity,
child: pw.Center(
child: pw.Column(
mainAxisAlignment: pw.MainAxisAlignment.center,
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// QR Code centré
pw.Container(
width: 20 * PdfPageFormat.mm,
height: 20 * PdfPageFormat.mm,
child: pw.Image(
qrImage,
fit: pw.BoxFit.contain,
),
),
if (reference != null) ...[
pw.SizedBox(height: 2 * PdfPageFormat.mm),
pw.Text(
reference,
style: _getTextStyle(fontSize: 8, bold: true),
textAlign: pw.TextAlign.center,
),
],
],
),
),
),
),
);
final pdfBytes = await pdf.save();
await Printing.layoutPdf(
onLayout: (format) async => pdfBytes,
name: 'QR_Niimbot_${reference ?? 'etiquette'}',
format: const PdfPageFormat(
50 * PdfPageFormat.mm,
30 * PdfPageFormat.mm,
marginAll: 0,
),
usePrinterSettings: false,
);
Get.snackbar(
'Succès',
'QR Code Niimbot envoyé à l\'imprimante',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
} catch (e) {
print('Erreur impression Niimbot: $e');
Get.snackbar(
'Erreur',
'Impossible d\'imprimer sur Niimbot: ${e.toString()}',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
}
}
/// Prévisualise le PDF avant impression
Future<void> previewQr(
String data, {
String? productName,
String? reference,
bool isLabelFormat = true,
}) async {
try {
final pdfBytes = await generateQrPdf(
data,
productName: productName,
reference: reference,
isLabelFormat: isLabelFormat,
);
await Printing.layoutPdf(
onLayout: (format) async => pdfBytes,
name: 'Aperçu_QR_Code',
);
} catch (e) {
print('Erreur aperçu: $e');
Get.snackbar(
'Erreur',
'Impossible d\'afficher l\'aperçu: ${e.toString()}',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
/// Sauvegarde le PDF
Future<void> saveQrPdf(
String data, {
String? productName,
String? reference,
bool isLabelFormat = true,
String? fileName,
}) async {
try {
final pdfBytes = await generateQrPdf(
data,
productName: productName,
reference: reference,
isLabelFormat: isLabelFormat,
);
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/${fileName ?? 'qr_code_${reference ?? DateTime.now().millisecondsSinceEpoch}'}.pdf');
await file.writeAsBytes(pdfBytes);
Get.snackbar(
'Succès',
'PDF sauvegardé: ${file.path}',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
} catch (e) {
print('Erreur sauvegarde: $e');
Get.snackbar(
'Erreur',
'Impossible de sauvegarder: ${e.toString()}',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
}