import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; /// Reusable component builder untuk thermal receipt printer class ReceiptComponentBuilder { final Generator generator; final int paperSize; ReceiptComponentBuilder({required this.generator, this.paperSize = 58}); PosFontType get _font => paperSize == 80 ? PosFontType.fontB : PosFontType.fontA; PosTextSize get _bodyHeight => paperSize == 80 ? PosTextSize.size2 : PosTextSize.size1; PosTextSize get _bodyWidth => paperSize == 80 ? PosTextSize.size2 : PosTextSize.size1; /// Characters per line based on paper size and font String get _separatorLine { if (paperSize == 80) { return _font == PosFontType.fontB ? '----------------------------------------------------------------' : '------------------------------------------------'; } else { return _font == PosFontType.fontB ? '------------------------------------------' : '--------------------------------'; } } // --------------------------------------------------------------------------- // Basic text // --------------------------------------------------------------------------- List textCenter( String text, { bool bold = false, PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { return generator.text( text, styles: PosStyles( bold: bold, align: PosAlign.center, height: height ?? _bodyHeight, width: width ?? _bodyWidth, fontType: fontType ?? _font, ), ); } List textLeft(String text, {bool bold = false, PosTextSize? height, PosTextSize? width, PosFontType? fontType,}) { return generator.text( text, styles: PosStyles( bold: bold, align: PosAlign.left, height: height ?? _bodyHeight, width: width ?? _bodyWidth, fontType: fontType ?? _font, ), ); } List textRight(String text, {bool bold = false, PosTextSize? height, PosTextSize? width, PosFontType? fontType,}) { return generator.text( text, styles: PosStyles( bold: bold, align: PosAlign.right, height: height ?? _bodyHeight, width: width ?? _bodyWidth, fontType: fontType ?? _font, ), ); } // --------------------------------------------------------------------------- // Layout helpers // --------------------------------------------------------------------------- List separator({bool bold = false}) { return generator.text( _separatorLine, styles: PosStyles( bold: bold, align: PosAlign.center, height: PosTextSize.size1, width: PosTextSize.size1, fontType: _font, ), ); } List row2Columns( String leftText, String rightText, { bool bold = false, int leftWidth = 6, int rightWidth = 6, PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { return generator.row([ PosColumn( text: leftText, width: leftWidth, styles: PosStyles( align: PosAlign.left, bold: bold, height: height ?? _bodyHeight, width: width ?? _bodyWidth, fontType: fontType ?? _font, ), ), PosColumn( text: rightText, width: rightWidth, styles: PosStyles( align: PosAlign.right, bold: bold, height: height ?? _bodyHeight, width: width ?? _bodyWidth, fontType: fontType ?? _font, ), ), ]); } List row3Columns( String leftText, String centerText, String rightText, { int leftWidth = 4, int centerWidth = 4, int rightWidth = 4, }) { return generator.row([ PosColumn( text: leftText, width: leftWidth, styles: const PosStyles(align: PosAlign.left), ), PosColumn( text: centerText, width: centerWidth, styles: const PosStyles(align: PosAlign.center), ), PosColumn( text: rightText, width: rightWidth, styles: const PosStyles(align: PosAlign.right), ), ]); } /// Item text — always size2 for kitchen/checker (prominent display) List itemText(String text, {bool bold = true}) { return generator.text( text, styles: PosStyles( bold: bold, align: PosAlign.left, fontType: _font, height: _bodyHeight, width: _bodyWidth, ), ); } List emptyLines(int count) => generator.emptyLines(count); List feed(int count) => generator.feed(count); // --------------------------------------------------------------------------- // Composite components // --------------------------------------------------------------------------- /// Outlet header (receipt/cashier style) List header({ required String outletName, required String address, required String phoneNumber, PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { List bytes = []; bytes += textCenter(outletName, height: PosTextSize.size2, width: PosTextSize.size2, fontType: fontType, bold: true); bytes += textCenter(address, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); bytes += textCenter(phoneNumber, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); bytes += separator(); return bytes; } /// Centered printer type label (e.g. KITCHEN, BAR) List printerType({required String printerType}) { return textCenter(printerType, bold: true); } /// Date + time row (receipt style) List dateTime(DateTime dateTime, { PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { return row2Columns( DateFormat('dd MMM yyyy').format(dateTime), DateFormat('HH:mm').format(dateTime), height: height ?? _bodyHeight, width: width ?? _bodyWidth, fontType: fontType ?? _font, ); } /// Order info block — label : value style (checker/kitchen style) List orderInfoSimple({ required String orderNumber, String? orderType, required String cashierName, String? customerName, }) { List bytes = []; final dateStr = DateFormat('dd-MM-yyyy HH:mm').format(DateTime.now()); bytes += textLeft('Order : $orderNumber'); bytes += textLeft('Date : $dateStr'); if(customerName != null) { bytes += textLeft('Customer : $customerName'); } if(orderType != null) { bytes += textLeft('Purpose : ${orderType.toUpperCase()}'); } bytes += textLeft('Waiter : $cashierName'); return bytes; } /// Order info block — row2Columns style (receipt/cashier style) List orderInfo({ required String orderNumber, required String customerName, required String cashierName, String? paymentMethod, String? tableNumber, PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { List bytes = []; final dateStr = DateFormat('dd-MM-yyyy HH:mm').format(DateTime.now()); bytes += textLeft('Order : $orderNumber', fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); bytes += textLeft('Date : $dateStr', fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); if (tableNumber != null && tableNumber.isNotEmpty) { bytes += textLeft('Table : $tableNumber', fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); } bytes += textLeft('Waiter : $cashierName', fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); bytes += textLeft('Customer : $customerName', fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); if (paymentMethod != null) { bytes += textLeft('Payment : $paymentMethod', fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); } return bytes; } /// Order type banner (separator + type + separator) List orderType(String type, { PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { List bytes = []; bytes += separator(); bytes += textCenter(type.toUpperCase(), fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth, bold: true); bytes += separator(); return bytes; } List tableName(String table, { PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { List bytes = []; bytes += separator(); bytes += textCenter( 'Table : ${table.isNotEmpty ? table : '-'}', fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth, bold: true); bytes += separator(); return bytes; } /// Single item row with price (receipt/cashier style) List orderItem({ required String productName, required int quantity, required String unitPrice, required String totalPrice, String? variantName, String? notes, PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { List bytes = []; final displayName = (variantName != null && variantName.isNotEmpty) ? '$productName ($variantName)' : productName; bytes += textLeft(displayName, bold: true, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth); bytes += row2Columns('${quantity}x $unitPrice', totalPrice, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); if (notes != null && notes.isNotEmpty) { bytes += row2Columns('Note', notes, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth, leftWidth: 4, rightWidth: 8); } bytes += emptyLines(1); return bytes; } /// Summary section List summary({ required int totalItems, required String subtotal, required String discount, required String total, required String paid, PosTextSize? height, PosTextSize? width, PosFontType? fontType, }) { List bytes = []; bytes += separator(); if (totalItems > 0) { bytes += row2Columns('Total Item', totalItems.toString(), fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); } bytes += row2Columns('Subtotal', subtotal, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); bytes += row2Columns('Diskon', discount, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); bytes += separator(); bytes += row2Columns('Total', total, bold: true, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); bytes += row2Columns('Bayar', paid, fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth,); bytes += separator(); return bytes; } /// Footer with thank-you message + cut List footer({String message = 'Terima kasih'}) { List bytes = []; bytes += emptyLines(2); bytes += textCenter(message, bold: true); if (kDebugMode) { bytes += textCenter('$paperSize MM'); } bytes += feed(paperSize == 80 ? 3 : 1); bytes += generator.cut(); return bytes; } /// Feed + cut without any message List cutOnly() { List bytes = []; bytes += feed(paperSize == 80 ? 3 : 1); bytes += generator.cut(); return bytes; } // --------------------------------------------------------------------------- // Media // --------------------------------------------------------------------------- List qrCode(String data, {PosAlign align = PosAlign.center}) { return generator.qrcode(data, align: align); } List barcode(String data) { return generator.barcode(Barcode.code128(data.codeUnits)); } }