first commit
This commit is contained in:
241
lib/thermal priting invoices/provider/custom_print_provider.dart
Normal file
241
lib/thermal priting invoices/provider/custom_print_provider.dart
Normal file
@@ -0,0 +1,241 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
|
||||
|
||||
import '../../constant.dart';
|
||||
import '../model/print_transaction_model.dart';
|
||||
|
||||
final printerPurchaseProviderNotifier = ChangeNotifierProvider((ref) => PrinterPurchase());
|
||||
|
||||
class PrinterPurchase extends ChangeNotifier {
|
||||
List<BluetoothInfo> availableBluetoothDevices = [];
|
||||
|
||||
Future<void> getBluetooth() async {
|
||||
final List<BluetoothInfo> bluetooths = await PrintBluetoothThermal.pairedBluetooths;
|
||||
availableBluetoothDevices = bluetooths;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<bool> setConnect(String mac) async {
|
||||
bool status = false;
|
||||
final bool result = await PrintBluetoothThermal.connect(macPrinterAddress: mac);
|
||||
if (result == true) {
|
||||
connected = true;
|
||||
status = true;
|
||||
}
|
||||
notifyListeners();
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<bool> printCustomTicket(
|
||||
{required PrintPurchaseTransactionModel printTransactionModel,
|
||||
required String data,
|
||||
required String paperSize}) async {
|
||||
bool isPrinted = false;
|
||||
bool? isConnected = await PrintBluetoothThermal.connectionStatus;
|
||||
if (isConnected == true) {
|
||||
List<int> bytes = await customPrintTicket(
|
||||
printTransactionModel: printTransactionModel,
|
||||
data: data,
|
||||
paperSize: paperSize,
|
||||
);
|
||||
await PrintBluetoothThermal.writeBytes(bytes);
|
||||
isPrinted = true;
|
||||
} else {
|
||||
isPrinted = false;
|
||||
}
|
||||
notifyListeners();
|
||||
return isPrinted;
|
||||
}
|
||||
|
||||
Future<List<int>> customPrintTicket(
|
||||
{required PrintPurchaseTransactionModel printTransactionModel,
|
||||
required String data,
|
||||
required String paperSize}) async {
|
||||
List<int> bytes = [];
|
||||
PaperSize? size;
|
||||
if (paperSize == '2 inch 58mm') {
|
||||
size = PaperSize.mm58;
|
||||
} else {
|
||||
size = PaperSize.mm80;
|
||||
}
|
||||
|
||||
try {
|
||||
CapabilityProfile profile = await CapabilityProfile.load();
|
||||
final generator = Generator(PaperSize.mm58, profile);
|
||||
|
||||
Future<void> addText(String text, {PosStyles? styles, int linesAfter = 0}) async {
|
||||
if (_isAscii(text)) {
|
||||
bytes += generator.text(
|
||||
text,
|
||||
linesAfter: linesAfter,
|
||||
styles: const PosStyles(
|
||||
align: PosAlign.center,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final imageBytes = await _textToImageBytes(
|
||||
generator,
|
||||
text,
|
||||
styles: const PosStyles(
|
||||
align: PosAlign.center,
|
||||
),
|
||||
);
|
||||
bytes += imageBytes;
|
||||
if (linesAfter > 0) {
|
||||
bytes += generator.feed(linesAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add company name
|
||||
final companyNameText = printTransactionModel.personalInformationModel.data?.companyName ?? '';
|
||||
bytes += generator.text(
|
||||
companyNameText,
|
||||
styles: const PosStyles(
|
||||
align: PosAlign.center,
|
||||
height: PosTextSize.size2,
|
||||
width: PosTextSize.size2,
|
||||
),
|
||||
linesAfter: 1,
|
||||
);
|
||||
|
||||
// Add address
|
||||
final address = printTransactionModel.personalInformationModel.data?.address ?? '';
|
||||
if (address.isNotEmpty) {
|
||||
bytes += generator.text(
|
||||
address,
|
||||
styles: const PosStyles(align: PosAlign.center),
|
||||
);
|
||||
}
|
||||
|
||||
// Add phone number
|
||||
final phoneNumber = printTransactionModel.personalInformationModel.data?.phoneNumber ?? '';
|
||||
if (phoneNumber.isNotEmpty) {
|
||||
bytes += generator.text(
|
||||
'Tel: $phoneNumber',
|
||||
styles: const PosStyles(align: PosAlign.center),
|
||||
linesAfter: printTransactionModel.personalInformationModel.data?.vatNo?.trim().isNotEmpty == true ? 0 : 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Add VAT information if available
|
||||
final vatNumber = printTransactionModel.personalInformationModel.data?.vatNo;
|
||||
if (vatNumber != null &&
|
||||
vatNumber.trim().isNotEmpty &&
|
||||
printTransactionModel.personalInformationModel.data?.meta?.showVat == 1) {
|
||||
final vatName = printTransactionModel.personalInformationModel.data?.vatName;
|
||||
final label = vatName != null ? '$vatName:' : 'Shop GST:';
|
||||
bytes += generator.text(
|
||||
'$label $vatNumber',
|
||||
styles: const PosStyles(align: PosAlign.center),
|
||||
linesAfter: 1,
|
||||
);
|
||||
}
|
||||
|
||||
await addText(
|
||||
data,
|
||||
styles: const PosStyles(
|
||||
align: PosAlign.center,
|
||||
),
|
||||
linesAfter: 1,
|
||||
);
|
||||
|
||||
// Add footer
|
||||
bytes += generator.text('Thank you!', styles: const PosStyles(align: PosAlign.center, bold: true));
|
||||
bytes += generator.text(
|
||||
'Note: Goods once sold will not be taken back or exchanged.',
|
||||
styles: const PosStyles(align: PosAlign.center, bold: false),
|
||||
linesAfter: 1,
|
||||
);
|
||||
|
||||
bytes += generator.text(
|
||||
'Developed By: $companyName',
|
||||
styles: const PosStyles(align: PosAlign.center),
|
||||
linesAfter: 1,
|
||||
);
|
||||
|
||||
bytes += generator.cut();
|
||||
return bytes;
|
||||
} catch (e) {
|
||||
print('Error generating print ticket: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isAscii(String input) {
|
||||
for (final c in input.runes) {
|
||||
if (c > 127) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<List<int>> _textToImageBytes(
|
||||
Generator generator,
|
||||
String text, {
|
||||
PosStyles? styles,
|
||||
}) async {
|
||||
try {
|
||||
const double fontSize = 26.0;
|
||||
const double horizontalPadding = 10.0;
|
||||
const double lineSpacing = 1.2;
|
||||
|
||||
const double printerWidthMm = 58.0;
|
||||
const double printerDpi = 203.0;
|
||||
|
||||
final double printerWidthPx = (printerWidthMm * printerDpi / 25.4) - (horizontalPadding * 2);
|
||||
const String fallbackFont = 'Arial Unicode MS';
|
||||
|
||||
final textStyle = TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: styles?.bold == true ? FontWeight.bold : FontWeight.normal,
|
||||
color: Colors.black,
|
||||
fontFamily: fallbackFont,
|
||||
height: lineSpacing,
|
||||
);
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(text: text, style: textStyle),
|
||||
textDirection: TextDirection.ltr,
|
||||
maxLines: 100,
|
||||
ellipsis: '...',
|
||||
);
|
||||
|
||||
textPainter.layout(maxWidth: printerWidthPx);
|
||||
|
||||
final double imageWidth = printerWidthPx + (horizontalPadding * 2);
|
||||
final double imageHeight = textPainter.height + 20.0;
|
||||
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(
|
||||
recorder,
|
||||
Rect.fromLTWH(0, 0, imageWidth, imageHeight),
|
||||
);
|
||||
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(horizontalPadding, 10.0),
|
||||
);
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
final uiImage = await picture.toImage(
|
||||
imageWidth.toInt(),
|
||||
imageHeight.toInt(),
|
||||
);
|
||||
|
||||
final byteData = await uiImage.toByteData(format: ui.ImageByteFormat.png);
|
||||
final pngBytes = byteData!.buffer.asUint8List();
|
||||
final image = img.decodePng(pngBytes)!;
|
||||
|
||||
return generator.image(image);
|
||||
} catch (e) {
|
||||
print('Error in _textToImageBytes: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
117
lib/thermal priting invoices/provider/print_lavel_provider.dart
Normal file
117
lib/thermal priting invoices/provider/print_lavel_provider.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:bluetooth_print_plus/bluetooth_print_plus.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||
|
||||
final labelPrinterProvider = ChangeNotifierProvider((ref) => ThermalPrinter());
|
||||
|
||||
class ThermalPrinter extends ChangeNotifier {
|
||||
@override
|
||||
void addListener(VoidCallback listener) async {
|
||||
// TODO: implement addListener
|
||||
super.addListener(listener);
|
||||
await BluetoothPrintPlus.startScan(timeout: Duration(seconds: 10));
|
||||
}
|
||||
|
||||
List<BluetoothDevice> availableBluetoothDevices = [];
|
||||
bool isBluetoothConnected = false;
|
||||
|
||||
// Future<void> getBluetooth() async {
|
||||
// availableBluetoothDevices = await BluetoothPrintPlus.scanResults;
|
||||
// isBluetoothConnected = await PrintBluetoothThermal.connectionStatus;
|
||||
// notifyListeners();
|
||||
// }
|
||||
//
|
||||
// Future<bool> setConnect(String mac) async {
|
||||
// bool status = false;
|
||||
// final bool result = await PrintBluetoothThermal.connect(macPrinterAddress: mac);
|
||||
// if (result == true) {
|
||||
// isBluetoothConnected = true;
|
||||
// status = true;
|
||||
// }
|
||||
// notifyListeners();
|
||||
// return status;
|
||||
// }
|
||||
|
||||
Future<dynamic> listOfBluDialog({required BuildContext context}) async {
|
||||
// begin scan
|
||||
|
||||
// final _scanResultsSubscription = BluetoothPrintPlus.scanResults.listen((event) {
|
||||
// print('${event.length}');
|
||||
// // if (mounted) {
|
||||
// // setState(() {
|
||||
// // _scanResults = event;
|
||||
// // });
|
||||
// // }
|
||||
// });
|
||||
return showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||
child: CupertinoAlertDialog(
|
||||
insetAnimationCurve: Curves.bounceInOut,
|
||||
// content: Container(
|
||||
// height: availableBluetoothDevices.isNotEmpty ? (availableBluetoothDevices.length * 80).toDouble() : 150,
|
||||
// width: double.maxFinite,
|
||||
// child: StreamBuilder(
|
||||
// stream: FlutterBluetoothPrinter.discovery,
|
||||
// builder: (context, snapshot){
|
||||
//
|
||||
//
|
||||
// // final List<BluetoothDevice> hh = snapshot.data as List<BluetoothDevice>;
|
||||
// print('this is it--------->$snapshot');
|
||||
// return ListView.builder(
|
||||
// itemCount: 0,
|
||||
// itemBuilder: (context, index){
|
||||
// // final device = hh.elementAt(index);
|
||||
// return ListTile(
|
||||
// // title: Text(device.name ?? 'No Name'),
|
||||
// // subtitle: Text(device.address),
|
||||
// onTap: (){
|
||||
// // do anything
|
||||
// // FlutterBluetoothPrinter.printImage(
|
||||
// // address: device.address,
|
||||
// // image: // some image
|
||||
// // );
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// )),
|
||||
title: Text(
|
||||
'Connect Your Device',
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
actions: <Widget>[
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
lang.S.of(context).cancel,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
onPressed: () async {
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
Navigator.pop(context);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Future<void> printSalesThermalInvoiceNow({required PrintSalesTransactionModel transaction, required List<SalesDetails>? productList, required BuildContext context}) async {
|
||||
// await getBluetooth();
|
||||
// isBluetoothConnected ? SalesThermalPrinterInvoice().printSalesTicket(printTransactionModel: transaction, productList: productList) : listOfBluDialog(context: context);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mobile_pos/Screens/Products/Model/product_total_stock_model.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||
import 'package:mobile_pos/model/business_info_model.dart';
|
||||
import 'package:nb_utils/nb_utils.dart';
|
||||
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
|
||||
|
||||
import '../../Screens/Products/Model/product_model.dart';
|
||||
import '../../Screens/Purchase/Model/purchase_transaction_model.dart' hide Product;
|
||||
import '../../constant.dart';
|
||||
import '../../model/sale_transaction_model.dart';
|
||||
import '../model/print_transaction_model.dart';
|
||||
import '../thermal_invoice_due.dart';
|
||||
import '../thermal_invoice_purchase.dart';
|
||||
import '../thermal_invoice_sales.dart';
|
||||
import '../thermal_invoice_stock.dart';
|
||||
import '../thermal_lebels_printing.dart';
|
||||
|
||||
final thermalPrinterProvider = ChangeNotifierProvider((ref) => ThermalPrinter());
|
||||
|
||||
class ThermalPrinter extends ChangeNotifier {
|
||||
List<BluetoothInfo> availableBluetoothDevices = [];
|
||||
bool isBluetoothConnected = false;
|
||||
|
||||
Future<void> getBluetooth() async {
|
||||
availableBluetoothDevices = await PrintBluetoothThermal.pairedBluetooths;
|
||||
isBluetoothConnected = await PrintBluetoothThermal.connectionStatus;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<bool> setConnect(String mac) async {
|
||||
bool status = false;
|
||||
final bool result = await PrintBluetoothThermal.connect(macPrinterAddress: mac);
|
||||
if (result == true) {
|
||||
isBluetoothConnected = true;
|
||||
status = true;
|
||||
}
|
||||
notifyListeners();
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<dynamic> listOfBluDialog({required BuildContext context}) async {
|
||||
return showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||
child: CupertinoAlertDialog(
|
||||
insetAnimationCurve: Curves.bounceInOut,
|
||||
content: Container(
|
||||
height: availableBluetoothDevices.isNotEmpty ? (availableBluetoothDevices.length * 80).toDouble() : 150,
|
||||
width: double.maxFinite,
|
||||
child: availableBluetoothDevices.isNotEmpty
|
||||
? ListView.builder(
|
||||
padding: EdgeInsets.all(0), // Removed padding from ListView
|
||||
shrinkWrap: true,
|
||||
itemCount: availableBluetoothDevices.isNotEmpty ? availableBluetoothDevices.length : 0,
|
||||
itemBuilder: (context1, index) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.all(0), // Removed padding from ListTile
|
||||
onTap: () async {
|
||||
BluetoothInfo select = availableBluetoothDevices[index];
|
||||
bool isConnect = await setConnect(select.macAdress);
|
||||
isConnect ? finish(context1) : toast(lang.S.of(context1).tryAgain);
|
||||
},
|
||||
title: Text(
|
||||
availableBluetoothDevices[index].name,
|
||||
style: TextStyle(fontSize: 12, color: Colors.black, fontWeight: FontWeight.w500),
|
||||
),
|
||||
subtitle: Text(
|
||||
lang.S.of(context1).clickToConnect,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: const Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.bluetooth_disabled,
|
||||
size: 40,
|
||||
color: kMainColor,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
'Not available',
|
||||
style: TextStyle(fontSize: 14, color: kGreyTextColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'Connect Your Device',
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
actions: <Widget>[
|
||||
CupertinoDialogAction(
|
||||
child: Text(
|
||||
lang.S.of(context).cancel,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
onPressed: () async {
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
Navigator.pop(context);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> printSalesThermalInvoiceNow({required PrintSalesTransactionModel transaction, required List<SalesDetails>? productList, required BuildContext context}) async {
|
||||
await getBluetooth();
|
||||
isBluetoothConnected
|
||||
? SalesThermalPrinterInvoice().printSalesTicket(printTransactionModel: transaction, productList: productList, context: context)
|
||||
: listOfBluDialog(context: context);
|
||||
}
|
||||
|
||||
Future<void> printPurchaseThermalInvoiceNow(
|
||||
{required PrintPurchaseTransactionModel transaction, required List<PurchaseDetails>? productList, required BuildContext context, required String? invoiceSize}) async {
|
||||
await getBluetooth();
|
||||
isBluetoothConnected
|
||||
? PurchaseThermalPrinterInvoice().printPurchaseThermalInvoice(printTransactionModel: transaction, productList: productList, context: context)
|
||||
: listOfBluDialog(context: context);
|
||||
}
|
||||
|
||||
Future<void> printDueThermalInvoiceNow({required PrintDueTransactionModel transaction, required String? invoiceSize, required BuildContext context}) async {
|
||||
await getBluetooth();
|
||||
isBluetoothConnected
|
||||
? DueThermalPrinterInvoice().printDueTicket(printDueTransactionModel: transaction, invoiceSize: invoiceSize, context: context)
|
||||
: listOfBluDialog(context: context);
|
||||
}
|
||||
|
||||
Future<void> printStockInvoiceNow(
|
||||
{required List<Product> products, required BusinessInformationModel businessInformationModel, required BuildContext context, required ProductListResponse totalStock}) async {
|
||||
await getBluetooth();
|
||||
isBluetoothConnected
|
||||
? StockThermalPrinterInvoice().printStockTicket(businessInformationModel: businessInformationModel, productList: products, stock: totalStock)
|
||||
: listOfBluDialog(context: context);
|
||||
}
|
||||
|
||||
Future<void> printLabelsNow({required List<Product> products, required BuildContext context}) async {
|
||||
await getBluetooth();
|
||||
isBluetoothConnected ? SalesThermalLabels().printLabels(productList: products) : listOfBluDialog(context: context);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user