diff --git a/lib/layouts/main_layout.dart b/lib/layouts/main_layout.dart index ad8b25f..7d16bb3 100644 --- a/lib/layouts/main_layout.dart +++ b/lib/layouts/main_layout.dart @@ -40,6 +40,8 @@ class _MainLayoutState extends State { return 6; case '/information': return 7; + case '/Setting': + return 8; default: return 0; } @@ -76,6 +78,9 @@ class _MainLayoutState extends State { case 7: route = '/information'; break; + case 8: + route = '/Setting'; + break; default: route = '/tables'; } diff --git a/lib/main.dart b/lib/main.dart index a1d2b91..a0352c2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'pages/login_screen.dart'; import 'pages/menus_screen.dart'; import 'pages/historique_commande.dart'; import 'pages/information.dart'; +import 'pages/printer_page.dart'; import 'pages/encaissement_screen.dart'; // NOUVEAU void main() { @@ -64,6 +65,11 @@ class MyApp extends StatelessWidget { currentRoute: '/information', child: PrintTemplateManagementScreen(), ), + '/Setting': + (context) => MainLayout( + currentRoute: '/Setting', + child: PrinterPage(), + ), }, ); } diff --git a/lib/models/printerModel.dart b/lib/models/printerModel.dart new file mode 100644 index 0000000..742c018 --- /dev/null +++ b/lib/models/printerModel.dart @@ -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 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 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 toTestJson() { + return { + 'ip_address': ipAddress, + 'port': port, + }; + } +} \ No newline at end of file diff --git a/lib/pages/cart_page.dart b/lib/pages/cart_page.dart index 3dbd4cd..27f286e 100644 --- a/lib/pages/cart_page.dart +++ b/lib/pages/cart_page.dart @@ -255,7 +255,9 @@ class _CartPageState extends State { actions: [ ElevatedButton( onPressed: () { - printOrderPDF(order); + printOrderWithFeedback(order, (message, isSuccess) { + print('$message - Success: $isSuccess'); + }); Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: diff --git a/lib/pages/commandes_screen.dart b/lib/pages/commandes_screen.dart index a8acc68..2798bf2 100644 --- a/lib/pages/commandes_screen.dart +++ b/lib/pages/commandes_screen.dart @@ -615,7 +615,9 @@ class OrderCard extends StatelessWidget { // ← Bouton imprimer ElevatedButton.icon( onPressed: () { - printOrderPDF(order); + printOrderWithFeedback(order, (message, isSuccess) { + print('$message - Success: $isSuccess'); + }); }, icon: const Icon(Icons.print, color: Colors.white, size: 16), label: const Text( diff --git a/lib/pages/printer_page.dart b/lib/pages/printer_page.dart index e5924c6..e6bd9c7 100644 --- a/lib/pages/printer_page.dart +++ b/lib/pages/printer_page.dart @@ -2,6 +2,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'dart:io'; import 'dart:convert'; +// Ajoutez l'import de votre service API et du modèle +import '../services/restaurant_api_service.dart'; class PrinterPage extends StatefulWidget { const PrinterPage({super.key}); @@ -11,18 +13,55 @@ class PrinterPage extends StatefulWidget { } class _PrinterPageState extends State { - static const String printerIP = '192.168.123.251'; - static const int printerPort = 9100; + // Remplacer les constantes par des variables + String printerIP = '192.168.123.251'; // Valeur par défaut + int printerPort = 9100; // Valeur par défaut bool _isPrinting = false; bool _isConnected = false; bool _isTestingConnection = false; + bool _isLoadingSettings = false; String _statusMessage = 'Non testé'; @override void initState() { super.initState(); - _testConnection(); + _loadPrinterSettings(); + } + + Future _loadPrinterSettings() async { + setState(() { + _isLoadingSettings = true; + _statusMessage = 'Chargement des paramètres...'; + }); + + try { + final printerSettings = await RestaurantApiService.getPrinterSettings(); + print(printerSettings.ipAddress); + + setState(() { + // Utilisation des propriétés du modèle PrinterSettings + printerIP = printerSettings.ipAddress ?? '192.168.123.251'; + printerPort = printerSettings.port ?? 9100; + _isLoadingSettings = false; + _statusMessage = 'Paramètres chargés: ${printerSettings.name ?? "Sans nom"}'; + }); + + // Test automatique de la connexion après chargement des paramètres + _testConnection(); + } catch (e) { + setState(() { + _isLoadingSettings = false; + _statusMessage = 'Erreur chargement: $e'; + }); + + if (kDebugMode) { + print('Erreur lors du chargement des paramètres d\'imprimante: $e'); + } + + // En cas d'erreur, on teste quand même avec les valeurs par défaut + _testConnection(); + } } Future _testConnection() async { @@ -135,6 +174,103 @@ class _PrinterPageState extends State { setState(() => _isPrinting = false); } + void _showEditPrinterDialog() { + final TextEditingController ipController = TextEditingController(text: printerIP); + final TextEditingController portController = TextEditingController(text: printerPort.toString()); + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Modifier les paramètres d\'imprimante'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: ipController, + decoration: const InputDecoration( + labelText: 'Adresse IP', + hintText: '192.168.1.100', + prefixIcon: Icon(Icons.computer), + ), + keyboardType: TextInputType.numberWithOptions(decimal: true), + ), + const SizedBox(height: 16), + TextField( + controller: portController, + decoration: const InputDecoration( + labelText: 'Port', + hintText: '9100', + prefixIcon: Icon(Icons.settings_ethernet), + ), + keyboardType: TextInputType.number, + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Annuler'), + ), + ElevatedButton( + onPressed: () async { + final String newIP = ipController.text.trim(); + final int? newPort = int.tryParse(portController.text.trim()); + + if (newIP.isNotEmpty && newPort != null && newPort > 0 && newPort <= 65535) { + // Appel API pour mise à jour + final apiService = RestaurantApiService(); + bool success = await apiService.updatePrinterSettings( + ipAddress: newIP, + port: newPort, + ); + + if (success) { + setState(() { + printerIP = newIP; + printerPort = newPort; + _isConnected = false; + _statusMessage = 'Paramètres modifiés - Test requis'; + }); + + Navigator.of(context).pop(); + + _testConnection(); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Paramètres mis à jour avec succès'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Erreur lors de la mise à jour des paramètres'), + backgroundColor: Colors.red, + ), + ); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Veuillez entrer une IP et un port valides'), + backgroundColor: Colors.red, + ), + ); + } + }, + child: const Text('Enregistrer'), +), + + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -142,164 +278,363 @@ class _PrinterPageState extends State { title: const Text('Imprimante POS'), backgroundColor: Colors.blue, foregroundColor: Colors.white, + actions: [ + // Bouton pour recharger les paramètres + IconButton( + onPressed: _isLoadingSettings ? null : _loadPrinterSettings, + icon: _isLoadingSettings + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), + ) + : const Icon(Icons.settings_backup_restore), + tooltip: 'Recharger les paramètres', + ), + ], ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // Status de connexion - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Status de l\'imprimante', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 10), - Row( - children: [ - if (_isTestingConnection) - const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ) - else - Icon( - _isConnected ? Icons.wifi : Icons.wifi_off, - color: _isConnected ? Colors.green : Colors.red, - size: 20, - ), - const SizedBox(width: 10), - Expanded( - child: Text( - 'IP: $printerIP:$printerPort\nStatus: $_statusMessage', - style: const TextStyle(fontSize: 14), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Status de connexion + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Status de l\'imprimante', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + Row( + children: [ + if (_isTestingConnection || _isLoadingSettings) + const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + else + Icon( + _isConnected ? Icons.wifi : Icons.wifi_off, + color: _isConnected ? Colors.green : Colors.red, + size: 20, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + 'IP: $printerIP:$printerPort\nStatus: $_statusMessage', + style: const TextStyle(fontSize: 14), + ), ), - ), - ], - ), - const SizedBox(height: 10), - ElevatedButton.icon( - onPressed: _isTestingConnection ? null : _testConnection, - icon: const Icon(Icons.refresh), - label: const Text('Tester la connexion'), - ), - ], - ), - ), - ), - - const SizedBox(height: 20), - - // Aperçu du reçu - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Aperçu du reçu', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 10), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(4), - color: Colors.grey[50], + ], ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, + const SizedBox(height: 10), + Row( children: [ - const Text( - 'REÇU DE VENTE', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + Expanded( + child: ElevatedButton.icon( + onPressed: (_isTestingConnection || _isLoadingSettings) ? null : _testConnection, + icon: const Icon(Icons.refresh), + label: const Text('Tester la connexion'), ), ), - const SizedBox(height: 10), - Align( - alignment: Alignment.centerLeft, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Date: ${DateTime.now().toString().substring(0, 16)}', - ), - const Text('Caissier: Admin'), - const Text('--------------------------------'), - const Text('Article Qte Prix'), - const Text('--------------------------------'), - const Text('Produit 1 2 25.00€'), - const Text('Produit 2 1 15.50€'), - const Text('--------------------------------'), - const Text( - 'TOTAL 40.50€', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ], + const SizedBox(width: 10), + Expanded( + child: ElevatedButton.icon( + onPressed: _isLoadingSettings ? null : _loadPrinterSettings, + icon: const Icon(Icons.download), + label: const Text('Recharger paramètres'), ), ), - const SizedBox(height: 10), - const Text('Merci de votre visite !'), ], ), - ), - ], + ], + ), ), ), - ), - const SizedBox(height: 20), - - // Bouton d'impression - ElevatedButton.icon( - onPressed: - (_isConnected && !_isPrinting && !_isTestingConnection) - ? _handlePrint - : null, - icon: - _isPrinting - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - Colors.white, - ), + const SizedBox(height: 20), + + // Aperçu du reçu + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Aperçu du reçu', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + color: Colors.grey[50], ), - ) - : const Icon(Icons.print), - label: Text(_isPrinting ? 'Impression...' : 'Imprimer le reçu'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 15), - textStyle: const TextStyle(fontSize: 16), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Table( + columnWidths: const { + 0: FlexColumnWidth(3), // Colonne gauche large + 1: FlexColumnWidth(2), // Colonne milieu + 2: FlexColumnWidth(1), // Colonne droite pour le bouton + }, + border: TableBorder.all(color: Colors.black12), + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + // Ligne d'en-tête + TableRow( + decoration: BoxDecoration(color: Colors.grey[300]), + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'IMPRESSION TEST', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'IP: $printerIP', // Affichage dynamique de l'IP + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + ), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'Actions', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ], + ), + + // Ligne infos facture + IP/Port + Bouton modifier + TableRow( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Date: ${DateTime.now().toString().substring(0, 16)}'), + const Text('Caissier: Admin'), + const SizedBox(height: 8), + const Divider(thickness: 1, color: Colors.black54), + + // Petit tableau imbriqué + Table( + defaultColumnWidth: const FixedColumnWidth(80), + border: TableBorder.all(color: Colors.black26), + children: const [ + TableRow( + decoration: BoxDecoration(color: Colors.grey), + children: [ + Padding( + padding: EdgeInsets.all(4), + child: Text('Article', style: TextStyle(fontWeight: FontWeight.bold)), + ), + Padding( + padding: EdgeInsets.all(4), + child: Text('Qte', style: TextStyle(fontWeight: FontWeight.bold)), + ), + Padding( + padding: EdgeInsets.all(4), + child: Text('Prix', style: TextStyle(fontWeight: FontWeight.bold)), + ), + ], + ), + TableRow( + children: [ + Padding( + padding: EdgeInsets.all(4), + child: Text('Produit 1'), + ), + Padding( + padding: EdgeInsets.all(4), + child: Text('2'), + ), + Padding( + padding: EdgeInsets.all(4), + child: Text('25.00€'), + ), + ], + ), + TableRow( + children: [ + Padding( + padding: EdgeInsets.all(4), + child: Text('Produit 2'), + ), + Padding( + padding: EdgeInsets.all(4), + child: Text('1'), + ), + Padding( + padding: EdgeInsets.all(4), + child: Text('15.50€'), + ), + ], + ), + ], + ), + + const SizedBox(height: 8), + const Divider(thickness: 1, color: Colors.black54), + + const Text( + 'TOTAL 40.50€', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ), + // Colonne milieu avec IP et Port + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Text( + 'Port: $printerPort', + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: _isConnected ? Colors.green.shade50 : Colors.red.shade50, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: _isConnected ? Colors.green : Colors.red, + width: 1, + ), + ), + child: Column( + children: [ + Icon( + _isConnected ? Icons.check_circle : Icons.error, + color: _isConnected ? Colors.green : Colors.red, + size: 20, + ), + const SizedBox(height: 4), + Text( + _isConnected ? 'Connecté' : 'Déconnecté', + style: TextStyle( + fontSize: 12, + color: _isConnected ? Colors.green.shade700 : Colors.red.shade700, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + // Colonne droite avec bouton de modification + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton.icon( + onPressed: _showEditPrinterDialog, + icon: const Icon(Icons.edit, size: 16), + label: const Text( + 'Modifier', + style: TextStyle(fontSize: 12), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + minimumSize: const Size(0, 32), + ), + ), + ), + ], + ), + + // Ligne vide + const TableRow( + children: [ + SizedBox(height: 20), + SizedBox(), + SizedBox(), + ], + ), + + // Ligne finale Merci + const TableRow( + children: [ + Padding( + padding: EdgeInsets.all(8), + child: Text('Merci de votre visite !'), + ), + SizedBox(), + SizedBox(), + ], + ), + ], + ), + ) + ), + ], + ), + ), ), - ), - if (!_isConnected && !_isTestingConnection) - const Padding( - padding: EdgeInsets.only(top: 10), - child: Text( - 'Vérifiez que l\'imprimante est allumée et connectée au réseau', - style: TextStyle(color: Colors.orange, fontSize: 12), - textAlign: TextAlign.center, + const SizedBox(height: 20), + + // Bouton d'impression + ElevatedButton.icon( + onPressed: + (_isConnected && !_isPrinting && !_isTestingConnection && !_isLoadingSettings) + ? _handlePrint + : null, + icon: + _isPrinting + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ) + : const Icon(Icons.print), + label: Text(_isPrinting ? 'Impression...' : 'Imprimer le reçu'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 15), + textStyle: const TextStyle(fontSize: 16), ), ), - ], + + if (!_isConnected && !_isTestingConnection && !_isLoadingSettings) + const Padding( + padding: EdgeInsets.only(top: 10), + child: Text( + 'Vérifiez que l\'imprimante est allumée et connectée au réseau', + style: TextStyle(color: Colors.orange, fontSize: 12), + textAlign: TextAlign.center, + ), + ), + ], + ), ), ), ); } -} +} \ No newline at end of file diff --git a/lib/services/pdf_impression_commande.dart b/lib/services/pdf_impression_commande.dart index dd0dfa9..f8f4ee7 100644 --- a/lib/services/pdf_impression_commande.dart +++ b/lib/services/pdf_impression_commande.dart @@ -1,77 +1,764 @@ -import 'package:pdf/widgets.dart' as pw; -import 'package:pdf/pdf.dart'; -import 'package:printing/printing.dart'; +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'; -Future printOrderPDF(Order order) async { - 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}'; +// É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; + + // 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 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; + + /// 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..."); + } + + // 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(); - -final pageFormat = PdfPageFormat( - 204.1, // 72 mm imprimable - 595.0, // 210 mm - marginAll: 0, -); - -pdf.addPage( - pw.Page( - pageFormat: pageFormat, - build: (pw.Context context) { - 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 - ], - ), + + /// Test de connexion avancé avec métriques + Future testConnection({ + Duration timeout = const Duration(seconds: 5), + int maxRetries = 3, + }) async { + if (!isConfigured) { + return const ConnectionTestResult( + isSuccessful: false, + message: 'Imprimante non configurée', + 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(), + }; + + 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; + } + + _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 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'); - await Printing.layoutPdf( - onLayout: (PdfPageFormat format) async => pdf.save(), - ); + return normalized.padRight(width); + } + + // Méthodes d'impression optimisées + Future 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 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 { + 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 { + 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}'; + } } + +// 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(); +} \ No newline at end of file diff --git a/lib/services/pdf_service.dart b/lib/services/pdf_service.dart index d1a4a4b..d213b6b 100644 --- a/lib/services/pdf_service.dart +++ b/lib/services/pdf_service.dart @@ -10,7 +10,6 @@ import 'package:printing/printing.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:permission_handler/permission_handler.dart'; -import '../models/command_detail.dart'; import 'package:intl/intl.dart'; import '../pages/information.dart'; diff --git a/lib/services/restaurant_api_service.dart b/lib/services/restaurant_api_service.dart index 091304b..e364ced 100644 --- a/lib/services/restaurant_api_service.dart +++ b/lib/services/restaurant_api_service.dart @@ -9,6 +9,7 @@ import 'package:itrimobe/models/command_detail.dart'; import 'package:itrimobe/models/payment_method.dart'; import 'package:itrimobe/models/tables_order.dart'; +import '../models/printerModel.dart'; import '../pages/information.dart'; class RestaurantApiService { @@ -59,6 +60,52 @@ static Future getPrintTemplate() async { throw Exception("Erreur lors de la récupération du template (${response.statusCode})"); } } + static Future getPrinterSettings() async { + final url = Uri.parse('$baseUrl/api/printer/settings'); + final response = await http.get(url, headers: _headers); + + if (response.statusCode == 200) { + final jsonResponse = json.decode(response.body); + print(jsonResponse); + // print(jsonResponse); + + // Adaptez selon la structure de réponse réelle de votre API + if (jsonResponse['data'] is List && (jsonResponse['data'] as List).isNotEmpty) { + return PrinterSettings.fromJson(jsonResponse['data'][0]); + } else if (jsonResponse['data'] is Map) { + return PrinterSettings.fromJson(jsonResponse['data']); + } else { + throw Exception("Aucune configuration d'imprimante trouvée"); + } + } else { + throw Exception("Erreur lors de la récupération des paramètres d'imprimante (${response.statusCode})"); + } + } + + Future updatePrinterSettings({ + required String ipAddress, + required int port, +}) async { + final url = Uri.parse('$baseUrl/api/printer/settings'); + + final response = await http.put( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'ip_address': ipAddress, + 'port': port, + }), + ); + + if (response.statusCode == 200) { + // Succès + return true; + } else { + // Erreur + print('Erreur API: ${response.statusCode} - ${response.body}'); + return false; + } +} diff --git a/lib/widgets/bottom_navigation.dart b/lib/widgets/bottom_navigation.dart index a87a715..a24ca6e 100644 --- a/lib/widgets/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation.dart @@ -255,7 +255,7 @@ class AppBottomNavigation extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - Icons.payment, + Icons.history, color: selectedIndex == 6 ? Colors.white @@ -299,7 +299,7 @@ class AppBottomNavigation extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - Icons.payment, + Icons.info, color: selectedIndex == 7 ? Colors.white @@ -324,6 +324,49 @@ class AppBottomNavigation extends StatelessWidget { ), ), ), + + const SizedBox(width: 20), + + GestureDetector( + onTap: () => onItemTapped(8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: + selectedIndex == 8 + ? Colors.green.shade700 + : Colors.transparent, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.settings, + color: + selectedIndex == 8 + ? Colors.white + : Colors.grey.shade600, + size: 16, + ), + const SizedBox(width: 6), + Text( + 'Setting', + style: TextStyle( + color: + selectedIndex == 8 + ? Colors.white + : Colors.grey.shade600, + fontWeight: + selectedIndex == 8 + ? FontWeight.w500 + : FontWeight.normal, + ), + ), + ], + ), + ), + ), const Spacer(), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 2dccc22..f577bc4 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) charset_converter_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "CharsetConverterPlugin"); + charset_converter_plugin_register_with_registrar(charset_converter_registrar); g_autoptr(FlPluginRegistrar) printing_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin"); printing_plugin_register_with_registrar(printing_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 45f2369..ff754dc 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + charset_converter printing url_launcher_linux ) diff --git a/pubspec.lock b/pubspec.lock index bc291d6..6a50846 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "3.6.1" async: dependency: transitive description: @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + charset_converter: + dependency: transitive + description: + name: charset_converter + sha256: a601f27b78ca86c3d88899d53059786d9c3f3c485b64974e9105c06c2569aef5 + url: "https://pub.dev" + source: hosted + version: "2.3.0" clock: dependency: transitive description: @@ -81,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -89,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + esc_pos_printer: + dependency: "direct main" + description: + name: esc_pos_printer + sha256: "312b05f909f3f7dd1e6a3332cf384dcee2c3a635138823654cd9c0133d8b5c45" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + esc_pos_utils: + dependency: "direct main" + description: + name: esc_pos_utils + sha256: "8ec0013d7a7f1e790ced6b09b95ce3bf2c6f9468a3e2bc49ece000761d86c6f8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" fake_async: dependency: transitive description: @@ -144,6 +176,30 @@ packages: description: flutter source: sdk version: "0.0.0" + gbk_codec: + dependency: transitive + description: + name: gbk_codec + sha256: "3af5311fc9393115e3650ae6023862adf998051a804a08fb804f042724999f61" + url: "https://pub.dev" + source: hosted + version: "0.4.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: "direct main" description: @@ -161,13 +217,13 @@ packages: source: hosted version: "4.1.2" image: - dependency: transitive + dependency: "direct overridden" description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.3.0" intl: dependency: "direct main" description: @@ -392,14 +448,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - posix: - dependency: transitive - description: - name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" - source: hosted - version: "6.0.3" printing: dependency: "direct main" description: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index cb63ddc..1851b0f 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + CharsetConverterPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("CharsetConverterPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); PrintingPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 0f8c9e2..8de5f02 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + charset_converter permission_handler_windows printing share_plus