Feat: add scan qr and slicing design search porter after scan qr

This commit is contained in:
orangdeso 2025-04-26 22:29:26 +07:00
parent 0668f8d3dc
commit b1f9d95907
4 changed files with 793 additions and 137 deletions

View File

@ -80,11 +80,6 @@ class TransactionRepositoryImpl implements TransactionRepository {
.doc(transactionId)
.set(transactionData);
// Update ID booking pada tiket
await _firestore.collection('tickets').doc(ticketId).update({
'idBooking': idBooking,
});
// Kelompokkan kursi berdasarkan kelas
Map<String, List<int>> seatsByClass = {};
for (String seatNumber in numberSeat) {

View File

@ -1,22 +1,59 @@
import 'dart:developer';
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/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';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
import '../../../../_core/component/appbar/appbar_component.dart';
import '../../../../_core/component/button/button_fill.dart';
import '../../../../_core/component/card/custome_shadow_cotainner.dart';
import '../../../../_core/utils/snackbar/snackbar_helper.dart';
import '../../../../domain/models/transaction_model.dart';
import '../../home/component/title_show_modal.dart';
class DetailTicketScreen extends StatelessWidget {
class DetailTicketScreen extends StatefulWidget {
const DetailTicketScreen({super.key});
@override
State<DetailTicketScreen> createState() => _DetailTicketScreenState();
}
class _DetailTicketScreenState extends State<DetailTicketScreen> {
late final String id_transaction;
late final String id_ticket;
final historyController = Get.find<HistoryController>();
@override
void initState() {
super.initState();
final args = Get.arguments as Map<String, dynamic>;
id_transaction = args['id_transaction'];
id_ticket = args['id_ticket'];
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadTransactionData();
});
}
Future<void> _loadTransactionData() async {
try {
await historyController.getTransactionFromFirestore(id_ticket, id_transaction);
} catch (e) {
log('Error getTransaction $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -30,145 +67,147 @@ class DetailTicketScreen extends StatelessWidget {
},
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.caption(
"Kode Booking Maskapai",
color: GrayColors.gray500,
fontWeight: FontWeight.w500,
),
SizedBox(height: 6.h),
TypographyStyles.body("I2L8JRL", color: GrayColors.gray800),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.h),
child: CustomDashedLine(),
),
Row(
child: Obx(
() {
if (historyController.isLoading.value) {
return 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));
}
log('ID Booking Detail Tiket: ${transaction.idBooking}');
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.body("Citilink (103)", color: GrayColors.gray800),
SizedBox(width: 10.w),
SvgPicture.asset('assets/images/citilink.svg', width: 40.w, height: 10.h),
],
),
SizedBox(height: 4.h),
Row(
children: [
TypographyStyles.small("Fast Track (FT)", color: GrayColors.gray600, fontWeight: FontWeight.w400),
SizedBox(width: 10.w),
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
SizedBox(width: 10.w),
TypographyStyles.small("Economy", color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
),
SizedBox(height: 16.h),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
TypographyStyles.caption(
"Kode Booking Maskapai",
color: GrayColors.gray500,
fontWeight: FontWeight.w500,
),
SizedBox(height: 6.h),
TypographyStyles.body("${transaction.idBooking}", color: GrayColors.gray800),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.h),
child: CustomDashedLine(),
),
Row(
children: [
TypographyStyles.caption("12:20", color: GrayColors.gray800),
TypographyStyles.small("Sen, 27 Jan", color: GrayColors.gray600, fontWeight: FontWeight.w400),
SizedBox(height: 20.h),
TypographyStyles.small("5j 40m", 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.body(
"${transaction.flightDetails['airLines'] ?? 'Airline'} (${transaction.flightDetails['code'] ?? ''})",
color: GrayColors.gray800,
),
SizedBox(width: 10.w),
SvgPicture.asset('assets/images/citilink.svg', width: 40.w, height: 10.h),
],
),
SizedBox(width: 20.w),
SvgPicture.asset('assets/images/garis.svg', height: 100.h),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.caption("Yogyakarta (YIA)", color: GrayColors.gray800),
TypographyStyles.caption(
"Bandar YIA, Terminal Domestic",
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
maxlines: 2,
overflow: TextOverflow.ellipsis,
SizedBox(height: 4.h),
Row(
children: [
TypographyStyles.small("Fast Track (FT)",
color: GrayColors.gray600, fontWeight: FontWeight.w400),
SizedBox(width: 10.w),
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
SizedBox(width: 10.w),
TypographyStyles.small("${transaction.flightDetails['flightClass']}",
color: GrayColors.gray600, fontWeight: FontWeight.w400),
],
),
SizedBox(height: 16.h),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.caption("12:20", color: GrayColors.gray800),
TypographyStyles.small("Sen, 27 Jan",
color: GrayColors.gray600, fontWeight: FontWeight.w400),
SizedBox(height: 20.h),
TypographyStyles.small("5j 40m", 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),
],
),
SizedBox(width: 20.w),
SvgPicture.asset('assets/images/garis.svg', height: 100.h),
SizedBox(width: 20.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.caption(
"${transaction.flightDetails['cityDeparture']} (${transaction.flightDetails['codeDeparture']})",
color: GrayColors.gray800,
),
TypographyStyles.caption(
"${transaction.bandaraDetails['departure']?['name']}",
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
maxlines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 58.h),
TypographyStyles.caption(
"${transaction.flightDetails['cityArrival']} (${transaction.flightDetails['codeArrival']})",
color: GrayColors.gray800,
),
TypographyStyles.caption(
"${transaction.bandaraDetails['arrival']?['name']}",
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
maxlines: 2,
overflow: TextOverflow.ellipsis,
)
],
),
SizedBox(height: 58.h),
TypographyStyles.caption("Lombok (LOP)", color: GrayColors.gray800),
TypographyStyles.caption(
"Bandar Zainuddin Abdul Madjid, Terminal Domestic",
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
maxlines: 2,
overflow: TextOverflow.ellipsis,
)
],
),
)
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.h),
child: CustomDashedLine(),
),
TypographyStyles.h6("Detail Penumpang", color: GrayColors.gray800),
SizedBox(height: 20.h),
ListView.builder(
itemCount: 1,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return CardDetailsPassenger(
name: 'AHMAD CHOIRUL UMAM ALI',
typeId: 'KTP',
noId: '3571••••••••••03',
seatClass: 'Economy',
numberSeat: '10 F',
);
},
)
],
),
),
),
),
bottomNavigationBar: CustomeShadowCotainner(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: ButtonFill(
text: 'Cetak Boarding Pass',
textColor: Colors.white,
onTap: () {
Get.bottomSheet(
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.r),
topRight: Radius.circular(10.r),
),
),
Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
child: Wrap(
children: [
TitleShowModal(text: 'Cetak Boarding Pass'),
SizedBox(height: 30.h),
)
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.h),
child: CustomDashedLine(),
),
TypographyStyles.h6("Detail Penumpang", color: GrayColors.gray800),
SizedBox(height: 20.h),
ListView.builder(
itemCount: 1,
itemCount: transaction.passengerDetails.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(top: 16.h),
child: _buildCetakBoardingPass(
context,
name: 'AHMAD CHOIRUL UMAM',
typeId: 'KTP',
noId: '3571••••••••••03',
onTap: () {
Get.toNamed(Routes.PRINTBOARDINGPASS);
},
),
final passenger = transaction.passengerDetails[index];
final seatNumber =
index < transaction.numberSeat.length ? transaction.numberSeat[index] : 'N/A';
String maskedId = 'N/A';
if (passenger['noId'] != null) {
String idNumber = passenger['noId'].toString();
if (idNumber.length > 4) {
maskedId = '${idNumber.substring(0, 4)}${'' * (idNumber.length - 4)}';
} else {
maskedId = idNumber;
}
}
return CardDetailsPassenger(
name: passenger['name'] ?? 'N/A',
typeId: passenger['typeId'] ?? 'N/A',
noId: maskedId,
seatClass: '${transaction.flightDetails['flightClass']}',
numberSeat: seatNumber,
);
},
)
@ -179,6 +218,151 @@ class DetailTicketScreen extends StatelessWidget {
},
),
),
bottomNavigationBar: Obx(() {
if (historyController.isLoading.value) {
return SizedBox.shrink();
}
final transaction = historyController.selectedTransaction.value;
if (transaction == null) {
return SizedBox.shrink();
}
if (transaction.status == 'pending') {
return CustomeShadowCotainner(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: ButtonFill(
text: 'Upload Bukti Pembayaran',
textColor: Colors.white,
onTap: () {
Get.toNamed(Routes.UPLOADFILE, arguments: {
'ticketId': transaction.ticketId,
'transactionId': transaction.id,
});
},
),
);
} else if (transaction.status == 'active') {
if (transaction.porterServiceDetails == null ||
(transaction.porterServiceDetails as Map<String, dynamic>).isEmpty) {
return CustomeShadowCotainner(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: ButtonFill(
text: 'Cetak Boarding Pass',
textColor: Colors.white,
onTap: () => _showCetakBoardingPassBottomSheet(transaction),
),
);
} else {
return CustomeShadowCotainner(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ButtonOutline(
text: 'Scan QR Code Porter',
textColor: PrimaryColors.primary800,
onTap: () async {
await Permission.camera.status;
// final argument = {
// 'ticketId': transaction.ticketId,
// 'transactionId': transaction.id,
// };
// Get.toNamed(Routes.SCANQR, arguments: argument);
final result = await Get.toNamed(
Routes.SCANQR,
arguments: {
'ticketId': transaction.ticketId,
'transactionId': transaction.id,
},
);
// 2. Jika porter sibuk, tampilkan Snackbar dan hentikan di sini
if (result == 'PORTER_BUSY') {
SnackbarHelper.showError(
'Porter Tidak Tersedia',
'Tidak ada porter yang tersedia atau semua porter sedang sibuk, coba nanti.',
);
return;
}
},
),
SizedBox(height: 12.h),
ButtonFill(
text: 'Cetak Boarding Pass',
textColor: Colors.white,
onTap: () => _showCetakBoardingPassBottomSheet(transaction),
),
],
),
);
}
} else {
return CustomeShadowCotainner(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: ButtonFill(
text: 'Cetak Boarding Pass',
textColor: Colors.white,
onTap: () => _showCetakBoardingPassBottomSheet(transaction),
),
);
}
}),
);
}
void _showCetakBoardingPassBottomSheet(TransactionModel transaction) {
Get.bottomSheet(
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.r),
topRight: Radius.circular(10.r),
),
),
Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
child: Wrap(
children: [
TitleShowModal(text: 'Cetak Boarding Pass'),
SizedBox(height: 30.h),
ListView.builder(
itemCount: transaction.passengerDetails.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final passenger = transaction.passengerDetails[index];
String maskedId = 'N/A';
if (passenger['noId'] != null) {
String idNumber = passenger['noId'].toString();
if (idNumber.length > 4) {
maskedId = '${idNumber.substring(0, 4)}${'' * (idNumber.length - 4)}';
} else {
maskedId = idNumber;
}
}
return Padding(
padding: EdgeInsets.only(top: 16.h),
child: _buildCetakBoardingPass(
context,
name: passenger['name'] ?? 'N/A',
typeId: passenger['typeId'] ?? '',
noId: maskedId,
onTap: () {
Get.toNamed(Routes.PRINTBOARDINGPASS,
arguments: {'transaction': transaction, 'passengerIndex': index});
},
),
);
},
)
],
),
),
);
}

