Feat: add features print ticket boarding pass

This commit is contained in:
orangdeso 2025-05-07 21:05:27 +07:00
parent 0a38f3f788
commit c0d27d6fdf
15 changed files with 402 additions and 85 deletions

View File

@ -7,6 +7,12 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.2" "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", "name": "args",
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0",
@ -31,6 +37,12 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "2.12" "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", "name": "boolean_selector",
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.1", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.1",
@ -295,6 +307,12 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "2.12" "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", "name": "intl",
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/intl-0.20.2", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/intl-0.20.2",
@ -397,6 +415,18 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.2" "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", "name": "permission_handler",
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/permission_handler-11.4.0", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/permission_handler-11.4.0",
@ -451,6 +481,18 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.0" "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", "name": "qr",
"rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/qr-3.0.2", "rootUri": "file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/qr-3.0.2",
@ -638,7 +680,7 @@
"languageVersion": "3.4" "languageVersion": "3.4"
} }
], ],
"generated": "2025-05-05T05:14:47.271498Z", "generated": "2025-05-07T08:39:04.087158Z",
"generator": "pub", "generator": "pub",
"generatorVersion": "3.5.0", "generatorVersion": "3.5.0",
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0", "flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",

View File

@ -2,6 +2,10 @@ _flutterfire_internals
3.2 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/
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/_flutterfire_internals-1.3.54/lib/ 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 args
3.3 3.3
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0/
@ -18,6 +22,10 @@ barcode_widget
2.12 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/
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/barcode_widget-2.0.4/lib/ 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 boolean_selector
2.17 2.17
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.1/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.1/
@ -182,6 +190,10 @@ http_parser
2.12 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/
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/http_parser-4.0.2/lib/ 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 intl
3.3 3.3
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/intl-0.20.2/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/intl-0.20.2/
@ -250,6 +262,14 @@ path_provider_windows
3.2 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/
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/lib/ 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 permission_handler
3.5 3.5
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/permission_handler-11.4.0/ 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 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/
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib/ 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 qr
3.4 3.4
file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/qr-3.0.2/ file:///C:/Users/ASUS/AppData/Local/Pub/Cache/hosted/pub.dev/qr-3.0.2/

View File

@ -73,11 +73,7 @@ class FlightModel {
factory FlightModel.fromDocument(DocumentSnapshot doc) { factory FlightModel.fromDocument(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>; final data = doc.data() as Map<String, dynamic>;
// Ambil field seat (Map) dari Firestore
final seatData = data['seat'] as Map<String, dynamic>? ?? {}; final seatData = data['seat'] as Map<String, dynamic>? ?? {};
// Konversi setiap entry di seatData menjadi SeatInfo
final seatMap = seatData.map<String, SeatInfo>((key, value) { final seatMap = seatData.map<String, SeatInfo>((key, value) {
return MapEntry( return MapEntry(
key, key,
@ -85,8 +81,6 @@ class FlightModel {
); );
}); });
// print("FlightModel.fromDocument - Doc ID: ${doc.id}, data: $data");
return FlightModel( return FlightModel(
id: doc.id, id: doc.id,
airLines: data['airlines'] ?? '', airLines: data['airlines'] ?? '',
@ -103,7 +97,7 @@ class FlightModel {
stop: data['stop'] ?? '', stop: data['stop'] ?? '',
price: data['price'] ?? 0, price: data['price'] ?? 0,
airlineLogo: data['airlineLogo'] ?? '', airlineLogo: data['airlineLogo'] ?? '',
seat: seatMap, // masukkan Map<String, SeatInfo> seat: seatMap,
); );
} }

View File

@ -288,8 +288,9 @@ class _BoardingPassScreenState extends State<BoardingPassScreen> with SingleTick
'id_transaction': transaction.id, 'id_transaction': transaction.id,
'id_ticket': transaction.ticketId, 'id_ticket': transaction.ticketId,
}; };
log('ID Transaction: ${transaction.id}'); log('Arrival Time: $arrivalTime');
log('ID Ticket: ${transaction.ticketId}'); log('Departure Time: $departureTime');
Get.toNamed(Routes.DETAILTICKET, arguments: argument); Get.toNamed(Routes.DETAILTICKET, arguments: argument);
}, },
), ),

View File

