diff --git a/android/app/src/main/res/drawable/ic_notification.png b/android/app/src/main/res/drawable/ic_notification.png new file mode 100644 index 0000000..ca65742 Binary files /dev/null and b/android/app/src/main/res/drawable/ic_notification.png differ diff --git a/lib/common/service/fcm_service.dart b/lib/common/service/fcm_service.dart index fb6ffa3..a5bf665 100644 --- a/lib/common/service/fcm_service.dart +++ b/lib/common/service/fcm_service.dart @@ -44,7 +44,7 @@ class FcmService { } Future _setupLocalNotifications() async { - const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + const androidSettings = AndroidInitializationSettings('@drawable/ic_notification'); const iosSettings = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, diff --git a/lib/presentation/components/print/print_ui.dart b/lib/presentation/components/print/print_ui.dart index 2bf05b2..a74824b 100644 --- a/lib/presentation/components/print/print_ui.dart +++ b/lib/presentation/components/print/print_ui.dart @@ -32,8 +32,6 @@ class PrintUi { phoneNumber: outlet.phoneNumber, ); - bytes += builder.dateTime(DateTime.now()); - bytes += builder.orderInfo( orderNumber: order.orderNumber, customerName: order.metadata['customer_name'] ?? '-', @@ -90,34 +88,34 @@ class PrintUi { bytes += generator.reset(); + // Header + bytes += builder.textCenter('Table Checker', bold: true); + bytes += builder.separator(); + bytes += builder.textCenter( + 'Table : ${order.tableNumber.isNotEmpty ? order.tableNumber : '-'}', + bold: true, + ); + bytes += builder.separator(); + + // Order info — label : value, left aligned + bytes += builder.orderInfoSimple( + orderNumber: order.orderNumber, + orderType: order.orderType, + cashierName: cashierName, + ); + + bytes += builder.separator(); + + // Items — qty NAMA (uppercase), variant indent for (final item in order.orderItems) { - bytes += builder.separator(); - bytes += builder.printerType(printerType: 'CHECKER'); - bytes += builder.separator(); - - bytes += builder.dateTime(DateTime.now()); - bytes += builder.orderInfo( - orderNumber: order.orderNumber, - customerName: order.metadata['customer_name'] ?? '-', - cashierName: cashierName, - paymentMethod: order.payments.isEmpty - ? '' - : order.payments.last.paymentMethodName, - tableNumber: order.tableNumber, - ); - - bytes += builder.orderType(order.orderType); - - bytes += builder.emptyLines(1); - - bytes += builder.orderItem( - productName: item.productName, - quantity: item.quantity, - unitPrice: item.unitPrice.currencyFormatRpV2, - totalPrice: item.totalPrice.currencyFormatRpV2, - variantName: item.productVariantName, - notes: item.notes, - ); + final name = item.productName.toUpperCase(); + bytes += builder.itemText('${item.quantity} $name'); + if (item.productVariantName.isNotEmpty) { + bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false); + } + if (item.notes.isNotEmpty) { + bytes += builder.itemText(' *${item.notes}', bold: false); + } } bytes += builder.separator(); @@ -144,47 +142,39 @@ class PrintUi { paperSize: paper, ); - // Group items by category - final Map> groupedItems = {}; for (final item in order.orderItems) { - final key = item.categoryName.isNotEmpty ? item.categoryName : 'Lainnya'; - groupedItems.putIfAbsent(key, () => []).add(item); - } - - for (final entry in groupedItems.entries) { - final categoryName = entry.key; - final items = entry.value; - bytes += generator.reset(); + // Header + bytes += builder.textCenter('Kitchen', bold: true); bytes += builder.separator(); - bytes += builder.printerType(printerType: 'KITCHEN'); + bytes += builder.textCenter( + 'Table : ${order.tableNumber.isNotEmpty ? order.tableNumber : '-'}', + bold: true, + ); bytes += builder.separator(); - bytes += builder.dateTime(DateTime.now()); - bytes += builder.orderInfo( + + // Order info + bytes += builder.orderInfoSimple( orderNumber: order.orderNumber, - customerName: order.metadata['customer_name'] ?? '-', + orderType: order.orderType, cashierName: cashierName, - tableNumber: order.tableNumber, ); - bytes += builder.orderType(categoryName); + bytes += builder.separator(); - bytes += builder.emptyLines(1); - - for (final item in items) { - bytes += builder.orderItem( - productName: item.productName, - quantity: item.quantity, - unitPrice: item.unitPrice.currencyFormatRpV2, - totalPrice: item.totalPrice.currencyFormatRpV2, - variantName: item.productVariantName, - notes: item.notes, - ); + // Single item + final name = item.productName.toUpperCase(); + bytes += builder.itemText('${item.quantity} $name'); + if (item.productVariantName.isNotEmpty) { + bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false); + } + if (item.notes.isNotEmpty) { + bytes += builder.itemText(' *${item.notes}', bold: false); } bytes += builder.separator(); - bytes += builder.footer(); + bytes += builder.cutOnly(); } return bytes; @@ -307,8 +297,6 @@ class PrintUi { phoneNumber: outlet.phoneNumber, ); - bytes += builder.dateTime(DateTime.now()); - bytes += builder.orderInfo( orderNumber: order.orderNumber, customerName: order.metadata['customer_name'] ?? '-', diff --git a/lib/presentation/components/print/receipt_component_builder.dart b/lib/presentation/components/print/receipt_component_builder.dart index bb351e7..d831b41 100644 --- a/lib/presentation/components/print/receipt_component_builder.dart +++ b/lib/presentation/components/print/receipt_component_builder.dart @@ -9,7 +9,31 @@ class ReceiptComponentBuilder { ReceiptComponentBuilder({required this.generator, this.paperSize = 58}); - /// Print text centered with custom style + /// Helper: returns size2 for 80mm paper, size1 for 58mm + PosTextSize get _titleSize => + paperSize == 80 ? PosTextSize.size3 : PosTextSize.size1; + + /// Font type per paper size — easy to change here + /// 58mm → fontA, 80mm → fontA + PosFontType get _font => + paperSize == 80 ? PosFontType.fontA : PosFontType.fontA; + + /// Text size for body — height size2 for 80mm to appear taller, size1 for 58mm + PosTextSize get _bodySize => + paperSize == 80 ? PosTextSize.size2 : PosTextSize.size1; + + /// Body width stays size1 always to prevent overflow + PosTextSize get _bodyWidth => PosTextSize.size1; + + /// Characters per line based on paper size (always size1 font) + String get _separatorLine => paperSize == 80 + ? '------------------------------------------------' + : '--------------------------------'; + + // --------------------------------------------------------------------------- + // Basic text + // --------------------------------------------------------------------------- + List textCenter( String text, { bool bold = false, @@ -23,32 +47,41 @@ class ReceiptComponentBuilder { align: PosAlign.center, height: height, width: width, + fontType: _font, ), ); } - /// Print text aligned left List textLeft(String text, {bool bold = false}) { return generator.text( text, - styles: PosStyles(bold: bold, align: PosAlign.left), + styles: PosStyles( + bold: bold, + align: PosAlign.left, + fontType: _font, + height: _bodySize, + width: _bodyWidth, + ), ); } - /// Print text aligned right List textRight(String text, {bool bold = false}) { return generator.text( text, - styles: PosStyles(bold: bold, align: PosAlign.right), + styles: PosStyles( + bold: bold, + align: PosAlign.right, + fontType: _font, + height: _bodySize, + width: _bodyWidth, + ), ); } - /// Characters per line based on paper size (always size1 font) - String get _separatorLine => paperSize == 80 - ? '------------------------------------------------' - : '--------------------------------'; + // --------------------------------------------------------------------------- + // Layout helpers + // --------------------------------------------------------------------------- - /// Print separator line List separator({bool bold = false}) { return generator.text( _separatorLine, @@ -57,11 +90,11 @@ class ReceiptComponentBuilder { align: PosAlign.center, height: PosTextSize.size1, width: PosTextSize.size1, + fontType: _font, ), ); } - /// Print row with 2 columns (label: value) List row2Columns( String leftText, String rightText, { @@ -73,17 +106,28 @@ class ReceiptComponentBuilder { PosColumn( text: leftText, width: leftWidth, - styles: PosStyles(align: PosAlign.left, bold: bold), + styles: PosStyles( + align: PosAlign.left, + bold: bold, + fontType: _font, + height: _bodySize, + width: _bodyWidth, + ), ), PosColumn( text: rightText, width: rightWidth, - styles: PosStyles(align: PosAlign.right, bold: bold), + styles: PosStyles( + align: PosAlign.right, + bold: bold, + fontType: _font, + height: _bodySize, + width: _bodyWidth, + ), ), ]); } - /// Print row with 3 columns List row3Columns( String leftText, String centerText, @@ -111,55 +155,48 @@ class ReceiptComponentBuilder { ]); } - /// Print empty lines - List emptyLines(int count) { - return generator.emptyLines(count); + /// 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: PosTextSize.size2, + width: PosTextSize.size2, + ), + ); } - /// Print feed lines - List feed(int count) { - return generator.feed(count); - } + List emptyLines(int count) => generator.emptyLines(count); - /// Helper: returns size2 for 80mm paper, size1 for 58mm - PosTextSize get _titleSize => - paperSize == 80 ? PosTextSize.size2 : PosTextSize.size1; + List feed(int count) => generator.feed(count); - /// Print header (outlet info) + // --------------------------------------------------------------------------- + // Composite components + // --------------------------------------------------------------------------- + + /// Outlet header (receipt/cashier style) List header({ required String outletName, required String address, required String phoneNumber, }) { List bytes = []; - - bytes += textCenter( - outletName, - bold: true, - height: _titleSize, - width: _titleSize, - ); + bytes += textCenter(outletName, bold: true, height: _titleSize, width: _titleSize); bytes += textCenter(address); bytes += textCenter(phoneNumber); bytes += separator(); - return bytes; } + /// Centered printer type label (e.g. KITCHEN, BAR) List printerType({required String printerType}) { - List bytes = []; - - bytes += textCenter( - printerType, - bold: true, - height: _titleSize, - width: _titleSize, - ); - - return bytes; + return textCenter(printerType, bold: true, height: _titleSize, width: _titleSize); } - /// Print date and time + /// Date + time row (receipt style) List dateTime(DateTime dateTime) { return row2Columns( DateFormat('dd MMM yyyy').format(dateTime), @@ -167,7 +204,22 @@ class ReceiptComponentBuilder { ); } - /// Print order info section + /// Order info block — label : value style (checker/kitchen style) + List orderInfoSimple({ + required String orderNumber, + required String orderType, + required String cashierName, + }) { + List bytes = []; + final dateStr = DateFormat('dd-MM-yyyy HH:mm').format(DateTime.now()); + bytes += textLeft('Order : $orderNumber'); + bytes += textLeft('Date : $dateStr'); + bytes += textLeft('Purpose : $orderType'); + bytes += textLeft('Waiter : $cashierName'); + return bytes; + } + + /// Order info block — row2Columns style (receipt/cashier style) List orderInfo({ required String orderNumber, required String customerName, @@ -176,34 +228,32 @@ class ReceiptComponentBuilder { String? tableNumber, }) { List bytes = []; - - bytes += row2Columns('Nomor', orderNumber); - bytes += row2Columns('Pelanggan', customerName); - bytes += row2Columns('Kasir', cashierName); - + final dateStr = DateFormat('dd-MM-yyyy HH:mm').format(DateTime.now()); + bytes += textLeft('Order : $orderNumber'); + bytes += textLeft('Date : $dateStr'); + if (tableNumber != null && tableNumber.isNotEmpty) { + bytes += textLeft('Table : $tableNumber'); + } + bytes += textLeft('Waiter : $cashierName'); + bytes += textLeft('Customer : $customerName'); + if (paymentMethod != null) { - bytes += row2Columns('Pembayaran', paymentMethod); + bytes += textLeft('Payment : $paymentMethod'); } - - if (tableNumber != null && tableNumber.isNotEmpty) { - bytes += row2Columns('Meja', tableNumber); - } - + return bytes; } - /// Print order type (Dine In, Take Away, etc) + /// Order type banner (separator + type + separator) List orderType(String type) { List bytes = []; - bytes += separator(); bytes += textCenter(type, bold: true, height: _titleSize, width: _titleSize); bytes += separator(); - return bytes; } - /// Print single item + /// Single item row with price (receipt/cashier style) List orderItem({ required String productName, required int quantity, @@ -213,29 +263,19 @@ class ReceiptComponentBuilder { String? notes, }) { List bytes = []; - final displayName = (variantName != null && variantName.isNotEmpty) ? '$productName ($variantName)' : productName; - bytes += textLeft(displayName, bold: paperSize == 80); - bytes += row2Columns( - '$quantity x $unitPrice', - totalPrice, - leftWidth: 8, - rightWidth: 4, - ); - + bytes += row2Columns('$quantity x $unitPrice', totalPrice, leftWidth: 8, rightWidth: 4); if (notes != null && notes.isNotEmpty) { bytes += row2Columns('Note', notes, leftWidth: 4, rightWidth: 8); } - bytes += emptyLines(1); - return bytes; } - /// Print summary section + /// Summary section List summary({ required int totalItems, required String subtotal, @@ -244,7 +284,6 @@ class ReceiptComponentBuilder { required String paid, }) { List bytes = []; - bytes += separator(); if (totalItems > 0) { bytes += row2Columns('Total Item', totalItems.toString()); @@ -255,32 +294,23 @@ class ReceiptComponentBuilder { bytes += row2Columns('Total', total, bold: true); bytes += row2Columns('Bayar', paid); bytes += separator(); - return bytes; } - /// Print footer (thank you message) + /// Footer with thank-you message + cut List footer({String message = 'Terima kasih'}) { List bytes = []; - bytes += emptyLines(2); - bytes += textCenter( - message, - bold: true, - height: _titleSize, - width: _titleSize, - ); + bytes += textCenter(message, bold: true, height: _titleSize, width: _titleSize); if (kDebugMode) { - bytes += textCenter("$paperSize MM"); + bytes += textCenter('$paperSize MM'); } bytes += feed(paperSize == 80 ? 3 : 1); - bytes += generator.cut(); - return bytes; } - /// Cut paper without printing footer message + /// Feed + cut without any message List cutOnly() { List bytes = []; bytes += feed(paperSize == 80 ? 3 : 1); @@ -288,12 +318,14 @@ class ReceiptComponentBuilder { return bytes; } - /// Print QR Code + // --------------------------------------------------------------------------- + // Media + // --------------------------------------------------------------------------- + List qrCode(String data, {PosAlign align = PosAlign.center}) { return generator.qrcode(data, align: align); } - /// Print barcode List barcode(String data) { return generator.barcode(Barcode.code128(data.codeUnits)); } diff --git a/lib/presentation/pages/main/pages/home/widgets/home_right_title.dart b/lib/presentation/pages/main/pages/home/widgets/home_right_title.dart index e54a836..e809036 100644 --- a/lib/presentation/pages/main/pages/home/widgets/home_right_title.dart +++ b/lib/presentation/pages/main/pages/home/widgets/home_right_title.dart @@ -59,8 +59,6 @@ class HomeRightTitle extends StatelessWidget { : (state.table?.tableName ?? '-'), Icons.table_restaurant_outlined, () { - if (state.table != null) return; - context.router.navigate( const MainRoute(children: [TableRoute()]), ); diff --git a/pubspec.yaml b/pubspec.yaml index 71e5829..960e1ed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "A new Flutter project." publish_to: "none" -version: 1.0.6+11 +version: 1.0.7+12 environment: sdk: ^3.8.1