// services/platform_print_service.dart import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:itrimobe/models/command_detail.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import '../models/command_detail.dart'; class PlatformPrintService { // Format spécifique 58mm pour petites imprimantes static const PdfPageFormat ticket58mmFormat = PdfPageFormat( 58 * PdfPageFormat.mm, // Largeur exacte 58mm double.infinity, // Hauteur automatique marginLeft: 1 * PdfPageFormat.mm, marginRight: 1 * PdfPageFormat.mm, marginTop: 2 * PdfPageFormat.mm, marginBottom: 2 * PdfPageFormat.mm, ); // Vérifier les permissions static Future _checkPermissions() async { if (!Platform.isAndroid) return true; final storagePermission = await Permission.storage.request(); return storagePermission == PermissionStatus.granted; } // Vérifier si l'impression est possible static Future canPrint() async { try { return await Printing.info().then((info) => info.canPrint); } catch (e) { return false; } } // Générer PDF optimisé pour 58mm static Future _generate58mmTicketPdf({ required CommandeDetail commande, required String paymentMethod, }) async { final pdf = pw.Document(); // Configuration pour 58mm (très petit) const double titleSize = 9; const double headerSize = 8; const double bodySize = 7; const double smallSize = 6; const double lineHeight = 1.2; final restaurantInfo = { 'nom': 'RESTAURANT', 'adresse': '123 Rue de la Paix', 'ville': '75000 PARIS', 'contact': '01.23.45.67.89', 'email': 'contact@restaurant.fr', }; final factureNumber = 'T${DateTime.now().millisecondsSinceEpoch.toString().substring(8)}'; final dateTime = DateTime.now(); pdf.addPage( pw.Page( pageFormat: ticket58mmFormat, build: (pw.Context context) { return pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.center, children: [ // En-tête Restaurant (centré et compact) pw.Text( restaurantInfo['nom']!, style: pw.TextStyle( fontSize: titleSize, fontWeight: pw.FontWeight.bold, ), textAlign: pw.TextAlign.center, ), pw.SizedBox(height: 1), pw.Text( restaurantInfo['adresse']!, style: pw.TextStyle(fontSize: smallSize), textAlign: pw.TextAlign.center, ), pw.Text( restaurantInfo['ville']!, style: pw.TextStyle(fontSize: smallSize), textAlign: pw.TextAlign.center, ), pw.Text( 'Tel: ${restaurantInfo['contact']!}', style: pw.TextStyle(fontSize: smallSize), textAlign: pw.TextAlign.center, ), pw.SizedBox(height: 3), // Ligne de séparation pw.Container( width: double.infinity, height: 0.5, color: PdfColors.black, ), pw.SizedBox(height: 2), // Informations ticket pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text( 'Ticket: $factureNumber', style: pw.TextStyle( fontSize: bodySize, fontWeight: pw.FontWeight.bold, ), ), // pw.Text( // 'Table: ${commande.tableName}', // style: pw.TextStyle(fontSize: bodySize), // ), ], ), pw.SizedBox(height: 1), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text( _formatDate(dateTime), style: pw.TextStyle(fontSize: smallSize), ), pw.Text( _formatTime(dateTime), style: pw.TextStyle(fontSize: smallSize), ), ], ), pw.SizedBox(height: 2), // Ligne de séparation pw.Container( width: double.infinity, height: 0.5, color: PdfColors.black, ), pw.SizedBox(height: 2), // Articles (format très compact) ...commande.items .map( (item) => pw.Container( margin: const pw.EdgeInsets.only(bottom: 1), child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ // Nom du plat pw.Text( "NOMPLAT", style: pw.TextStyle(fontSize: bodySize), maxLines: 2, ), // Quantité, prix unitaire et total sur une ligne pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text( '${item.quantite}x ${item.prixUnitaire.toStringAsFixed(2)}€', style: pw.TextStyle(fontSize: smallSize), ), pw.Text( '${(item.prixUnitaire * item.quantite).toStringAsFixed(2)}€', style: pw.TextStyle( fontSize: bodySize, fontWeight: pw.FontWeight.bold, ), ), ], ), ], ), ), ) .toList(), pw.SizedBox(height: 2), // Ligne de séparation pw.Container( width: double.infinity, height: 0.5, color: PdfColors.black, ), pw.SizedBox(height: 2), // Total pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text( 'TOTAL', style: pw.TextStyle( fontSize: titleSize, fontWeight: pw.FontWeight.bold, ), ), pw.Text( '${commande.totalTtc.toStringAsFixed(2)}€', style: pw.TextStyle( fontSize: titleSize, fontWeight: pw.FontWeight.bold, ), ), ], ), pw.SizedBox(height: 3), // Mode de paiement pw.Text( 'Paiement: ${_getPaymentMethodText(paymentMethod)}', style: pw.TextStyle(fontSize: bodySize), textAlign: pw.TextAlign.center, ), pw.SizedBox(height: 3), // Ligne de séparation pw.Container( width: double.infinity, height: 0.5, color: PdfColors.black, ), pw.SizedBox(height: 2), // Message de remerciement pw.Text( 'Merci de votre visite !', style: pw.TextStyle( fontSize: bodySize, fontStyle: pw.FontStyle.italic, ), textAlign: pw.TextAlign.center, ), pw.Text( 'A bientôt !', style: pw.TextStyle(fontSize: smallSize), textAlign: pw.TextAlign.center, ), pw.SizedBox(height: 3), // Code de suivi (optionnel) pw.Text( 'Code: ${factureNumber}', style: pw.TextStyle(fontSize: smallSize), textAlign: pw.TextAlign.center, ), pw.SizedBox(height: 4), // Ligne de découpe pw.Text( '- - - - - - - - - - - - - - - -', style: pw.TextStyle(fontSize: smallSize), textAlign: pw.TextAlign.center, ), pw.SizedBox(height: 2), ], ); }, ), ); return pdf.save(); } // Imprimer ticket 58mm static Future printTicket({ required CommandeDetail commande, required String paymentMethod, }) async { try { final hasPermission = await _checkPermissions(); if (!hasPermission) { throw Exception('Permissions requises pour l\'impression'); } final pdfData = await _generate58mmTicketPdf( commande: commande, paymentMethod: paymentMethod, ); final fileName = 'Ticket_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}'; await Printing.layoutPdf( onLayout: (PdfPageFormat format) async => pdfData, name: fileName, format: ticket58mmFormat, ); return true; } catch (e) { print('Erreur impression 58mm: $e'); return false; } } // Sauvegarder ticket 58mm static Future saveTicketPdf({ required CommandeDetail commande, required String paymentMethod, }) async { try { final hasPermission = await _checkPermissions(); if (!hasPermission) return false; final pdfData = await _generate58mmTicketPdf( commande: commande, paymentMethod: paymentMethod, ); Directory directory; if (Platform.isAndroid) { directory = Directory('/storage/emulated/0/Download'); if (!directory.existsSync()) { directory = await getExternalStorageDirectory() ?? await getApplicationDocumentsDirectory(); } } else { directory = await getApplicationDocumentsDirectory(); } final fileName = 'Ticket_58mm_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}.pdf'; final file = File('${directory.path}/$fileName'); await file.writeAsBytes(pdfData); await Share.shareXFiles( [XFile(file.path)], subject: 'Ticket ${commande.numeroCommande}', text: 'Ticket de caisse 58mm', ); return true; } catch (e) { print('Erreur sauvegarde 58mm: $e'); return false; } } // Méthodes pour compatibilité static Future saveFacturePdf({ required CommandeDetail commande, required String paymentMethod, }) async { return await saveTicketPdf( commande: commande, paymentMethod: paymentMethod, ); } static Future printFacture({ required CommandeDetail commande, required String paymentMethod, }) async { return await printTicket(commande: commande, paymentMethod: paymentMethod); } // Utilitaires de formatage static String _formatDate(DateTime dateTime) { return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}'; } static String _formatTime(DateTime dateTime) { return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; } static String _getPaymentMethodText(String method) { switch (method) { case 'cash': return 'Espèces'; case 'card': return 'Carte bancaire'; case 'mobile': return 'Paiement mobile'; default: return 'Non spécifié'; } } }