@ -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/component/dotted/dashed_line_component.dart';
import 'package:e_porter/_core/constants/colors.dart'; import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.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/controllers/history_controller.dart';
import 'package:e_porter/presentation/screens/boarding_pass/component/card_details_passenger.dart'; import 'package:e_porter/presentation/screens/boarding_pass/component/card_details_passenger.dart';
import 'package:e_porter/presentation/screens/routes/app_rountes.dart'; import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
@ -84,7 +85,14 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
return Center(child: TypographyStyles.body('Data transaksi tidak ditemukan', color: GrayColors.gray400)); 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( return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
@ -110,7 +118,7 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
color: GrayColors.gray800, color: GrayColors.gray800,
), ),
SizedBox(width: 10.w), 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), SizedBox(height: 4.h),
@ -131,15 +139,14 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
TypographyStyles.caption("12:20", color: GrayColors.gray800), TypographyStyles.caption(departureTime, color: GrayColors.gray800),
TypographyStyles.small("Sen, 27 Jan", TypographyStyles.small(departureDate,
color: GrayColors.gray600, fontWeight: FontWeight.w400), color: GrayColors.gray600, fontWeight: FontWeight.w400),
SizedBox(height: 20.h), 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), SizedBox(height: 20.h),
TypographyStyles.caption("12:20", color: GrayColors.gray800), TypographyStyles.caption(arrivalTime, color: GrayColors.gray800),
TypographyStyles.small("Sen, 27 Jan", TypographyStyles.small(arrivalDate, color: GrayColors.gray600, fontWeight: FontWeight.w400),
color: GrayColors.gray600, fontWeight: FontWeight.w400),
], ],
), ),
SizedBox(width: 20.w), SizedBox(width: 20.w),
@ -237,11 +244,7 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
onTap: () { onTap: () {
Get.toNamed( Get.toNamed(
Routes.UPLOADFILE, Routes.UPLOADFILE,
arguments: { arguments: {'ticketId': transaction.ticketId, 'transactionId': transaction.id, 'mode': 'history'},
'ticketId': transaction.ticketId,
'transactionId': transaction.id,
'mode': 'history'
},
); );
}, },
), ),

View File

@ -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:barcode_widget/barcode_widget.dart';
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.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/component/dotted/dashed_line_component.dart';
import 'package:e_porter/_core/constants/colors.dart'; import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.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:e_porter/presentation/screens/home/component/card_tickets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:printing/printing.dart';
import '../../../../_core/component/appbar/appbar_component.dart'; import '../../../../_core/component/appbar/appbar_component.dart';
class PrintBoardingPassScreen extends StatelessWidget { class PrintBoardingPassScreen extends StatefulWidget {
const PrintBoardingPassScreen({super.key}); const PrintBoardingPassScreen({super.key});
@override
State<PrintBoardingPassScreen> createState() => _PrintBoardingPassScreenState();
}
class _PrintBoardingPassScreenState extends State<PrintBoardingPassScreen> {
final GlobalKey _printKey = GlobalKey();
final historyController = Get.find<HistoryController>();
late final String transactionTicketId;
late final String ticketId;
late final int passenger;
@override
void initState() {
super.initState();
final args = Get.arguments as Map<String, dynamic>;
transactionTicketId = args['transactionId'];
ticketId = args['ticketId'];
passenger = args['passengerIndex'];
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadTransactionData();
});
}
Future<void> _loadTransactionData() async {
try {
await historyController.getTransactionFromFirestore(ticketId, transactionTicketId);
} catch (e) {
log('[Print Boarding Pass] Error getTransaction $e');
}
}
Future<void> _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -26,68 +112,117 @@ class PrintBoardingPassScreen extends StatelessWidget {
}, },
), ),
body: SafeArea( body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: RepaintBoundary(
children: [ key: _printKey,
CustomeShadowCotainner( 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<dynamic>)[passenger] as Map<String, dynamic>;
final idBarcode = passengerMap['idBarcode'] ?? '';
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Row( CustomeShadowCotainner(
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Column(
children: [ children: [
TypographyStyles.caption( Row(
'Kode Booking Maskapai', mainAxisAlignment: MainAxisAlignment.spaceBetween,
color: GrayColors.gray500, children: [
fontWeight: FontWeight.w400, TypographyStyles.caption(
), 'Kode Booking Maskapai',
TypographyStyles.h6('text', color: GrayColors.gray800), color: GrayColors.gray500,
], fontWeight: FontWeight.w400,
), ),
Padding( TypographyStyles.h6(transaction.idBooking, color: GrayColors.gray800),
padding: EdgeInsets.symmetric(vertical: 10), ],
child: Divider(thickness: 1, color: GrayColors.gray200), ),
), Padding(
CardTickets( padding: EdgeInsets.symmetric(vertical: 10),
withContainer: false, child: Divider(thickness: 1, color: GrayColors.gray200),
showFooter: false, ),
departureCity: 'departureCity', CardTickets(
date: 'date', withContainer: false,
arrivalCity: 'arrivalCity', showFooter: false,
departureCode: 'YIA', airlineLogo: transaction.flightDetails['airlineLogo'],
arrivalCode: 'LOP', departureCity: transaction.flightDetails['cityDeparture'],
departureTime: 'departureTime', arrivalCity: transaction.flightDetails['cityArrival'],
arrivalTime: 'arrivalTime', date: ticketDate,
duration: 'duration', departureCode: transaction.flightDetails['codeDeparture'],
seatClass: 'seatClass', arrivalCode: transaction.flightDetails['codeArrival'],
price: 'price', departureTime: departureTime,
), arrivalTime: arrivalTime,
Padding( duration: duration,
padding: EdgeInsets.symmetric(vertical: 10), ),
child: Divider(thickness: 1, color: GrayColors.gray200), Padding(
), padding: EdgeInsets.symmetric(vertical: 10),
Row( child: Divider(thickness: 1, color: GrayColors.gray200),
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ Row(
_buildColumnText(context, label: 'Layanan', value: 'value'), mainAxisAlignment: MainAxisAlignment.spaceBetween,
_buildColumnText(context, label: 'Class', value: 'value'), children: [
_buildColumnText(context, label: 'Gate', value: 'value'), _buildColumnText(context, label: 'Layanan', value: 'value'),
_buildColumnText(context, label: 'Seat', value: 'value'), _buildColumnText(context,
], label: 'Class', value: transaction.flightDetails['flightClass']),
), _buildColumnText(context, label: 'Gate', value: 'value'),
Padding( _buildColumnText(context, label: 'Seat', value: seatNumber),
padding: EdgeInsets.symmetric(vertical: 20.h), ],
child: CustomDashedLine(), ),
), Padding(
_buildBarcode(context, barcodeData: 'PK230222BE143') 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,
), ),
); );
} }

View File

@ -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<HistoryController>();
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');
}
}
},
),
);
}
}

