INFRA: Set Up Project.

This commit is contained in:
2025-11-28 11:10:49 +05:00
commit c798279f7d
609 changed files with 77436 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:customer/models/rental_order_model.dart';
import 'package:customer/screen_ui/auth_screens/login_screen.dart';
import 'package:customer/screen_ui/multi_vendor_service/wallet_screen/wallet_screen.dart';
import 'package:customer/screen_ui/rental_service/rental_order_details_screen.dart';
import 'package:customer/themes/round_button_fill.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/constant.dart';
import '../../controllers/my_rental_booking_controller.dart';
import '../../controllers/theme_controller.dart';
import '../../themes/app_them_data.dart';
class MyRentalBookingScreen extends StatelessWidget {
const MyRentalBookingScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX<MyRentalBookingController>(
init: MyRentalBookingController(),
builder: (controller) {
return DefaultTabController(
length: controller.tabTitles.length,
initialIndex: controller.tabTitles.indexOf(controller.selectedTab.value),
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: AppThemeData.primary300,
title: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(children: [const SizedBox(width: 10), Text("Rental History".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900))]),
),
bottom: TabBar(
onTap: (index) {
controller.selectTab(controller.tabTitles[index]);
},
indicatorColor: AppThemeData.parcelService500,
labelColor: AppThemeData.parcelService500,
unselectedLabelColor: AppThemeData.parcelService500,
labelStyle: AppThemeData.boldTextStyle(fontSize: 13),
unselectedLabelStyle: AppThemeData.mediumTextStyle(fontSize: 13),
tabs: controller.tabTitles.map((title) => Tab(child: Center(child: Text(title)))).toList(),
),
),
body:
controller.isLoading.value
? Constant.loader()
: Constant.userModel == null
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Please Log In to Continue".tr, style: TextStyle(color: isDark ? AppThemeData.grey100 : AppThemeData.grey800, fontSize: 22, fontFamily: AppThemeData.semiBold)),
const SizedBox(height: 5),
Text(
"Youre not logged in. Please sign in to access your account and explore all features.".tr,
textAlign: TextAlign.center,
style: TextStyle(color: isDark ? AppThemeData.grey50 : AppThemeData.grey500, fontSize: 16, fontFamily: AppThemeData.bold),
),
const SizedBox(height: 20),
RoundedButtonFill(
title: "Log in".tr,
width: 55,
height: 5.5,
color: AppThemeData.primary300,
textColor: AppThemeData.grey50,
onPress: () async {
Get.offAll(const LoginScreen());
},
),
],
),
)
: TabBarView(
children:
controller.tabTitles.map((title) {
List<RentalOrderModel> 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) {
RentalOrderModel order = orders[index]; //use this
return InkWell(
onTap: () {
Get.to(() => RentalOrderDetailsScreen(), arguments: order);
},
child: 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),
margin: const EdgeInsets.only(bottom: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(padding: const EdgeInsets.only(top: 5), child: Image.asset("assets/icons/pickup.png", height: 18, width: 18)),
const SizedBox(width: 10),
Expanded(
//prevents overflow
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
//text wraps if too long
child: Text(
order.sourceLocationName ?? "-",
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
overflow: TextOverflow.ellipsis, //safe cutoff
maxLines: 2,
),
),
if (order.status != null) ...[
const SizedBox(width: 8),
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(order.status ?? '', style: AppThemeData.boldTextStyle(fontSize: 14, color: AppThemeData.info500)),
),
],
],
),
if (order.bookingDateTime != null)
Text(
Constant.timestampToDateTime(order.bookingDateTime!),
style: AppThemeData.mediumTextStyle(fontSize: 12, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
],
),
const SizedBox(height: 12),
Text("Vehicle Type :".tr, style: AppThemeData.boldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
child: Row(
children: [
ClipRRect(
//borderRadius: BorderRadius.circular(10),
child: CachedNetworkImage(
imageUrl: order.rentalVehicleType!.rentalVehicleIcon.toString(),
height: 60,
width: 60,
imageBuilder:
(context, imageProvider) => Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), image: DecorationImage(image: imageProvider, fit: BoxFit.cover)),
),
placeholder: (context, url) => Center(child: CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
errorWidget: (context, url, error) => Image.network(Constant.placeHolderImage, fit: BoxFit.cover),
fit: BoxFit.cover,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${order.rentalVehicleType!.name}",
style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(
"${order.rentalVehicleType!.shortDescription}",
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
),
],
),
),
),
],
),
),
Text("Package info :".tr, style: AppThemeData.boldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
order.rentalPackageModel!.name.toString(),
style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
const SizedBox(height: 4),
Text(
order.rentalPackageModel!.description.toString(),
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
SizedBox(width: 10),
Text(
Constant.amountShow(amount: order.rentalPackageModel!.baseFare.toString()),
style: AppThemeData.boldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
if (Constant.isEnableOTPTripStartForRental == true)
Text("${'OTP :'.tr} ${order.otpCode}", style: AppThemeData.boldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
SizedBox(height: 10),
Row(
children: [
order.status == Constant.orderInTransit && order.paymentStatus == false
? Expanded(
child: RoundedButtonFill(
title: "Pay Now",
onPress: () {
Get.to(() => RentalOrderDetailsScreen(), arguments: order);
},
color: AppThemeData.primary300,
textColor: AppThemeData.grey900,
),
)
: SizedBox(),
order.status == Constant.orderPlaced || order.status == Constant.driverAccepted
? Expanded(
child: RoundedButtonFill(
title: "Cancel Booking",
onPress: () => controller.cancelRentalRequest(order, taxList: order.taxSetting),
color: AppThemeData.danger300,
textColor: AppThemeData.surface,
),
)
: SizedBox(),
],
),
],
),
),
);
},
);
}).toList(),
),
),
);
},
);
}
Obx cardDecoration(MyRentalBookingController controller, PaymentGateway value, isDark, String image) {
return Obx(
() => Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Column(
children: [
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),
),
),
const Expanded(child: SizedBox()),
Radio(
value: value.name,
groupValue: controller.selectedPaymentMethod.value,
activeColor: isDark ? AppThemeData.primary300 : AppThemeData.primary300,
onChanged: (value) {
controller.selectedPaymentMethod.value = value.toString();
},
),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,332 @@
import 'package:customer/constant/constant.dart';
import 'package:customer/models/coupon_model.dart';
import 'package:customer/screen_ui/rental_service/rental_coupon_screen.dart';
import 'package:customer/themes/show_toast_dialog.dart';
import 'package:customer/utils/network_image_widget.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 '../../controllers/rental_conformation_controller.dart';
import '../../controllers/theme_controller.dart';
import '../../themes/app_them_data.dart';
import '../../themes/round_button_fill.dart';
class RentalConformationScreen extends StatelessWidget {
const RentalConformationScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX(
init: RentalConformationController(),
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("Confirm Rent a Car".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
],
),
),
),
body:
controller.isLoading.value
? Center(child: Constant.loader())
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 20),
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: [
Image.asset("assets/icons/pickup.png", height: 15, width: 15),
SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${controller.rentalOrderModel.value.sourceLocationName}",
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
Text(
Constant.timestampToDate(controller.rentalOrderModel.value.bookingDateTime!),
style: AppThemeData.semiBoldTextStyle(fontSize: 12, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
],
),
),
SizedBox(height: 20),
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("Your Preference".tr, style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500)),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.rentalOrderModel.value.rentalPackageModel!.name.toString(),
style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
const SizedBox(height: 4),
Text(
controller.rentalOrderModel.value.rentalPackageModel!.description.toString(),
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
SizedBox(width: 10),
Text(
Constant.amountShow(amount: controller.rentalOrderModel.value.rentalPackageModel!.baseFare.toString()),
style: AppThemeData.boldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
],
),
),
SizedBox(height: 20),
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("Vehicle Type".tr, style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500)),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadiusGeometry.circular(10),
child: NetworkImageWidget(imageUrl: controller.rentalOrderModel.value.rentalVehicleType!.rentalVehicleIcon.toString(), height: 50, width: 50, borderRadius: 10),
),
SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${controller.rentalOrderModel.value.rentalVehicleType!.name}",
style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
Text(
"${controller.rentalOrderModel.value.rentalVehicleType!.shortDescription}",
style: AppThemeData.mediumTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
],
),
],
),
),
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(RentalCouponScreen())!.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.calculateAmount();
} 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: TextFormField(
controller: controller.couponController.value,
style: AppThemeData.semiBoldTextStyle(color: AppThemeData.grey900),
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.calculateAmount();
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: 10),
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(controller.rentalOrderModel.value.taxSetting!.length, (index) {
final taxModel = controller.rentalOrderModel.value.taxSetting![index];
final taxTitle = "${taxModel.title} ${taxModel.type == 'fix' ? '(${Constant.amountShow(amount: taxModel.tax)})' : '(${taxModel.tax}%)'}";
return _summaryTile(
taxTitle,
Constant.amountShow(
amount:
Constant.getTaxValue(
amount: (controller.subTotal.value - controller.discount.value).toString(),
taxModel: controller.rentalOrderModel.value.taxSetting![index],
).toString(),
),
isDark,
null,
);
}),
const Divider(),
// Total
_summaryTile("Order Total".tr, Constant.amountShow(amount: controller.totalAmount.value.toString()), isDark, null),
],
),
),
SizedBox(height: 20),
RoundedButtonFill(
title: "Book now".tr,
onPress: () {
controller.placeOrder();
},
color: AppThemeData.primary300,
textColor: AppThemeData.grey900,
),
SizedBox(height: 20),
],
),
),
),
);
},
);
}
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,138 @@
import 'package:customer/constant/constant.dart';
import 'package:customer/controllers/rental_coupon_controller.dart';
import 'package:customer/controllers/theme_controller.dart';
import 'package:customer/models/coupon_model.dart';
import 'package:customer/themes/app_them_data.dart';
import 'package:customer/themes/responsive.dart';
import 'package:customer/widget/my_separator.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class RentalCouponScreen extends StatelessWidget {
const RentalCouponScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX(
init: RentalCouponController(),
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("Coupon".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
],
),
),
),
body:
controller.isLoading.value
? Constant.loader()
: controller.cabCouponList.isEmpty
? Constant.showEmptyView(message: "Coupon not found".tr)
: ListView.builder(
shrinkWrap: true,
itemCount: controller.cabCouponList.length,
itemBuilder: (context, index) {
CouponModel couponModel = controller.cabCouponList[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Container(
height: Responsive.height(16, context),
decoration: ShapeDecoration(color: isDark ? AppThemeData.grey900 : AppThemeData.grey50, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
child: Row(
children: [
ClipRRect(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
child: Stack(
children: [
Image.asset("assets/images/ic_coupon_image.png", height: Responsive.height(16, context), fit: BoxFit.fill),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Align(
alignment: Alignment.center,
child: RotatedBox(
quarterTurns: -1,
child: Text(
"${couponModel.discountType == "Fix Price" ? Constant.amountShow(amount: couponModel.discount) : "${couponModel.discount}%"} ${'Off'.tr}",
textAlign: TextAlign.start,
style: TextStyle(fontFamily: AppThemeData.semiBold, fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey50),
),
),
),
),
],
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
DottedBorder(
options: RoundedRectDottedBorderOptions(strokeWidth: 1, radius: const Radius.circular(6), color: isDark ? AppThemeData.grey400 : AppThemeData.grey500),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
"${couponModel.code}",
textAlign: TextAlign.start,
style: TextStyle(fontFamily: AppThemeData.semiBold, fontSize: 16, color: isDark ? AppThemeData.grey400 : AppThemeData.grey500),
),
),
),
const Expanded(child: SizedBox(height: 10)),
InkWell(
onTap: () {
Get.back(result: couponModel);
},
child: Text(
"Tap To Apply".tr,
textAlign: TextAlign.start,
style: TextStyle(fontFamily: AppThemeData.medium, color: isDark ? AppThemeData.primary300 : AppThemeData.primary300),
),
),
],
),
const SizedBox(height: 20),
MySeparator(color: isDark ? AppThemeData.grey700 : AppThemeData.grey200),
const SizedBox(height: 20),
Text(
"${couponModel.description}",
textAlign: TextAlign.start,
style: TextStyle(fontFamily: AppThemeData.medium, fontSize: 16, color: isDark ? AppThemeData.grey50 : AppThemeData.grey900),
),
],
),
),
),
],
),
),
);
},
),
);
},
);
}
}

View File

@@ -0,0 +1,81 @@
import 'package:customer/constant/constant.dart';
import 'package:customer/controllers/cab_dashboard_controller.dart';
import 'package:customer/controllers/theme_controller.dart';
import 'package:customer/themes/app_them_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import '../../controllers/cab_rental_dashboard_controllers.dart';
class RentalDashboardScreen extends StatelessWidget {
const RentalDashboardScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
return Obx(() {
final isDark = themeController.isDark.value;
return GetX(
init: CabRentalDashboardControllers(),
builder: (controller) {
return Scaffold(
body: controller.pageList[controller.selectedIndex.value],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
showUnselectedLabels: true,
showSelectedLabels: true,
selectedFontSize: 12,
selectedLabelStyle: const TextStyle(fontFamily: AppThemeData.bold),
unselectedLabelStyle: const TextStyle(fontFamily: AppThemeData.bold),
currentIndex: controller.selectedIndex.value,
backgroundColor: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
selectedItemColor: isDark ? AppThemeData.primary300 : AppThemeData.primary300,
unselectedItemColor: isDark ? AppThemeData.grey300 : AppThemeData.grey600,
onTap: (int index) {
if (index == 0) {
Get.put(CabDashboardController());
}
controller.selectedIndex.value = index;
},
items:
Constant.walletSetting == false
? [
navigationBarItem(isDark, index: 0, assetIcon: "assets/icons/ic_home_cab.svg", label: 'Home'.tr, controller: controller),
navigationBarItem(isDark, index: 1, assetIcon: "assets/icons/ic_booking_cab.svg", label: 'My Bookings'.tr, controller: controller),
navigationBarItem(isDark, index: 2, assetIcon: "assets/icons/ic_profile.svg", label: 'Profile'.tr, controller: controller),
]
: [
navigationBarItem(isDark, index: 0, assetIcon: "assets/icons/ic_home_cab.svg", label: 'Home'.tr, controller: controller),
navigationBarItem(isDark, index: 1, assetIcon: "assets/icons/ic_booking_cab.svg", label: 'My Bookings'.tr, controller: controller),
navigationBarItem(isDark, index: 2, assetIcon: "assets/icons/ic_wallet_cab.svg", label: 'Wallet'.tr, controller: controller),
navigationBarItem(isDark, index: 3, assetIcon: "assets/icons/ic_profile.svg", label: 'Profile'.tr, controller: controller),
],
),
);
},
);
});
}
BottomNavigationBarItem navigationBarItem(isDark, {required int index, required String label, required String assetIcon, required CabRentalDashboardControllers controller}) {
return BottomNavigationBarItem(
icon: Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: SvgPicture.asset(
assetIcon,
height: 22,
width: 22,
color:
controller.selectedIndex.value == index
? isDark
? AppThemeData.primary300
: AppThemeData.primary300
: isDark
? AppThemeData.grey300
: AppThemeData.grey600,
),
),
label: label,
);
}
}

