commit 09082025
This commit is contained in:
parent
2ab6254576
commit
3c8dfbcac7
@ -40,6 +40,8 @@ class _MainLayoutState extends State<MainLayout> {
|
|||||||
return 6;
|
return 6;
|
||||||
case '/information':
|
case '/information':
|
||||||
return 7;
|
return 7;
|
||||||
|
case '/Setting':
|
||||||
|
return 8;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -76,6 +78,9 @@ class _MainLayoutState extends State<MainLayout> {
|
|||||||
case 7:
|
case 7:
|
||||||
route = '/information';
|
route = '/information';
|
||||||
break;
|
break;
|
||||||
|
case 8:
|
||||||
|
route = '/Setting';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
route = '/tables';
|
route = '/tables';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'pages/login_screen.dart';
|
|||||||
import 'pages/menus_screen.dart';
|
import 'pages/menus_screen.dart';
|
||||||
import 'pages/historique_commande.dart';
|
import 'pages/historique_commande.dart';
|
||||||
import 'pages/information.dart';
|
import 'pages/information.dart';
|
||||||
|
import 'pages/printer_page.dart';
|
||||||
import 'pages/encaissement_screen.dart'; // NOUVEAU
|
import 'pages/encaissement_screen.dart'; // NOUVEAU
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -64,6 +65,11 @@ class MyApp extends StatelessWidget {
|
|||||||
currentRoute: '/information',
|
currentRoute: '/information',
|
||||||
child: PrintTemplateManagementScreen(),
|
child: PrintTemplateManagementScreen(),
|
||||||
),
|
),
|
||||||
|
'/Setting':
|
||||||
|
(context) => MainLayout(
|
||||||
|
currentRoute: '/Setting',
|
||||||
|
child: PrinterPage(),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
52
lib/models/printerModel.dart
Normal file
52
lib/models/printerModel.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// models/printer_settings.dart
|
||||||
|
class PrinterSettings {
|
||||||
|
final String? ipAddress;
|
||||||
|
final int? port;
|
||||||
|
final String? name;
|
||||||
|
final String? type;
|
||||||
|
final int? id;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
|
PrinterSettings({
|
||||||
|
this.ipAddress,
|
||||||
|
this.port,
|
||||||
|
this.name,
|
||||||
|
this.type,
|
||||||
|
this.id,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PrinterSettings.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PrinterSettings(
|
||||||
|
ipAddress: json['ip_address'],
|
||||||
|
port: json['port'] ?? 9100,
|
||||||
|
name: json['name'],
|
||||||
|
type: json['type'],
|
||||||
|
id: json['id'],
|
||||||
|
createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null,
|
||||||
|
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'ip_address': ipAddress,
|
||||||
|
'port': port,
|
||||||
|
'name': name,
|
||||||
|
'type': type,
|
||||||
|
'id': id,
|
||||||
|
'created_at': createdAt?.toIso8601String(),
|
||||||
|
'updated_at': updatedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode pour créer un objet de test de connexion
|
||||||
|
Map<String, dynamic> toTestJson() {
|
||||||
|
return {
|
||||||
|
'ip_address': ipAddress,
|
||||||
|
'port': port,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -255,7 +255,9 @@ class _CartPageState extends State<CartPage> {
|
|||||||
actions: [
|
actions: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
printOrderPDF(order);
|
printOrderWithFeedback(order, (message, isSuccess) {
|
||||||
|
print('$message - Success: $isSuccess');
|
||||||
|
});
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder:
|
builder:
|
||||||
|
|||||||
@ -615,7 +615,9 @@ class OrderCard extends StatelessWidget {
|
|||||||
// ← Bouton imprimer
|
// ← Bouton imprimer
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
printOrderPDF(order);
|
printOrderWithFeedback(order, (message, isSuccess) {
|
||||||
|
print('$message - Success: $isSuccess');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.print, color: Colors.white, size: 16),
|
icon: const Icon(Icons.print, color: Colors.white, size: 16),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
// Ajoutez l'import de votre service API et du modèle
|
||||||
|
import '../services/restaurant_api_service.dart';
|
||||||
|
|
||||||
class PrinterPage extends StatefulWidget {
|
class PrinterPage extends StatefulWidget {
|
||||||
const PrinterPage({super.key});
|
const PrinterPage({super.key});
|
||||||
@ -11,18 +13,55 @@ class PrinterPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PrinterPageState extends State<PrinterPage> {
|
class _PrinterPageState extends State<PrinterPage> {
|
||||||
static const String printerIP = '192.168.123.251';
|
// Remplacer les constantes par des variables
|
||||||
static const int printerPort = 9100;
|
String printerIP = '192.168.123.251'; // Valeur par défaut
|
||||||
|
int printerPort = 9100; // Valeur par défaut
|
||||||
|
|
||||||
bool _isPrinting = false;
|
bool _isPrinting = false;
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
bool _isTestingConnection = false;
|
bool _isTestingConnection = false;
|
||||||
|
bool _isLoadingSettings = false;
|
||||||
String _statusMessage = 'Non testé';
|
String _statusMessage = 'Non testé';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_testConnection();
|
_loadPrinterSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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<void> _testConnection() async {
|
Future<void> _testConnection() async {
|
||||||
@ -135,6 +174,103 @@ class _PrinterPageState extends State<PrinterPage> {
|
|||||||
setState(() => _isPrinting = false);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -142,164 +278,363 @@ class _PrinterPageState extends State<PrinterPage> {
|
|||||||
title: const Text('Imprimante POS'),
|
title: const Text('Imprimante POS'),
|
||||||
backgroundColor: Colors.blue,
|
backgroundColor: Colors.blue,
|
||||||
foregroundColor: Colors.white,
|
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(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(16.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
// Status de connexion
|
children: [
|
||||||
Card(
|
// Status de connexion
|
||||||
child: Padding(
|
Card(
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(16.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
'Status de l\'imprimante',
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
'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(
|
const SizedBox(height: 10),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
if (_isTestingConnection || _isLoadingSettings)
|
||||||
'REÇU DE VENTE',
|
const SizedBox(
|
||||||
style: TextStyle(
|
width: 20,
|
||||||
fontSize: 18,
|
height: 20,
|
||||||
fontWeight: FontWeight.bold,
|
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),
|
|
||||||
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: 10),
|
||||||
],
|
Row(
|
||||||
),
|
children: [
|
||||||
),
|
Expanded(
|
||||||
),
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: (_isTestingConnection || _isLoadingSettings) ? null : _testConnection,
|
||||||
const SizedBox(height: 20),
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: const Text('Tester la connexion'),
|
||||||
// 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 SizedBox(width: 10),
|
||||||
)
|
Expanded(
|
||||||
: const Icon(Icons.print),
|
child: ElevatedButton.icon(
|
||||||
label: Text(_isPrinting ? 'Impression...' : 'Imprimer le reçu'),
|
onPressed: _isLoadingSettings ? null : _loadPrinterSettings,
|
||||||
style: ElevatedButton.styleFrom(
|
icon: const Icon(Icons.download),
|
||||||
backgroundColor: Colors.green,
|
label: const Text('Recharger paramètres'),
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
|
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<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 && !_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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,77 +1,764 @@
|
|||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'dart:async';
|
||||||
import 'package:pdf/pdf.dart';
|
|
||||||
import 'package:printing/printing.dart';
|
|
||||||
import '../pages/commandes_screen.dart';
|
|
||||||
Future<void> printOrderPDF(Order order) async {
|
|
||||||
|
|
||||||
String _formatTime(DateTime dateTime) {
|
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;
|
||||||
|
|
||||||
|
// Getters publics
|
||||||
|
PrinterSettings? get settings => _settings;
|
||||||
|
PrinterConnectionStatus get connectionStatus => _connectionStatus;
|
||||||
|
bool get isConfigured => _settings != null &&
|
||||||
|
_settings!.ipAddress != null &&
|
||||||
|
_settings!.ipAddress!.isNotEmpty &&
|
||||||
|
_settings!.port != null;
|
||||||
|
|
||||||
|
// Constantes ESC/POS
|
||||||
|
static const List<int> ESC_CONDENSED_ON = [0x1B, 0x0F];
|
||||||
|
static const List<int> ESC_CONDENSED_OFF = [0x1B, 0x12];
|
||||||
|
static const List<int> ESC_INIT = [0x1B, 0x40];
|
||||||
|
static const List<int> ESC_ALIGN_CENTER = [0x1B, 0x61, 0x01];
|
||||||
|
static const List<int> ESC_ALIGN_LEFT = [0x1B, 0x61, 0x00];
|
||||||
|
static const List<int> ESC_ALIGN_RIGHT = [0x1B, 0x61, 0x02];
|
||||||
|
static const List<int> ESC_DOUBLE_SIZE = [0x1D, 0x21, 0x11];
|
||||||
|
static const List<int> ESC_NORMAL_SIZE = [0x1D, 0x23, 0x00];
|
||||||
|
static const List<int> ESC_BOLD_ON = [0x1B, 0x45, 0x01];
|
||||||
|
static const List<int> ESC_BOLD_OFF = [0x1B, 0x45, 0x00];
|
||||||
|
static const List<int> ESC_CUT = [0x1D, 0x56, 0x00];
|
||||||
|
static const List<int> ESC_PAGE_WIDTH_80MM = [0x1D, 0x57, 0x00, 0x02];
|
||||||
|
static const List<int> ESC_LEFT_MARGIN_0 = [0x1D, 0x4C, 0x015, 0x00];
|
||||||
|
|
||||||
|
// Configuration d'impression
|
||||||
|
static const int maxCharsPerLine = 42;
|
||||||
|
static const String checkbox = '[ ]';
|
||||||
|
static const String rightWord = 'cocher';
|
||||||
|
static const int padding = 1;
|
||||||
|
|
||||||
|
/// Initialise la configuration de l'imprimante avec gestion d'erreurs robuste
|
||||||
|
Future<bool> initialize() async {
|
||||||
|
try {
|
||||||
|
_connectionStatus = PrinterConnectionStatus.connecting;
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("🔧 Initialisation de la configuration imprimante...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupération des paramètres avec timeout via votre API
|
||||||
|
var printerSettings = await RestaurantApiService.getPrinterSettings()
|
||||||
|
.timeout(
|
||||||
|
const Duration(seconds: 10),
|
||||||
|
onTimeout: () => throw TimeoutException('Timeout lors de la récupération des paramètres'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Validation des paramètres
|
||||||
|
if (printerSettings.ipAddress == null || printerSettings.ipAddress!.isEmpty) {
|
||||||
|
throw ArgumentError('Adresse IP de l\'imprimante non définie');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printerSettings.port == null || printerSettings.port! <= 0 || printerSettings.port! > 65535) {
|
||||||
|
throw ArgumentError('Port de l\'imprimante invalide: ${printerSettings.port}');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation du format IP
|
||||||
|
if (!_isValidIpAddress(printerSettings.ipAddress!)) {
|
||||||
|
throw ArgumentError('Format d\'adresse IP invalide: ${printerSettings.ipAddress}');
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings=null;
|
||||||
|
// Configuration réussie
|
||||||
|
_settings = printerSettings;
|
||||||
|
_connectionStatus = PrinterConnectionStatus.disconnected;
|
||||||
|
_settings = PrinterSettings();
|
||||||
|
_settings = printerSettings;
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("✅ Imprimante configurée : ${_settings!.ipAddress}:${_settings!.port}");
|
||||||
|
if (_settings!.name != null) {
|
||||||
|
print("📋 Nom: ${_settings!.name}");
|
||||||
|
}
|
||||||
|
if (_settings!.type != null) {
|
||||||
|
print("🏷️ Type: ${_settings!.type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} on TimeoutException catch (e) {
|
||||||
|
_connectionStatus = PrinterConnectionStatus.error;
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("⏰ Timeout lors de l'initialisation: $e");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} on ArgumentError catch (e) {
|
||||||
|
_connectionStatus = PrinterConnectionStatus.error;
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("❌ Paramètres invalides: $e");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
_connectionStatus = PrinterConnectionStatus.error;
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("💥 Erreur lors de la récupération des paramètres d'imprimante : $e");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Valide le format d'une adresse IP
|
||||||
|
bool _isValidIpAddress(String ip) {
|
||||||
|
final ipRegex = RegExp(r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$');
|
||||||
|
return ipRegex.hasMatch(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test de connexion avancé avec métriques
|
||||||
|
Future<ConnectionTestResult> 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<bool> testConnectionSimple() async {
|
||||||
|
final result = await testConnection();
|
||||||
|
return result.isSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Diagnostic complet de l'imprimante
|
||||||
|
Future<Map<String, dynamic>> runDiagnostics() async {
|
||||||
|
final diagnostics = <String, dynamic>{
|
||||||
|
'timestamp': DateTime.now().toIso8601String(),
|
||||||
|
'configured': isConfigured,
|
||||||
|
'connectionStatus': _connectionStatus.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_settings != null) {
|
||||||
|
diagnostics['settings'] = {
|
||||||
|
'ipAddress': _settings!.ipAddress,
|
||||||
|
'port': _settings!.port,
|
||||||
|
'name': _settings!.name,
|
||||||
|
'type': _settings!.type,
|
||||||
|
'id': _settings!.id,
|
||||||
|
'createdAt': _settings!.createdAt?.toIso8601String(),
|
||||||
|
'updatedAt': _settings!.updatedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
diagnostics['settings'] = 'Non configuré';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isConfigured) {
|
||||||
|
// Test de résolution DNS (si applicable)
|
||||||
|
try {
|
||||||
|
final addresses = await InternetAddress.lookup(_settings!.ipAddress!);
|
||||||
|
diagnostics['dnsResolution'] = {
|
||||||
|
'success': true,
|
||||||
|
'addresses': addresses.map((addr) => addr.address).toList(),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
diagnostics['dnsResolution'] = {
|
||||||
|
'success': false,
|
||||||
|
'error': e.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test de connectivité
|
||||||
|
final connectionResult = await testConnection(maxRetries: 1);
|
||||||
|
diagnostics['connectionTest'] = {
|
||||||
|
'success': connectionResult.isSuccessful,
|
||||||
|
'message': connectionResult.message,
|
||||||
|
'responseTime': '${connectionResult.responseTime.inMilliseconds}ms',
|
||||||
|
'status': connectionResult.status.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test de ping réseau (optionnel)
|
||||||
|
diagnostics['networkReachability'] = await _testNetworkReachability();
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test de accessibilité réseau
|
||||||
|
Future<Map<String, dynamic>> _testNetworkReachability() async {
|
||||||
|
try {
|
||||||
|
final result = await Process.run('ping', [
|
||||||
|
'-c', '1', // Une seule tentative
|
||||||
|
'-W', '3000', // Timeout 3 secondes
|
||||||
|
_settings!.ipAddress!,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': result.exitCode == 0,
|
||||||
|
'output': result.stdout,
|
||||||
|
'error': result.stderr,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
'success': false,
|
||||||
|
'error': 'Ping non disponible: $e',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Met à jour la configuration manuellement
|
||||||
|
bool updateConfiguration(String ipAddress, int port, {String? name, String? type}) {
|
||||||
|
if (!_isValidIpAddress(ipAddress)) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("❌ Adresse IP invalide: $ipAddress");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port <= 0 || port > 65535) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("❌ Port invalide: $port");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings = PrinterSettings(
|
||||||
|
ipAddress: ipAddress,
|
||||||
|
port: port,
|
||||||
|
name: name,
|
||||||
|
type: type,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
_connectionStatus = PrinterConnectionStatus.disconnected;
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("🔧 Configuration mise à jour: $ipAddress:$port");
|
||||||
|
if (name != null) print("📋 Nom: $name");
|
||||||
|
if (type != null) print("🏷️ Type: $type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recharge la configuration depuis l'API
|
||||||
|
Future<bool> reloadConfiguration() async {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("🔄 Rechargement de la configuration depuis l'API...");
|
||||||
|
}
|
||||||
|
return await initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Réinitialise la configuration
|
||||||
|
void resetConfiguration() {
|
||||||
|
_settings = null;
|
||||||
|
_connectionStatus = PrinterConnectionStatus.notConfigured;
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("🔄 Configuration de l'imprimante réinitialisée");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode pour normaliser les caractères accentués
|
||||||
|
String safePadRight(String text, int width) {
|
||||||
|
// Remplace temporairement les accents par des lettres simples
|
||||||
|
final normalized = text
|
||||||
|
.replaceAll(RegExp(r'[éèêë]'), 'e')
|
||||||
|
.replaceAll(RegExp(r'[àâä]'), 'a')
|
||||||
|
.replaceAll(RegExp(r'[ôö]'), 'o')
|
||||||
|
.replaceAll(RegExp(r'[ûü]'), 'u')
|
||||||
|
.replaceAll(RegExp(r'[ç]'), 'c')
|
||||||
|
.replaceAll(RegExp(r'[ÉÈÊË]'), 'E')
|
||||||
|
.replaceAll(RegExp(r'[ÀÂÄ]'), 'A')
|
||||||
|
.replaceAll(RegExp(r'[ÔÖ]'), 'O')
|
||||||
|
.replaceAll(RegExp(r'[ÛÜ]'), 'U')
|
||||||
|
.replaceAll(RegExp(r'[Ç]'), 'C');
|
||||||
|
|
||||||
|
return normalized.padRight(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthodes d'impression optimisées
|
||||||
|
Future<bool> printOrderESCPOS(Order order) async {
|
||||||
|
final connectionTest = await testConnection();
|
||||||
|
if (!connectionTest.isSuccessful) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("❌ Impossible d'imprimer: ${connectionTest.message}");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final socket = await Socket.connect(_settings!.ipAddress!, _settings!.port!);
|
||||||
|
final List<int> bytes = [];
|
||||||
|
|
||||||
|
// Initialisation et configuration pour 80mm
|
||||||
|
bytes.addAll(ESC_INIT);
|
||||||
|
bytes.addAll(ESC_PAGE_WIDTH_80MM);
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
bytes.addAll(latin1.encode('${'-' * maxCharsPerLine}\n'));
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
|
||||||
|
// En-tête centré
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(ESC_BOLD_ON);
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
bytes.addAll(latin1.encode('Commande n° ${order.numeroCommande}\n'));
|
||||||
|
|
||||||
|
// Informations commande
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(ESC_NORMAL_SIZE);
|
||||||
|
|
||||||
|
bytes.addAll(latin1.encode('Table: ${order.tablename}\n'));
|
||||||
|
bytes.addAll(ESC_BOLD_OFF);
|
||||||
|
bytes.addAll(latin1.encode('Date: ${_formatTime(order.dateCommande)}\n'));
|
||||||
|
bytes.addAll(latin1.encode('${'-' * maxCharsPerLine}\n'));
|
||||||
|
|
||||||
|
// En-tête du tableau
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(ESC_BOLD_ON);
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
String header = 'Designation'.padRight(maxCharsPerLine - rightWord.length - padding) +
|
||||||
|
' ' * padding + rightWord + '\n';
|
||||||
|
bytes.addAll(latin1.encode(header));
|
||||||
|
bytes.addAll(ESC_BOLD_OFF);
|
||||||
|
bytes.addAll(latin1.encode('${'-' * maxCharsPerLine}\n'));
|
||||||
|
|
||||||
|
// Articles
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
for (var item in order.items) {
|
||||||
|
String designation = '${item.quantite}x ${item.nom ?? 'Item'}';
|
||||||
|
int maxDesignationLength = maxCharsPerLine - checkbox.length - padding;
|
||||||
|
|
||||||
|
if (designation.length > maxDesignationLength) {
|
||||||
|
designation = designation.substring(0, maxDesignationLength - 3) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
String line = designation.padRight(maxDesignationLength) +
|
||||||
|
' ' * padding + checkbox + '\n';
|
||||||
|
bytes.addAll(utf8.encode(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pied de page
|
||||||
|
bytes.addAll(utf8.encode('${'-' * maxCharsPerLine}\n'));
|
||||||
|
|
||||||
|
// Espacement avant coupe
|
||||||
|
bytes.addAll([0x0A, 0x0A, 0x0A]);
|
||||||
|
bytes.addAll(ESC_CUT);
|
||||||
|
|
||||||
|
socket.add(bytes);
|
||||||
|
await socket.flush();
|
||||||
|
await socket.close();
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("✅ Impression réussie!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('💥 Erreur impression: $e');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> printOrderESC80MM(Order order) async {
|
||||||
|
final connectionTest = await testConnection();
|
||||||
|
if (!connectionTest.isSuccessful) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("❌ Impossible d'imprimer: ${connectionTest.message}");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final socket = await Socket.connect(_settings!.ipAddress!, _settings!.port!);
|
||||||
|
final List<int> bytes = [];
|
||||||
|
|
||||||
|
// Configuration spécifique 80mm
|
||||||
|
bytes.addAll(ESC_INIT);
|
||||||
|
bytes.addAll(ESC_PAGE_WIDTH_80MM);
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
|
||||||
|
// En-tête centré et stylisé
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(ESC_DOUBLE_SIZE);
|
||||||
|
bytes.addAll(ESC_BOLD_ON);
|
||||||
|
bytes.addAll(ESC_PAGE_WIDTH_80MM);
|
||||||
|
bytes.addAll(utf8.encode('COMMANDE\n'));
|
||||||
|
bytes.addAll(ESC_BOLD_OFF);
|
||||||
|
bytes.addAll(ESC_NORMAL_SIZE);
|
||||||
|
bytes.addAll(utf8.encode('${'-' * maxCharsPerLine}\n'));
|
||||||
|
|
||||||
|
// Informations commande avec formatage amélioré
|
||||||
|
bytes.addAll(utf8.encode(_createInfoLine('N° Commande', order.numeroCommande.toString())));
|
||||||
|
bytes.addAll(utf8.encode(_createInfoLine('Date/Heure', _formatTime(order.dateCommande))));
|
||||||
|
bytes.addAll(utf8.encode('${'=' * maxCharsPerLine}\n'));
|
||||||
|
|
||||||
|
// En-tête du tableau avec colonnes bien définies
|
||||||
|
bytes.addAll(ESC_BOLD_ON);
|
||||||
|
bytes.addAll(ESC_PAGE_WIDTH_80MM);
|
||||||
|
bytes.addAll(utf8.encode(_createTableHeader()));
|
||||||
|
bytes.addAll(ESC_BOLD_OFF);
|
||||||
|
bytes.addAll(utf8.encode('${'-' * maxCharsPerLine}\n'));
|
||||||
|
|
||||||
|
// Articles avec formatage en colonnes
|
||||||
|
for (var item in order.items) {
|
||||||
|
bytes.addAll(utf8.encode(_createItemLine(item.quantite, item.nom ?? 'Item')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pied de page
|
||||||
|
bytes.addAll(utf8.encode('${'=' * maxCharsPerLine}\n'));
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(ESC_PAGE_WIDTH_80MM);
|
||||||
|
|
||||||
|
// Coupe avec espacement
|
||||||
|
bytes.addAll([0x0A, 0x0A]);
|
||||||
|
bytes.addAll(ESC_CUT);
|
||||||
|
|
||||||
|
socket.add(bytes);
|
||||||
|
await socket.flush();
|
||||||
|
await socket.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('Erreur impression: $e');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> printOrderESCPOSOptimized(Order order) async {
|
||||||
|
final connectionTest = await testConnection();
|
||||||
|
if (!connectionTest.isSuccessful) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("❌ Impossible d'imprimer: ${connectionTest.message}");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final socket = await Socket.connect(_settings!.ipAddress!, _settings!.port!);
|
||||||
|
final List<int> bytes = [];
|
||||||
|
|
||||||
|
// Configuration initiale optimisée pour 72mm
|
||||||
|
bytes.addAll(ESC_INIT);
|
||||||
|
bytes.addAll(ESC_LEFT_MARGIN_0);
|
||||||
|
|
||||||
|
// Définir largeur de page explicite
|
||||||
|
bytes.addAll([0x1B, 0x51, 180]);
|
||||||
|
|
||||||
|
// En-tête
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(ESC_DOUBLE_SIZE);
|
||||||
|
bytes.addAll(utf8.encode('COMMANDE\n\n'));
|
||||||
|
|
||||||
|
// Corps avec police condensée
|
||||||
|
bytes.addAll(ESC_NORMAL_SIZE);
|
||||||
|
bytes.addAll(ESC_CONDENSED_ON);
|
||||||
|
|
||||||
|
const int compactMaxChars = 32;
|
||||||
|
|
||||||
|
bytes.addAll(utf8.encode('Cmd: ${order.numeroCommande}\n'));
|
||||||
|
bytes.addAll(utf8.encode('Table: ${order.tablename}\n'));
|
||||||
|
bytes.addAll(utf8.encode('${_formatTime(order.dateCommande)}\n'));
|
||||||
|
bytes.addAll(utf8.encode('${'=' * compactMaxChars}\n'));
|
||||||
|
|
||||||
|
// Articles avec formatage optimisé
|
||||||
|
for (var item in order.items) {
|
||||||
|
String qty = '${item.quantite}x';
|
||||||
|
String name = item.nom ?? 'Item';
|
||||||
|
int availableSpace = compactMaxChars - qty.length - checkbox.length - 2;
|
||||||
|
|
||||||
|
if (name.length > availableSpace) {
|
||||||
|
name = name.substring(0, availableSpace - 3) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
String line = '$qty $name'.padRight(compactMaxChars - checkbox.length) + checkbox + '\n';
|
||||||
|
bytes.addAll(utf8.encode(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalisation
|
||||||
|
bytes.addAll(utf8.encode('${'=' * compactMaxChars}\n'));
|
||||||
|
bytes.addAll(ESC_CONDENSED_OFF);
|
||||||
|
bytes.addAll(ESC_ALIGN_CENTER);
|
||||||
|
bytes.addAll(utf8.encode('\nBon service !\n\n\n'));
|
||||||
|
bytes.addAll(ESC_CUT);
|
||||||
|
|
||||||
|
socket.add(bytes);
|
||||||
|
await socket.flush();
|
||||||
|
await socket.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('Erreur impression: $e');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonctions utilitaires pour formatage
|
||||||
|
String _createInfoLine(String label, String value) {
|
||||||
|
int labelWidth = 12;
|
||||||
|
int valueWidth = maxCharsPerLine - labelWidth - 2;
|
||||||
|
|
||||||
|
String truncatedLabel = label.length > labelWidth ?
|
||||||
|
label.substring(0, labelWidth) : label.padRight(labelWidth);
|
||||||
|
String truncatedValue = value.length > valueWidth ?
|
||||||
|
value.substring(0, valueWidth - 3) + '...' : value;
|
||||||
|
|
||||||
|
return '$truncatedLabel: $truncatedValue\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _createTableHeader() {
|
||||||
|
int qtyWidth = 4;
|
||||||
|
int checkboxWidth = checkbox.length;
|
||||||
|
int nameWidth = maxCharsPerLine - qtyWidth - checkboxWidth - 2;
|
||||||
|
|
||||||
|
String header = 'Qty'.padRight(qtyWidth) +
|
||||||
|
'Designation'.padRight(nameWidth) +
|
||||||
|
checkbox + '\n';
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _createItemLine(int quantity, String itemName) {
|
||||||
|
int qtyWidth = 4;
|
||||||
|
int checkboxWidth = checkbox.length;
|
||||||
|
int nameWidth = maxCharsPerLine - qtyWidth - checkboxWidth - 2;
|
||||||
|
|
||||||
|
String qtyStr = quantity.toString().padRight(qtyWidth);
|
||||||
|
String nameStr = itemName.length > nameWidth ?
|
||||||
|
itemName.substring(0, nameWidth - 3) + '...' : itemName.padRight(nameWidth);
|
||||||
|
|
||||||
|
return qtyStr + nameStr + checkbox + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formatage du temps
|
||||||
|
String _formatTime(DateTime dateTime) {
|
||||||
return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')} ${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}';
|
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}';
|
||||||
}
|
}
|
||||||
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
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await Printing.layoutPdf(
|
|
||||||
onLayout: (PdfPageFormat format) async => pdf.save(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
@ -10,7 +10,6 @@ import 'package:printing/printing.dart';
|
|||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import '../models/command_detail.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import '../pages/information.dart';
|
import '../pages/information.dart';
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:itrimobe/models/command_detail.dart';
|
|||||||
import 'package:itrimobe/models/payment_method.dart';
|
import 'package:itrimobe/models/payment_method.dart';
|
||||||
import 'package:itrimobe/models/tables_order.dart';
|
import 'package:itrimobe/models/tables_order.dart';
|
||||||
|
|
||||||
|
import '../models/printerModel.dart';
|
||||||
import '../pages/information.dart';
|
import '../pages/information.dart';
|
||||||
|
|
||||||
class RestaurantApiService {
|
class RestaurantApiService {
|
||||||
@ -59,6 +60,52 @@ static Future<PrintTemplate> getPrintTemplate() async {
|
|||||||
throw Exception("Erreur lors de la récupération du template (${response.statusCode})");
|
throw Exception("Erreur lors de la récupération du template (${response.statusCode})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static Future<PrinterSettings> 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<bool> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -255,7 +255,7 @@ class AppBottomNavigation extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.payment,
|
Icons.history,
|
||||||
color:
|
color:
|
||||||
selectedIndex == 6
|
selectedIndex == 6
|
||||||
? Colors.white
|
? Colors.white
|
||||||
@ -299,7 +299,7 @@ class AppBottomNavigation extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.payment,
|
Icons.info,
|
||||||
color:
|
color:
|
||||||
selectedIndex == 7
|
selectedIndex == 7
|
||||||
? Colors.white
|
? 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(),
|
const Spacer(),
|
||||||
|
|||||||
@ -6,10 +6,14 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <charset_converter/charset_converter_plugin.h>
|
||||||
#include <printing/printing_plugin.h>
|
#include <printing/printing_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
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 =
|
g_autoptr(FlPluginRegistrar) printing_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
|
||||||
printing_plugin_register_with_registrar(printing_registrar);
|
printing_plugin_register_with_registrar(printing_registrar);
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
charset_converter
|
||||||
printing
|
printing
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|||||||
74
pubspec.lock
74
pubspec.lock
@ -5,10 +5,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.7"
|
version: "3.6.1"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -49,6 +49,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
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:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -81,6 +89,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.0.6"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -89,6 +105,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
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:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -144,6 +176,30 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -161,13 +217,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
image:
|
image:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.4"
|
version: "4.3.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -392,14 +448,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
posix:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: posix
|
|
||||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.0.3"
|
|
||||||
printing:
|
printing:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -6,12 +6,15 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <charset_converter/charset_converter_plugin.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <printing/printing_plugin.h>
|
#include <printing/printing_plugin.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
CharsetConverterPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("CharsetConverterPlugin"));
|
||||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
PrintingPluginRegisterWithRegistrar(
|
PrintingPluginRegisterWithRegistrar(
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
charset_converter
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
printing
|
printing
|
||||||
share_plus
|
share_plus
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user