Feat: done page ticket_booking_step1

This commit is contained in:
orangdeso 2025-03-15 22:32:14 +07:00
parent 7b4506a256
commit 7c79dfab1e
9 changed files with 309 additions and 195 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
#Fri Mar 14 19:50:32 WIB 2025 #Sat Mar 15 19:23:50 WIB 2025
base.0=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeExtDexDebug\\classes.dex base.0=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeExtDexDebug\\classes.dex
base.1=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeLibDexDebug\\0\\classes.dex base.1=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeLibDexDebug\\0\\classes.dex
base.2=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeProjectDexDebug\\0\\classes.dex base.2=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeProjectDexDebug\\0\\classes.dex

View File

@ -10,6 +10,7 @@ class ButtonFill extends StatelessWidget {
final Color? textColor; final Color? textColor;
final VoidCallback? onTap; final VoidCallback? onTap;
final bool isLoading; final bool isLoading;
final Color? backgroundColor;
const ButtonFill({ const ButtonFill({
Key? key, Key? key,
@ -17,6 +18,7 @@ class ButtonFill extends StatelessWidget {
required this.textColor, required this.textColor,
this.onTap, this.onTap,
this.isLoading = false, this.isLoading = false,
this.backgroundColor,
}) : super(key: key); }) : super(key: key);
@override @override
@ -27,8 +29,7 @@ class ButtonFill extends StatelessWidget {
child: ElevatedButton( child: ElevatedButton(
onPressed: onTap, onPressed: onTap,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor: isLoading ? GrayColors.gray400 : (backgroundColor ?? PrimaryColors.primary800),
isLoading ? GrayColors.gray500 : PrimaryColors.primary800,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(35.r), borderRadius: BorderRadius.circular(35.r),
), ),

View File

@ -16,6 +16,8 @@ class CardFlightInformation extends StatelessWidget {
final String seatClass; final String seatClass;
final String? servicePorter; final String? servicePorter;
final String passenger; final String passenger;
final String? transiAirplane;
final String? stop;
const CardFlightInformation({ const CardFlightInformation({
Key? key, Key? key,
@ -27,6 +29,8 @@ class CardFlightInformation extends StatelessWidget {
required this.seatClass, required this.seatClass,
this.servicePorter, this.servicePorter,
required this.passenger, required this.passenger,
this.transiAirplane,
this.stop,
}); });
@override @override
@ -39,12 +43,13 @@ class CardFlightInformation extends StatelessWidget {
SizedBox(height: 10.h), SizedBox(height: 10.h),
Row( Row(
children: [ children: [
TypographyStyles.small( if (stop != null && stop!.isNotEmpty) ...[
date, TypographyStyles.small('${stop}', color: GrayColors.gray600, fontWeight: FontWeight.w400),
color: GrayColors.gray600, SizedBox(width: 10.w),
letterSpacing: 0.2, CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
fontWeight: FontWeight.w400, SizedBox(width: 10.w),
), ],
TypographyStyles.small(date, color: GrayColors.gray600, fontWeight: FontWeight.w400),
SizedBox(width: 10.w), SizedBox(width: 10.w),
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)), CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
SizedBox(width: 10.w), SizedBox(width: 10.w),
@ -63,7 +68,13 @@ class CardFlightInformation extends StatelessWidget {
SizedBox(width: 10.w), SizedBox(width: 10.w),
CustomeIcons.PlaneRightFilled(color: PrimaryColors.primary800), CustomeIcons.PlaneRightFilled(color: PrimaryColors.primary800),
SizedBox(width: 10.w), SizedBox(width: 10.w),
TypographyStyles.body(arrivalCity, color: GrayColors.gray800) if (transiAirplane != null && transiAirplane!.isNotEmpty) ...[
TypographyStyles.body('${transiAirplane}', color: GrayColors.gray800),
SizedBox(width: 10.w),
CustomeIcons.PlaneRightFilled(color: PrimaryColors.primary800),
SizedBox(width: 10.w),
],
TypographyStyles.body(arrivalCity, color: GrayColors.gray800),
], ],
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),

View File

@ -203,6 +203,8 @@ class _HomeScreenState extends State<HomeScreen> {
color: GrayColors.gray600, color: GrayColors.gray600,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
SizedBox(height: 16.h),
], ],
), ),
) )

View File

@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use
import 'package:e_porter/_core/component/appbar/appbar_component.dart'; import 'package:e_porter/_core/component/appbar/appbar_component.dart';
import 'package:e_porter/_core/component/button/button_fill.dart'; import 'package:e_porter/_core/component/button/button_fill.dart';
import 'package:e_porter/_core/component/button/switch_button.dart'; import 'package:e_porter/_core/component/button/switch_button.dart';
@ -9,6 +11,7 @@ import 'package:e_porter/_core/service/logger_service.dart';
import 'package:e_porter/_core/service/preferences_service.dart'; import 'package:e_porter/_core/service/preferences_service.dart';
import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart'; import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
import 'package:e_porter/domain/models/ticket_model.dart'; import 'package:e_porter/domain/models/ticket_model.dart';
import 'package:e_porter/domain/models/user_entity.dart';
import 'package:e_porter/presentation/controllers/profil_controller.dart'; import 'package:e_porter/presentation/controllers/profil_controller.dart';
import 'package:e_porter/presentation/controllers/ticket_controller.dart'; import 'package:e_porter/presentation/controllers/ticket_controller.dart';
import 'package:e_porter/presentation/screens/home/component/card_flight_information.dart'; import 'package:e_porter/presentation/screens/home/component/card_flight_information.dart';
@ -40,6 +43,7 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
final ProfilController profilController = Get.find<ProfilController>(); final ProfilController profilController = Get.find<ProfilController>();
final currencyFormatter = NumberFormat.decimalPattern('id_ID'); final currencyFormatter = NumberFormat.decimalPattern('id_ID');
dynamic _loggedUser; dynamic _loggedUser;
List<PassengerModel?> selectedPassengers = [];
@override @override
void initState() { void initState() {
@ -53,6 +57,8 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
ticketController = Get.find<TicketController>(); ticketController = Get.find<TicketController>();
_flightFuture = ticketController.getFlightById(ticketId: ticketId, flightId: flightId); _flightFuture = ticketController.getFlightById(ticketId: ticketId, flightId: flightId);
selectedPassengers = List.filled(passenger, null, growable: false);
} }
Future<void> _loadPassengers() async { Future<void> _loadPassengers() async {
@ -66,6 +72,14 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
logger.d('User ID: $userId'); logger.d('User ID: $userId');
} }
bool isAllPassengersFilled() {
if (isToggled && _loggedUser != null) {
return selectedPassengers.skip(1).every((p) => p != null);
} else {
return selectedPassengers.every((p) => p != null);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -100,13 +114,15 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CardFlightInformation( CardFlightInformation(
date: '${ticketDate}', date: ticketDate,
time: '$departureTime - $arrivalTime', time: '$departureTime - $arrivalTime',
departureCity: '${flight.cityDeparture}', departureCity: flight.cityDeparture,
arrivalCity: '${flight.cityArrival}', arrivalCity: flight.cityArrival,
plane: '${flight.airLines} (${flight.code})', plane: '${flight.airLines} (${flight.code})',
seatClass: '${flight.flightClass}', seatClass: flight.flightClass,
passenger: '$passenger', passenger: '$passenger',
transiAirplane: flight.transitAirplane,
stop: flight.stop,
), ),
SizedBox(height: 32.h), SizedBox(height: 32.h),
TypographyStyles.h6('Detail Pemesanan', color: GrayColors.gray800), TypographyStyles.h6('Detail Pemesanan', color: GrayColors.gray800),
@ -122,14 +138,19 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
), ),
); );
}, },
), ),
bottomNavigationBar: CustomeShadowCotainner( bottomNavigationBar: CustomeShadowCotainner(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: ButtonFill( child: ButtonFill(
text: 'Lanjutkan', text: 'Lanjutkan',
textColor: Colors.white, textColor: Colors.white,
backgroundColor: isAllPassengersFilled() ? PrimaryColors.primary800 : GrayColors.gray400,
onTap: () { onTap: () {
Get.toNamed(Routes.TICKETBOOKINGSTEP2); if (!isAllPassengersFilled()) {
SnackbarHelper.showError('Error', 'Harap lengkapi slot penumpang');
} else {
Get.toNamed(Routes.TICKETBOOKINGSTEP2);
}
}, },
), ),
), ),
@ -208,186 +229,185 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
Widget _buildCardDetailPessenger() { Widget _buildCardDetailPessenger() {
return Column( return Column(
children: List.generate( children: List.generate(
passenger, passenger,
(index) { (index) {
if (isToggled && index == 0 && _loggedUser != null) { if (isToggled && index == 0 && _loggedUser != null) {
return Padding( return _buildUserPassengerCard(_loggedUser);
padding: EdgeInsets.only(bottom: 16.h), } else {
child: CustomeShadowCotainner( final p = selectedPassengers[index];
child: Row( if (p != null) {
mainAxisAlignment: MainAxisAlignment.spaceBetween, return _buildSelectedPassengerCard(p, index);
children: [ } else {
Column( return _buildEmptyPassengerCard(index);
crossAxisAlignment: CrossAxisAlignment.start, }
children: [ }
TypographyStyles.body( },
'${_loggedUser.name}', ),
color: GrayColors.gray800, );
fontWeight: FontWeight.w500, }
),
SizedBox(height: 4.h), Widget _buildUserPassengerCard(dynamic user) {
TypographyStyles.caption( return Padding(
"${_loggedUser.tipeId} - ${_loggedUser.noId}", padding: EdgeInsets.only(bottom: 16.h),
color: GrayColors.gray800, child: CustomeShadowCotainner(
fontWeight: FontWeight.w400, child: Row(
) mainAxisAlignment: MainAxisAlignment.spaceBetween,
], children: [
), Column(
], crossAxisAlignment: CrossAxisAlignment.start,
), children: [
TypographyStyles.body(
'${_loggedUser.name}',
color: GrayColors.gray800,
fontWeight: FontWeight.w500,
),
SizedBox(height: 4.h),
TypographyStyles.caption(
"${_loggedUser.tipeId} - ${_loggedUser.noId}",
color: GrayColors.gray800,
fontWeight: FontWeight.w400,
)
],
), ),
); ZoomTapAnimation(
} else { child: GestureDetector(
return Padding( child: CustomeIcons.EditOutline(),
padding: EdgeInsets.only(bottom: 16.h), onTap: () {
child: CustomeShadowCotainner( Get.bottomSheet(
child: Row( Padding(
mainAxisAlignment: MainAxisAlignment.spaceBetween, padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
children: [ child: Wrap(
TypographyStyles.body( children: [
'Penumpang ${index + 1} (Dewasa)', TitleShowModal(
color: GrayColors.gray800, text: 'Informasi Penumpang',
fontWeight: FontWeight.w500, onTap: () async {
), if (Get.isBottomSheetOpen ?? false) {
ZoomTapAnimation( Get.back();
child: GestureDetector( }
child: CustomeIcons.EditOutline(), await Future.delayed(Duration(seconds: 1));
onTap: () { var result = await Get.toNamed(Routes.ADDPASSENGER);
Get.bottomSheet( if (result == true) {
Padding( _loadPassengers().then((_) => setState(() {}));
padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h), }
child: Wrap( },
children: [ ),
TitleShowModal( Obx(
text: 'Informasi Penumpang', () {
onTap: () async { if (profilController.passengerList.isEmpty) {
if (Get.isBottomSheetOpen ?? false) { return Center(
Get.back(); child: TypographyStyles.body(
} "Belum ada penumpang",
await Future.delayed(Duration(seconds: 1)); color: GrayColors.gray400,
var result = await Get.toNamed(Routes.ADDPASSENGER); fontWeight: FontWeight.w500,
if (result == true) { ),
_loadPassengers().then((_) => setState(() {})); );
// SnackbarHelper.showSuccess('Berhasil', 'Penumpang berhasil ditambahkan'); }
} return ListView.builder(
}, itemCount: profilController.passengerList.length,
), shrinkWrap: true,
Obx( itemBuilder: (context, index) {
() { final passenger = profilController.passengerList[index];
if (profilController.passengerList.isEmpty) { logger.d("Passenger Models : ${passenger.noId}");
return Center( return Padding(
child: TypographyStyles.body( padding: EdgeInsets.only(top: 16.h),
"Belum ada penumpang", child: _buildAddPassenger(
color: GrayColors.gray400, context,
fontWeight: FontWeight.w500, title: "${passenger.name}",
), subTitle: "${passenger.typeId} - ${passenger.noId}",
); onTap: () {
} selectedPassengers[index] = passenger;
return ListView.builder( Get.back();
itemCount: profilController.passengerList.length, setState(() {});
shrinkWrap: true,
itemBuilder: (context, index) {
final passenger = profilController.passengerList[index];
logger.d("Passenger Models : ${passenger.noId}");
return Padding(
padding: EdgeInsets.only(top: 16.h),
child: _buildAddPassenger(
context,
title: "${passenger.name}",
subTitle: "${passenger.noId}",
),
);
}, },
); ),
}, );
), },
], );
), },
), ),
backgroundColor: Colors.white, ],
isScrollControlled: true, ),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.r),
topRight: Radius.circular(10.r),
),
),
);
// showModalBottomSheet(
// context: context,
// backgroundColor: Colors.white,
// isScrollControlled: true,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.only(
// topLeft: Radius.circular(10.r),
// topRight: Radius.circular(10.r),
// ),
// ),
// builder: (context) {
// return Padding(
// padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
// child: Wrap(
// children: [
// TitleShowModal(
// text: 'Informasi Penumpang',
// onTap: () {
// Navigator.pop(context);
// Future.delayed(Duration(milliseconds: 300), () {
// Get.toNamed(Routes.ADDPASSENGER)?.then((result) {
// if (result == true) {
// _loadPassengers().then((_) => setState(() {}));
// SnackbarHelper.showSuccess('Berhasil', 'Penumpang berhasil ditambahkan');
// }
// });
// });
// },
// ),
// SizedBox(height: 16.h),
// Obx(
// () {
// if (profilController.passengerList.isEmpty) {
// return Center(
// child: TypographyStyles.body(
// "Belum ada penumpang",
// color: GrayColors.gray400,
// fontWeight: FontWeight.w500,
// ),
// );
// }
// return ListView.builder(
// itemCount: profilController.passengerList.length,
// shrinkWrap: true,
// itemBuilder: (context, index) {
// final passenger = profilController.passengerList[index];
// logger.d("Passenger Models : ${passenger.noId}");
// return Padding(
// padding: EdgeInsets.only(top: 16.h),
// child: _buildAddPassenger(
// context,
// title: "${passenger.name}",
// subTitle: "${passenger.noId}",
// ),
// );
// },
// );
// },
// ),
// ],
// ),
// );
// },
// );
},
), ),
) backgroundColor: Colors.white,
], isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.r),
topRight: Radius.circular(10.r),
),
),
);
},
), ),
)
],
),
),
);
}
Widget _buildSelectedPassengerCard(PassengerModel p, int slotIndex) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: CustomeShadowCotainner(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TypographyStyles.body(
'${p.name}',
color: GrayColors.gray800,
fontWeight: FontWeight.w500,
),
SizedBox(height: 4.h),
TypographyStyles.caption(
"${p.typeId} - ${p.noId}",
color: GrayColors.gray800,
fontWeight: FontWeight.w400,
)
],
), ),
); ZoomTapAnimation(
} child: GestureDetector(
}, child: CustomeIcons.EditOutline(),
)); onTap: () {
_onEditPassenger(slotIndex);
},
),
)
],
),
),
);
}
Widget _buildEmptyPassengerCard(int slotIndex) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: CustomeShadowCotainner(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TypographyStyles.body(
'Penumpang ${slotIndex + 1} (Dewasa)',
color: GrayColors.gray800,
fontWeight: FontWeight.w500,
),
ZoomTapAnimation(
child: GestureDetector(
child: CustomeIcons.EditOutline(),
onTap: () {
_onEditPassenger(slotIndex);
},
),
)
],
),
),
);
} }
Widget _buildAddPassenger( Widget _buildAddPassenger(
@ -395,10 +415,11 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
required String title, required String title,
required String subTitle, required String subTitle,
VoidCallback? onTap, VoidCallback? onTap,
bool enabled = true,
}) { }) {
return ZoomTapAnimation( return ZoomTapAnimation(
child: GestureDetector( child: GestureDetector(
onTap: onTap, onTap: enabled ? onTap : null,
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -412,16 +433,95 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
TypographyStyles.body(title, color: GrayColors.gray800), TypographyStyles.body(title, color: enabled ? GrayColors.gray800 : GrayColors.gray300),
SizedBox(height: 4.h), SizedBox(height: 4.h),
TypographyStyles.caption("KTP - ${subTitle}", color: GrayColors.gray800, fontWeight: FontWeight.w400) TypographyStyles.caption(
"${subTitle}",
color: enabled ? GrayColors.gray800 : GrayColors.gray300,
fontWeight: FontWeight.w400,
)
], ],
), ),
SvgPicture.asset('assets/icons/ic_more _than.svg') SvgPicture.asset(
'assets/icons/ic_more _than.svg',
color: enabled ? PrimaryColors.primary800 : GrayColors.gray300,
)
], ],
), ),
), ),
), ),
); );
} }
void _onEditPassenger(int slotIndex) {
Get.bottomSheet(
Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
child: Wrap(
children: [
TitleShowModal(
text: 'Informasi Penumpang',
onTap: () async {
if (Get.isBottomSheetOpen ?? false) {
Get.back();
}
await Future.delayed(Duration(seconds: 1));
var result = await Get.toNamed(Routes.ADDPASSENGER);
if (result == true) {
_loadPassengers().then((_) => setState(() {}));
}
},
),
Obx(
() {
final usedNoIds = selectedPassengers.where((p) => p != null).map((p) => p!.noId).toSet();
if (profilController.passengerList.isEmpty) {
return Center(
child: TypographyStyles.body(
"Belum ada penumpang",
color: GrayColors.gray400,
fontWeight: FontWeight.w500,
),
);
}
return ListView.builder(
itemCount: profilController.passengerList.length,
shrinkWrap: true,
itemBuilder: (context, pIndex) {
final passenger = profilController.passengerList[pIndex];
final isUsed = usedNoIds.contains(passenger.noId);
logger.d("Passenger Models : ${passenger.noId}");
return Padding(
padding: EdgeInsets.only(top: 16.h),
child: _buildAddPassenger(
context,
title: "${passenger.name}",
subTitle: "${passenger.typeId} - ${passenger.noId}",
enabled: !isUsed,
onTap: isUsed
? null
: () {
selectedPassengers[slotIndex] = passenger;
Get.back();
setState(() {});
},
),
);
},
);
},
),
],
),
),
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.r),
topRight: Radius.circular(10.r),
),
),
);
}
} }