first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View 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;
}
}

View 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);
// }
}

View File

@@ -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);
}
}