import 'package:customer/constant/const_texts.dart'; import 'package:customer/screen_ui/parcel_service/parcel_review_screen.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; import '../../constant/constant.dart'; import '../../controllers/parcel_order_details_controller.dart'; import '../../controllers/theme_controller.dart'; import '../../models/user_model.dart'; import '../../service/fire_store_utils.dart'; import '../../themes/app_them_data.dart'; import '../../themes/round_button_border.dart'; import '../../themes/round_button_fill.dart'; import '../../themes/show_toast_dialog.dart'; import '../../utils/network_image_widget.dart'; import '../multi_vendor_service/chat_screens/chat_screen.dart'; class ParcelOrderDetails extends StatelessWidget { const ParcelOrderDetails({super.key}); @override Widget build(BuildContext context) { final themeController = Get.find(); final isDark = themeController.isDark.value; return GetX( init: ParcelOrderDetailsController(), builder: (controller) { return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, backgroundColor: AppThemeData.primary300, title: Padding( padding: const EdgeInsets.only(bottom: 10), child: Row( children: [ InkWell( borderRadius: BorderRadius.circular(50), 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), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Order Details".tr, style: AppThemeData.boldTextStyle( fontSize: 18, color: AppThemeData.grey900, ), ), Text( "Your parcel is on the way. Track it in real time below." .tr, maxLines: 1, overflow: TextOverflow.ellipsis, style: AppThemeData.mediumTextStyle( fontSize: 14, color: AppThemeData.grey900, ), ), ], ), ), ], ), ), ), body: controller.isLoading.value ? Constant.loader() : SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50, border: Border.all( color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200, ), ), width: double.infinity, padding: const EdgeInsets.all(16), child: Text( "${'Order Id:'.tr} ${Constant.orderId(orderId: controller.parcelOrder.value.id.toString())}" .tr, textAlign: TextAlign.start, style: TextStyle( fontFamily: AppThemeData.semiBold, fontSize: 18, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900, ), ), ), const SizedBox(height: 16), 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: [ 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 Divider(), if (controller.parcelOrder.value.isSchedule == true) Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text( "${'Schedule Pickup time:'.tr} ${controller.formatDate(controller.parcelOrder.value.senderPickupDateTime!)}", style: AppThemeData.mediumTextStyle( fontSize: 14, color: AppThemeData.info400, ), ), ), Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text( "${'Order Date:'.tr}${controller.parcelOrder.value.isSchedule == true ? controller.formatDate(controller.parcelOrder.value.createdAt!) : controller.formatDate(controller.parcelOrder.value.senderPickupDateTime!)}", style: AppThemeData.mediumTextStyle( fontSize: 14, color: AppThemeData.info400, ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Parcel Type:".tr, style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800, ), ), Row( children: [ Text( controller .parcelOrder .value .parcelType ?? '', style: AppThemeData.semiBoldTextStyle( fontSize: 16, color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800, ), ), const SizedBox(width: 8), if (controller .getSelectedCategory() ?.image != null && controller .getSelectedCategory()! .image! .isNotEmpty) NetworkImageWidget( imageUrl: controller .getSelectedCategory() ?.image ?? '', height: 20, width: 20, ), ], ), ], ), controller.parcelOrder.value.parcelImages!.isEmpty ? SizedBox() : SizedBox( height: 120, child: ListView.builder( itemCount: controller .parcelOrder .value .parcelImages! .length, shrinkWrap: true, scrollDirection: Axis.horizontal, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(8.0), child: ClipRRect( borderRadius: BorderRadius.circular( 10, ), child: NetworkImageWidget( imageUrl: controller .parcelOrder .value .parcelImages![index], width: 100, fit: BoxFit.cover, borderRadius: 10, ), ), ); }, ), ), ], ), ), 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 ?? '--'} ${Constant.distanceType}", "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: 16), if (controller.parcelOrder.value.driver != null) Column( children: [ 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( "About Driver".tr, style: AppThemeData.boldTextStyle( fontSize: 14, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500, ), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( children: [ SizedBox( width: 52, height: 52, child: ClipRRect( borderRadius: BorderRadiusGeometry.circular( 10, ), child: NetworkImageWidget( imageUrl: controller .driverUser .value ?.profilePictureURL ?? '', height: 70, width: 70, borderRadius: 35, ), ), ), SizedBox(width: 20), Text( controller .parcelOrder .value .driver ?.fullName() ?? '', style: AppThemeData.boldTextStyle( color: isDark ? AppThemeData .greyDark900 : AppThemeData.grey900, fontSize: 18, ), ), ], ), RoundedButtonBorder( title: controller .driverUser .value! .averageRating .toStringAsFixed(1), width: 20, height: 3.5, radius: 10, isRight: false, isCenter: true, textColor: AppThemeData.warning400, borderColor: AppThemeData.warning400, color: AppThemeData.warning50, icon: SvgPicture.asset( "assets/icons/ic_start.svg", ), onPress: () {}, ), ], ), Visibility( visible: controller.parcelOrder.value.status == Constant.orderCompleted ? true : false, child: Padding( padding: const EdgeInsets.symmetric( vertical: 10, ), child: RoundedButtonFill( title: controller.ratingModel.value.id != null && controller .ratingModel .value .id! .isNotEmpty ? 'Update Review'.tr : 'Add Review'.tr, onPress: () async { final result = await Get.to( () => ParcelReviewScreen(), arguments: { 'order': controller .parcelOrder .value, }, ); // If review was submitted successfully if (result == true) { await controller .fetchDriverDetails(); } }, height: 5, borderRadius: 15, color: Colors.orange, textColor: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), ), if (controller.parcelOrder.value.status != Constant.orderCompleted) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ InkWell( onTap: () { Constant.makePhoneCall( controller .parcelOrder .value .driver! .phoneNumber .toString(), ); }, child: Container( width: 150, height: 42, decoration: ShapeDecoration( shape: RoundedRectangleBorder( side: BorderSide( width: 1, color: isDark ? AppThemeData .grey700 : AppThemeData .grey200, ), borderRadius: BorderRadius.circular( 120, ), ), ), child: Padding( padding: const EdgeInsets.all( 8.0, ), child: SvgPicture.asset( "assets/icons/ic_phone_call.svg", ), ), ), ), const SizedBox(width: 10), InkWell( onTap: () async { ShowToastDialog.showLoader( ConstTexts.pleaseWait.tr, ); UserModel? customer = await FireStoreUtils.getUserProfile( controller .parcelOrder .value .authorID ?? '', ); UserModel? driverUser = await FireStoreUtils.getUserProfile( controller .parcelOrder .value .driverId ?? '', ); ShowToastDialog.closeLoader(); Get.to( const ChatScreen(), arguments: { "customerName": customer?.fullName(), "restaurantName": driverUser?.fullName(), "orderId": controller .parcelOrder .value .id, "restaurantId": driverUser?.id, "customerId": customer?.id, "customerProfileImage": customer ?.profilePictureURL, "restaurantProfileImage": driverUser ?.profilePictureURL, "token": driverUser?.fcmToken, "chatType": "Driver", }, ); }, child: Container( width: 150, height: 42, decoration: ShapeDecoration( shape: RoundedRectangleBorder( side: BorderSide( width: 1, color: isDark ? AppThemeData .grey700 : AppThemeData .grey200, ), borderRadius: BorderRadius.circular( 120, ), ), ), child: Padding( padding: const EdgeInsets.all( 8.0, ), child: SvgPicture.asset( "assets/icons/ic_wechat.svg", ), ), ), ), ], ), ], ), ), const SizedBox(height: 15), ], ), 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, ), // Discount _summaryTile( "Discount".tr, Constant.amountShow( amount: controller.discount.value.toString(), ), isDark, ), // Tax List ...List.generate( controller.parcelOrder.value.taxSetting!.length, (index) { return _summaryTile( "${controller.parcelOrder.value.taxSetting![index].title} ${controller.parcelOrder.value.taxSetting![index].type == 'fix' ? '' : '(${controller.parcelOrder.value.taxSetting![index].tax}%)'}", Constant.amountShow( amount: Constant.getTaxValue( amount: ((double.tryParse( controller .parcelOrder .value .subTotal .toString(), ) ?? 0.0) - (double.tryParse( controller .parcelOrder .value .discount .toString(), ) ?? 0.0)) .toString(), taxModel: controller .parcelOrder .value .taxSetting![index], ).toString(), ), isDark, ); }, ), const Divider(), // Total _summaryTile( "Order Total".tr, Constant.amountShow( amount: controller.totalAmount.value.toString(), ), isDark, ), ], ), ), ], ), ), bottomNavigationBar: controller.parcelOrder.value.status == Constant.orderPlaced ? Padding( padding: const EdgeInsets.all(16.0), child: RoundedButtonFill( title: "Cancel Parcel".tr, onPress: () { controller.cancelParcelOrder(); }, height: 5, borderRadius: 15, color: AppThemeData.primary300, textColor: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ) : SizedBox(), ); }, ); } Widget statusBottomSheet( BuildContext context, ParcelOrderDetailsController controller, bool isDark, ) { return DraggableScrollableSheet( initialChildSize: 0.30, minChildSize: 0.20, maxChildSize: 0.6, 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: SingleChildScrollView( controller: scrollController, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Parcel Status Timeline".tr, style: AppThemeData.semiBoldTextStyle( color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, fontSize: 18, ), ), const SizedBox(height: 8), // Dynamic List Obx(() { final history = controller.parcelOrder.value.statusHistory ?? []; if (history.isEmpty) { return SizedBox( height: 80, child: Center( child: Text( "No status updates yet".tr, style: AppThemeData.mediumTextStyle( color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), ), ); } return SizedBox( height: history.length * 70.0, child: ListView.builder( physics: const BouncingScrollPhysics(), itemCount: history.length, itemBuilder: (context, index) { final statusUpdate = history[index]; final isCompleted = index < history.length; return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( isCompleted ? "assets/images/image_status_timeline.png" : "assets/images/image_timeline.png", height: 48, width: 48, ), const SizedBox(width: 20), Expanded( child: Text( statusUpdate.status ?? '', style: AppThemeData.semiBoldTextStyle( color: isCompleted ? (isDark ? AppThemeData.greyDark900 : AppThemeData.grey900) : AppThemeData.grey500, fontSize: 18, ), ), ), ], ), ); }, ), ); }), ], ), ), ); }, ); } 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) { 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".tr ? 18 : 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, ), ), ], ), ); } }