View File

@ -0,0 +1,337 @@
import 'dart:developer';
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import '../../../../_core/constants/colors.dart';
import '../../../../_core/constants/typography.dart';
import '../../../../_core/utils/snackbar/snackbar_helper.dart';
import '../../../../presentation/controllers/porter_queue_controller.dart';
import '../../../../presentation/controllers/transaction_controller.dart';
class ScanQRScreen extends StatefulWidget {
const ScanQRScreen({Key? key}) : super(key: key);
@override
State<ScanQRScreen> createState() => _ScanQRScreenState();
}
class _ScanQRScreenState extends State<ScanQRScreen> {
final MobileScannerController cameraController = MobileScannerController(
formats: [BarcodeFormat.qrCode, BarcodeFormat.aztec, BarcodeFormat.dataMatrix],
detectionSpeed: DetectionSpeed.unrestricted,
detectionTimeoutMs: 1000,
facing: CameraFacing.back,
torchEnabled: false,
returnImage: true,
);
bool isProcessing = false;
bool _isTorchOn = false;
String ticketId = '';
String transactionId = '';
final PorterQueueController _porterQueueController = Get.find<PorterQueueController>();
final TransactionController _transactionController = Get.find<TransactionController>();
@override
void initState() {
super.initState();
final args = Get.arguments as Map<String, dynamic>;
ticketId = args['ticketId'] ?? '';
transactionId = args['transactionId'] ?? '';
log('Transaction ID: $transactionId');
log('Current Transaction: ${_transactionController.currentTransaction.value}');
log('User Details: ${_transactionController.currentTransaction.value?.userDetails}');
}
@override
void dispose() {
cameraController.dispose();
super.dispose();
}
void _resetScanner() {
if (mounted) {
setState(() => isProcessing = false);
cameraController.start();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Scan QR Code Porter'),
backgroundColor: PrimaryColors.primary800,
foregroundColor: Colors.white,
actions: [
IconButton(
color: Colors.white,
icon: Icon(
_isTorchOn ? Icons.flash_on : Icons.flash_off,
color: _isTorchOn ? Colors.yellow : Colors.white,
),
onPressed: () {
cameraController.toggleTorch();
setState(() {
_isTorchOn = !_isTorchOn;
});
},
),
],
),
body: Stack(
children: [
MobileScanner(
// controller: cameraController,
scanWindow: Rect.fromCenter(
center: Offset(
MediaQuery.of(context).size.width / 2,
MediaQuery.of(context).size.height / 2,
),
width: MediaQuery.of(context).size.width * 0.7,
height: MediaQuery.of(context).size.width * 0.7,
),
onDetect: (capture) {
final raw = capture.barcodes.isNotEmpty ? capture.barcodes[0].rawValue : null;
if (raw != null && !isProcessing) {
_processQRCode(raw);
}
},
),
ClipPath(
clipper: OverlayClipper(),
child: Container(
color: Colors.black.withOpacity(0.5),
),
),
Center(
child: Container(
width: MediaQuery.of(context).size.width * 0.7,
height: MediaQuery.of(context).size.width * 0.7,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 3),
borderRadius: BorderRadius.circular(10),
),
),
),
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.6,
height: MediaQuery.of(context).size.width * 0.6,
child: CustomPaint(
painter: CornerPainter(
cornerColor: Colors.green,
cornerSize: 20,
),
),
),
),
Positioned(
bottom: 40,
left: 0,
right: 0,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
margin: EdgeInsets.symmetric(horizontal: 16.w),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(10.r),
),
child: Column(
children: [
TypographyStyles.body(
'Scan QR code di lokasi porter',
color: GrayColors.gray800,
textAlign: TextAlign.center,
),
SizedBox(height: 4.h),
TypographyStyles.caption(
'Posisikan kamera ke QR code yang tersedia di lokasi porter',
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
textAlign: TextAlign.center,
),
],
),
),
),
if (isProcessing)
Container(
color: Colors.black.withOpacity(0.6),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: Colors.white),
SizedBox(height: 16.h),
TypographyStyles.body(
'Sedang mencari porter…',
color: Colors.white,
textAlign: TextAlign.center,
),
],
),
),
),
],
),
);
}
Future<void> _processQRCode(String rawLocation) async {
log('[ScanQRScreen] Starting QR process (loc: $rawLocation)');
setState(() => isProcessing = true);
await cameraController.stop();
try {
// 1. Load transaksi jika perlu
if (_transactionController.currentTransaction.value == null) {
await _transactionController.getTransactionById(
ticketId: ticketId,
transactionId: transactionId,
);
}
// 2. Ambil passengerId
final userDetails = _transactionController.currentTransaction.value?.userDetails;
final passengerId = userDetails?['uid'] as String?;
if (passengerId == null || passengerId.isEmpty) {
SnackbarHelper.showError(
'Error',
'User tidak dikenali, silakan login ulang atau coba lagi.',
);
// Reset scanner agar bisa scan lagi
setState(() => isProcessing = false);
await cameraController.start();
return;
}
// 3. Request porter
final result = await _porterQueueController.requestPorter(
passengerId: passengerId,
ticketId: ticketId,
transactionId: transactionId,
location: rawLocation,
);
await Future.delayed(Duration(seconds: 3));
log('[ScanQRScreen] requestPorter succeeded: $result');
SnackbarHelper.showSuccess(
'Porter Ditemukan!',
'Anda berhasil mendapatkan porter',
);
// 4. Sukses: navigasi ke Processing
Get.toNamed(
Routes.PROCESSING,
arguments: {
'location': rawLocation,
'ticketId': ticketId,
'transactionId': transactionId,
'porterId': result['porterId']!,
'porterTransactionId': result['transactionId']!,
},
);
} on Exception catch (e) {
final msg = e.toString();
if (msg.contains('Porter tidak tersedia')) {
// Porter sibuk langsung pop dengan result
Get.back(result: 'PORTER_BUSY');
return;
}
// Error umum lain
log('[ScanQRScreen] _processQRCode error: $e');
SnackbarHelper.showError('Error', 'Terjadi kesalahan: $e');
// Reset scanner agar bisa coba lagi
setState(() => isProcessing = false);
await cameraController.start();
}
}
}
class OverlayClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final scanAreaSize = size.width * 0.7;
final rect = Rect.fromCenter(
center: Offset(size.width / 2, size.height / 2),
width: scanAreaSize,
height: scanAreaSize,
);
return Path()
..addRect(Rect.fromLTWH(0, 0, size.width, size.height))
..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(10)))
..fillType = PathFillType.evenOdd;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
class CornerPainter extends CustomPainter {
final Color cornerColor;
final double cornerSize;
CornerPainter({
required this.cornerColor,
required this.cornerSize,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = cornerColor
..style = PaintingStyle.stroke
..strokeWidth = 5;
// Top left
canvas.drawPath(
Path()
..moveTo(0, cornerSize)
..lineTo(0, 0)
..lineTo(cornerSize, 0),
paint,
);
// Top right
canvas.drawPath(
Path()
..moveTo(size.width - cornerSize, 0)
..lineTo(size.width, 0)
..lineTo(size.width, cornerSize),
paint,
);
// Bottom right
canvas.drawPath(
Path()
..moveTo(size.width, size.height - cornerSize)
..lineTo(size.width, size.height)
..lineTo(size.width - cornerSize, size.height),
paint,
);
// Bottom left
canvas.drawPath(
Path()
..moveTo(cornerSize, size.height)
..lineTo(0, size.height)
..lineTo(0, size.height - cornerSize),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@ -0,0 +1,140 @@
import 'package:e_porter/_core/component/button/button_fill.dart';
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
import 'package:e_porter/_core/constants/typography.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../../../_core/component/appbar/appbar_component.dart';
import '../../../../_core/constants/colors.dart';
class ProcessingPorterScreen extends StatefulWidget {
const ProcessingPorterScreen({Key? key}) : super(key: key);
@override
State<ProcessingPorterScreen> createState() => _ProcessingPorterScreenState();
}
class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
@override
void initState() {
super.initState();
// final args = Get.arguments as Map<String, dynamic>;
// final location = args['location'] ?? '';
// final ticketId = args['ticketId'] ?? '';
// final transactionId = args['transactionId'] ?? '';
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: GrayColors.gray50,
appBar: DefaultAppbarComponent(
title: 'Mencari Porter',
textColor: Colors.white,
backgroundColors: PrimaryColors.primary800,
onTab: () {
Get.back();
},
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: SingleChildScrollView(
child: Column(
children: [
TypographyStyles.h1('Ilustrasi'),
SizedBox(height: 32.h),
CustomeShadowCotainner(
child: Column(
children: [
TypographyStyles.h6('Tunggu Portermu', color: GrayColors.gray800),
SizedBox(height: 4.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TypographyStyles.caption(
'Tunngu dari pihak Porter merespon',
color: GrayColors.gray500,
fontWeight: FontWeight.w400,
),
SizedBox(width: 4.w),
Icon(Icons.timelapse_outlined)
],
),
],
),
),
SizedBox(height: 20.h),
CustomeShadowCotainner(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.body('Ahmad Choirul Umam', color: GrayColors.gray800),
SizedBox(height: 10.h),
Divider(thickness: 1, color: GrayColors.gray200),
SizedBox(height: 10.h),
TypographyStyles.body('Lokasi', color: GrayColors.gray800),
SizedBox(height: 10.h),
_buildRowLocation(location: 'Gate Penerbangan', desc: 'Lokasi Anda'),
SizedBox(height: 10.h),
_buildRowLocation(location: 'Guyangan', desc: 'Lokasi Porter Anda'),
SizedBox(height: 10.h),
_buildRowLocation(location: 'Porter menuju ke lokasi anda', desc: 'Porter bergerak'),
],
),
),
],
),
),
),
),
bottomNavigationBar: CustomeShadowCotainner(
child: ButtonFill(
text: 'Kembali ke menu',
textColor: Colors.white,
onTap: () {},
),
),
);
}
Widget _buildRowLocation({required String location, required String desc}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
TypographyStyles.caption(
'10/10/2025',
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
),
TypographyStyles.caption(
'11:11',
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
),
],
),
SizedBox(width: 20.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.caption(
location,
color: GrayColors.gray800,
fontWeight: FontWeight.w600,
),
TypographyStyles.small(
desc,
color: GrayColors.gray600,
fontWeight: FontWeight.w400,
)
],
),
],
);
}
}