Open frehiwott opened 3 years ago
Here is the code i have implemented a printing queue.
import 'dart:async';
import 'dart:collection';
import 'package:bluetooth_enable_fork/bluetooth_enable_fork.dart';
import 'package:collection/collection.dart';
import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart';
import 'package:esc_pos_utils/esc_pos_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:location/location.dart' as loc;
import 'package:permission_handler/permission_handler.dart';
import 'package:shopto/models/MOrder.dart';
import 'package:shopto/models/Usr.dart';
import 'package:shopto/utils/reusable.dart';
import 'package:shopto/utils/theme.dart';
class BTPrinter {
static final PrinterBluetoothManager _printerManager =
PrinterBluetoothManager();
static List<PrinterBluetooth> _devices = [];
static Duration duration = const Duration(seconds: 10);
static bool _isScanning = false;
static const PaperSize paper = PaperSize.mm58;
static final Queue<List<int>> _printingQueue = Queue<List<int>>();
static stopScanDevices() {
_printerManager.stopScan();
_isScanning = false;
}
static Future<bool> initPrinter() async {
_devices = [];
List<Permission> permissions = [
Permission.bluetooth,
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
];
if (await Usr.runOnce("pos_printer_permission")) {
// Show a consent dialogue to allow background location for printing
await showConsent();
}
await Future.delayed(const Duration(milliseconds: 500));
Map<Permission, PermissionStatus> statuses = await permissions.request();
if (statuses.values.any((p) => p != PermissionStatus.granted)) {
showToast(
'Permission denied ! Bluetooth and Location permission required to print');
return false;
}
await enableBluetooth();
await enableLocation();
Completer<bool> completer = Completer();
if (!_isScanning && _devices.isEmpty) {
try {
_printerManager.scanResults.listen((devices) async {
pp('UI: Devices found ${devices.length}');
_devices = devices;
if (_devices.isNotEmpty) {
stopScanDevices();
if (!completer.isCompleted) completer.complete(true);
}
});
_printerManager.startScan(duration);
_isScanning = true;
await Future.delayed(duration);
_isScanning = false;
if (_devices.isEmpty) {
showToast(
"No printer founds ! Make sure printer is on and try unpairing the printer.");
if (!completer.isCompleted) completer.complete(false);
}
} on Exception catch (_) {
if (!completer.isCompleted) completer.complete(false);
}
} else if (!completer.isCompleted) {
completer.complete(true);
}
return completer.future;
}
static showConsent() async {
await Future.delayed(const Duration(milliseconds: 320));
await showModalBottomSheet(
context: navigator.context,
builder: (context) => Container(
height: 300,
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text('Allow background location', style: Txt.h2.bold()),
const SizedBox(height: 20),
Text(
'To print receipts, we need to access your location in the background.',
textAlign: TextAlign.center,
style: Txt.h6,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
goBack();
},
child: const Center(child: Text('Allow')),
),
],
),
),
);
}
static _print(List<int> ticketIns) async {
// Add the ticket to the printing queue
_printingQueue.add(ticketIns);
// If the queue has more than one item, wait for the previous item to finish printing
if (_printingQueue.length > 1) {
return;
}
// Process the printing queue
while (_printingQueue.isNotEmpty) {
final currentTicket = _printingQueue.first;
if (_devices.isEmpty) {
bool initialized = await initPrinter();
if (!initialized) {
_printingQueue.removeFirst(); // Remove the failed ticket
continue;
}
}
if (_devices.isEmpty) {
showToast(
"No printer founds ! Make sure printer is on and try unpairing the printer.");
_printingQueue.removeFirst(); // Remove the failed ticket
continue;
}
_printerManager.selectPrinter(_devices.first);
final PosPrintResult res =
await _printerManager.printTicket((currentTicket));
showToast(res.msg);
await Future.delayed(const Duration(seconds: 5));
_printingQueue.removeFirst(); // Remove the printed ticket
}
}
static List<int> _getHeader(Generator ticket, String orderId) {
List<int> bytes = [];
// if (Usr.seller.avatarBytes != null) {
// final ByteData data = Usr.seller.avatarBytes;
// final Uint8List imageBytes = data.buffer.asUint8List();
// final Image image = decodeImage(imageBytes);
// bytes += ticket.image(image);
// }
bytes += ticket.text(Usr.seller.businessName,
styles: const PosStyles(
align: PosAlign.center,
height: PosTextSize.size2,
width: PosTextSize.size2),
linesAfter: 1);
bytes += ticket.text('Ph: ${Usr.seller.phone}',
styles: const PosStyles(align: PosAlign.center));
bytes += ticket.text('Web: ${Usr.seller.storeLink}',
styles: const PosStyles(align: PosAlign.center));
if (Usr.seller.gstEnabled) {
bytes += ticket.text('GST: ${Usr.seller.gstIN}',
styles: const PosStyles(align: PosAlign.center));
}
bytes += ticket.text('Order No: #$orderId',
styles: const PosStyles(align: PosAlign.center), linesAfter: 1);
return bytes;
}
static List<int> _getFooter(Generator ticket, MOrder order) {
List<int> bytes = [];
bytes += ticket.hr();
bytes += ticket.feed(1);
if (order.tableId?.isEmpty ?? false) {
bytes += ticket.text('Table No. : ${order.tableId}',
styles: const PosStyles(align: PosAlign.center, underline: true),
linesAfter: 1);
}
bytes += ticket.text('Thank you!',
styles: const PosStyles(align: PosAlign.center, bold: true));
final now = DateTime.now();
final formatter = DateFormat('MM/dd/yyyy H:m');
final String timestamp = formatter.format(now);
bytes += ticket.text(timestamp,
styles: const PosStyles(align: PosAlign.center), linesAfter: 1);
bytes += ticket.cut();
return bytes;
}
static printKoT(List<MOrder> orders) async {
DateTime now = DateTime.now();
pp("Printing ....");
List<int> bytes = await getKoTData(orders);
pp("Get KOT data: ${DateTime.now().difference(now).inMilliseconds}");
await _print(bytes);
pp("Print KOT: ${DateTime.now().difference(now).inMilliseconds}");
}
static printBill(List<MOrder> orders) async {
List<int> bytes = await getBillData(orders);
await _print(bytes);
}
static printKoTAndBill(List<MOrder> orders) async {
List<int> bytes = await getKoTData(orders);
bytes += await getBillData(orders);
await _print(bytes);
}
static Future<List<int>> getKoTData(List<MOrder> orders) async {
List<List<MOrder>> groupedOrders =
groupBy(orders, (o) => o.pid).values.toList();
final profile = await CapabilityProfile.load();
final Generator ticket = Generator(paper, profile);
List<int> bytes = [];
bytes += _getHeader(ticket, orders.first.transId);
bytes += ticket.text("KITCHEN ORDER TICKET",
styles: const PosStyles(align: PosAlign.center, bold: true));
bytes += ticket.hr();
bytes += ticket.row([
PosColumn(text: 'Qty', width: 1),
PosColumn(text: 'Item', width: 9),
PosColumn(
text: 'Price',
width: 2,
styles: const PosStyles(align: PosAlign.right)),
]);
bytes += ticket.hr();
groupedOrders.forEach((og) {
bytes += ticket.row([
PosColumn(text: '${og.length}', width: 1),
PosColumn(text: og.first.title, width: 9),
PosColumn(
text: og.first.price.toStringAsFixed(2),
width: 2,
styles: const PosStyles(align: PosAlign.right)),
]);
});
bytes += _getFooter(ticket, orders.first);
return bytes;
}
static Future<List<int>> getBillData(List<MOrder> orders) async {
List<List<MOrder>> groupedOrders =
groupBy(orders, (o) => o.pid).values.toList();
final profile = await CapabilityProfile.load();
final Generator ticket = Generator(paper, profile);
List<int> bytes = [];
bytes += _getHeader(ticket, orders.first.transId);
bytes += ticket.row([
PosColumn(text: 'Qty', width: 1),
PosColumn(text: 'Item', width: 7),
PosColumn(
text: 'Price',
width: 2,
styles: const PosStyles(align: PosAlign.right)),
PosColumn(
text: 'Total',
width: 2,
styles: const PosStyles(align: PosAlign.right)),
]);
bytes += ticket.hr();
groupedOrders.forEach((og) {
bytes += ticket.row([
PosColumn(text: '${og.length}', width: 1),
PosColumn(text: og.first.title, width: 7),
PosColumn(
text: og.first.price.toStringAsFixed(2),
width: 2,
styles: const PosStyles(align: PosAlign.right)),
PosColumn(
text: (og.first.price * og.length).toStringAsFixed(2),
width: 2,
styles: const PosStyles(align: PosAlign.right)),
]);
});
double subTotal = 0;
orders.forEach((o) => subTotal += o.price);
double taxRate = orders.first.taxRate;
double taxable = subTotal / (1 + (taxRate / 100));
double tax = subTotal - taxable;
if (Usr.seller.gstEnabled) {
bytes += ticket.hr();
bytes += ticket.row([
PosColumn(
text: 'Sub Total :',
width: 7,
styles: const PosStyles(align: PosAlign.right)),
PosColumn(
text: subTotal.toStringAsFixed(2),
width: 5,
styles: const PosStyles(align: PosAlign.right)),
]);
bytes += ticket.row([
PosColumn(
text: 'Taxable :',
width: 7,
styles: const PosStyles(align: PosAlign.right)),
PosColumn(
text: taxable.toStringAsFixed(2),
width: 5,
styles: const PosStyles(align: PosAlign.right)),
]);
bytes += ticket.row([
PosColumn(
text: 'S.G.S.T @${(taxRate / 2).toStringAsFixed(1)}% :',
width: 7,
styles: const PosStyles(align: PosAlign.right)),
PosColumn(
text: (tax / 2).toStringAsFixed(2),
width: 5,
styles: const PosStyles(align: PosAlign.right)),
]);
bytes += ticket.row([
PosColumn(
text: 'C.G.S.T @${(taxRate / 2).toStringAsFixed(1)}% :',
width: 7,
styles: const PosStyles(align: PosAlign.right)),
PosColumn(
text: (tax / 2).toStringAsFixed(2),
width: 5,
styles: const PosStyles(align: PosAlign.right)),
]);
}
bytes += ticket.hr(ch: '=');
bytes += ticket.row([
PosColumn(
text: 'TOTAL',
width: 6,
styles: const PosStyles(
height: PosTextSize.size2,
width: PosTextSize.size2,
)),
PosColumn(
text: subTotal.toStringAsFixed(2),
width: 6,
styles: const PosStyles(
align: PosAlign.right,
height: PosTextSize.size2,
width: PosTextSize.size2,
)),
]);
bytes += _getFooter(ticket, orders.first);
return bytes;
}
static enableLocation() async {
loc.Location location = loc.Location();
bool serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
showToast("Location is required to use printer");
return;
}
}
}
static enableBluetooth() async {
BluetoothEnable.enableBluetooth.then((result) {
if (result == "true") {
// Bluetooth has been enabled
} else if (result == "false") {
showToast("Bluetooth is required to use printer");
}
});
}
}
I was trying to print more than one ticket per user input. But it is not working. I tried different options, but no result.