apskel-pos-flutter-v2/lib/presentation/components/print/receipt_component_builder.dart
2026-05-27 19:51:43 +07:00

383 lines
12 KiB
Dart

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<int> 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<int> 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<int> 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<int> separator({bool bold = false}) {
return generator.text(
_separatorLine,
styles: PosStyles(
bold: bold,
align: PosAlign.center,
height: PosTextSize.size1,
width: PosTextSize.size1,
fontType: _font,
),
);
}
List<int> 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<int> 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<int> itemText(String text, {bool bold = true}) {
return generator.text(
text,
styles: PosStyles(
bold: bold,
align: PosAlign.left,
fontType: _font,
height: _bodyHeight,
width: _bodyWidth,
),
);
}
List<int> emptyLines(int count) => generator.emptyLines(count);
List<int> feed(int count) => generator.feed(count);
// ---------------------------------------------------------------------------
// Composite components
// ---------------------------------------------------------------------------
/// Outlet header (receipt/cashier style)
List<int> header({
required String outletName,
required String address,
required String phoneNumber,
PosTextSize? height,
PosTextSize? width,
PosFontType? fontType,
}) {
List<int> 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<int> printerType({required String printerType}) {
return textCenter(printerType, bold: true);
}
/// Date + time row (receipt style)
List<int> 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<int> orderInfoSimple({
required String orderNumber,
String? orderType,
required String cashierName,
String? customerName,
}) {
List<int> 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<int> orderInfo({
required String orderNumber,
required String customerName,
required String cashierName,
String? paymentMethod,
String? tableNumber,
PosTextSize? height,
PosTextSize? width,
PosFontType? fontType,
}) {
List<int> 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<int> orderType(String type, {
PosTextSize? height,
PosTextSize? width,
PosFontType? fontType,
}) {
List<int> bytes = [];
bytes += separator();
bytes += textCenter(type.toUpperCase(), fontType: fontType, height: height ?? _bodyHeight, width: width ?? _bodyWidth, bold: true);
bytes += separator();
return bytes;
}
List<int> tableName(String table, {
PosTextSize? height,
PosTextSize? width,
PosFontType? fontType,
}) {
List<int> 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<int> orderItem({
required String productName,
required int quantity,
required String unitPrice,
required String totalPrice,
String? variantName,
String? notes,
PosTextSize? height,
PosTextSize? width,
PosFontType? fontType,
}) {
List<int> 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<int> summary({
required int totalItems,
required String subtotal,
required String discount,
required String total,
required String paid,
PosTextSize? height,
PosTextSize? width,
PosFontType? fontType,
}) {
List<int> 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<int> footer({String message = 'Terima kasih'}) {
List<int> 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<int> cutOnly() {
List<int> bytes = [];
bytes += feed(paperSize == 80 ? 3 : 1);
bytes += generator.cut();
return bytes;
}
// ---------------------------------------------------------------------------
// Media
// ---------------------------------------------------------------------------
List<int> qrCode(String data, {PosAlign align = PosAlign.center}) {
return generator.qrcode(data, align: align);
}
List<int> barcode(String data) {
return generator.barcode(Barcode.code128(data.codeUnits));
}
}