View File

@@ -0,0 +1,618 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:customer/constant/constant.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../controllers/rental_home_controller.dart';
import '../../controllers/theme_controller.dart';
import '../../models/rental_vehicle_type.dart';
import '../../themes/app_them_data.dart';
import '../../themes/round_button_fill.dart';
import '../../themes/show_toast_dialog.dart';
import '../../themes/text_field_widget.dart';
import '../../utils/utils.dart';
import '../../widget/osm_map/map_picker_page.dart';
import '../../widget/place_picker/location_picker_screen.dart';
import '../../widget/place_picker/selected_location_model.dart';
import '../auth_screens/login_screen.dart';
import '../multi_vendor_service/wallet_screen/wallet_screen.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as latlong;
class RentalHomeScreen extends StatelessWidget {
const RentalHomeScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX(
init: RentalHomeController(),
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),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Constant.userModel == null
? InkWell(
onTap: () {
Get.offAll(const LoginScreen());
},
child: Text("Login".tr, style: AppThemeData.boldTextStyle(fontSize: 14, color: AppThemeData.grey900)),
)
: Text(Constant.userModel!.fullName(), style: AppThemeData.boldTextStyle(fontSize: 14, color: AppThemeData.grey900)),
],
),
),
],
),
),
),
body: Stack(
children: [
controller.isLoading.value
? Center(child: Constant.loader())
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
InkWell(
onTap: () async {
if (Constant.selectedMapType == 'osm') {
final result = await Get.to(() => MapPickerPage());
if (result != null) {
final firstPlace = result;
if (Constant.checkZoneCheck(firstPlace.coordinates.latitude, firstPlace.coordinates.longitude) == true) {
final address = firstPlace.address;
final lat = firstPlace.coordinates.latitude;
final lng = firstPlace.coordinates.longitude;
controller.sourceTextEditController.value.text = address;
controller.departureLatLongOsm.value = latlong.LatLng(lat, lng);
} else {
ShowToastDialog.showToast("Service is unavailable at the selected address.".tr);
}
}
} else {
Get.to(LocationPickerScreen())!.then((value) async {
if (value != null) {
SelectedLocationModel selectedLocationModel = value;
if (Constant.checkZoneCheck(selectedLocationModel.latLng!.latitude, selectedLocationModel.latLng!.longitude) == true) {
controller.sourceTextEditController.value.text = Utils.formatAddress(selectedLocation: selectedLocationModel);
controller.departureLatLong.value = latlong.LatLng(selectedLocationModel.latLng!.latitude, selectedLocationModel.latLng!.longitude);
} else {
ShowToastDialog.showToast("Service is unavailable at the selected address.".tr);
}
}
});
}
},
hoverColor: Colors.transparent,
child: TextFieldWidget(
controller: controller.sourceTextEditController.value,
hintText: "Your current location".tr,
title: "Pickup Location".tr,
enable: false,
prefix: Padding(padding: EdgeInsets.only(left: 10, right: 10), child: Icon(Icons.stop_circle_outlined, color: Colors.green)),
),
),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Select Your Vehicle Type".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
const SizedBox(width: 10),
GestureDetector(
onTap: () => controller.pickDate(context),
child: Row(
children: [
Text(
"${controller.selectedDate.value.day}-${controller.selectedDate.value.month}-${controller.selectedDate.value.year}",
style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
const SizedBox(width: 5),
const Icon(Icons.date_range, size: 20),
],
),
),
],
),
const SizedBox(height: 15),
ListView.builder(
itemCount: controller.vehicleTypes.length,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
RentalVehicleType vehicleType = controller.vehicleTypes[index];
return Obx(
() => InkWell(
onTap: () {
controller.selectedVehicleType.value = controller.vehicleTypes[index];
},
child: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(
color:
isDark
? controller.selectedVehicleType.value?.id == vehicleType.id
? AppThemeData.carRentDark300
: Colors.transparent
: controller.selectedVehicleType.value?.id == vehicleType.id
? AppThemeData.carRent300
: Colors.transparent,
width: 1,
),
color:
controller.selectedVehicleType.value?.id == vehicleType.id
? AppThemeData.carRent50
: isDark
? AppThemeData.carRentDark50
: Colors.white,
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
child: Row(
children: [
ClipRRect(
//borderRadius: BorderRadius.circular(10),
child: CachedNetworkImage(
imageUrl: vehicleType.rentalVehicleIcon.toString(),
height: 60,
width: 60,
imageBuilder:
(context, imageProvider) =>
Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), image: DecorationImage(image: imageProvider, fit: BoxFit.cover))),
placeholder: (context, url) => Center(child: CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppThemeData.primary300))),
errorWidget: (context, url, error) => Image.network(Constant.placeHolderImage, fit: BoxFit.cover),
fit: BoxFit.cover,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${vehicleType.name}",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 1,
color:
isDark
? controller.selectedVehicleType.value?.id == vehicleType.id
? AppThemeData.greyDark50
: AppThemeData.grey50
: AppThemeData.greyDark50,
),
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(
"${vehicleType.description}",
style: TextStyle(
fontWeight: FontWeight.w400,
letterSpacing: 1,
color:
isDark
? controller.selectedVehicleType.value?.id == vehicleType.id
? AppThemeData.greyDark50
: AppThemeData.grey50
: AppThemeData.greyDark50,
),
),
),
],
),
),
),
],
),
),
),
),
),
);
},
),
const SizedBox(height: 25),
RoundedButtonFill(
title: "Continue".tr,
onPress: () async {
final sourceText = controller.sourceTextEditController.value.text.trim();
if (Constant.userModel == null) {
ShowToastDialog.showToast("Please login to continue".tr);
return;
}
if (sourceText.isEmpty) {
ShowToastDialog.showToast("Please select source location".tr);
return;
}
if (controller.selectedVehicleType.value == null) {
ShowToastDialog.showToast("Please select a vehicle type".tr);
return;
}
await controller.getRentalPackage();
if (controller.rentalPackages.isEmpty) {
ShowToastDialog.showToast("No preference available for the selected vehicle type".tr);
return;
}
// Open bottom sheet if packages exist
Get.bottomSheet(selectPreferences(context, controller, isDark), isScrollControlled: true, backgroundColor: Colors.transparent);
},
color: AppThemeData.primary300,
textColor: AppThemeData.grey900,
),
],
),
),
),
],
),
);
},
);
}
Widget selectPreferences(BuildContext context, RentalHomeController controller, bool isDark) {
return DraggableScrollableSheet(
initialChildSize: 0.40,
minChildSize: 0.40,
maxChildSize: 0.7,
expand: false,
builder: (context, scrollController) {
return Container(
decoration: BoxDecoration(color: isDark ? Colors.black : Colors.white, borderRadius: const BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24))),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10),
child: Column(
children: [
// handle bar
Container(height: 4, width: 33, decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.grey.shade400)),
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text("Select Preferences".tr, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: isDark ? Colors.white : Colors.black)),
),
),
Expanded(
child: ListView.builder(
controller: scrollController,
itemCount: controller.rentalPackages.length,
itemBuilder: (context, index) {
final package = controller.rentalPackages[index];
return Obx(
() => InkWell(
onTap: () => controller.selectedPackage.value = package,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: controller.selectedPackage.value?.id == package.id ? (isDark ? AppThemeData.carRentDark300 : AppThemeData.carRent300) : Colors.transparent,
width: 1,
),
color:
controller.selectedPackage.value?.id == package.id
? AppThemeData.carRent50
: isDark
? AppThemeData.carRentDark50
: Colors.white,
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
package.name ?? "",
style: AppThemeData.semiBoldTextStyle(
fontSize: 18,
color:
isDark
? controller.selectedPackage.value?.id == package.id
? AppThemeData.greyDark50
: AppThemeData.grey50
: AppThemeData.greyDark50,
),
),
const SizedBox(height: 4),
Text(
package.description ?? "",
style: AppThemeData.mediumTextStyle(
fontSize: 14,
color:
isDark
? controller.selectedPackage.value?.id == package.id
? AppThemeData.greyDark50
: AppThemeData.grey50
: AppThemeData.greyDark50,
),
),
],
),
),
Text(
Constant.amountShow(amount: package.baseFare.toString()),
style: AppThemeData.boldTextStyle(
fontSize: 18,
color:
isDark
? controller.selectedPackage.value?.id == package.id
? AppThemeData.greyDark50
: AppThemeData.grey50
: AppThemeData.greyDark50,
),
),
],
),
),
),
),
);
},
),
),
const SizedBox(height: 10),
RoundedButtonFill(
title: "Continue".tr,
onPress: () {
Get.bottomSheet(paymentBottomSheet(context, controller, isDark), isScrollControlled: true, backgroundColor: Colors.transparent);
},
color: AppThemeData.primary300,
textColor: AppThemeData.grey900,
),
],
),
),
);
},
);
}
Widget paymentBottomSheet(BuildContext context, RentalHomeController controller, bool isDark) {
return DraggableScrollableSheet(
initialChildSize: 0.70,
// Start height
minChildSize: 0.30,
// Minimum height
maxChildSize: 0.8,
// Maximum height
expand: false,
// Prevents full-screen takeover
builder: (context, scrollController) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
decoration: BoxDecoration(color: isDark ? AppThemeData.grey500 : Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(24))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
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: const Icon(Icons.close)),
],
),
const SizedBox(height: 20),
// Payment options list
Expanded(
child: ListView(
padding: EdgeInsets.zero,
controller: scrollController,
children: [
Text("Preferred Payment".tr, 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)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Text("Other Payment Options".tr, style: AppThemeData.boldTextStyle(fontSize: 15, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500)),
const SizedBox(height: 10),
],
),
// Other gateways
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")),
],
),
),
),
const SizedBox(height: 20),
],
),
),
// Continue button
RoundedButtonFill(
title: "Continue".tr,
color: AppThemeData.primary300,
textColor: AppThemeData.grey900,
onPress: () async {
if (controller.selectedPaymentMethod.value.isEmpty) {
ShowToastDialog.showToast("Please select a payment method".tr);
return;
}
// Only check wallet if payment method is wallet
if (controller.selectedPaymentMethod.value == "wallet") {
num walletAmount = controller.userModel.value.walletAmount ?? 0;
num baseFare = double.tryParse(controller.selectedPackage.value?.baseFare.toString() ?? "0") ?? 0;
if (walletAmount < baseFare) {
ShowToastDialog.showToast("You do not have sufficient wallet balance".tr);
return;
}
}
// Complete the order
controller.completeOrder();
},
),
],
),
);
},
);
}
Obx cardDecoration(RentalHomeController controller, PaymentGateway value, isDark, String image) {
return Obx(
() => Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Column(
children: [
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: controller.userModel.value.walletAmount == null ? '0.0' : controller.userModel.value.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),
),
),
const Expanded(child: SizedBox()),
Radio(
value: value.name,
groupValue: controller.selectedPaymentMethod.value,
activeColor: isDark ? AppThemeData.primary300 : AppThemeData.primary300,
onChanged: (value) {
controller.selectedPaymentMethod.value = value.toString();
},
),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,839 @@
import 'package:customer/constant/constant.dart';
import 'package:customer/models/rental_order_model.dart';
import 'package:customer/payment/createRazorPayOrderModel.dart';
import 'package:customer/payment/rozorpayConroller.dart';
import 'package:customer/screen_ui/multi_vendor_service/wallet_screen/wallet_screen.dart';
import 'package:customer/screen_ui/rental_service/rental_review_screen.dart';
import 'package:customer/themes/responsive.dart';
import 'package:customer/themes/show_toast_dialog.dart';
import 'package:customer/utils/network_image_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import '../../controllers/rental_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 '../multi_vendor_service/chat_screens/chat_screen.dart';
class RentalOrderDetailsScreen extends StatelessWidget {
const RentalOrderDetailsScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX(
init: RentalOrderDetailsController(),
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 Details".tr, style: AppThemeData.boldTextStyle(fontSize: 18, color: AppThemeData.grey900)),
],
),
),
),
body:
controller.isLoading.value
? Center(child: Constant.loader())
: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
border: Border.all(color: isDark ? AppThemeData.greyDark200 : AppThemeData.grey200),
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
"${'Booking Id :'.tr} ${controller.order.value.id}",
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark700 : AppThemeData.grey700),
),
),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: controller.order.value.id.toString()));
ShowToastDialog.showToast("Booking ID copied to clipboard".tr);
},
child: Icon(Icons.copy),
),
],
),
SizedBox(height: 10),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(padding: const EdgeInsets.only(top: 5), child: Image.asset("assets/icons/pickup.png", height: 15, width: 15)),
const SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.order.value.sourceLocationName ?? "-",
style: AppThemeData.semiBoldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
if (controller.order.value.bookingDateTime != null)
Text(
Constant.timestampToDate(controller.order.value.bookingDateTime!),
style: AppThemeData.semiBoldTextStyle(fontSize: 12, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
],
),
],
),
),
const SizedBox(height: 15),
if (controller.order.value.rentalPackageModel != null)
Container(
padding: const EdgeInsets.all(16),
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: [
Text("Your Preference".tr, style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500)),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.order.value.rentalPackageModel!.name ?? "-",
style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
const SizedBox(height: 4),
Text(
controller.order.value.rentalPackageModel!.description ?? "",
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
const SizedBox(width: 10),
Text(
Constant.amountShow(amount: controller.order.value.rentalPackageModel!.baseFare.toString()),
style: AppThemeData.boldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
],
),
),
const SizedBox(height: 15),
if (controller.order.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.start,
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),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.order.value.driver?.fullName() ?? '',
style: AppThemeData.boldTextStyle(color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900, fontSize: 18),
),
Text(
"${controller.order.value.driver?.vehicleType ?? ''} | ${controller.order.value.driver?.carMakes.toString()}",
style: TextStyle(fontFamily: AppThemeData.medium, color: isDark ? AppThemeData.greyDark700 : AppThemeData.grey700, fontSize: 14),
),
Text(
controller.order.value.driver?.carNumber ?? '',
style: AppThemeData.boldTextStyle(color: isDark ? AppThemeData.greyDark700 : AppThemeData.grey700, fontSize: 16),
),
],
),
],
),
RoundedButtonBorder(
title: controller.driverUser.value?.averageRating.toString() ?? '',
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.order.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(() => RentalReviewScreen(), arguments: {'order': controller.order.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,
),
),
),
controller.order.value.status == Constant.orderCompleted || controller.order.value.status == Constant.orderCancelled
? SizedBox()
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
Constant.makePhoneCall(controller.order.value.driver!.phoneNumber ?? '');
},
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("Please wait...".tr);
UserModel? customer = await FireStoreUtils.getUserProfile(controller.order.value.authorID ?? '');
UserModel? driverUser = await FireStoreUtils.getUserProfile(controller.order.value.driverId ?? '');
ShowToastDialog.closeLoader();
Get.to(
const ChatScreen(),
arguments: {
"customerName": customer?.fullName(),
"restaurantName": driverUser?.fullName(),
"orderId": controller.order.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),
],
),
if (controller.order.value.rentalVehicleType != null)
Container(
padding: const EdgeInsets.all(16),
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: [
Text("Vehicle Type".tr, style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark500 : AppThemeData.grey500)),
SizedBox(height: 10),
Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: NetworkImageWidget(imageUrl: controller.order.value.rentalVehicleType!.rentalVehicleIcon ?? "", height: 50, width: 50),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.order.value.rentalVehicleType!.name ?? "",
style: AppThemeData.semiBoldTextStyle(fontSize: 18, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
Text(
controller.order.value.rentalVehicleType!.shortDescription ?? "",
style: AppThemeData.mediumTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark600 : AppThemeData.grey600),
),
],
),
),
const SizedBox(width: 10),
],
),
],
),
),
const SizedBox(height: 15),
Container(
width: Responsive.width(100, context),
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.symmetric(horizontal: 16, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Rental Details".tr, style: AppThemeData.boldTextStyle(fontSize: 16, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900)),
Divider(color: isDark ? AppThemeData.greyDark300 : AppThemeData.grey300),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'Rental Package'.tr,
textAlign: TextAlign.start,
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
),
Text(
controller.order.value.rentalPackageModel!.name.toString().tr,
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'Rental Package Price'.tr,
textAlign: TextAlign.start,
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
),
Text(
Constant.amountShow(amount: controller.order.value.rentalPackageModel!.baseFare.toString()).tr,
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'${'Including'.tr} ${Constant.distanceType.tr}',
textAlign: TextAlign.start,
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
),
Text(
"${controller.order.value.rentalPackageModel!.includedDistance.toString()} ${Constant.distanceType}".tr,
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'Including Hours'.tr,
textAlign: TextAlign.start,
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
),
Text(
"${controller.order.value.rentalPackageModel!.includedHours.toString()} ${'Hr'.tr}".tr,
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'${'Extra'.tr} ${Constant.distanceType}',
textAlign: TextAlign.start,
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
),
Text(
controller.getExtraKm(),
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 10),
// child: Row(
// children: [
// Expanded(
// child: Text(
// 'Extra ${Constant.distanceType}',
// textAlign: TextAlign.start,
// style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
// ),
// ),
// Text(
// "${(double.parse(controller.order.value.endKitoMetersReading!.toString()) - double.parse(controller.order.value.startKitoMetersReading!.toString()) - double.parse(controller.order.value.rentalPackageModel!.includedDistance!.toString()))} ${Constant.distanceType}",
// textAlign: TextAlign.start,
// style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
// ),
// ],
// ),
// ),
controller.order.value.endTime == null
? SizedBox()
: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'Extra Minutes'.tr,
textAlign: TextAlign.start,
style: AppThemeData.mediumTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
),
Text(
"${controller.order.value.endTime == null ? "0" : (((controller.order.value.endTime!.toDate().difference(controller.order.value.startTime!.toDate()).inMinutes) - (int.parse(controller.order.value.rentalPackageModel!.includedHours.toString()) * 60)).clamp(0, double.infinity).toInt().toString())} ${'Min'.tr}",
textAlign: TextAlign.start,
style: AppThemeData.boldTextStyle(fontSize: 14, color: isDark ? AppThemeData.greyDark900 : AppThemeData.grey900),
),
],
),
),
],
),
),
),
const SizedBox(height: 15),
Container(
padding: const EdgeInsets.all(16),
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: [
Text("Order Summary".tr, style: AppThemeData.boldTextStyle(fontSize: 14, color: AppThemeData.grey500)),
const SizedBox(height: 8),
_summaryTile("Subtotal".tr, Constant.amountShow(amount: controller.subTotal.value.toString()), isDark, null),
_summaryTile("Discount".tr, Constant.amountShow(amount: controller.discount.value.toString()), isDark, AppThemeData.dangerDark300),
...List.generate(controller.order.value.taxSetting?.length ?? 0, (index) {
final taxModel = controller.order.value.taxSetting![index];
final taxTitle = "${taxModel.title} ${taxModel.type == 'fix' ? '(${Constant.amountShow(amount: taxModel.tax)})' : '(${taxModel.tax}%)'}";
return _summaryTile(
taxTitle,
Constant.amountShow(amount: Constant.getTaxValue(amount: (controller.subTotal.value - controller.discount.value).toString(), taxModel: taxModel).toString()),
isDark,
null,
);
}),
const Divider(),
_summaryTile("Order Total".tr, Constant.amountShow(amount: controller.totalAmount.value.toString()), isDark, null),
],
),
),
],
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(
children: [
if (controller.order.value.status == Constant.orderInTransit && controller.order.value.paymentStatus == false)
Expanded(
child: RoundedButtonFill(
title: "Pay Now",
onPress: () {
if (controller.order.value.endKitoMetersReading == null ||
controller.order.value.endKitoMetersReading == "0.0" ||
controller.order.value.endKitoMetersReading!.isEmpty) {
ShowToastDialog.showToast("You are not able to pay now until driver adds kilometer".tr);
} else {
Get.bottomSheet(paymentBottomSheet(context, controller, isDark, controller.order.value), isScrollControlled: true, backgroundColor: Colors.transparent);
}
},
color: AppThemeData.primary300,
textColor: AppThemeData.grey900,
),
),
if (controller.order.value.status == Constant.orderPlaced || controller.order.value.status == Constant.driverAccepted)
Expanded(
child: RoundedButtonFill(
title: "Cancel Booking",
onPress: () {
controller.cancelRentalRequest(controller.order.value);
},
color: AppThemeData.danger300,
textColor: AppThemeData.surface,
),
),
],
),
),
],
),
);
},
);
}
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, RentalOrderDetailsController controller, bool isDark, RentalOrderModel orderModel) {
return DraggableScrollableSheet(
initialChildSize: 0.70,
// Start height
minChildSize: 0.30,
// Minimum height
maxChildSize: 0.8,
// Maximum height
expand: false,
//Prevents full-screen takeover
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(
padding: EdgeInsets.zero,
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)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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.primary300,
textColor: AppThemeData.grey900,
onPress: () async {
if (controller.selectedPaymentMethod.value.isEmpty) {
ShowToastDialog.showToast("Please select a payment method".tr);
} else {
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.completeOrder();
} else if (controller.selectedPaymentMethod.value == PaymentGateway.wallet.name) {
if (Constant.userModel!.walletAmount == null || Constant.userModel!.walletAmount! < controller.totalAmount.value) {
ShowToastDialog.showToast("You do not have sufficient wallet balance".tr);
} else {
controller.completeOrder();
}
} else if (controller.selectedPaymentMethod.value == PaymentGateway.cod.name) {
controller.completeOrder();
} 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(RentalOrderDetailsController controller, PaymentGateway value, isDark, String image) {
return Obx(
() => Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Column(
children: [
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),
),
),
const Expanded(child: SizedBox()),
Radio(
value: value.name,
groupValue: controller.selectedPaymentMethod.value,
activeColor: isDark ? AppThemeData.primary300 : AppThemeData.primary300,
onChanged: (value) {
controller.selectedPaymentMethod.value = value.toString();
},
),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:get/get.dart';
import '../../constant/constant.dart';
import '../../controllers/rental_review_controller.dart';
import '../../controllers/theme_controller.dart';
import '../../themes/app_them_data.dart';
import '../../themes/round_button_fill.dart';
import '../../themes/text_field_widget.dart';
import '../../utils/network_image_widget.dart';
class RentalReviewScreen extends StatelessWidget {
const RentalReviewScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX<RentalReviewController>(
init: RentalReviewController(),
builder: (controller) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
elevation: 0,
backgroundColor: AppThemeData.primary300,
leading: GestureDetector(
onTap: () => Get.back(),
child: Icon(Icons.arrow_back_ios, color: isDark ? Colors.white : Colors.black),
),
title: Text(
controller.ratingModel.value != null ? "Update Review".tr : "Add Review".tr,
style: TextStyle(color: isDark ? Colors.white : Colors.black, fontSize: 16),
),
),
body: Obx(
() =>
controller.isLoading.value
? Constant.loader()
: Padding(
padding: const EdgeInsets.only(top: 20),
child: Stack(
children: [
Padding(
padding: const EdgeInsets.only(left: 20, right: 20, top: 50, bottom: 20),
child: Card(
elevation: 2,
color: isDark ? AppThemeData.greyDark50 : AppThemeData.grey50,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(top: 65),
child: Column(
children: [
// Driver Name
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
controller.order.value!.driver?.fullName() ?? "",
style: TextStyle(
color: isDark ? Colors.white : Colors.black87,
fontFamily: AppThemeData.medium,
fontSize: 18,
),
),
),
// Car info
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
controller.driverUser.value?.carNumber?.toUpperCase() ?? '',
style: TextStyle(
color: isDark ? Colors.white : Colors.black87,
fontFamily: AppThemeData.medium,
),
),
const SizedBox(width: 8),
Text(
"${controller.driverUser.value?.carName} ${controller.driverUser.value?.carMakes}",
style: TextStyle(
color: isDark ? Colors.white : Colors.black38,
fontFamily: AppThemeData.medium,
),
),
],
),
const Padding(padding: EdgeInsets.symmetric(vertical: 12), child: Divider(color: Colors.grey)),
// Title
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text(
'How is your trip?'.tr,
style: TextStyle(
fontSize: 18,
color: isDark ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
),
),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Your feedback will help us improve \n driving experience better'.tr,
textAlign: TextAlign.center,
style: TextStyle(
color: isDark ? Colors.white : Colors.black.withOpacity(0.60),
letterSpacing: 0.8,
),
),
),
// Rating
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text(
'Rate for'.tr,
style: TextStyle(
fontSize: 16,
color: isDark ? Colors.white : Colors.black.withOpacity(0.60),
letterSpacing: 0.8,
),
),
),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
controller.order.value!.driver?.fullName() ?? "",
style: TextStyle(
fontSize: 18,
color: isDark ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
),
),
Padding(
padding: const EdgeInsets.only(top: 10),
child: RatingBar.builder(
initialRating: controller.ratings.value,
minRating: 1,
direction: Axis.horizontal,
allowHalfRating: true,
itemCount: 5,
itemBuilder: (context, _) => const Icon(Icons.star, color: Colors.amber),
unratedColor: isDark ? AppThemeData.greyDark400 : AppThemeData.grey400,
onRatingUpdate: (rating) => controller.ratings.value = rating,
),
),
// Comment
Padding(
padding: const EdgeInsets.all(20.0),
child: TextFieldWidget(
hintText: "Type comment....".tr,
controller: controller.comment.value,
maxLine: 5,
),
),
// Submit Button
Padding(
padding: const EdgeInsets.all(20.0),
child: RoundedButtonFill(
title: controller.ratingModel.value != null ? "Update Review" : "Add Review",
color: AppThemeData.primary300,
textColor: isDark ? Colors.white : Colors.black,
onPress: controller.submitReview,
),
),
],
),
),
),
),
),
Align(
alignment: Alignment.topCenter,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(60),
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.15), blurRadius: 8, spreadRadius: 6)],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(60),
child: NetworkImageWidget(
imageUrl: controller.order.value?.driver?.profilePictureURL ?? '',
fit: BoxFit.cover,
height: 110,
width: 110,
),
),
),
),
],
),
),
),
);
},
);
}
}