import 'package:customer/models/coupon_model.dart'; import 'package:customer/screen_ui/parcel_service/parcel_coupon_screen.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart' hide Trans; import '../../constant/constant.dart'; import '../../controllers/parcel_order_confirmation_controller.dart'; import '../../controllers/theme_controller.dart'; import '../../payment/createRazorPayOrderModel.dart'; import '../../payment/rozorpayConroller.dart'; import '../../themes/app_them_data.dart'; import '../../themes/round_button_fill.dart'; import '../../themes/show_toast_dialog.dart'; import '../multi_vendor_service/wallet_screen/wallet_screen.dart'; class ParcelOrderConfirmationScreen extends StatelessWidget { const ParcelOrderConfirmationScreen({super.key}); @override Widget build(BuildContext context) { final themeController = Get.find(); final isDark = themeController.isDark.value; return GetX( init: ParcelOrderConfirmationController(), builder: (controller) { return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, backgroundColor: AppThemeData.primary300, title: Padding( padding: const EdgeInsets.only(bottom: 10), child: Row( children: [ GestureDetector( onTap: () => Get.back(), child: Container( height: 42, width: 42, decoration: BoxDecoration( shape: BoxShape.circle, color: AppThemeData.grey50, ), child: Center( child: Padding( padding: const EdgeInsets.only(left: 5), child: Icon( Icons.arrow_back_ios, color: AppThemeData.grey900, size: 20, ), ), ), ), ), const SizedBox(width: 10), Text( "Order Confirmation".tr(), style: AppThemeData.boldTextStyle( fontSize: 18, color: AppThemeData.grey900, ), ), ], ), ), ), body: controller.isLoading.value ? Constant.loader() : SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Pickup and Delivery Info Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50, border: Border.all( color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200, ), ), padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Timeline with icons and line Column( children: [ Image.asset( "assets/images/image_parcel.png", height: 32, width: 32, ), DottedBorder( options: CustomPathDottedBorderOptions( color: Colors.grey.shade400, strokeWidth: 2, dashPattern: [4, 4], customPath: (size) => Path() ..moveTo(size.width / 2, 0) ..lineTo( size.width / 2, size.height, ), ), child: const SizedBox( width: 20, height: 95, ), ), Image.asset( "assets/images/image_parcel.png", height: 32, width: 32, ), ], ), const SizedBox(width: 12), // Address Details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _infoSection( "Pickup Address (Sender):".tr(), controller .parcelOrder .value .sender ?.name ?? '', controller .parcelOrder .value .sender ?.address ?? '', controller .parcelOrder .value .sender ?.phone ?? '', // controller.parcelOrder.value.senderPickupDateTime != null // ? "Pickup Time: ${controller.formatDate(controller.parcelOrder.value.senderPickupDateTime!)}" // : '', isDark, ), const SizedBox(height: 16), _infoSection( "Delivery Address (Receiver):".tr(), controller .parcelOrder .value .receiver ?.name ?? '', controller .parcelOrder .value .receiver ?.address ?? '', controller .parcelOrder .value .receiver ?.phone ?? '', // controller.parcelOrder.value.receiverPickupDateTime != null // ? "Delivery Time: ${controller.formatDate(controller.parcelOrder.value.receiverPickupDateTime!)}" // : '', isDark, ), ], ), ), ], ), ), const SizedBox(height: 16), // Distance, Weight, Rate Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50, border: Border.all( color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200, ), ), padding: EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _iconTile( "${controller.parcelOrder.value.distance ?? '--'} ${'KM'.tr()}", "Distance".tr(), "assets/icons/ic_distance_parcel.svg", isDark, ), _iconTile( controller.parcelOrder.value.parcelWeight ?? '--', "Weight".tr(), "assets/icons/ic_weight_parcel.svg", isDark, ), _iconTile( Constant.amountShow( amount: controller.parcelOrder.value.subTotal, ), "Rate".tr(), "assets/icons/ic_rate_parcel.svg", isDark, ), ], ), ), const SizedBox(height: 10), Row( children: [ Expanded( child: Text( "Coupons".tr(), style: AppThemeData.boldTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), ), InkWell( onTap: () { Get.to(ParcelCouponScreen())!.then((value) { if (value != null) { double couponAmount = Constant.calculateDiscount( amount: controller.subTotal.value .toString(), offerModel: value, ); if (couponAmount < controller.subTotal.value) { controller.selectedCouponModel.value = value; controller.calculatePrice(); } else { ShowToastDialog.showToast( "This offer not eligible for this booking" .tr(), ); } } }); }, child: Text( "View All".tr(), style: AppThemeData.boldTextStyle( decoration: TextDecoration.underline, fontSize: 14, color: isDark ? AppThemeData.primary300 : AppThemeData.primary300, ), ), ), ], ), const SizedBox(height: 5), // Coupon input DottedBorder( options: RoundedRectDottedBorderOptions( strokeWidth: 1, radius: const Radius.circular(10), color: isDark ? AppThemeData.parcelServiceDark300 : AppThemeData.primary300, ), child: Container( decoration: BoxDecoration( color: AppThemeData.parcelService50, borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), child: Row( children: [ SvgPicture.asset( "assets/icons/ic_coupon_parcel.svg", height: 28, width: 28, ), SizedBox(width: 15), Expanded( child: TextField( controller: controller.couponController.value, style: AppThemeData.semiBoldTextStyle( color: AppThemeData.parcelService500, fontSize: 16, ), decoration: InputDecoration( hintText: "Write coupon code".tr(), hintStyle: AppThemeData.mediumTextStyle( fontSize: 16, color: AppThemeData.parcelService500, ), border: InputBorder.none, ), ), ), RoundedButtonFill( title: "Redeem now".tr(), onPress: () { if (controller.couponList .where( (element) => element.code!.toLowerCase() == controller .couponController .value .text .toLowerCase(), ) .isNotEmpty) { CouponModel couponModel = controller .couponList .firstWhere( (p0) => p0.code!.toLowerCase() == controller .couponController .value .text .toLowerCase(), ); if (couponModel.expiresAt! .toDate() .isAfter(DateTime.now())) { double couponAmount = Constant.calculateDiscount( amount: controller.subTotal.value .toString(), offerModel: couponModel, ); if (couponAmount < controller.subTotal.value) { controller.selectedCouponModel.value = couponModel; controller.calculatePrice(); controller.update(); } else { ShowToastDialog.showToast( "This offer not eligible for this booking" .tr(), ); } } else { ShowToastDialog.showToast( "This coupon code has been expired" .tr(), ); } } else { ShowToastDialog.showToast( "Invalid coupon code".tr(), ); } }, borderRadius: 10, height: 4, width: 28, fontSizes: 14, color: AppThemeData.primary300, textColor: AppThemeData.grey900, ), ], ), ), ), const SizedBox(height: 24), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50, border: Border.all( color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200, ), ), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Order Summary".tr(), style: AppThemeData.boldTextStyle( fontSize: 14, color: AppThemeData.grey500, ), ), const SizedBox(height: 8), // Subtotal _summaryTile( "Subtotal".tr(), Constant.amountShow( amount: controller.subTotal.value.toString(), ), isDark, null, ), // Discount _summaryTile( "Discount".tr(), "-${Constant.amountShow(amount: controller.discount.value.toString())}", isDark, AppThemeData.dangerDark300, ), // Tax List ...List.generate(Constant.taxList.length, ( index, ) { final taxModel = Constant.taxList[index]; final taxTitle = "${taxModel.title} ${taxModel.type == 'fix' ? '(${Constant.amountShow(amount: taxModel.tax)})' : '(${taxModel.tax}%)'}"; final taxAmount = Constant.getTaxValue( amount: (controller.subTotal.value - controller.discount.value) .toString(), taxModel: taxModel, ).toString(); return _summaryTile( taxTitle, Constant.amountShow(amount: taxAmount), isDark, null, ); }), const Divider(), // Total _summaryTile( "Order Total".tr(), Constant.amountShow( amount: controller.totalAmount.value.toString(), ), isDark, null, ), ], ), ), const SizedBox(height: 24), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50, border: Border.all( color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200, ), ), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Title Text( "Payment by".tr(), style: AppThemeData.boldTextStyle( fontSize: 14, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500, ), ), const SizedBox(height: 12), // Row with Sender and Receiver options Row( children: [ // Sender GestureDetector( onTap: () => controller.paymentBy.value = "Sender", child: Row( children: [ Icon( controller.paymentBy.value == "Sender" ? Icons.radio_button_checked : Icons.radio_button_off, color: controller.paymentBy.value == "Sender" ? AppThemeData.primary300 : (isDark ? AppThemeData.greyDark500 : AppThemeData.grey500), size: 20, ), const SizedBox(width: 6), Text( "Sender".tr(), style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800, ), ), ], ), ), const SizedBox(width: 60), // Receiver GestureDetector( onTap: () => controller.paymentBy.value = "Receiver", child: Row( children: [ Icon( controller.paymentBy.value == "Receiver" ? Icons.radio_button_checked : Icons.radio_button_off, color: controller.paymentBy.value == "Receiver" ? AppThemeData.primary300 : (isDark ? AppThemeData.greyDark500 : AppThemeData.grey500), size: 20, ), const SizedBox(width: 6), Text( "Receiver".tr(), style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800, ), ), ], ), ), ], ), ], ), ), const SizedBox(height: 24), // Continue button RoundedButtonFill( title: controller.paymentBy.value == "Sender" ? "Select Payment Method".tr() : "Continue".tr(), onPress: () async { if (controller.paymentBy.value == "Sender") { Get.bottomSheet( paymentBottomSheet( context, controller, isDark, ), // your widget isScrollControlled: true, // ✅ allows full drag scrolling backgroundColor: Colors .transparent, // so your rounded corners are visible ); } else { controller.placeOrder(); } }, color: AppThemeData.primary300, textColor: AppThemeData.grey900, ), ], ), ), ); }, ); } Widget _infoSection( String title, String name, String address, String phone, bool isDark, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: AppThemeData.semiBoldTextStyle( fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), Text( name, style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), Text( address, style: AppThemeData.mediumTextStyle( fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), Text( phone, style: AppThemeData.mediumTextStyle( fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), // Text(time, style: AppThemeData.semiBoldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)), ], ); } Widget _iconTile(String value, title, icon, bool isDark) { return Column( children: [ // Icon(icon, color: AppThemeData.primary300), SvgPicture.asset( icon, height: 28, width: 28, color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800, ), const SizedBox(height: 6), Text( value, style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800, ), ), const SizedBox(height: 6), Text( title, style: AppThemeData.semiBoldTextStyle( fontSize: 12, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), ], ); } Widget _summaryTile(String title, String value, bool isDark, Color? colors) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: AppThemeData.mediumTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800, ), ), Text( value, style: AppThemeData.semiBoldTextStyle( fontSize: title == "Order Total" ? 18 : 16, color: colors ?? (isDark ? AppThemeData.greyDark900 : AppThemeData.grey900), ), ), ], ), ); } Widget paymentBottomSheet( BuildContext context, ParcelOrderConfirmationController controller, bool isDark, ) { return DraggableScrollableSheet( initialChildSize: 0.70, minChildSize: 0.30, maxChildSize: 0.8, expand: false, builder: (context, scrollController) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), decoration: BoxDecoration( color: isDark ? AppThemeData.grey500 : Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Select Payment Method".tr(), style: AppThemeData.mediumTextStyle( fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), GestureDetector( onTap: () { Get.back(); }, child: Icon(Icons.close), ), ], ), const SizedBox(height: 20), Expanded( child: ListView( controller: scrollController, children: [ Text( "Preferred Payment".tr(), textAlign: TextAlign.start, style: AppThemeData.boldTextStyle( fontSize: 15, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500, ), ), const SizedBox(height: 10), if (controller.walletSettingModel.value.isEnabled == true || controller.cashOnDeliverySettingModel.value.isEnabled == true) Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50, border: Border.all( color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200, ), ), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Visibility( visible: controller .walletSettingModel .value .isEnabled == true, child: cardDecoration( controller, PaymentGateway.wallet, isDark, "assets/images/ic_wallet.png", ), ), Visibility( visible: controller .cashOnDeliverySettingModel .value .isEnabled == true, child: cardDecoration( controller, PaymentGateway.cod, isDark, "assets/images/ic_cash.png", ), ), ], ), ), ), if (controller.walletSettingModel.value.isEnabled == true || controller.cashOnDeliverySettingModel.value.isEnabled == true) const SizedBox(height: 10), Text( "Other Payment Options".tr(), textAlign: TextAlign.start, style: AppThemeData.boldTextStyle( fontSize: 15, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500, ), ), const SizedBox(height: 10), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50, border: Border.all( color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200, ), ), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Visibility( visible: controller.stripeModel.value.isEnabled == true, child: cardDecoration( controller, PaymentGateway.stripe, isDark, "assets/images/stripe.png", ), ), Visibility( visible: controller.payPalModel.value.isEnabled == true, child: cardDecoration( controller, PaymentGateway.paypal, isDark, "assets/images/paypal.png", ), ), Visibility( visible: controller.payStackModel.value.isEnable == true, child: cardDecoration( controller, PaymentGateway.payStack, isDark, "assets/images/paystack.png", ), ), Visibility( visible: controller.mercadoPagoModel.value.isEnabled == true, child: cardDecoration( controller, PaymentGateway.mercadoPago, isDark, "assets/images/mercado-pago.png", ), ), Visibility( visible: controller.flutterWaveModel.value.isEnable == true, child: cardDecoration( controller, PaymentGateway.flutterWave, isDark, "assets/images/flutterwave_logo.png", ), ), Visibility( visible: controller.payFastModel.value.isEnable == true, child: cardDecoration( controller, PaymentGateway.payFast, isDark, "assets/images/payfast.png", ), ), Visibility( visible: controller.razorPayModel.value.isEnabled == true, child: cardDecoration( controller, PaymentGateway.razorpay, isDark, "assets/images/razorpay.png", ), ), Visibility( visible: controller.midTransModel.value.enable == true, child: cardDecoration( controller, PaymentGateway.midTrans, isDark, "assets/images/midtrans.png", ), ), Visibility( visible: controller.orangeMoneyModel.value.enable == true, child: cardDecoration( controller, PaymentGateway.orangeMoney, isDark, "assets/images/orange_money.png", ), ), Visibility( visible: controller.xenditModel.value.enable == true, child: cardDecoration( controller, PaymentGateway.xendit, isDark, "assets/images/xendit.png", ), ), ], ), ), ), SizedBox(height: 20), ], ), ), RoundedButtonFill( title: "Continue".tr(), color: AppThemeData.taxiBooking300, textColor: AppThemeData.grey900, onPress: () async { if (controller.selectedPaymentMethod.value == PaymentGateway.stripe.name) { controller.stripeMakePayment( amount: controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.paypal.name) { controller.paypalPaymentSheet( controller.totalAmount.value.toString(), context, ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.payStack.name) { controller.payStackPayment( controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.mercadoPago.name) { controller.mercadoPagoMakePayment( context: context, amount: controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.flutterWave.name) { controller.flutterWaveInitiatePayment( context: context, amount: controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.payFast.name) { controller.payFastPayment( context: context, amount: controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.cod.name) { controller.placeOrder(); } else if (controller.selectedPaymentMethod.value == PaymentGateway.wallet.name) { double walletBalance = double.tryParse( controller.userModel.value.walletAmount.toString(), ) ?? 0.0; double amountToPay = double.tryParse( controller.totalAmount.value.toString(), ) ?? 0.0; if (walletBalance < amountToPay) { ShowToastDialog.showToast( "Insufficient wallet balance".tr(), ); return; } controller.placeOrder(); } // else if (controller.selectedPaymentMethod.value == PaymentGateway.wallet.name) { // controller.placeOrder(); // } else if (controller.selectedPaymentMethod.value == PaymentGateway.midTrans.name) { controller.midtransMakePayment( context: context, amount: controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.orangeMoney.name) { controller.orangeMakePayment( context: context, amount: controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.xendit.name) { controller.xenditPayment( context, controller.totalAmount.value.toString(), ); } else if (controller.selectedPaymentMethod.value == PaymentGateway.razorpay.name) { RazorPayController() .createOrderRazorPay( amount: double.parse( controller.totalAmount.value.toString(), ), razorpayModel: controller.razorPayModel.value, ) .then((value) { if (value == null) { Get.back(); ShowToastDialog.showToast( "Something went wrong, please contact admin." .tr(), ); } else { CreateRazorPayOrderModel result = value; controller.openCheckout( amount: controller.totalAmount.value.toString(), orderId: result.id, ); } }); } else { ShowToastDialog.showToast( "Please select payment method".tr(), ); } }, ), ], ), ); }, ); } Obx cardDecoration(controller, PaymentGateway value, isDark, String image) { return Obx( () => Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: InkWell( onTap: () { controller.selectedPaymentMethod.value = value.name; }, child: Row( children: [ Container( width: 50, height: 50, decoration: ShapeDecoration( shape: RoundedRectangleBorder( side: const BorderSide(width: 1, color: Color(0xFFE5E7EB)), borderRadius: BorderRadius.circular(8), ), ), child: Padding( padding: EdgeInsets.all(value.name == "payFast" ? 0 : 8.0), child: Image.asset(image), ), ), const SizedBox(width: 10), value.name == "wallet" ? Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( value.name.capitalizeString(), textAlign: TextAlign.start, style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900, ), ), Text( Constant.amountShow( amount: Constant.userModel?.walletAmount == null ? '0.0' : Constant.userModel?.walletAmount .toString(), ), textAlign: TextAlign.start, style: AppThemeData.semiBoldTextStyle( fontSize: 14, color: isDark ? AppThemeData.primary300 : AppThemeData.primary300, ), ), ], ), ) : Expanded( child: Text( value.name.capitalizeString(), textAlign: TextAlign.start, style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900, ), ), ), Radio( value: value.name, groupValue: controller.selectedPaymentMethod.value, activeColor: isDark ? AppThemeData.primary300 : AppThemeData.primary300, onChanged: (value) { controller.selectedPaymentMethod.value = value.toString(); }, ), ], ), ), ), ); } }