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.
1094 lines
34 KiB
1094 lines
34 KiB
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<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;
|
|
|
|
/// 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<bool> 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<bool> 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<bool> 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<ConnectionTestResult> 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<ConnectionTestResult> 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<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(),
|
|
'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<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;
|
|
}
|
|
|
|
// 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<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;
|
|
_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<String, dynamic> 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<bool> _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<bool> 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<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 {
|
|
// 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<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 {
|
|
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<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}';
|
|
}
|
|
}
|
|
|
|
// Fonction utilitaire pour forcer la mise à jour
|
|
Future<bool> 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<void> 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<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();
|
|
}
|
|
|
|
return await printer.testConnection();
|
|
}
|