View File

@ -18,8 +18,8 @@ class CardTickets extends StatelessWidget {
final String departureTime; final String departureTime;
final String arrivalTime; final String arrivalTime;
final String duration; final String duration;
final String seatClass; final String? seatClass;
final String price; final String? price;
final VoidCallback? onTap; final VoidCallback? onTap;
final bool withContainer; final bool withContainer;
final bool showFooter; final bool showFooter;
@ -35,8 +35,8 @@ class CardTickets extends StatelessWidget {
required this.departureTime, required this.departureTime,
required this.arrivalTime, required this.arrivalTime,
required this.duration, required this.duration,
required this.seatClass, this.seatClass,
required this.price, this.price,
this.onTap, this.onTap,
this.withContainer = true, this.withContainer = true,
this.showFooter = true, this.showFooter = true,
@ -115,12 +115,12 @@ class CardTickets extends StatelessWidget {
children: [ children: [
CustomeIcons.FlightSeatFilled(), CustomeIcons.FlightSeatFilled(),
SizedBox(width: 6.w), SizedBox(width: 6.w),
TypographyStyles.caption(seatClass, color: GrayColors.gray800), TypographyStyles.caption('$seatClass', color: GrayColors.gray800),
], ],
), ),
Row( Row(
children: [ children: [
TypographyStyles.body(price, color: PrimaryColors.primary800), TypographyStyles.body('$price', color: PrimaryColors.primary800),
SizedBox(width: 2.w), SizedBox(width: 2.w),
TypographyStyles.small('/orang', color: GrayColors.gray600, fontWeight: FontWeight.w400), TypographyStyles.small('/orang', color: GrayColors.gray600, fontWeight: FontWeight.w400),
], ],

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <printing/printing_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { 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);
} }

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
printing
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -14,6 +14,7 @@ import firebase_database
import firebase_storage import firebase_storage
import mobile_scanner import mobile_scanner
import path_provider_foundation import path_provider_foundation
import printing
import shared_preferences_foundation import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
@ -26,5 +27,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@ -9,6 +9,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.54" version: "1.3.54"
archive:
dependency: transitive
description:
name: archive
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
url: "https://pub.dev"
source: hosted
version: "4.0.7"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -41,6 +49,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.4" version: "2.0.4"
bidi:
dependency: transitive
description:
name: bidi
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
url: "https://pub.dev"
source: hosted
version: "2.0.13"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -384,6 +400,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev"
source: hosted
version: "4.5.4"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -520,6 +544,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" 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: permission_handler:
dependency: "direct main" dependency: "direct main"
description: description:
@ -592,6 +632,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" 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: qr:
dependency: transitive dependency: transitive
description: description:

View File

@ -60,6 +60,8 @@ dependencies:
permission_handler: ^11.4.0 permission_handler: ^11.4.0
device_info_plus: ^11.3.0 device_info_plus: ^11.3.0
mobile_scanner: ^6.0.10 mobile_scanner: ^6.0.10
pdf: ^3.11.3
printing: ^5.14.2
# workmanager: ^0.5.2 # workmanager: ^0.5.2
# pin_code_fields: ^8.0.1 # pin_code_fields: ^8.0.1

View File

@ -11,6 +11,7 @@
#include <firebase_core/firebase_core_plugin_c_api.h> #include <firebase_core/firebase_core_plugin_c_api.h>
#include <firebase_storage/firebase_storage_plugin_c_api.h> #include <firebase_storage/firebase_storage_plugin_c_api.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <printing/printing_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
CloudFirestorePluginCApiRegisterWithRegistrar( CloudFirestorePluginCApiRegisterWithRegistrar(
@ -23,4 +24,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi")); registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
PrintingPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PrintingPlugin"));
} }

View File

@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
firebase_core firebase_core
firebase_storage firebase_storage
permission_handler_windows permission_handler_windows
printing
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST