Initial commit

This commit is contained in:
2025-12-08 23:25:00 +05:00
commit ee5cb4ac1a
851 changed files with 115172 additions and 0 deletions

View File

@@ -0,0 +1,678 @@
import 'package:driver/app/auth_screen/login_screen.dart';
import 'package:driver/app/change%20langauge/change_language_screen.dart';
import 'package:driver/app/chat_screens/driver_inbox_screen.dart';
import 'package:driver/app/edit_profile_screen/edit_profile_screen.dart';
import 'package:driver/app/parcel_screen/parcel_home_screen.dart';
import 'package:driver/app/parcel_screen/parcel_order_list_screen.dart';
import 'package:driver/app/terms_and_condition/terms_and_condition_screen.dart';
import 'package:driver/app/verification_screen/verification_screen.dart';
import 'package:driver/app/wallet_screen/wallet_screen.dart';
import 'package:driver/app/withdraw_method_setup_screens/withdraw_method_setup_screen.dart';
import 'package:driver/constant/constant.dart';
import 'package:driver/constant/show_toast_dialog.dart' show ShowToastDialog;
import 'package:driver/controllers/parcel_dashboard_controller.dart';
import 'package:driver/services/audio_player_service.dart';
import 'package:driver/themes/app_them_data.dart';
import 'package:driver/themes/custom_dialog_box.dart';
import 'package:driver/themes/theme_controller.dart';
import 'package:driver/utils/fire_store_utils.dart';
import 'package:driver/utils/network_image_widget.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:in_app_review/in_app_review.dart';
import 'package:share_plus/share_plus.dart';
class ParcelDashboardScreen extends StatelessWidget {
const ParcelDashboardScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
return Obx(() {
final isDark = themeController.isDark.value;
return GetX(
init: ParcelDashboardController(),
builder: (controller) {
return Scaffold(
drawerEnableOpenDragGesture: false,
appBar: AppBar(
//backgroundColor: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
titleSpacing: 5,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Welcome Back 👋'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey50 : AppThemeData.grey900,
fontSize: 12,
fontFamily: AppThemeData.medium,
),
),
Text(
Constant.userModel!.fullName().tr,
style: TextStyle(
color: isDark ? AppThemeData.grey50 : AppThemeData.grey900,
fontSize: 14,
fontFamily: AppThemeData.semiBold,
),
)
],
),
actions: [
Constant.userModel!.ownerId != null && Constant.userModel!.ownerId!.isNotEmpty
? SizedBox()
: InkWell(
onTap: () {
Get.to(const WalletScreen(isAppBarShow: true));
},
child: SvgPicture.asset("assets/icons/ic_wallet_home.svg")),
const SizedBox(
width: 10,
),
InkWell(
onTap: () {
Get.to(const EditProfileScreen());
},
child: SvgPicture.asset("assets/icons/ic_user_business.svg")),
const SizedBox(
width: 10,
),
],
leading: Builder(builder: (context) {
return InkWell(
onTap: () {
Scaffold.of(context).openDrawer();
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: ShapeDecoration(
color: isDark ? AppThemeData.carRent600 : AppThemeData.carRent50,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(120),
),
),
child: Padding(
padding: const EdgeInsets.all(8),
child: SvgPicture.asset("assets/icons/ic_drawer_open.svg"),
)),
),
);
}),
),
drawer: const DrawerView(),
body: controller.drawerIndex.value == 0
? const ParcelHomeScreen()
: controller.drawerIndex.value == 1
? ParcelOrderListScreen()
: controller.drawerIndex.value == 2
? const WalletScreen(
isAppBarShow: false,
)
: controller.drawerIndex.value == 3
? const WithdrawMethodSetupScreen()
: controller.drawerIndex.value == 4
? const VerificationScreen()
: controller.drawerIndex.value == 5
? const DriverInboxScreen()
: controller.drawerIndex.value == 6
? const ChangeLanguageScreen()
: controller.drawerIndex.value == 7
? const TermsAndConditionScreen(type: "temsandcondition")
: const TermsAndConditionScreen(type: "privacy"),
);
},
);
});
}
}
class DrawerView extends StatelessWidget {
const DrawerView({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
return Obx(() {
final isDark = themeController.isDark.value;
return GetX(
init: ParcelDashboardController(),
builder: (controller) {
return Drawer(
backgroundColor: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
child: Padding(
padding: EdgeInsets.only(top: MediaQuery.of(context).viewPadding.top + 20, left: 16, right: 16),
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
Row(
children: [
ClipOval(
child: NetworkImageWidget(
imageUrl: Constant.userModel == null ? "" : Constant.userModel!.profilePictureURL.toString(),
height: 55,
width: 55,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
Constant.userModel!.fullName().tr,
style: TextStyle(
color: isDark ? AppThemeData.grey50 : AppThemeData.grey900,
fontSize: 18,
fontFamily: AppThemeData.semiBold,
),
),
Text(
'${Constant.userModel!.email}'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey50 : AppThemeData.grey900,
fontSize: 14,
fontFamily: AppThemeData.regular,
),
)
],
),
)
],
),
const SizedBox(
height: 10,
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
trailing: Transform.scale(
scale: 0.8,
child: CupertinoSwitch(
value: controller.userModel.value.isActive ?? false,
activeTrackColor: AppThemeData.primary300,
onChanged: (value) async {
if (Constant.isDriverVerification == true) {
if (controller.userModel.value.isDocumentVerify == true) {
controller.userModel.value.isActive = value;
if (controller.userModel.value.isActive == true) {
controller.updateCurrentLocation();
}
await FireStoreUtils.updateUser(controller.userModel.value);
} else {
ShowToastDialog.showToast("Document verification is pending. Please proceed to set up your document verification.".tr);
}
} else {
controller.userModel.value.isActive = value;
if (controller.userModel.value.isActive == true) {
controller.updateCurrentLocation();
}
await FireStoreUtils.updateUser(controller.userModel.value);
}
},
),
),
dense: true,
title: Text(
'Available Status'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'About App'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey400 : AppThemeData.grey500,
fontSize: 12,
fontFamily: AppThemeData.medium,
),
),
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_home_add.svg",
width: 20,
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Home'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 0;
},
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_shoping_cart.svg",
colorFilter: ColorFilter.mode(AppThemeData.primary300, BlendMode.srcIn),
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Orders'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 1;
},
),
Constant.userModel!.ownerId != null && Constant.userModel!.ownerId!.isNotEmpty
? SizedBox()
: ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_wallet.svg",
colorFilter: ColorFilter.mode(AppThemeData.primary300, BlendMode.srcIn),
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Wallet'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 2;
},
),
Constant.userModel!.ownerId != null && Constant.userModel!.ownerId!.isNotEmpty
? SizedBox()
: ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_settings.svg",
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Withdrawal Method'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 3;
},
),
(((Constant.userModel?.ownerId == null || Constant.userModel!.ownerId!.isEmpty) && Constant.isDriverVerification == true) &&
!((Constant.userModel?.ownerId != null && Constant.userModel!.ownerId!.isNotEmpty && Constant.isOwnerVerification == true)))
? ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset("assets/icons/ic_notes.svg"),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Document Verification'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 4;
},
)
: SizedBox.shrink(),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_chat.svg",
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Inbox'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 5;
},
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'App Preferences'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey400 : AppThemeData.grey500,
fontSize: 12,
fontFamily: AppThemeData.medium,
),
),
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_change_language.svg",
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Change Language'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 6;
},
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_light_dark.svg",
),
trailing: Transform.scale(
scale: 0.8,
child: CupertinoSwitch(
value: controller.isDarkModeSwitch.value,
activeTrackColor: AppThemeData.primary300,
onChanged: (value) {
controller.toggleDarkMode(value);
},
),
),
dense: true,
title: Text(
'Dark Mode'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Social'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey400 : AppThemeData.grey500,
fontSize: 12,
fontFamily: AppThemeData.medium,
),
),
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_share.svg",
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Share app'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
Share.share(
'${'Check out eMart, your ultimate food delivery application!'.tr} \n\n${'Google Play:'.tr} ${Constant.googlePlayLink} \n\n${'App Store:'.tr} ${Constant.appStoreLink}',
subject: 'Look what I made!'.tr);
},
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_rate.svg",
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Rate the app'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
final InAppReview inAppReview = InAppReview.instance;
inAppReview.requestReview();
},
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
'Legal'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey400 : AppThemeData.grey500,
fontSize: 12,
fontFamily: AppThemeData.medium,
),
),
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_terms_condition.svg",
colorFilter: ColorFilter.mode(AppThemeData.primary300, BlendMode.srcIn),
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Terms and Conditions'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 7;
},
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_privacyPolicy.svg",
colorFilter: const ColorFilter.mode(AppThemeData.danger300, BlendMode.srcIn),
),
trailing: const Icon(Icons.keyboard_arrow_right_rounded, size: 24),
dense: true,
title: Text(
'Privacy Policy'.tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
controller.drawerIndex.value = 8;
},
),
const SizedBox(
height: 10,
),
ListTile(
visualDensity: const VisualDensity(horizontal: 0, vertical: -2),
contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0),
leading: SvgPicture.asset(
"assets/icons/ic_logout.svg",
colorFilter: const ColorFilter.mode(AppThemeData.danger300, BlendMode.srcIn),
),
trailing: const Icon(
Icons.keyboard_arrow_right_rounded,
size: 24,
color: AppThemeData.danger300,
),
dense: true,
title: Text(
'Log out'.tr,
style: TextStyle(
color: isDark ? AppThemeData.danger300 : AppThemeData.danger300,
fontFamily: AppThemeData.semiBold,
),
),
onTap: () {
Get.back();
showDialog(
context: context,
builder: (BuildContext context) {
return CustomDialogBox(
title: "Log out".tr,
descriptions: "Are you sure you want to log out? You will need to enter your credentials to log back in.".tr,
positiveString: "Log out".tr,
negativeString: "Cancel".tr,
positiveClick: () async {
await AudioPlayerService.playSound(false);
Constant.userModel!.fcmToken = "";
await FireStoreUtils.updateUser(Constant.userModel!);
await FirebaseAuth.instance.signOut();
Get.offAll(const LoginScreen());
},
negativeClick: () {
Get.back();
},
img: Image.asset(
'assets/images/ic_logout.gif',
height: 50,
width: 50,
),
);
});
},
),
const SizedBox(
height: 20,
),
InkWell(
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return CustomDialogBox(
title: "Delete Account".tr,
descriptions: "Are you sure you want to delete your account? This action is irreversible and will permanently remove all your data.".tr,
positiveString: "Delete".tr,
negativeString: "Cancel".tr,
positiveClick: () async {
ShowToastDialog.showLoader("Please wait".tr);
await FireStoreUtils.deleteUser().then((value) {
ShowToastDialog.closeLoader();
if (value == true) {
ShowToastDialog.showToast("Account deleted successfully".tr);
Get.offAll(const LoginScreen());
} else {
ShowToastDialog.showToast("Contact Administrator".tr);
}
});
},
negativeClick: () {
Get.back();
},
img: Image.asset(
'assets/icons/delete_dialog.gif',
height: 50,
width: 50,
),
);
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgPicture.asset(
"assets/icons/ic_delete.svg",
colorFilter: const ColorFilter.mode(AppThemeData.danger300, BlendMode.srcIn),
),
const SizedBox(
width: 10,
),
Text(
'Delete Account'.tr,
style: TextStyle(
color: isDark ? AppThemeData.danger300 : AppThemeData.danger300,
fontFamily: AppThemeData.semiBold,
),
)
],
),
),
const SizedBox(
height: 10,
),
Center(
child: Text(
"V : ${Constant.appVersion}",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: AppThemeData.medium,
fontSize: 14,
color: isDark ? AppThemeData.grey50 : AppThemeData.grey900,
),
),
),
const SizedBox(
height: 10,
),
],
),
),
);
});
});
}
}

View File

@@ -0,0 +1,543 @@
import 'package:driver/app/parcel_screen/parcel_order_details.dart';
import 'package:driver/app/parcel_screen/parcel_search_screen.dart';
import 'package:driver/app/parcel_screen/parcel_tracking_screen.dart';
import 'package:driver/constant/constant.dart';
import 'package:driver/controllers/parcel_dashboard_controller.dart';
import 'package:driver/controllers/parcel_home_controller.dart';
import 'package:driver/models/parcel_order_model.dart';
import 'package:driver/themes/app_them_data.dart';
import 'package:driver/themes/round_button_fill.dart';
import 'package:driver/themes/theme_controller.dart';
import 'package:driver/utils/network_image_widget.dart';
import 'package:driver/widget/dotted_line.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:timelines_plus/timelines_plus.dart';
import '../../constant/show_toast_dialog.dart';
import '../../models/user_model.dart';
import '../../utils/fire_store_utils.dart';
import '../chat_screens/chat_screen.dart';
class ParcelHomeScreen extends StatelessWidget {
const ParcelHomeScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
return Obx(() {
final isDark = themeController.isDark.value;
return GetX(
init: ParcelHomeController(),
builder: (controller) {
return Scaffold(
backgroundColor: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
body: controller.isLoading.value
? Constant.loader()
: Constant.isDriverVerification == true && Constant.userModel!.isDocumentVerify == false
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
decoration: ShapeDecoration(
color: isDark ? AppThemeData.grey700 : AppThemeData.grey200,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(120),
),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: SvgPicture.asset("assets/icons/ic_document.svg"),
),
),
const SizedBox(
height: 12,
),
Text(
"Document Verification in Pending".tr,
style: TextStyle(
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
fontSize: 22,
fontFamily: AppThemeData.semiBold),
),
const SizedBox(
height: 5,
),
Text(
"Your documents are being reviewed. We will notify you once the verification is complete.".tr,
textAlign: TextAlign.center,
style: TextStyle(
color: isDark ? AppThemeData.grey50 : AppThemeData.grey500,
fontSize: 16,
fontFamily: AppThemeData.bold),
),
const SizedBox(
height: 20,
),
RoundedButtonFill(
title: "View Status".tr,
width: 55,
height: 5.5,
color: AppThemeData.primary300,
textColor: AppThemeData.grey50,
onPress: () async {
ParcelDashboardController dashBoardController = Get.put(ParcelDashboardController());
dashBoardController.drawerIndex.value = 4;
},
),
],
),
)
: controller.userModel.value.isActive == false
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/images/empty_parcel.svg"),
SizedBox(
height: 20,
),
Text(
'Youre Currently Offline'.tr,
textAlign: TextAlign.center,
style: AppThemeData.mediumTextStyle(
fontSize: 18,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900,
),
),
SizedBox(
height: 10,
),
Text(
'Switch to online mode to accept and deliver parcel orders.'.tr,
textAlign: TextAlign.center,
style: AppThemeData.mediumTextStyle(
fontSize: 14,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900,
),
),
],
),
)
: controller.parcelOrdersList.isEmpty
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Obx(() {
final user = controller.userModel.value;
final controllerOwner = controller.ownerModel.value;
final num wallet = user.walletAmount ?? 0.0;
final num ownerWallet = controllerOwner.walletAmount ?? 0.0;
final String? ownerId = user.ownerId;
final num minDeposit = double.parse(Constant.minimumDepositToRideAccept);
// 🧠 Logic:
// If individual driver → check driver's own wallet
// If owner driver → check owner's wallet
if ((ownerId == null || ownerId.isEmpty) && wallet < minDeposit) {
// Individual driver case
return Padding(
padding: const EdgeInsets.only(bottom: 10,left: 10,right: 10),
child: Container(
decoration: BoxDecoration(
color: AppThemeData.danger50,
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"${'You must have at least'.tr} ${Constant.amountShow(amount: Constant.minimumDepositToRideAccept.toString())} ${'in your wallet to receive orders'.tr}",
style: TextStyle(
color: AppThemeData.grey900,
fontSize: 14,
fontFamily: AppThemeData.semiBold,
),
),
),
),
);
}
else if (ownerId != null && ownerId.isNotEmpty && ownerWallet < minDeposit) {
// Owner-driver case
return Padding(
padding: const EdgeInsets.only(bottom: 10,left: 10,right: 10),
child: Container(
decoration: BoxDecoration(
color: AppThemeData.danger50,
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Your owner doesn't have the minimum wallet amount to receive orders. Please contact your owner.".tr,
style: TextStyle(
color:AppThemeData.grey900,
fontSize: 14,
fontFamily: AppThemeData.semiBold,
),
),
),
),
);
} else {
return const SizedBox();
}
}),
// (double.parse(Constant.userModel!.walletAmount == null ? "0.0" : Constant.userModel!.walletAmount.toString()) <
// double.parse(Constant.minimumDepositToRideAccept) &&
// (Constant.userModel?.ownerId == null || Constant.userModel!.ownerId!.isEmpty))
// ? Container(
// decoration: BoxDecoration(color: AppThemeData.danger50, borderRadius: BorderRadius.circular(10)),
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Text(
// "${'You have to minimum'.tr} ${Constant.amountShow(amount: Constant.minimumDepositToRideAccept.toString())} ${'wallet amount to receiving Order'.tr}",
// style: TextStyle(color: isDark ? AppThemeData.danger300 : AppThemeData.danger300, fontSize: 14, fontFamily: AppThemeData.semiBold),
// ),
// ),
// )
// : const SizedBox(),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/images/empty_parcel.svg"),
SizedBox(
height: 20,
),
Text(
'No parcel requests available in your selected zone.'.tr,
textAlign: TextAlign.center,
style: AppThemeData.mediumTextStyle(
fontSize: 18,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900,
),
),
SizedBox(
height: 10,
),
Text(
'Try changing the location or date.'.tr,
textAlign: TextAlign.center,
style: AppThemeData.mediumTextStyle(
fontSize: 14,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900,
),
),
SizedBox(
height: 20,
),
RoundedButtonFill(
title: "Search Parcel".tr,
height: 5.5,
color: AppThemeData.primary300,
textColor: AppThemeData.grey50,
onPress: () {
Get.to(ParcelSearchScreen())!.then((value) {
if (value != null && value is bool && value) {
controller.getParcelList();
}
});
},
)
],
),
)
],
),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
child: RefreshIndicator(
onRefresh: () async {
await controller.getParcelList();
},
child: ListView.builder(
itemCount: controller.parcelOrdersList.length,
shrinkWrap: true,
itemBuilder: (context, index) {
ParcelOrderModel parcelBookingData = controller.parcelOrdersList[index];
return InkWell(
onTap: () {
Get.to(() => const ParcelOrderDetails(), arguments: parcelBookingData);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
border: Border.all(color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: isDark ? AppThemeData.greyDark100 : AppThemeData.grey100,
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Timeline.tileBuilder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
theme: TimelineThemeData(
nodePosition: 0,
// indicatorPosition: 0,
),
builder: TimelineTileBuilder.connected(
contentsAlign: ContentsAlign.basic,
indicatorBuilder: (context, index) {
return index == 0
? SvgPicture.asset("assets/icons/ic_source.svg")
: index == 1
? SvgPicture.asset("assets/icons/ic_destination.svg")
: SizedBox();
},
connectorBuilder: (context, index, connectorType) {
return DashedLineConnector(
color: isDark ? AppThemeData.greyDark300 : AppThemeData.grey300,
gap: 4,
);
},
contentsBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
child: Text(
index == 0
? "${parcelBookingData.sender!.address}"
: "${parcelBookingData.receiver!.address}",
style: AppThemeData.mediumTextStyle(
fontSize: 14,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
);
},
itemCount: 2,
),
),
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
children: [
ClipOval(
child: NetworkImageWidget(
imageUrl: parcelBookingData.author!.profilePictureURL.toString(),
width: 52,
height: 52,
fit: BoxFit.cover,
),
),
SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
parcelBookingData.author!.fullName().tr,
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(
fontSize: 16,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
InkWell(
onTap: () async {
ShowToastDialog.showLoader("Please wait".tr);
UserModel? customer =
await FireStoreUtils.getUserProfile(parcelBookingData.authorID.toString());
UserModel? driver =
await FireStoreUtils.getUserProfile(parcelBookingData.driverId.toString());
ShowToastDialog.closeLoader();
Get.to(const ChatScreen(), arguments: {
"customerName": customer!.fullName(),
"restaurantName": driver!.fullName(),
"orderId": parcelBookingData.id,
"restaurantId": driver.id,
"customerId": customer.id,
"customerProfileImage": customer.profilePictureURL ?? "",
"restaurantProfileImage": driver.profilePictureURL ?? "",
"token": customer.fcmToken,
"chatType": "Driver",
});
},
child: Container(
width: 50,
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"),
),
),
)
],
),
),
SizedBox(
height: 12,
),
Row(
children: [
Expanded(
child: Column(
children: [
SvgPicture.asset(
"assets/icons/ic_amount.svg",
colorFilter: ColorFilter.mode(
isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, BlendMode.srcIn),
),
SizedBox(
height: 5,
),
Text(
Constant.amountShow(
amount: controller.calculateParcelTotalAmountBooking(parcelBookingData))
.tr,
textAlign: TextAlign.start,
style: AppThemeData.semiBoldTextStyle(
fontSize: 14,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
)
],
),
),
Expanded(
child: Column(
children: [
SvgPicture.asset(
"assets/icons/ic_date.svg",
colorFilter: ColorFilter.mode(
isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, BlendMode.srcIn),
),
SizedBox(
height: 5,
),
Text(
'${Constant.timestampToDate(parcelBookingData.senderPickupDateTime!)} '.tr,
textAlign: TextAlign.start,
style: AppThemeData.semiBoldTextStyle(
fontSize: 14,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
)
],
),
),
Expanded(
child: Column(
children: [
SvgPicture.asset(
"assets/icons/weight-line.svg",
colorFilter: ColorFilter.mode(
isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, BlendMode.srcIn),
),
SizedBox(
height: 5,
),
Text(
'${parcelBookingData.parcelWeight}'.tr,
textAlign: TextAlign.start,
style: AppThemeData.semiBoldTextStyle(
fontSize: 14,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
)
],
),
)
],
),
const SizedBox(height: 16),
DottedLine(
dashColor: Colors.grey,
lineThickness: 1.0,
dashLength: 4.0,
dashGapLength: 3.0,
direction: Axis.horizontal,
),
const SizedBox(height: 16),
parcelBookingData.status == Constant.driverAccepted
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: RoundedButtonFill(
title: "Pickup Parcel".tr,
height: 5.5,
color: AppThemeData.success400,
textColor: AppThemeData.grey50,
onPress: () async {
controller.pickupParcel(parcelBookingData);
},
),
)
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: RoundedButtonFill(
title: "Deliver Parcel".tr,
height: 5.5,
color: AppThemeData.success400,
textColor: AppThemeData.grey50,
onPress: () async {
controller.completeParcel(parcelBookingData);
},
),
),
parcelBookingData.status == Constant.driverAccepted ||
parcelBookingData.status == Constant.orderInTransit
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
children: [
const SizedBox(height: 16),
RoundedButtonFill(
title: "Parcel Track".tr,
height: 5.5,
color: AppThemeData.success400,
textColor: AppThemeData.grey50,
onPress: () async {
Get.to(() => ParcelTrackingScreen(),
arguments: {'parcelOrder': parcelBookingData});
},
),
],
),
)
: SizedBox.shrink(),
const SizedBox(height: 16),
],
),
),
);
},
),
),
),
);
});
});
}
}

View File

@@ -0,0 +1,407 @@
import 'package:dotted_border/dotted_border.dart';
import 'package:driver/themes/responsive.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 '../../themes/app_them_data.dart';
import '../../themes/theme_controller.dart';
import '../../utils/network_image_widget.dart';
class ParcelOrderDetails extends StatelessWidget {
const ParcelOrderDetails({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX(
init: ParcelOrderDetailsController(),
builder: (controller) {
return Scaffold(
appBar: AppBar(
title: Text(
"Order Details".tr,
style: TextStyle(
color: isDark ? Colors.white : Colors.black,
),
),
backgroundColor: isDark ? Colors.black : Colors.white,
iconTheme: IconThemeData(
color: isDark ? Colors.white : Colors.black,
),
),
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: ${controller.formatDate(controller.parcelOrder.value.senderPickupDateTime!)}".tr,
style: AppThemeData.mediumTextStyle(fontSize: 14, color: AppThemeData.info400),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"Order Date:${controller.parcelOrder.value.isSchedule == true ? controller.formatDate(controller.parcelOrder.value.createdAt!) : controller.formatDate(controller.parcelOrder.value.senderPickupDateTime!)}".tr,
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 == null || 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),
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 Customer".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.parcelOrder.value.author?.profilePictureURL ?? '',
height: 70,
width: 70,
borderRadius: 35),
),
),
SizedBox(width: 20),
Text(
controller.parcelOrder.value.author?.fullName() ?? '',
style: AppThemeData.boldTextStyle(
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, fontSize: 18),
),
],
),
],
),
],
),
),
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, null),
// Discount
_summaryTile("Discount".tr, Constant.amountShow(amount: controller.discount.value.toString()), isDark, null),
// 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,
null);
}),
const Divider(),
// Total
_summaryTile(
"Order Total".tr, Constant.amountShow(amount: controller.totalAmount.value.toString()), isDark, null),
_summaryTile(
"Admin Commission (${controller.parcelOrder.value.adminCommission}${controller.parcelOrder.value.adminCommissionType == "Percentage" || controller.parcelOrder.value.adminCommissionType == "percentage" ? "%" : Constant.currencyModel!.symbol})"
.tr,
Constant.amountShow(amount: controller.adminCommission.value.toString()),
isDark,
AppThemeData.danger300,
),
// controller.parcelOrder.value.driver?.ownerId != null &&
// controller.parcelOrder.value.driver?.ownerId.isNotEmpty ||
// controller.parcelOrder.value.status == Constant.orderPlaced
((controller.parcelOrder.value.driver?.ownerId != null &&
(controller.parcelOrder.value.driver?.ownerId?.isNotEmpty ?? false)) ||
controller.parcelOrder.value.status == Constant.orderPlaced)
? SizedBox()
: Container(
width: Responsive.width(100, context),
decoration: BoxDecoration(
border: Border.all(color: isDark ? AppThemeData.danger50 : AppThemeData.danger50),
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Note : Admin commission will be debited from your wallet balance. \n \nAdmin commission will apply on your booking Amount minus Discount(if applicable).",
style: AppThemeData.boldTextStyle(
fontSize: 16, color: isDark ? AppThemeData.danger300 : AppThemeData.danger300),
),
],
),
),
),
],
),
),
const SizedBox(height: 24),
],
),
),
);
},
);
}
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)),
),
],
),
);
}
}

View File

@@ -0,0 +1,193 @@
import 'package:driver/app/parcel_screen/parcel_order_details.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/constant.dart';
import '../../controllers/parcel_order_list_controller.dart';
import '../../themes/app_them_data.dart';
import 'package:dotted_border/dotted_border.dart';
import '../../themes/theme_controller.dart';
class ParcelOrderListScreen extends StatelessWidget {
const ParcelOrderListScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
return Obx(() {
final isDark = themeController.isDark.value;
return GetX<ParcelOrderListController>(
init: ParcelOrderListController(),
builder: (controller) {
return DefaultTabController(
length: controller.tabTitles.length,
initialIndex: controller.tabTitles.indexOf(controller.selectedTab.value),
child: Scaffold(
body: Column(
children: [
// TabBar
TabBar(
onTap: (index) {
controller.selectTab(controller.tabTitles[index]);
},
indicatorColor: AppThemeData.parcelService500,
labelColor: AppThemeData.parcelService500,
dividerColor: Colors.transparent,
unselectedLabelColor: AppThemeData.grey500,
labelStyle: AppThemeData.boldTextStyle(fontSize: 16),
unselectedLabelStyle: AppThemeData.mediumTextStyle(fontSize: 16),
tabs: controller.tabTitles.map((title) => Tab(child: Text(title))).toList(),
),
// Body: loader or TabBarView
Expanded(
child: controller.isLoading.value
? Constant.loader()
: TabBarView(
children: controller.tabTitles.map((title) {
// filter by tab using controller helper
final orders = controller.getOrdersForTab(title);
if (orders.isEmpty) {
return Center(
child: Text(
"No orders found".tr,
style: AppThemeData.mediumTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: orders.length,
itemBuilder: (context, index) {
final order = orders[index];
return GestureDetector(
onTap: () {
Get.to(() => const ParcelOrderDetails(), arguments: order);
},
child: Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"Order Date:${order.isSchedule == true ? controller.formatDate(order.createdAt!) : controller.formatDate(order.senderPickupDateTime!)}",
style: AppThemeData.mediumTextStyle(fontSize: 14, color: AppThemeData.info400),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_infoSection(
"Pickup Address (Sender):".tr,
order.sender?.name ?? '',
order.sender?.address ?? '',
order.sender?.phone ?? '',
// order.senderPickupDateTime != null
// ? "Pickup Time: ${controller.formatDate(order.senderPickupDateTime!)}"
// : '',
order.status,
isDark,
),
const SizedBox(height: 16),
_infoSection(
"Delivery Address (Receiver):".tr,
order.receiver?.name ?? '',
order.receiver?.address ?? '',
order.receiver?.phone ?? '',
// order.receiverPickupDateTime != null
// ? "Delivery Time: ${controller.formatDate(order.receiverPickupDateTime!)}"
// : '',
null,
isDark,
),
],
),
),
],
),
],
),
),
);
},
);
}).toList(),
),
),
],
),
),
);
},
);
});
}
Widget _infoSection(String title, String name, String address, String phone, String? status, bool isDark) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
title,
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (status != null) ...[
Container(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
decoration: BoxDecoration(
color: AppThemeData.info50,
border: Border.all(color: AppThemeData.info300),
borderRadius: BorderRadius.circular(12),
),
child: Text(status, style: AppThemeData.boldTextStyle(fontSize: 14, color: AppThemeData.info500)),
),
],
],
),
Text(name, style: AppThemeData.semiBoldTextStyle(fontSize: 14, 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)),
],
);
}
}

View File

@@ -0,0 +1,447 @@
import 'package:driver/app/parcel_screen/parcel_order_details.dart';
import 'package:driver/constant/constant.dart';
import 'package:driver/constant/show_toast_dialog.dart';
import 'package:driver/controllers/parcel_search_controller.dart';
import 'package:driver/models/parcel_order_model.dart';
import 'package:driver/themes/app_them_data.dart';
import 'package:driver/themes/responsive.dart';
import 'package:driver/themes/round_button_fill.dart';
import 'package:driver/themes/text_field_widget.dart';
import 'package:driver/themes/theme_controller.dart';
import 'package:driver/utils/network_image_widget.dart';
import 'package:driver/widget/dotted_line.dart';
import 'package:driver/widget/osm_map/map_picker_page.dart';
import 'package:driver/widget/osm_map/place_model.dart';
import 'package:driver/widget/place_picker/location_picker_screen.dart';
import 'package:driver/widget/place_picker/selected_location_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as latlong;
import 'package:timelines_plus/timelines_plus.dart';
class ParcelSearchScreen extends StatelessWidget {
const ParcelSearchScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX(
init: ParcelSearchController(),
builder: (controller) {
return Scaffold(
backgroundColor: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
appBar: AppBar(
backgroundColor: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
centerTitle: false,
titleSpacing: 0,
iconTheme: IconThemeData(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, size: 20),
title: Text(
"Search parcel".tr,
style: TextStyle(color: isDark ? AppThemeData.grey50 : AppThemeData.grey900, fontSize: 18, fontFamily: AppThemeData.medium),
),
),
body: controller.isLoading.value
? Constant.loader()
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Row(
children: [
Expanded(
child: TextFieldWidget(
readOnly: true,
controller: controller.sourceTextEditController.value,
onClick: () async {
if (Constant.selectedMapType == 'osm') {
PlaceModel? result = await Get.to(() => MapPickerPage());
if (result != null) {
controller.sourceTextEditController.value.text = '';
final firstPlace = result;
final lat = firstPlace.coordinates.latitude;
final lng = firstPlace.coordinates.longitude;
controller.sourceTextEditController.value.text = result.address.toString();
controller.departureLatLongOsm.value = latlong.LatLng(lat, lng);
}
} else {
Get.to(LocationPickerScreen())!.then((value) async {
if (value != null) {
SelectedLocationModel selectedLocationModel = value;
final place = selectedLocationModel.address;
// ✅ Build full readable address from Placemark fields
controller.sourceTextEditController.value
.text = '${place?.name ?? ''}, ${place?.street ?? ''}, ${place?.subLocality ?? ''}, '
'${place?.locality ?? ''}, ${place?.administrativeArea ?? ''}, ${place?.postalCode ?? ''}, ${place?.country ?? ''}'
.replaceAll(RegExp(r', ,|, , ,'), ',')
.trim()
.replaceAll(RegExp(r',+$'), '');
controller.departureLatLong.value = latlong.LatLng(
selectedLocationModel.latLng!.latitude,
selectedLocationModel.latLng!.longitude,
);
}
});
}
},
hintText: 'Where you want to go?',
prefix: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SvgPicture.asset("assets/icons/ic_source.svg"),
),
),
),
SizedBox(
width: 10,
),
Expanded(
child: TextFieldWidget(
readOnly: true,
controller: controller.destinationTextEditController.value,
onClick: () async {
if (Constant.selectedMapType == 'osm') {
PlaceModel? result = await Get.to(() => MapPickerPage());
if (result != null) {
controller.destinationTextEditController.value.text = '';
final firstPlace = result;
final lat = firstPlace.coordinates.latitude;
final lng = firstPlace.coordinates.longitude;
// ignore: unused_local_variable
final address = firstPlace.address;
controller.destinationTextEditController.value.text = result.address.toString();
controller.destinationLatLongOsm.value = latlong.LatLng(lat, lng);
}
} else {
Get.to(LocationPickerScreen())!.then(
(value) async {
if (value != null) {
SelectedLocationModel selectedLocationModel = value;
final place = selectedLocationModel.address;
controller.destinationTextEditController.value
.text = '${place?.name ?? ''}, ${place?.street ?? ''}, ${place?.subLocality ?? ''}, '
'${place?.locality ?? ''}, ${place?.administrativeArea ?? ''}, ${place?.postalCode ?? ''}, ${place?.country ?? ''}'
.replaceAll(RegExp(r', ,|, , ,'), ',')
.trim()
.replaceAll(RegExp(r',+$'), '');
controller.destinationLatLong.value = latlong.LatLng(
selectedLocationModel.latLng!.latitude,
selectedLocationModel.latLng!.longitude,
);
}
},
);
}
},
hintText: 'Where to?',
prefix: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SvgPicture.asset("assets/icons/ic_destination.svg"),
),
),
),
],
),
TextFieldWidget(
controller: controller.dateTimeTextEditController.value,
hintText: 'Select Date',
readOnly: true,
onClick: () async {
controller.pickDateTime();
},
prefix: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SvgPicture.asset("assets/images/ic_data.svg"),
),
),
RoundedButtonFill(
title: "Search Parcel".tr,
height: 5.5,
color: AppThemeData.primary300,
textColor: AppThemeData.grey50,
onPress: () async {
FocusScope.of(context).unfocus();
controller.searchParcel();
},
),
Expanded(
child: controller.parcelList.isEmpty
? Constant.showEmptyView(message: "Parcel Booking not found".tr, isDark: isDark)
: ListView.builder(
itemCount: controller.parcelList.length,
shrinkWrap: true,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
ParcelOrderModel parcelBookingData = controller.parcelList[index];
return InkWell(
onTap: () {
Get.to(() => const ParcelOrderDetails(), arguments: parcelBookingData);
},
child: Container(
width: Responsive.width(100, context),
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(16),
decoration: ShapeDecoration(
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
shadows: [
BoxShadow(
color: isDark ? AppThemeData.greyDark200 : Color(0x14000000),
blurRadius: 23,
offset: Offset(0, 0),
spreadRadius: 0,
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: isDark ? AppThemeData.greyDark100 : AppThemeData.grey100,
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Timeline.tileBuilder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
theme: TimelineThemeData(
nodePosition: 0,
// indicatorPosition: 0,
),
builder: TimelineTileBuilder.connected(
contentsAlign: ContentsAlign.basic,
indicatorBuilder: (context, index) {
return index == 0
? SvgPicture.asset("assets/icons/ic_source.svg")
: index == 1
? SvgPicture.asset("assets/icons/ic_destination.svg")
: SizedBox();
},
connectorBuilder: (context, index, connectorType) {
return DashedLineConnector(
color: isDark ? AppThemeData.greyDark300 : AppThemeData.grey300,
gap: 4,
);
},
contentsBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
child: Text(
index == 0
? "${parcelBookingData.sender!.address}"
: "${parcelBookingData.receiver!.address}",
style: AppThemeData.mediumTextStyle(
fontSize: 14,
color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
);
},
itemCount: 2,
),
),
),
),
const SizedBox(height: 16),
Row(
children: [
ClipOval(
child: NetworkImageWidget(
imageUrl: parcelBookingData.author!.profilePictureURL.toString(),
width: 52,
height: 52,
fit: BoxFit.cover,
),
),
SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
parcelBookingData.author!.fullName(),
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(
fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
)
],
),
SizedBox(
height: 12,
),
Row(
children: [
Expanded(
child: Column(
children: [
SvgPicture.asset(
"assets/icons/ic_amount.svg",
colorFilter: ColorFilter.mode(
isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, BlendMode.srcIn),
),
SizedBox(
height: 5,
),
Text(
Constant.amountShow(
amount: controller.calculateParcelTotalAmountBooking(parcelBookingData))
.tr,
textAlign: TextAlign.start,
style: AppThemeData.semiBoldTextStyle(
fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
)
],
),
),
Expanded(
child: Column(
children: [
SvgPicture.asset(
"assets/icons/ic_date.svg",
colorFilter: ColorFilter.mode(
isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, BlendMode.srcIn),
),
SizedBox(
height: 5,
),
Text(
'${Constant.timestampToDate(parcelBookingData.senderPickupDateTime!)} '.tr,
textAlign: TextAlign.start,
style: AppThemeData.semiBoldTextStyle(
fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
)
],
),
),
Expanded(
child: Column(
children: [
SvgPicture.asset(
"assets/icons/weight-line.svg",
colorFilter: ColorFilter.mode(
isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, BlendMode.srcIn),
),
SizedBox(
height: 5,
),
Text(
'${parcelBookingData.parcelWeight}'.tr,
textAlign: TextAlign.start,
style: AppThemeData.semiBoldTextStyle(
fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
)
],
),
)
],
),
SizedBox(
height: 12,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Parcel Type:".tr,
style: AppThemeData.semiBoldTextStyle(
fontSize: 16,
color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800,
),
),
Row(
children: [
Text(
parcelBookingData.parcelType ?? '',
style: AppThemeData.semiBoldTextStyle(
fontSize: 16,
color: isDark ? AppThemeData.greyDark800 : AppThemeData.grey800,
),
),
const SizedBox(width: 8),
if (controller.getSelectedCategory(parcelBookingData)?.image != null &&
controller.getSelectedCategory(parcelBookingData)!.image!.isNotEmpty)
NetworkImageWidget(
imageUrl: controller.getSelectedCategory(parcelBookingData)?.image ?? '',
height: 20,
width: 20),
],
),
],
),
if (parcelBookingData.isSchedule == true)
SizedBox(
height: 12,
),
if (parcelBookingData.isSchedule == true)
Text(
"Schedule Pickup time: ${controller.formatDate(parcelBookingData.senderPickupDateTime!)}",
style: AppThemeData.mediumTextStyle(fontSize: 14, color: AppThemeData.info400),
),
const SizedBox(height: 16),
DottedLine(
dashColor: Colors.grey,
lineThickness: 1.0,
dashLength: 4.0,
dashGapLength: 3.0,
direction: Axis.horizontal,
),
const SizedBox(height: 16),
RoundedButtonFill(
title: "Accept".tr,
height: 5.5,
color: AppThemeData.primary300,
textColor: AppThemeData.grey50,
onPress: () async {
if (controller.driverModel.value.ownerId != null &&
controller.driverModel.value.ownerId!.isNotEmpty) {
if (controller.ownerModel.value.walletAmount != null &&
controller.ownerModel.value.walletAmount! >=
double.parse(Constant.ownerMinimumDepositToRideAccept)) {
controller.acceptParcelBooking(parcelBookingData);
} else {
ShowToastDialog.showToast(
"Your owner has to maintain minimum {amount} wallet balance to accept the parcel booking. Please contact your owner"
.trParams({"amount": Constant.amountShow(amount: Constant.ownerMinimumDepositToRideAccept)}).tr);
}
} else {
if (controller.driverModel.value.walletAmount != null &&
controller.driverModel.value.walletAmount! >=
double.parse(Constant.minimumDepositToRideAccept)) {
controller.acceptParcelBooking(parcelBookingData);
} else {
ShowToastDialog.showToast(
"Your owner has to maintain minimum {amount} wallet balance to accept the parcel booking. Please contact your owner"
.trParams({"amount": Constant.amountShow(amount: Constant.ownerMinimumDepositToRideAccept)}).tr);
}
}
},
)
],
),
),
);
},
),
)
],
),
),
);
});
}
}

