2 changed files with 310 additions and 0 deletions
@ -0,0 +1,305 @@ |
|||
import 'package:flutter/foundation.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'dart:io'; |
|||
import 'dart:convert'; |
|||
|
|||
class PrinterPage extends StatefulWidget { |
|||
const PrinterPage({super.key}); |
|||
|
|||
@override |
|||
_PrinterPageState createState() => _PrinterPageState(); |
|||
} |
|||
|
|||
class _PrinterPageState extends State<PrinterPage> { |
|||
static const String printerIP = '192.168.123.251'; |
|||
static const int printerPort = 9100; |
|||
|
|||
bool _isPrinting = false; |
|||
bool _isConnected = false; |
|||
bool _isTestingConnection = false; |
|||
String _statusMessage = 'Non testé'; |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_testConnection(); |
|||
} |
|||
|
|||
Future<void> _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<bool> _printReceipt() async { |
|||
try { |
|||
final socket = await Socket.connect(printerIP, printerPort); |
|||
final List<int> 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<void> _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); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Scaffold( |
|||
appBar: AppBar( |
|||
title: const Text('Imprimante POS'), |
|||
backgroundColor: Colors.blue, |
|||
foregroundColor: Colors.white, |
|||
), |
|||
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), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
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, |
|||
children: [ |
|||
const Text( |
|||
'REÇU DE VENTE', |
|||
style: TextStyle( |
|||
fontSize: 18, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
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(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<Color>( |
|||
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) |
|||
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, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue