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}); @override _PrinterPageState createState() => _PrinterPageState(); } class _PrinterPageState extends State { // 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(); _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 { setState(() { _isTestingConnection = true; _statusMessage = 'Test en cours...'; }); try { final socket = await Socket.connect( printerIP, printerPort, ).timeout(const Duration(seconds: 5)); await socket.close(); setState(() { _isConnected = true; _statusMessage = 'Connectée'; _isTestingConnection = false; }); } catch (e) { setState(() { _isConnected = false; _statusMessage = 'Erreur: $e'; _isTestingConnection = false; }); } } Future _printReceipt() async { try { final socket = await Socket.connect(printerIP, printerPort); final List bytes = []; // Initialisation ESC/POS bytes.addAll([0x1B, 0x40]); // ESC @ // Titre centré et agrandi bytes.addAll([0x1B, 0x61, 0x01]); // Centre bytes.addAll([0x1D, 0x21, 0x11]); // Taille double bytes.addAll(utf8.encode('REÇU DE VENTE')); bytes.addAll([0x0A, 0x0A]); // Retour taille normale bytes.addAll([0x1D, 0x21, 0x00]); bytes.addAll([0x1B, 0x61, 0x00]); // Alignement gauche // Informations transaction final now = DateTime.now(); final dateStr = '${now.day.toString().padLeft(2, '0')}/${now.month.toString().padLeft(2, '0')}/${now.year} ${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}'; bytes.addAll(utf8.encode('Date: $dateStr\n')); bytes.addAll(utf8.encode('Caissier: Admin\n')); bytes.addAll(utf8.encode('--------------------------------\n')); // En-têtes colonnes bytes.addAll(utf8.encode('Article Qte Prix\n')); bytes.addAll(utf8.encode('--------------------------------\n')); // Articles bytes.addAll(utf8.encode('Produit 1 2 25.00€\n')); bytes.addAll(utf8.encode('Produit 2 1 15.50€\n')); bytes.addAll(utf8.encode('--------------------------------\n')); // Total bytes.addAll([0x1B, 0x45, 0x01]); // Gras ON bytes.addAll(utf8.encode('TOTAL 40.50€\n')); bytes.addAll([0x1B, 0x45, 0x00]); // Gras OFF bytes.addAll([0x0A, 0x0A]); // Message de remerciement centré bytes.addAll([0x1B, 0x61, 0x01]); // Centre bytes.addAll(utf8.encode('Merci de votre visite !\n')); bytes.addAll([0x0A, 0x0A]); // Coupe papier bytes.addAll([0x1D, 0x56, 0x00]); // GS V 0 // Envoi des données socket.add(bytes); await socket.flush(); await socket.close(); return true; } catch (e) { if (kDebugMode) { print('Erreur impression: $e'); } return false; } } Future _handlePrint() async { setState(() => _isPrinting = true); final success = await _printReceipt(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( success ? 'Impression réussie!' : 'Échec de l\'impression', ), backgroundColor: success ? Colors.green : Colors.red, duration: const Duration(seconds: 3), ), ); 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( appBar: AppBar( 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: 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), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: (_isTestingConnection || _isLoadingSettings) ? null : _testConnection, icon: const Icon(Icons.refresh), label: const Text('Tester la connexion'), ), ), 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: 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: 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(), ], ), ], ), ) ), ], ), ), ), 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, ), ), ], ), ), ), ); } }