From c0d27d6fdf96ebc3edb14f981b890759f5e8bfb0 Mon Sep 17 00:00:00 2001 From: orangdeso Date: Wed, 7 May 2025 21:05:27 +0700 Subject: [PATCH] Feat: add features print ticket boarding pass --- .dart_tool/package_config.json | 44 +++- .dart_tool/package_config_subset | 28 ++ lib/domain/models/ticket_model.dart | 8 +- .../pages/boarding_pass_screen.dart | 5 +- .../pages/detail_ticket_screen.dart | 29 +- .../pages/print_boarding_pass_screen.dart | 247 ++++++++++++++---- .../pages/scan_barcode_screen.dart | 45 ++++ .../screens/home/component/card_tickets.dart | 12 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 56 ++++ pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 15 files changed, 402 insertions(+), 85 deletions(-) create mode 100644 lib/presentation/screens/boarding_pass/pages/scan_barcode_screen.dart diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index 7a81036..a1a00d2 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -7,6 +7,12 @@ "packageUri": "lib/", "languageVersion": "3.2" }, + { + "name": "archive", + "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/archive-4.0.7", + "packageUri": "lib/", + "languageVersion": "3.0" + }, { "name": "args", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0", @@ -31,6 +37,12 @@ "packageUri": "lib/", "languageVersion": "2.12" }, + { + "name": "bidi", + "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/bidi-2.0.13", + "packageUri": "lib/", + "languageVersion": "2.12" + }, { "name": "boolean_selector", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.1", @@ -295,6 +307,12 @@ "packageUri": "lib/", "languageVersion": "2.12" }, + { + "name": "image", + "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/image-4.5.4", + "packageUri": "lib/", + "languageVersion": "3.0" + }, { "name": "intl", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/intl-0.20.2", @@ -397,6 +415,18 @@ "packageUri": "lib/", "languageVersion": "3.2" }, + { + "name": "pdf", + "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/pdf-3.11.3", + "packageUri": "lib/", + "languageVersion": "2.19" + }, + { + "name": "pdf_widget_wrapper", + "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/pdf_widget_wrapper-1.0.4", + "packageUri": "lib/", + "languageVersion": "2.18" + }, { "name": "permission_handler", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/permission_handler-11.4.0", @@ -451,6 +481,18 @@ "packageUri": "lib/", "languageVersion": "3.0" }, + { + "name": "posix", + "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/posix-6.0.2", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "printing", + "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/printing-5.14.2", + "packageUri": "lib/", + "languageVersion": "3.3" + }, { "name": "qr", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/qr-3.0.2", @@ -638,7 +680,7 @@ "languageVersion": "3.4" } ], - "generated": "2025-05-05T05:14:47.271498Z", + "generated": "2025-05-07T08:39:04.087158Z", "generator": "pub", "generatorVersion": "3.5.0", "flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0", diff --git a/.dart_tool/package_config_subset b/.dart_tool/package_config_subset index 2937913..883b78d 100644 --- a/.dart_tool/package_config_subset +++ b/.dart_tool/package_config_subset @@ -2,6 +2,10 @@ _flutterfire_internals 3.2 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/_flutterfire_internals-1.3.54/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/_flutterfire_internals-1.3.54/lib/ +archive +3.0 +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/archive-4.0.7/ +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/archive-4.0.7/lib/ args 3.3 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0/ @@ -18,6 +22,10 @@ barcode_widget 2.12 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/barcode_widget-2.0.4/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/barcode_widget-2.0.4/lib/ +bidi +2.12 +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/bidi-2.0.13/ +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/bidi-2.0.13/lib/ boolean_selector 2.17 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.1/ @@ -182,6 +190,10 @@ http_parser 2.12 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/http_parser-4.0.2/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/http_parser-4.0.2/lib/ +image +3.0 +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/image-4.5.4/ +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/image-4.5.4/lib/ intl 3.3 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/intl-0.20.2/ @@ -250,6 +262,14 @@ path_provider_windows 3.2 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/lib/ +pdf +2.19 +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/pdf-3.11.3/ +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/pdf-3.11.3/lib/ +pdf_widget_wrapper +2.18 +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/pdf_widget_wrapper-1.0.4/ +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/pdf_widget_wrapper-1.0.4/lib/ permission_handler 3.5 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/permission_handler-11.4.0/ @@ -286,6 +306,14 @@ plugin_platform_interface 3.0 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/plugin_platform_interface-2.1.8/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib/ +posix +3.0 +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/posix-6.0.2/ +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/posix-6.0.2/lib/ +printing +3.3 +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/printing-5.14.2/ +file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/printing-5.14.2/lib/ qr 3.4 file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/qr-3.0.2/ diff --git a/lib/domain/models/ticket_model.dart b/lib/domain/models/ticket_model.dart index cb51be2..aa77481 100644 --- a/lib/domain/models/ticket_model.dart +++ b/lib/domain/models/ticket_model.dart @@ -73,11 +73,7 @@ class FlightModel { factory FlightModel.fromDocument(DocumentSnapshot doc) { final data = doc.data() as Map; - - // Ambil field seat (Map) dari Firestore final seatData = data['seat'] as Map? ?? {}; - - // Konversi setiap entry di seatData menjadi SeatInfo final seatMap = seatData.map((key, value) { return MapEntry( key, @@ -85,8 +81,6 @@ class FlightModel { ); }); - // print("FlightModel.fromDocument - Doc ID: ${doc.id}, data: $data"); - return FlightModel( id: doc.id, airLines: data['airlines'] ?? '', @@ -103,7 +97,7 @@ class FlightModel { stop: data['stop'] ?? '', price: data['price'] ?? 0, airlineLogo: data['airlineLogo'] ?? '', - seat: seatMap, // masukkan Map + seat: seatMap, ); } diff --git a/lib/presentation/screens/boarding_pass/pages/boarding_pass_screen.dart b/lib/presentation/screens/boarding_pass/pages/boarding_pass_screen.dart index e34c330..be25c67 100644 --- a/lib/presentation/screens/boarding_pass/pages/boarding_pass_screen.dart +++ b/lib/presentation/screens/boarding_pass/pages/boarding_pass_screen.dart @@ -288,8 +288,9 @@ class _BoardingPassScreenState extends State with SingleTick 'id_transaction': transaction.id, 'id_ticket': transaction.ticketId, }; - log('ID Transaction: ${transaction.id}'); - log('ID Ticket: ${transaction.ticketId}'); + log('Arrival Time: $arrivalTime'); + log('Departure Time: $departureTime'); + Get.toNamed(Routes.DETAILTICKET, arguments: argument); }, ), diff --git a/lib/presentation/screens/boarding_pass/pages/detail_ticket_screen.dart b/lib/presentation/screens/boarding_pass/pages/detail_ticket_screen.dart index 4bc63ff..5e3129a 100644 --- a/lib/presentation/screens/boarding_pass/pages/detail_ticket_screen.dart +++ b/lib/presentation/screens/boarding_pass/pages/detail_ticket_screen.dart @@ -4,6 +4,7 @@ import 'package:e_porter/_core/component/button/button_outline.dart'; import 'package:e_porter/_core/component/dotted/dashed_line_component.dart'; import 'package:e_porter/_core/constants/colors.dart'; import 'package:e_porter/_core/constants/typography.dart'; +import 'package:e_porter/_core/utils/formatter/date_helper.dart'; import 'package:e_porter/presentation/controllers/history_controller.dart'; import 'package:e_porter/presentation/screens/boarding_pass/component/card_details_passenger.dart'; import 'package:e_porter/presentation/screens/routes/app_rountes.dart'; @@ -84,7 +85,14 @@ class _DetailTicketScreenState extends State { return Center(child: TypographyStyles.body('Data transaksi tidak ditemukan', color: GrayColors.gray400)); } - log('ID Booking Detail Tiket: ${transaction.idBooking}'); + final departureTime = DateFormatterHelper.formatFlightTime(transaction.flightDetails['departureTime']); + final arrivalTime = DateFormatterHelper.formatFlightTime(transaction.flightDetails['arrivalTime']); + final departureDate = DateFormatterHelper.formatFlightDate(transaction.flightDetails['departureTime']); + final arrivalDate = DateFormatterHelper.formatFlightDate(transaction.flightDetails['arrivalTime']); + final duration = DateFormatterHelper.calculateFlightDuration( + transaction.flightDetails['departureTime'], + transaction.flightDetails['arrivalTime'], + ); return Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), @@ -110,7 +118,7 @@ class _DetailTicketScreenState extends State { color: GrayColors.gray800, ), SizedBox(width: 10.w), - SvgPicture.asset('assets/images/citilink.svg', width: 40.w, height: 10.h), + Image.network(transaction.flightDetails['airlineLogo'], width: 40.w), ], ), SizedBox(height: 4.h), @@ -131,15 +139,14 @@ class _DetailTicketScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TypographyStyles.caption("12:20", color: GrayColors.gray800), - TypographyStyles.small("Sen, 27 Jan", + TypographyStyles.caption(departureTime, color: GrayColors.gray800), + TypographyStyles.small(departureDate, color: GrayColors.gray600, fontWeight: FontWeight.w400), SizedBox(height: 20.h), - TypographyStyles.small("5j 40m", color: GrayColors.gray600, fontWeight: FontWeight.w400), + TypographyStyles.small(duration, color: GrayColors.gray600, fontWeight: FontWeight.w400), SizedBox(height: 20.h), - TypographyStyles.caption("12:20", color: GrayColors.gray800), - TypographyStyles.small("Sen, 27 Jan", - color: GrayColors.gray600, fontWeight: FontWeight.w400), + TypographyStyles.caption(arrivalTime, color: GrayColors.gray800), + TypographyStyles.small(arrivalDate, color: GrayColors.gray600, fontWeight: FontWeight.w400), ], ), SizedBox(width: 20.w), @@ -237,11 +244,7 @@ class _DetailTicketScreenState extends State { onTap: () { Get.toNamed( Routes.UPLOADFILE, - arguments: { - 'ticketId': transaction.ticketId, - 'transactionId': transaction.id, - 'mode': 'history' - }, + arguments: {'ticketId': transaction.ticketId, 'transactionId': transaction.id, 'mode': 'history'}, ); }, ), diff --git a/lib/presentation/screens/boarding_pass/pages/print_boarding_pass_screen.dart b/lib/presentation/screens/boarding_pass/pages/print_boarding_pass_screen.dart index d666259..ac5eb5e 100644 --- a/lib/presentation/screens/boarding_pass/pages/print_boarding_pass_screen.dart +++ b/lib/presentation/screens/boarding_pass/pages/print_boarding_pass_screen.dart @@ -1,18 +1,104 @@ +import 'dart:developer'; +import 'dart:ui' as ui; +import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart'; +import 'package:pdf/pdf.dart'; +import 'package:pdf/widgets.dart' as pw; import 'package:barcode_widget/barcode_widget.dart'; import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart'; import 'package:e_porter/_core/component/dotted/dashed_line_component.dart'; import 'package:e_porter/_core/constants/colors.dart'; import 'package:e_porter/_core/constants/typography.dart'; +import 'package:e_porter/_core/utils/formatter/date_helper.dart'; +import 'package:e_porter/presentation/controllers/history_controller.dart'; import 'package:e_porter/presentation/screens/home/component/card_tickets.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:printing/printing.dart'; import '../../../../_core/component/appbar/appbar_component.dart'; -class PrintBoardingPassScreen extends StatelessWidget { +class PrintBoardingPassScreen extends StatefulWidget { const PrintBoardingPassScreen({super.key}); + @override + State createState() => _PrintBoardingPassScreenState(); +} + +class _PrintBoardingPassScreenState extends State { + final GlobalKey _printKey = GlobalKey(); + final historyController = Get.find(); + + late final String transactionTicketId; + late final String ticketId; + late final int passenger; + + @override + void initState() { + super.initState(); + final args = Get.arguments as Map; + transactionTicketId = args['transactionId']; + ticketId = args['ticketId']; + passenger = args['passengerIndex']; + + WidgetsBinding.instance.addPostFrameCallback((_) { + _loadTransactionData(); + }); + } + + Future _loadTransactionData() async { + try { + await historyController.getTransactionFromFirestore(ticketId, transactionTicketId); + } catch (e) { + log('[Print Boarding Pass] Error getTransaction $e'); + } + } + + Future _printPass() async { + Get.dialog( + const Center(child: CircularProgressIndicator()), + barrierDismissible: false, + ); + + final pw.Document doc = pw.Document(); + final pageFormat = PdfPageFormat.a6.copyWith( + marginLeft: 0, + marginTop: 0, + marginRight: 0, + marginBottom: 0, + ); + + try { + final ctx = _printKey.currentContext; + if (ctx == null) throw 'Boarding pass belum siap'; + + final boundary = ctx.findRenderObject() as RenderRepaintBoundary; + final ui.Image image = await boundary.toImage(pixelRatio: 3.0); + final byteData = await image.toByteData(format: ui.ImageByteFormat.png); + final pngBytes = byteData!.buffer.asUint8List(); + + final pw.MemoryImage pwImage = pw.MemoryImage(pngBytes); + doc.addPage( + pw.Page( + pageFormat: pageFormat, + build: (c) => pw.Center(child: pw.Image(pwImage)), + ), + ); + } catch (e) { + if (Get.isDialogOpen ?? false) Get.back(); + SnackbarHelper.showError('Error', e.toString()); + return; + } + + if (Get.isDialogOpen ?? false) Get.back(); + + await Printing.layoutPdf( + onLayout: (PdfPageFormat format) async => doc.save(), + format: pageFormat, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -26,68 +112,117 @@ class PrintBoardingPassScreen extends StatelessWidget { }, ), body: SafeArea( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), child: SingleChildScrollView( - child: Column( - children: [ - CustomeShadowCotainner( + child: RepaintBoundary( + key: _printKey, + child: Obx( + () { + if (historyController.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + if (historyController.errorMessage.value.isNotEmpty) { + return Center( + child: TypographyStyles.body( + historyController.errorMessage.value, + color: GrayColors.gray400, + ), + ); + } + + final transaction = historyController.selectedTransaction.value; + if (transaction == null) { + return Center( + child: TypographyStyles.body( + 'Data transaksi tidak ditemukan', + color: GrayColors.gray400, + ), + ); + } + + final departureTime = DateFormatterHelper.formatFlightTime(transaction.flightDetails['departureTime']); + final arrivalTime = DateFormatterHelper.formatFlightTime(transaction.flightDetails['arrivalTime']); + final ticketDate = DateFormatterHelper.formatFlightDate(transaction.flightDetails['departureTime']); + final duration = DateFormatterHelper.calculateFlightDuration( + transaction.flightDetails['departureTime'], + transaction.flightDetails['arrivalTime'], + ); + + final seatNumber = transaction.numberSeat.length > passenger ? transaction.numberSeat[passenger] : '-'; + final passengerMap = (transaction.passengerDetails as List)[passenger] as Map; + final idBarcode = passengerMap['idBarcode'] ?? ''; + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), + child: SingleChildScrollView( child: Column( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TypographyStyles.caption( - 'Kode Booking Maskapai', - color: GrayColors.gray500, - fontWeight: FontWeight.w400, - ), - TypographyStyles.h6('text', color: GrayColors.gray800), - ], - ), - Padding( - padding: EdgeInsets.symmetric(vertical: 10), - child: Divider(thickness: 1, color: GrayColors.gray200), - ), - CardTickets( - withContainer: false, - showFooter: false, - departureCity: 'departureCity', - date: 'date', - arrivalCity: 'arrivalCity', - departureCode: 'YIA', - arrivalCode: 'LOP', - departureTime: 'departureTime', - arrivalTime: 'arrivalTime', - duration: 'duration', - seatClass: 'seatClass', - price: 'price', - ), - Padding( - padding: EdgeInsets.symmetric(vertical: 10), - child: Divider(thickness: 1, color: GrayColors.gray200), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildColumnText(context, label: 'Layanan', value: 'value'), - _buildColumnText(context, label: 'Class', value: 'value'), - _buildColumnText(context, label: 'Gate', value: 'value'), - _buildColumnText(context, label: 'Seat', value: 'value'), - ], - ), - Padding( - padding: EdgeInsets.symmetric(vertical: 20.h), - child: CustomDashedLine(), - ), - _buildBarcode(context, barcodeData: 'PK230222BE143') + CustomeShadowCotainner( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TypographyStyles.caption( + 'Kode Booking Maskapai', + color: GrayColors.gray500, + fontWeight: FontWeight.w400, + ), + TypographyStyles.h6(transaction.idBooking, color: GrayColors.gray800), + ], + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Divider(thickness: 1, color: GrayColors.gray200), + ), + CardTickets( + withContainer: false, + showFooter: false, + airlineLogo: transaction.flightDetails['airlineLogo'], + departureCity: transaction.flightDetails['cityDeparture'], + arrivalCity: transaction.flightDetails['cityArrival'], + date: ticketDate, + departureCode: transaction.flightDetails['codeDeparture'], + arrivalCode: transaction.flightDetails['codeArrival'], + departureTime: departureTime, + arrivalTime: arrivalTime, + duration: duration, + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Divider(thickness: 1, color: GrayColors.gray200), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildColumnText(context, label: 'Layanan', value: 'value'), + _buildColumnText(context, + label: 'Class', value: transaction.flightDetails['flightClass']), + _buildColumnText(context, label: 'Gate', value: 'value'), + _buildColumnText(context, label: 'Seat', value: seatNumber), + ], + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 20.h), + child: CustomDashedLine(), + ), + _buildBarcode(context, barcodeData: idBarcode) + ], + ), + ) ], ), - ) - ], - ), + ), + ); + }, ), ), + )), + floatingActionButton: FloatingActionButton.extended( + icon: Icon(Icons.print_outlined, color: Colors.white, size: 24), + label: TypographyStyles.caption('Cetak', color: Colors.white), + onPressed: _printPass, + backgroundColor: PrimaryColors.primary800, ), ); } diff --git a/lib/presentation/screens/boarding_pass/pages/scan_barcode_screen.dart b/lib/presentation/screens/boarding_pass/pages/scan_barcode_screen.dart new file mode 100644 index 0000000..8654bb8 --- /dev/null +++ b/lib/presentation/screens/boarding_pass/pages/scan_barcode_screen.dart @@ -0,0 +1,45 @@ +import 'package:e_porter/presentation/controllers/history_controller.dart'; +import 'package:e_porter/presentation/screens/boarding_pass/pages/print_boarding_pass_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; + +class ScanBarcodeScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Scan Boarding Pass')), + body: MobileScanner( + // Versi baru onDetect hanya terima satu arg + onDetect: (capture) { + for (final bar in capture.barcodes) { + final raw = bar.rawValue; + if (raw != null && raw.startsWith('P-')) { + // hentikan scanner + MobileScannerController().stop(); + // ambil tx dari HistoryController + final history = Get.find(); + final tx = history.selectedTransaction.value; + if (tx != null) { + final idx = (tx.passengerDetails as List).indexWhere((p) => p['idBarcode'] == raw); + if (idx >= 0) { + // navigasi ke print screen + Get.to( + () => PrintBoardingPassScreen(), + arguments: { + 'transactionId': tx.id, + 'ticketId': tx.ticketId, + 'passengerIndex': idx, + }, + ); + return; + } + } + Get.snackbar('Error', 'Penumpang tidak ditemukan'); + } + } + }, + ), + ); + } +} diff --git a/lib/presentation/screens/home/component/card_tickets.dart b/lib/presentation/screens/home/component/card_tickets.dart index 4f8efa5..51f5a79 100644 --- a/lib/presentation/screens/home/component/card_tickets.dart +++ b/lib/presentation/screens/home/component/card_tickets.dart @@ -18,8 +18,8 @@ class CardTickets extends StatelessWidget { final String departureTime; final String arrivalTime; final String duration; - final String seatClass; - final String price; + final String? seatClass; + final String? price; final VoidCallback? onTap; final bool withContainer; final bool showFooter; @@ -35,8 +35,8 @@ class CardTickets extends StatelessWidget { required this.departureTime, required this.arrivalTime, required this.duration, - required this.seatClass, - required this.price, + this.seatClass, + this.price, this.onTap, this.withContainer = true, this.showFooter = true, @@ -115,12 +115,12 @@ class CardTickets extends StatelessWidget { children: [ CustomeIcons.FlightSeatFilled(), SizedBox(width: 6.w), - TypographyStyles.caption(seatClass, color: GrayColors.gray800), + TypographyStyles.caption('$seatClass', color: GrayColors.gray800), ], ), Row( children: [ - TypographyStyles.body(price, color: PrimaryColors.primary800), + TypographyStyles.body('$price', color: PrimaryColors.primary800), SizedBox(width: 2.w), TypographyStyles.small('/orang', color: GrayColors.gray600, fontWeight: FontWeight.w400), ], diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..ce0e550 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) printing_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin"); + printing_plugin_register_with_registrar(printing_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..0c2c3c3 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + printing ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index ab85d98..56f61eb 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,6 +14,7 @@ import firebase_database import firebase_storage import mobile_scanner import path_provider_foundation +import printing import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -26,5 +27,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 30b3c6f..2fced6f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.54" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" args: dependency: transitive description: @@ -41,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + bidi: + dependency: transitive + description: + name: bidi + sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" + url: "https://pub.dev" + source: hosted + version: "2.0.13" boolean_selector: dependency: transitive description: @@ -384,6 +400,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" intl: dependency: "direct main" description: @@ -520,6 +544,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" + url: "https://pub.dev" + source: hosted + version: "3.11.3" + pdf_widget_wrapper: + dependency: transitive + description: + name: pdf_widget_wrapper + sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5 + url: "https://pub.dev" + source: hosted + version: "1.0.4" permission_handler: dependency: "direct main" description: @@ -592,6 +632,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + printing: + dependency: "direct main" + description: + name: printing + sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93" + url: "https://pub.dev" + source: hosted + version: "5.14.2" qr: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 67394bf..99d5870 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,8 @@ dependencies: permission_handler: ^11.4.0 device_info_plus: ^11.3.0 mobile_scanner: ^6.0.10 + pdf: ^3.11.3 + printing: ^5.14.2 # workmanager: ^0.5.2 # pin_code_fields: ^8.0.1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 124f904..eace780 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { CloudFirestorePluginCApiRegisterWithRegistrar( @@ -23,4 +24,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + PrintingPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PrintingPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index aa86fdf..2b48493 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST firebase_core firebase_storage permission_handler_windows + printing ) list(APPEND FLUTTER_FFI_PLUGIN_LIST