15 changed files with 1466 additions and 231 deletions
@ -0,0 +1,52 @@ |
|||||
|
// models/printer_settings.dart |
||||
|
class PrinterSettings { |
||||
|
final String? ipAddress; |
||||
|
final int? port; |
||||
|
final String? name; |
||||
|
final String? type; |
||||
|
final int? id; |
||||
|
final DateTime? createdAt; |
||||
|
final DateTime? updatedAt; |
||||
|
|
||||
|
PrinterSettings({ |
||||
|
this.ipAddress, |
||||
|
this.port, |
||||
|
this.name, |
||||
|
this.type, |
||||
|
this.id, |
||||
|
this.createdAt, |
||||
|
this.updatedAt, |
||||
|
}); |
||||
|
|
||||
|
factory PrinterSettings.fromJson(Map<String, dynamic> json) { |
||||
|
return PrinterSettings( |
||||
|
ipAddress: json['ip_address'], |
||||
|
port: json['port'] ?? 9100, |
||||
|
name: json['name'], |
||||
|
type: json['type'], |
||||
|
id: json['id'], |
||||
|
createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null, |
||||
|
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Map<String, dynamic> toJson() { |
||||
|
return { |
||||
|
'ip_address': ipAddress, |
||||
|
'port': port, |
||||
|
'name': name, |
||||
|
'type': type, |
||||
|
'id': id, |
||||
|
'created_at': createdAt?.toIso8601String(), |
||||
|
'updated_at': updatedAt?.toIso8601String(), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// Méthode pour créer un objet de test de connexion |
||||
|
Map<String, dynamic> toTestJson() { |
||||
|
return { |
||||
|
'ip_address': ipAddress, |
||||
|
'port': port, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -1,77 +1,764 @@ |
|||||
import 'package:pdf/widgets.dart' as pw; |
import 'dart:async'; |
||||
import 'package:pdf/pdf.dart'; |
|
||||
import 'package:printing/printing.dart'; |
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'; |
import '../pages/commandes_screen.dart'; |
||||
Future<void> printOrderPDF(Order order) async { |
|
||||
|
|
||||
String _formatTime(DateTime dateTime) { |
// Énumération pour les différents états de connexion |
||||
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}'; |
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; |
||||
|
|
||||
|
// Getters publics |
||||
|
PrinterSettings? get settings => _settings; |
||||
|
PrinterConnectionStatus get connectionStatus => _connectionStatus; |
||||
|
bool get isConfigured => _settings != null && |
||||
|
_settings!.ipAddress != null && |
||||
|
_settings!.ipAddress!.isNotEmpty && |
||||
|
_settings!.port != null; |
||||
|
|
||||
|
// Constantes ESC/POS |
||||
|
static const List<int> ESC_CONDENSED_ON = [0x1B, 0x0F]; |
||||
|
static const List<int> ESC_CONDENSED_OFF = [0x1B, 0x12]; |
||||
|
static const List<int> ESC_INIT = [0x1B, 0x40]; |
||||
|
static const List<int> ESC_ALIGN_CENTER = [0x1B, 0x61, 0x01]; |
||||
|
static const List<int> ESC_ALIGN_LEFT = [0x1B, 0x61, 0x00]; |
||||
|
static const List<int> ESC_ALIGN_RIGHT = [0x1B, 0x61, 0x02]; |
||||
|
static const List<int> ESC_DOUBLE_SIZE = [0x1D, 0x21, 0x11]; |
||||
|
static const List<int> ESC_NORMAL_SIZE = [0x1D, 0x23, 0x00]; |
||||
|
static const List<int> ESC_BOLD_ON = [0x1B, 0x45, 0x01]; |
||||
|
static const List<int> ESC_BOLD_OFF = [0x1B, 0x45, 0x00]; |
||||
|
static const List<int> ESC_CUT = [0x1D, 0x56, 0x00]; |
||||
|
static const List<int> ESC_PAGE_WIDTH_80MM = [0x1D, 0x57, 0x00, 0x02]; |
||||
|
static const List<int> 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; |
||||
|
|
||||
|
/// Initialise la configuration de l'imprimante avec gestion d'erreurs robuste |
||||
|
Future<bool> initialize() async { |
||||
|
try { |
||||
|
_connectionStatus = PrinterConnectionStatus.connecting; |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print("🔧 Initialisation de la configuration imprimante..."); |
||||
|
} |
||||
|
|
||||
|
// Récupération des paramètres avec timeout via votre API |
||||
|
var printerSettings = await RestaurantApiService.getPrinterSettings() |
||||
|
.timeout( |
||||
|
const Duration(seconds: 10), |
||||
|
onTimeout: () => throw TimeoutException('Timeout lors de la récupération des paramètres'), |
||||
|
); |
||||
|
|
||||
|
// 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}'); |
||||
|
} |
||||
|
|
||||
|
_settings=null; |
||||
|
// Configuration réussie |
||||
|
_settings = printerSettings; |
||||
|
_connectionStatus = PrinterConnectionStatus.disconnected; |
||||
|
_settings = PrinterSettings(); |
||||
|
_settings = printerSettings; |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print("✅ Imprimante configurée : ${_settings!.ipAddress}:${_settings!.port}"); |
||||
|
if (_settings!.name != null) { |
||||
|
print("📋 Nom: ${_settings!.name}"); |
||||
|
} |
||||
|
if (_settings!.type != null) { |
||||
|
print("🏷️ Type: ${_settings!.type}"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
|
||||
|
} on TimeoutException catch (e) { |
||||
|
_connectionStatus = PrinterConnectionStatus.error; |
||||
|
if (kDebugMode) { |
||||
|
print("⏰ Timeout lors de l'initialisation: $e"); |
||||
|
} |
||||
|
return false; |
||||
|
} on ArgumentError catch (e) { |
||||
|
_connectionStatus = PrinterConnectionStatus.error; |
||||
|
if (kDebugMode) { |
||||
|
print("❌ Paramètres invalides: $e"); |
||||
|
} |
||||
|
return false; |
||||
|
} catch (e) { |
||||
|
_connectionStatus = PrinterConnectionStatus.error; |
||||
|
if (kDebugMode) { |
||||
|
print("💥 Erreur lors de la récupération des paramètres d'imprimante : $e"); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 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); |
||||
} |
} |
||||
final pdf = pw.Document(); |
|
||||
|
/// Test de connexion avancé avec métriques |
||||
final pageFormat = PdfPageFormat( |
Future<ConnectionTestResult> testConnection({ |
||||
204.1, // 72 mm imprimable |
Duration timeout = const Duration(seconds: 5), |
||||
595.0, // 210 mm |
int maxRetries = 3, |
||||
marginAll: 0, |
}) async { |
||||
); |
if (!isConfigured) { |
||||
|
return const ConnectionTestResult( |
||||
pdf.addPage( |
isSuccessful: false, |
||||
pw.Page( |
message: 'Imprimante non configurée', |
||||
pageFormat: pageFormat, |
status: PrinterConnectionStatus.notConfigured, |
||||
build: (pw.Context context) { |
responseTime: Duration.zero, |
||||
return pw.Padding( |
|
||||
padding: const pw.EdgeInsets.fromLTRB(6, 6, 6, 18), |
|
||||
child: pw.Column( |
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
||||
children: [ |
|
||||
pw.Divider(), |
|
||||
pw.Text('Commande n° ${order.numeroCommande}', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold)), |
|
||||
pw.Text('Table: ${order.tablename}', style: pw.TextStyle(fontSize: 8)), |
|
||||
pw.Text('Date: ${_formatTime(order.dateCommande)}', style: pw.TextStyle(fontSize: 8)), |
|
||||
pw.SizedBox(height: 8), |
|
||||
pw.Divider(), |
|
||||
pw.SizedBox(height: 8), |
|
||||
pw.Row( |
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, |
|
||||
children: [ |
|
||||
pw.Text('Désignation', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)), |
|
||||
pw.Text('Cocher', style: pw.TextStyle(fontSize: 9)), |
|
||||
], |
|
||||
), |
|
||||
pw.SizedBox(height: 8), |
|
||||
pw.Divider(), |
|
||||
...order.items.map( |
|
||||
(item) => pw.Row( |
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, |
|
||||
children: [ |
|
||||
pw.Container( |
|
||||
width: 120, |
|
||||
child: pw.Text( |
|
||||
'${item.quantite} x ${item.nom ?? 'Item'}', |
|
||||
style: pw.TextStyle(fontSize: 8), |
|
||||
), |
|
||||
), |
|
||||
pw.Container( |
|
||||
width: 10, |
|
||||
height: 10, |
|
||||
decoration: pw.BoxDecoration( |
|
||||
border: pw.Border.all(width: 1), |
|
||||
), |
|
||||
), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
pw.Divider(), |
|
||||
pw.Spacer(), |
|
||||
pw.SizedBox(height: 10), // marge visible en bas |
|
||||
], |
|
||||
), |
|
||||
); |
); |
||||
}, |
} |
||||
), |
|
||||
); |
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<bool> testConnectionSimple() async { |
||||
|
final result = await testConnection(); |
||||
|
return result.isSuccessful; |
||||
|
} |
||||
|
|
||||
|
/// Diagnostic complet de l'imprimante |
||||
|
Future<Map<String, dynamic>> runDiagnostics() async { |
||||
|
final diagnostics = <String, dynamic>{ |
||||
|
'timestamp': DateTime.now().toIso8601String(), |
||||
|
'configured': isConfigured, |
||||
|
'connectionStatus': _connectionStatus.toString(), |
||||
|
}; |
||||
|
|
||||
|
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<Map<String, dynamic>> _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; |
||||
|
} |
||||
|
|
||||
|
_settings = PrinterSettings( |
||||
|
ipAddress: ipAddress, |
||||
|
port: port, |
||||
|
name: name, |
||||
|
type: type, |
||||
|
createdAt: DateTime.now(), |
||||
|
updatedAt: DateTime.now(), |
||||
|
); |
||||
|
|
||||
|
_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<bool> 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; |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print("🔄 Configuration de l'imprimante réinitialisée"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 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); |
||||
|
} |
||||
|
|
||||
|
// Méthodes d'impression optimisées |
||||
|
Future<bool> printOrderESCPOS(Order order) async { |
||||
|
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<int> 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<bool> printOrderESC80MM(Order order) async { |
||||
|
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<int> 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<bool> printOrderESCPOSOptimized(Order order) async { |
||||
|
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<int> 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}'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fonctions utilitaires pour rétrocompatibilité |
||||
|
Future<bool> 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<void> 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<Map<String, dynamic>> 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<ConnectionTestResult> testPrinterConnection() async { |
||||
|
final printer = OrderPrinter.instance; |
||||
|
|
||||
|
if (!printer.isConfigured) { |
||||
|
await printer.initialize(); |
||||
|
} |
||||
|
|
||||
await Printing.layoutPdf( |
return await printer.testConnection(); |
||||
onLayout: (PdfPageFormat format) async => pdf.save(), |
|
||||
); |
|
||||
} |
} |
||||
Loading…
Reference in new issue