import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:itrimobe/services/restaurant_api_service.dart'; import 'package:itrimobe/models/printerModel.dart'; import 'dart:io'; import 'dart:convert'; import '../pages/commandes_screen.dart'; // Énumération pour les différents états de connexion enum PrinterConnectionStatus { notConfigured, connecting, connected, disconnected, error } // Classe pour les résultats de test de connexion class ConnectionTestResult { final bool isSuccessful; final String message; final PrinterConnectionStatus status; final Duration responseTime; const ConnectionTestResult({ required this.isSuccessful, required this.message, required this.status, required this.responseTime, }); @override String toString() => 'ConnectionTestResult(success: $isSuccessful, message: "$message", time: ${responseTime.inMilliseconds}ms)'; } class OrderPrinter { // Singleton pattern pour garantir une seule instance static OrderPrinter? _instance; static OrderPrinter get instance => _instance ??= OrderPrinter._internal(); OrderPrinter._internal(); // Configuration de l'imprimante PrinterSettings? _settings; PrinterConnectionStatus _connectionStatus = PrinterConnectionStatus.notConfigured; DateTime? _lastConfigUpdate; static const Duration _configCacheTimeout = Duration(minutes: 5); // Getters publics PrinterSettings? get settings => _settings; PrinterConnectionStatus get connectionStatus => _connectionStatus; // Getter isConfigured modifié pour inclure validation temporelle bool get isConfigured { // Vérifier si la configuration existe if (_settings == null || _settings!.ipAddress == null || _settings!.ipAddress!.isEmpty || _settings!.port == null) { return false; } // Vérifier si la configuration n'est pas trop ancienne if (_lastConfigUpdate != null && DateTime.now().difference(_lastConfigUpdate!) > _configCacheTimeout) { if (kDebugMode) { print("⚠️ Configuration expirée, rechargement nécessaire"); } return false; } return true; } // Constantes ESC/POS static const List ESC_CONDENSED_ON = [0x1B, 0x0F]; static const List ESC_CONDENSED_OFF = [0x1B, 0x12]; static const List ESC_INIT = [0x1B, 0x40]; static const List ESC_ALIGN_CENTER = [0x1B, 0x61, 0x01]; static const List ESC_ALIGN_LEFT = [0x1B, 0x61, 0x00]; static const List ESC_ALIGN_RIGHT = [0x1B, 0x61, 0x02]; static const List ESC_DOUBLE_SIZE = [0x1D, 0x21, 0x11]; static const List ESC_NORMAL_SIZE = [0x1D, 0x23, 0x00]; static const List ESC_BOLD_ON = [0x1B, 0x45, 0x01]; static const List ESC_BOLD_OFF = [0x1B, 0x45, 0x00]; static const List ESC_CUT = [0x1D, 0x56, 0x00]; static const List ESC_PAGE_WIDTH_80MM = [0x1D, 0x57, 0x00, 0x02]; static const List ESC_LEFT_MARGIN_0 = [0x1D, 0x4C, 0x015, 0x00]; // Configuration d'impression static const int maxCharsPerLine = 42; static const String checkbox = '[ ]'; static const String rightWord = 'cocher'; static const int padding = 1; /// Force la réinitialisation complète de l'instance static void resetInstance() { if (kDebugMode) { print("🔄 Réinitialisation complète de l'instance OrderPrinter"); } _instance = null; } /// Méthode pour vider complètement le cache et recharger Future forceClearAndReload() async { if (kDebugMode) { print("🧹 Nettoyage complet de la configuration..."); } // Effacer complètement tout _settings = null; _lastConfigUpdate = null; _connectionStatus = PrinterConnectionStatus.notConfigured; if (kDebugMode) { print("🔄 Rechargement forcé depuis l'API..."); } // Attendre un peu pour s'assurer que tout est nettoyé await Future.delayed(const Duration(milliseconds: 100)); // Recharger depuis l'API avec un timeout plus court pour déboguer try { var printerSettings = await RestaurantApiService.getPrinterSettings() .timeout( const Duration(seconds: 8), onTimeout: () => throw TimeoutException('Timeout lors de la récupération'), ); if (kDebugMode) { print("📡 Nouvelle config reçue de l'API:"); print(" IP: ${printerSettings.ipAddress}"); print(" Port: ${printerSettings.port}"); } // Validation if (printerSettings.ipAddress == null || printerSettings.ipAddress!.isEmpty) { throw ArgumentError('Adresse IP non définie'); } if (printerSettings.port == null || printerSettings.port! <= 0) { throw ArgumentError('Port invalide'); } if (!_isValidIpAddress(printerSettings.ipAddress!)) { throw ArgumentError('Format IP invalide: ${printerSettings.ipAddress}'); } // Appliquer la nouvelle configuration _settings = printerSettings; _lastConfigUpdate = DateTime.now(); _connectionStatus = PrinterConnectionStatus.disconnected; if (kDebugMode) { print("✅ Configuration mise à jour avec succès:"); print(" Nouvelle IP: ${_settings!.ipAddress}"); print(" Nouveau Port: ${_settings!.port}"); } return true; } catch (e) { if (kDebugMode) { print("❌ Erreur lors du rechargement forcé: $e"); } _connectionStatus = PrinterConnectionStatus.error; return false; } } /// Méthode de débogage pour vérifier l'état actuel void debugCurrentState() { if (kDebugMode) { print("🔍 État actuel de OrderPrinter:"); print(" isConfigured: $isConfigured"); print(" connectionStatus: $_connectionStatus"); print(" settings: ${_settings != null ? '${_settings!.ipAddress}:${_settings!.port}' : 'null'}"); print(" lastConfigUpdate: $_lastConfigUpdate"); if (_lastConfigUpdate != null) { final age = DateTime.now().difference(_lastConfigUpdate!); print(" config age: ${age.inMinutes} minutes"); } } } /// Initialise la configuration de l'imprimante avec gestion d'erreurs robuste Future initialize() async { try { _connectionStatus = PrinterConnectionStatus.connecting; if (kDebugMode) { print("🔧 Initialisation de la configuration imprimante..."); print(" État avant: ${_settings != null ? '${_settings!.ipAddress}:${_settings!.port}' : 'null'}"); } // Forcer la récupération depuis l'API var printerSettings = await RestaurantApiService.getPrinterSettings() .timeout( const Duration(seconds: 10), onTimeout: () => throw TimeoutException('Timeout lors de la récupération des paramètres'), ); if (kDebugMode) { print("📡 Réponse de l'API:"); print(" IP reçue: ${printerSettings.ipAddress}"); print(" Port reçu: ${printerSettings.port}"); print(" Nom: ${printerSettings.name}"); } // Validation des paramètres if (printerSettings.ipAddress == null || printerSettings.ipAddress!.isEmpty) { throw ArgumentError('Adresse IP de l\'imprimante non définie'); } if (printerSettings.port == null || printerSettings.port! <= 0 || printerSettings.port! > 65535) { throw ArgumentError('Port de l\'imprimante invalide: ${printerSettings.port}'); } // Validation du format IP if (!_isValidIpAddress(printerSettings.ipAddress!)) { throw ArgumentError('Format d\'adresse IP invalide: ${printerSettings.ipAddress}'); } // Vérifier si la configuration a changé bool configChanged = _settings == null || _settings!.ipAddress != printerSettings.ipAddress || _settings!.port != printerSettings.port; if (configChanged) { if (kDebugMode) { print("🔄 Changement de configuration détecté:"); if (_settings != null) { print(" Ancienne: ${_settings!.ipAddress}:${_settings!.port}"); } else { print(" Ancienne: null"); } print(" Nouvelle: ${printerSettings.ipAddress}:${printerSettings.port}"); } // Réinitialiser le statut de connexion _connectionStatus = PrinterConnectionStatus.disconnected; } else { if (kDebugMode) { print("ℹ️ Aucun changement de configuration"); } } // Configuration réussie _settings = printerSettings; _lastConfigUpdate = DateTime.now(); if (kDebugMode) { print("✅ Configuration appliquée:"); print(" IP finale: ${_settings!.ipAddress}"); print(" Port final: ${_settings!.port}"); print(" Statut: $_connectionStatus"); } return true; } on TimeoutException catch (e) { _connectionStatus = PrinterConnectionStatus.error; _settings = null; _lastConfigUpdate = null; if (kDebugMode) { print("⏰ Timeout lors de l'initialisation: $e"); } return false; } on ArgumentError catch (e) { _connectionStatus = PrinterConnectionStatus.error; _settings = null; _lastConfigUpdate = null; if (kDebugMode) { print("❌ Paramètres invalides: $e"); } return false; } catch (e) { _connectionStatus = PrinterConnectionStatus.error; _settings = null; _lastConfigUpdate = null; if (kDebugMode) { print("💥 Erreur lors de la récupération des paramètres d'imprimante : $e"); } return false; } } /// Nouvelle méthode pour forcer la revalidation Future forceReload() async { if (kDebugMode) { print("🔄 Rechargement forcé de la configuration imprimante"); } // Effacer la configuration actuelle _settings = null; _lastConfigUpdate = null; _connectionStatus = PrinterConnectionStatus.notConfigured; // Recharger depuis l'API return await initialize(); } /// Valide le format d'une adresse IP bool _isValidIpAddress(String ip) { final ipRegex = RegExp(r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'); return ipRegex.hasMatch(ip); } /// Version améliorée du test de connexion avec plus de logs Future testConnectionWithDebug({ Duration timeout = const Duration(seconds: 5), int maxRetries = 3, }) async { // Debug de l'état avant test debugCurrentState(); // Vérifier et recharger si nécessaire if (!isConfigured) { if (kDebugMode) { print("🔄 Configuration non valide, rechargement forcé..."); } final reloadSuccess = await forceClearAndReload(); if (!reloadSuccess) { return const ConnectionTestResult( isSuccessful: false, message: 'Impossible de recharger la configuration', status: PrinterConnectionStatus.notConfigured, responseTime: Duration.zero, ); } } if (kDebugMode) { print("🎯 Test de connexion vers: ${_settings!.ipAddress}:${_settings!.port}"); } return await testConnection(timeout: timeout, maxRetries: maxRetries); } /// Test de connexion avancé avec métriques Future testConnection({ Duration timeout = const Duration(seconds: 5), int maxRetries = 3, }) async { // Vérifier et recharger la configuration si nécessaire if (!isConfigured) { if (kDebugMode) { print("🔄 Configuration non valide, tentative de rechargement..."); } final reloadSuccess = await initialize(); if (!reloadSuccess) { return const ConnectionTestResult( isSuccessful: false, message: 'Impossible de recharger la configuration imprimante', status: PrinterConnectionStatus.notConfigured, responseTime: Duration.zero, ); } } final stopwatch = Stopwatch()..start(); for (int attempt = 1; attempt <= maxRetries; attempt++) { try { _connectionStatus = PrinterConnectionStatus.connecting; if (kDebugMode) { print("🔍 Test de connexion (tentative $attempt/$maxRetries) vers ${_settings!.ipAddress}:${_settings!.port}"); } final socket = await Socket.connect( _settings!.ipAddress!, _settings!.port!, ).timeout(timeout); // Test d'écriture simple pour vérifier que l'imprimante répond socket.add(ESC_INIT); await socket.flush(); await socket.close(); stopwatch.stop(); _connectionStatus = PrinterConnectionStatus.connected; final result = ConnectionTestResult( isSuccessful: true, message: 'Connexion réussie en ${stopwatch.elapsedMilliseconds}ms', status: PrinterConnectionStatus.connected, responseTime: stopwatch.elapsed, ); if (kDebugMode) { print("✅ ${result.message}"); } return result; } on SocketException catch (e) { if (kDebugMode) { print("🔌 Erreur de socket (tentative $attempt): $e"); } if (attempt == maxRetries) { stopwatch.stop(); _connectionStatus = PrinterConnectionStatus.disconnected; return ConnectionTestResult( isSuccessful: false, message: 'Imprimante inaccessible: ${e.message}', status: PrinterConnectionStatus.disconnected, responseTime: stopwatch.elapsed, ); } // Attendre avant la prochaine tentative await Future.delayed(Duration(milliseconds: 500 * attempt)); } on TimeoutException { if (kDebugMode) { print("⏰ Timeout de connexion (tentative $attempt)"); } if (attempt == maxRetries) { stopwatch.stop(); _connectionStatus = PrinterConnectionStatus.disconnected; return ConnectionTestResult( isSuccessful: false, message: 'Timeout de connexion après ${timeout.inSeconds}s', status: PrinterConnectionStatus.disconnected, responseTime: stopwatch.elapsed, ); } await Future.delayed(Duration(milliseconds: 500 * attempt)); } catch (e) { if (kDebugMode) { print("💥 Erreur inattendue (tentative $attempt): $e"); } if (attempt == maxRetries) { stopwatch.stop(); _connectionStatus = PrinterConnectionStatus.error; return ConnectionTestResult( isSuccessful: false, message: 'Erreur de connexion: $e', status: PrinterConnectionStatus.error, responseTime: stopwatch.elapsed, ); } await Future.delayed(Duration(milliseconds: 500 * attempt)); } } // Ce code ne devrait jamais être atteint stopwatch.stop(); _connectionStatus = PrinterConnectionStatus.error; return ConnectionTestResult( isSuccessful: false, message: 'Échec après $maxRetries tentatives', status: PrinterConnectionStatus.error, responseTime: stopwatch.elapsed, ); } /// Test de connexion simple (rétrocompatibilité) Future testConnectionSimple() async { final result = await testConnection(); return result.isSuccessful; } /// Diagnostic complet de l'imprimante Future> runDiagnostics() async { final diagnostics = { 'timestamp': DateTime.now().toIso8601String(), 'configured': isConfigured, 'connectionStatus': _connectionStatus.toString(), 'lastConfigUpdate': _lastConfigUpdate?.toIso8601String(), 'configAge': _lastConfigUpdate != null ? DateTime.now().difference(_lastConfigUpdate!).inMinutes : null, }; if (_settings != null) { diagnostics['settings'] = { 'ipAddress': _settings!.ipAddress, 'port': _settings!.port, 'name': _settings!.name, 'type': _settings!.type, 'id': _settings!.id, 'createdAt': _settings!.createdAt?.toIso8601String(), 'updatedAt': _settings!.updatedAt?.toIso8601String(), }; } else { diagnostics['settings'] = 'Non configuré'; } if (isConfigured) { // Test de résolution DNS (si applicable) try { final addresses = await InternetAddress.lookup(_settings!.ipAddress!); diagnostics['dnsResolution'] = { 'success': true, 'addresses': addresses.map((addr) => addr.address).toList(), }; } catch (e) { diagnostics['dnsResolution'] = { 'success': false, 'error': e.toString(), }; } // Test de connectivité final connectionResult = await testConnection(maxRetries: 1); diagnostics['connectionTest'] = { 'success': connectionResult.isSuccessful, 'message': connectionResult.message, 'responseTime': '${connectionResult.responseTime.inMilliseconds}ms', 'status': connectionResult.status.toString(), }; // Test de ping réseau (optionnel) diagnostics['networkReachability'] = await _testNetworkReachability(); } return diagnostics; } /// Test de accessibilité réseau Future> _testNetworkReachability() async { try { final result = await Process.run('ping', [ '-c', '1', // Une seule tentative '-W', '3000', // Timeout 3 secondes _settings!.ipAddress!, ]); return { 'success': result.exitCode == 0, 'output': result.stdout, 'error': result.stderr, }; } catch (e) { return { 'success': false, 'error': 'Ping non disponible: $e', }; } } /// Met à jour la configuration manuellement bool updateConfiguration(String ipAddress, int port, {String? name, String? type}) { if (!_isValidIpAddress(ipAddress)) { if (kDebugMode) { print("❌ Adresse IP invalide: $ipAddress"); } return false; } if (port <= 0 || port > 65535) { if (kDebugMode) { print("❌ Port invalide: $port"); } return false; } // Vérifier si c'est un changement bool configChanged = _settings == null || _settings!.ipAddress != ipAddress || _settings!.port != port; _settings = PrinterSettings( ipAddress: ipAddress, port: port, name: name, type: type, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); _lastConfigUpdate = DateTime.now(); // Réinitialiser le statut si changement if (configChanged) { _connectionStatus = PrinterConnectionStatus.disconnected; } if (kDebugMode) { print("🔧 Configuration mise à jour: $ipAddress:$port"); if (name != null) print("📋 Nom: $name"); if (type != null) print("🏷️ Type: $type"); } return true; } /// Recharge la configuration depuis l'API Future reloadConfiguration() async { if (kDebugMode) { print("🔄 Rechargement de la configuration depuis l'API..."); } return await initialize(); } /// Réinitialise la configuration void resetConfiguration() { _settings = null; _connectionStatus = PrinterConnectionStatus.notConfigured; _lastConfigUpdate = null; if (kDebugMode) { print("🔄 Configuration de l'imprimante réinitialisée"); } } /// Vérifie si la configuration a changé bool hasConfigurationChanged(PrinterSettings newSettings) { if (_settings == null) return true; return _settings!.ipAddress != newSettings.ipAddress || _settings!.port != newSettings.port || _settings!.name != newSettings.name || _settings!.type != newSettings.type; } /// Informations de debug Map getDebugInfo() { return { 'isConfigured': isConfigured, 'connectionStatus': _connectionStatus.toString(), 'lastConfigUpdate': _lastConfigUpdate?.toIso8601String(), 'configAge': _lastConfigUpdate != null ? DateTime.now().difference(_lastConfigUpdate!).inMinutes : null, 'settings': _settings != null ? { 'ipAddress': _settings!.ipAddress, 'port': _settings!.port, 'name': _settings!.name, 'type': _settings!.type, } : null, }; } /// Méthode pour normaliser les caractères accentués String safePadRight(String text, int width) { // Remplace temporairement les accents par des lettres simples final normalized = text .replaceAll(RegExp(r'[éèêë]'), 'e') .replaceAll(RegExp(r'[àâä]'), 'a') .replaceAll(RegExp(r'[ôö]'), 'o') .replaceAll(RegExp(r'[ûü]'), 'u') .replaceAll(RegExp(r'[ç]'), 'c') .replaceAll(RegExp(r'[ÉÈÊË]'), 'E') .replaceAll(RegExp(r'[ÀÂÄ]'), 'A') .replaceAll(RegExp(r'[ÔÖ]'), 'O') .replaceAll(RegExp(r'[ÛÜ]'), 'U') .replaceAll(RegExp(r'[Ç]'), 'C'); return normalized.padRight(width); } Future _ensureValidConfiguration() async { if (!isConfigured) { if (kDebugMode) { print("🔄 Configuration non valide, rechargement..."); } final reloaded = await initialize(); if (!reloaded) { if (kDebugMode) { print("❌ Impossible de recharger la configuration imprimante"); } return false; } } return true; } // Méthodes d'impression optimisées Future printOrderESCPOS(Order order) async { if (!await _ensureValidConfiguration()) { return false; } final connectionTest = await testConnection(); if (!connectionTest.isSuccessful) { if (kDebugMode) { print("❌ Impossible d'imprimer: ${connectionTest.message}"); } return false; } try { final socket = await Socket.connect(_settings!.ipAddress!, _settings!.port!); final List bytes = []; // Initialisation et configuration pour 80mm bytes.addAll(ESC_INIT); bytes.addAll(ESC_PAGE_WIDTH_80MM); bytes.addAll(ESC_LEFT_MARGIN_0); bytes.addAll(latin1.encode('${'-' * maxCharsPerLine}\n')); bytes.addAll(ESC_LEFT_MARGIN_0); // En-tête centré bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(ESC_BOLD_ON); bytes.addAll(ESC_LEFT_MARGIN_0); bytes.addAll(latin1.encode('Commande n° ${order.numeroCommande}\n')); // Informations commande bytes.addAll(ESC_LEFT_MARGIN_0); bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(ESC_NORMAL_SIZE); bytes.addAll(latin1.encode('Table: ${order.tablename}\n')); bytes.addAll(ESC_BOLD_OFF); bytes.addAll(latin1.encode('Date: ${_formatTime(order.dateCommande)}\n')); bytes.addAll(latin1.encode('${'-' * maxCharsPerLine}\n')); // En-tête du tableau bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(ESC_BOLD_ON); bytes.addAll(ESC_LEFT_MARGIN_0); String header = 'Designation'.padRight(maxCharsPerLine - rightWord.length - padding) + ' ' * padding + rightWord + '\n'; bytes.addAll(latin1.encode(header)); bytes.addAll(ESC_BOLD_OFF); bytes.addAll(latin1.encode('${'-' * maxCharsPerLine}\n')); // Articles bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(ESC_LEFT_MARGIN_0); for (var item in order.items) { String designation = '${item.quantite}x ${item.nom ?? 'Item'}'; int maxDesignationLength = maxCharsPerLine - checkbox.length - padding; if (designation.length > maxDesignationLength) { designation = designation.substring(0, maxDesignationLength - 3) + '...'; } String line = designation.padRight(maxDesignationLength) + ' ' * padding + checkbox + '\n'; bytes.addAll(utf8.encode(line)); } // Pied de page bytes.addAll(utf8.encode('${'-' * maxCharsPerLine}\n')); // Espacement avant coupe bytes.addAll([0x0A, 0x0A, 0x0A]); bytes.addAll(ESC_CUT); socket.add(bytes); await socket.flush(); await socket.close(); if (kDebugMode) { print("✅ Impression réussie!"); } return true; } catch (e) { if (kDebugMode) { print('💥 Erreur impression: $e'); } return false; } } Future printOrderESC80MM(Order order) async { // Auto-revalidation avant impression if (!await _ensureValidConfiguration()) { return false; } final connectionTest = await testConnection(); if (!connectionTest.isSuccessful) { if (kDebugMode) { print("❌ Impossible d'imprimer: ${connectionTest.message}"); } return false; } try { final socket = await Socket.connect(_settings!.ipAddress!, _settings!.port!); final List bytes = []; // Configuration spécifique 80mm bytes.addAll(ESC_INIT); bytes.addAll(ESC_PAGE_WIDTH_80MM); bytes.addAll(ESC_LEFT_MARGIN_0); // En-tête centré et stylisé bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(ESC_DOUBLE_SIZE); bytes.addAll(ESC_BOLD_ON); bytes.addAll(ESC_PAGE_WIDTH_80MM); bytes.addAll(utf8.encode('COMMANDE\n')); bytes.addAll(ESC_BOLD_OFF); bytes.addAll(ESC_NORMAL_SIZE); bytes.addAll(utf8.encode('${'-' * maxCharsPerLine}\n')); // Informations commande avec formatage amélioré bytes.addAll(utf8.encode(_createInfoLine('N° Commande', order.numeroCommande.toString()))); bytes.addAll(utf8.encode(_createInfoLine('Date/Heure', _formatTime(order.dateCommande)))); bytes.addAll(utf8.encode('${'=' * maxCharsPerLine}\n')); // En-tête du tableau avec colonnes bien définies bytes.addAll(ESC_BOLD_ON); bytes.addAll(ESC_PAGE_WIDTH_80MM); bytes.addAll(utf8.encode(_createTableHeader())); bytes.addAll(ESC_BOLD_OFF); bytes.addAll(utf8.encode('${'-' * maxCharsPerLine}\n')); // Articles avec formatage en colonnes for (var item in order.items) { bytes.addAll(utf8.encode(_createItemLine(item.quantite, item.nom ?? 'Item'))); } // Pied de page bytes.addAll(utf8.encode('${'=' * maxCharsPerLine}\n')); bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(ESC_PAGE_WIDTH_80MM); // Coupe avec espacement bytes.addAll([0x0A, 0x0A]); bytes.addAll(ESC_CUT); socket.add(bytes); await socket.flush(); await socket.close(); return true; } catch (e) { if (kDebugMode) { print('Erreur impression: $e'); } return false; } } Future printOrderESCPOSOptimized(Order order) async { if (!await _ensureValidConfiguration()) { return false; } final connectionTest = await testConnection(); if (!connectionTest.isSuccessful) { if (kDebugMode) { print("❌ Impossible d'imprimer: ${connectionTest.message}"); } return false; } try { final socket = await Socket.connect(_settings!.ipAddress!, _settings!.port!); final List bytes = []; // Configuration initiale optimisée pour 72mm bytes.addAll(ESC_INIT); bytes.addAll(ESC_LEFT_MARGIN_0); // Définir largeur de page explicite bytes.addAll([0x1B, 0x51, 180]); // En-tête bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(ESC_DOUBLE_SIZE); bytes.addAll(utf8.encode('COMMANDE\n\n')); // Corps avec police condensée bytes.addAll(ESC_NORMAL_SIZE); bytes.addAll(ESC_CONDENSED_ON); const int compactMaxChars = 32; bytes.addAll(utf8.encode('Cmd: ${order.numeroCommande}\n')); bytes.addAll(utf8.encode('Table: ${order.tablename}\n')); bytes.addAll(utf8.encode('${_formatTime(order.dateCommande)}\n')); bytes.addAll(utf8.encode('${'=' * compactMaxChars}\n')); // Articles avec formatage optimisé for (var item in order.items) { String qty = '${item.quantite}x'; String name = item.nom ?? 'Item'; int availableSpace = compactMaxChars - qty.length - checkbox.length - 2; if (name.length > availableSpace) { name = name.substring(0, availableSpace - 3) + '...'; } String line = '$qty $name'.padRight(compactMaxChars - checkbox.length) + checkbox + '\n'; bytes.addAll(utf8.encode(line)); } // Finalisation bytes.addAll(utf8.encode('${'=' * compactMaxChars}\n')); bytes.addAll(ESC_CONDENSED_OFF); bytes.addAll(ESC_ALIGN_CENTER); bytes.addAll(utf8.encode('\nBon service !\n\n\n')); bytes.addAll(ESC_CUT); socket.add(bytes); await socket.flush(); await socket.close(); return true; } catch (e) { if (kDebugMode) { print('Erreur impression: $e'); } return false; } } // Fonctions utilitaires pour formatage String _createInfoLine(String label, String value) { int labelWidth = 12; int valueWidth = maxCharsPerLine - labelWidth - 2; String truncatedLabel = label.length > labelWidth ? label.substring(0, labelWidth) : label.padRight(labelWidth); String truncatedValue = value.length > valueWidth ? value.substring(0, valueWidth - 3) + '...' : value; return '$truncatedLabel: $truncatedValue\n'; } String _createTableHeader() { int qtyWidth = 4; int checkboxWidth = checkbox.length; int nameWidth = maxCharsPerLine - qtyWidth - checkboxWidth - 2; String header = 'Qty'.padRight(qtyWidth) + 'Designation'.padRight(nameWidth) + checkbox + '\n'; return header; } String _createItemLine(int quantity, String itemName) { int qtyWidth = 4; int checkboxWidth = checkbox.length; int nameWidth = maxCharsPerLine - qtyWidth - checkboxWidth - 2; String qtyStr = quantity.toString().padRight(qtyWidth); String nameStr = itemName.length > nameWidth ? itemName.substring(0, nameWidth - 3) + '...' : itemName.padRight(nameWidth); return qtyStr + nameStr + checkbox + '\n'; } /// Formatage du temps String _formatTime(DateTime dateTime) { return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')} ${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}'; } } // Fonction utilitaire pour forcer la mise à jour Future forceUpdatePrinterConfig() async { final printer = OrderPrinter.instance; if (kDebugMode) { print("🔧 Forçage de la mise à jour de configuration..."); } // Méthode 1: Utiliser forceClearAndReload bool success = await printer.forceClearAndReload(); if (success) { if (kDebugMode) { print("✅ Configuration mise à jour avec succès"); } return true; } else { if (kDebugMode) { print("❌ Échec de la mise à jour, tentative de réinitialisation complète..."); } // Méthode 2: Réinitialisation complète de l'instance OrderPrinter.resetInstance(); // Obtenir la nouvelle instance et initialiser final newPrinter = OrderPrinter.instance; return await newPrinter.initialize(); } } // Fonction pour tester avec débogage complet Future testPrinterWithFullDebug() async { final printer = OrderPrinter.instance; print("🔍 === DÉBOGAGE COMPLET IMPRIMANTE ==="); // État initial printer.debugCurrentState(); // Force la mise à jour print("🔄 Forçage de la mise à jour..."); await printer.forceClearAndReload(); // État après mise à jour print("📊 État après mise à jour:"); printer.debugCurrentState(); // Test de connexion print("🔗 Test de connexion..."); final result = await printer.testConnectionWithDebug(); print("📋 Résultat: ${result.isSuccessful ? '✅' : '❌'} ${result.message}"); // Diagnostic complet print("🏥 Diagnostic complet:"); final diagnostics = await printer.runDiagnostics(); diagnostics.forEach((key, value) { print(" $key: $value"); }); } // Fonctions utilitaires pour rétrocompatibilité Future printOrderPDF(Order order) async { final printer = OrderPrinter.instance; // Initialiser si nécessaire if (!printer.isConfigured) { final initialized = await printer.initialize(); if (!initialized) { if (kDebugMode) { print('❌ Impossible d\'initialiser l\'imprimante'); } return false; } } return await printer.printOrderESC80MM(order); } Future printOrderWithFeedback(Order order, Function(String, bool) onResult) async { final printer = OrderPrinter.instance; try { // Initialisation si nécessaire if (!printer.isConfigured) { onResult('Initialisation de l\'imprimante...', true); final initialized = await printer.initialize(); if (!initialized) { onResult('Échec de l\'initialisation', false); return; } } // Test de connexion onResult('Test de connexion...', true); final connectionResult = await printer.testConnection(); if (!connectionResult.isSuccessful) { onResult(connectionResult.message, false); return; } // Impression onResult('Impression en cours...', true); final success = await printer.printOrderESCPOS(order); if (success) { onResult('Impression réussie!', true); } else { onResult('Échec de l\'impression', false); } } catch (e) { onResult('Erreur: $e', false); } } // Fonction utilitaire pour obtenir des informations sur l'imprimante Future> getPrinterInfo() async { final printer = OrderPrinter.instance; if (!printer.isConfigured) { await printer.initialize(); } return await printer.runDiagnostics(); } // Fonction pour tester la connexion avec retour détaillé Future testPrinterConnection() async { final printer = OrderPrinter.instance; if (!printer.isConfigured) { await printer.initialize(); } return await printer.testConnection(); }