View File

@@ -0,0 +1,97 @@
import 'dart:io';
import 'package:driver/constant/constant.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart' as flutterMap;
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:latlong2/latlong.dart' as location;
import '../../controllers/parcel_tracking_controller.dart';
import '../../themes/app_them_data.dart';
import '../../themes/theme_controller.dart';
class ParcelTrackingScreen extends StatelessWidget {
const ParcelTrackingScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX<ParcelTrackingController>(
init: ParcelTrackingController(),
builder: (controller) {
return Scaffold(
appBar: AppBar(
elevation: 2,
backgroundColor: AppThemeData.primary300,
title: Text("Map view".tr),
leading: InkWell(
onTap: () {
Get.back();
},
child: const Icon(
Icons.arrow_back,
)),
),
body: controller.isLoading.value
? Constant.loader()
: Constant.selectedMapType == 'osm'
? flutterMap.FlutterMap(
mapController: controller.osmMapController,
options: flutterMap.MapOptions(
initialCenter: location.LatLng(
Constant.userModel?.location?.latitude ?? 45.521563,
Constant.userModel?.location?.longitude ??
-122.677433),
initialZoom: 10,
),
children: [
flutterMap.TileLayer(
urlTemplate:
'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: Platform.isAndroid
? 'felix.fondex.driver'
: 'felix.fondex.driver',
),
flutterMap.MarkerLayer(markers: controller.osmMarkers),
if (controller.routePoints.isNotEmpty)
flutterMap.PolylineLayer(
polylines: [
flutterMap.Polyline(
points: controller.routePoints,
strokeWidth: 5.0,
color: AppThemeData.primary300,
),
],
),
],
)
: Obx(
() => GoogleMap(
myLocationEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.terrain,
zoomControlsEnabled: false,
polylines:
Set<Polyline>.of(controller.polyLines.values),
padding: const EdgeInsets.only(
top: 22.0,
),
markers: Set<Marker>.of(controller.markers.values),
onMapCreated: (GoogleMapController mapController) {
controller.mapController = mapController;
},
initialCameraPosition: CameraPosition(
zoom: 15,
target: LatLng(
Constant.userModel?.location?.latitude ?? 45.521563,
Constant.userModel?.location?.longitude ??
-122.677433,
),
),
),
),
);
},
);
}
}