feat: Gemini AI (doc OCR + face match) + role-assignment endpoints

- GeminiService (integrations/gemini, @Global): extractDocument (OCR fields) + faceMatch; graceful if no GEMINI_API_KEY
- documents: face-match now real via Gemini; POST /admin/documents/:id/ocr extracts fields for CS auto-fill
- roles: PATCH /admin/users/:id/role (admin) + POST /platform/users/role (bootstrap via x-platform-secret)
- config: gemini + platform.secret; .env.example entries

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 20:01:26 +03:00
co-authored by Claude Opus 4.8
parent 04200db438
commit 6234e66471
1541 changed files with 267713 additions and 16 deletions
@@ -0,0 +1,641 @@
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/links.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/home/map_passenger_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../../constant/box_name.dart';
import '../../../controller/firebase/notification_service.dart';
import '../../../controller/functions/launch.dart';
import '../../../main.dart';
class ApplyOrderWidget extends StatelessWidget {
const ApplyOrderWidget({super.key});
@override
Widget build(BuildContext context) {
Color parseColor(String colorHex) {
if (colorHex.isEmpty) return Colors.grey;
try {
String processedHex = colorHex.replaceFirst('#', '').trim();
if (processedHex.length == 6) processedHex = 'FF$processedHex';
return Color(int.parse('0x$processedHex'));
} catch (e) {
return Colors.grey;
}
}
return Obx(() {
final controller = Get.find<MapPassengerController>();
final bool isVisible =
controller.currentRideState.value == RideState.driverApplied ||
controller.currentRideState.value == RideState.driverArrived;
return AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.elasticOut,
// تغيير: جعلنا الإخفاء للأسفل أقل حدة ليكون التحريك أسرع
bottom: isVisible ? 0 : -400,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
boxShadow: [
BoxShadow(
blurRadius: 20,
spreadRadius: 1,
color: Colors.black.withOpacity(0.1),
offset: const Offset(0, -3),
)
],
),
// تغيير: تقليل الحواف الخارجية بشكل كبير
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: GetBuilder<MapPassengerController>(
builder: (c) {
return Column(
mainAxisSize:
MainAxisSize.min, // مهم جداً: يأخذ أقل مساحة ممكنة
children: [
// مقبض صغير
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 10), // تقليل المسافة
// 1. [تغيير جوهري] دمج السعر مع الحالة في صف واحد لتوفير المساحة
_buildCompactHeaderRow(context, c),
const SizedBox(height: 10), // مسافة مضغوطة
// 2. كرت المعلومات المضغوط
_buildCompactInfoCard(context, c, parseColor),
const SizedBox(height: 10), // مسافة مضغوطة
// 3. أزرار الاتصال (Slim)
_buildCompactButtonsRow(context, c),
const SizedBox(height: 10), // مسافة مضغوطة
// 4. شريط الوقت
c.currentRideState.value == RideState.driverArrived
? const DriverArrivePassengerAndWaitMinute()
: const TimeDriverToPassenger(),
],
);
},
),
),
);
});
}
// ---------------------------------------------------------------------------
// [NEW] 1. صف الرأس المضغوط (يحتوي الحالة + الإحصائيات + السعر)
// ---------------------------------------------------------------------------
Widget _buildCompactHeaderRow(
BuildContext context, MapPassengerController controller) {
// تنسيق السعر
final formatter = NumberFormat("#,###");
String formattedPrice = formatter.format(controller.totalPassenger);
// حساب الدقائق
int minutes =
(controller.timeToPassengerFromDriverAfterApplied / 60).ceil();
if (minutes < 1) minutes = 1;
// تنسيق المسافة
String distanceDisplay = "";
try {
double distMeters = double.parse(controller.distanceByPassenger);
if (distMeters >= 1000) {
distanceDisplay = "${(distMeters / 1000).toStringAsFixed(1)} km";
} else {
distanceDisplay = "${distMeters.toInt()} m";
}
} catch (e) {
distanceDisplay = controller.distanceByPassenger;
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// القسم الأيسر: الحالة + Chips
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Driver is on the way'.tr,
style: AppStyle.subtitle.copyWith(
color: Colors.grey[600],
fontWeight: FontWeight.w600,
fontSize: 13, // تصغير الخط
),
),
const SizedBox(height: 6),
Row(
children: [
_buildMiniStatChip(
icon: Icons.access_time_filled_rounded,
text: "$minutes ${'min'.tr}",
color: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1),
),
const SizedBox(width: 8),
_buildMiniStatChip(
icon: Icons.near_me_rounded,
text: distanceDisplay,
color: Colors.orange[800]!,
bgColor: Colors.orange.withOpacity(0.1),
),
],
),
],
),
),
// القسم الأيمن: السعر (كبير وواضح في الزاوية)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
formattedPrice,
style: AppStyle.title.copyWith(
fontSize: 24, // تصغير من 32 إلى 24
fontWeight: FontWeight.w900,
color: AppColor.primaryColor,
height: 1.0,
),
),
Text(
'SYP'.tr,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
),
],
),
],
);
}
Widget _buildMiniStatChip({
required IconData icon,
required String text,
required Color color,
required Color bgColor,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 12, color: color), // تصغير الأيقونة
const SizedBox(width: 4),
Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 12, // تصغير الخط
),
),
],
),
);
}
// ---------------------------------------------------------------------------
// [MODIFIED] 2. كرت المعلومات المضغوط جداً
// ---------------------------------------------------------------------------
Widget _buildCompactInfoCard(BuildContext context,
MapPassengerController controller, Color Function(String) parseColor) {
return Container(
// تقليل الحواف الداخلية للكرت
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.withOpacity(0.1)),
),
child: Column(
children: [
// الصف العلوي: سائق + سيارة
Row(
children: [
// صورة السائق (أصغر)
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.2), width: 2),
),
child: CircleAvatar(
radius: 22, // تصغير من 28 إلى 22
backgroundColor: Colors.grey[200],
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (_, __) =>
const Icon(Icons.person, color: Colors.grey, size: 20),
),
),
const SizedBox(width: 10),
// معلومات نصية
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.driverName,
style: const TextStyle(
fontSize: 15, // تصغير الخط
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Row(
children: [
const Icon(Icons.star_rounded,
color: Colors.amber, size: 14),
Text(
" ${controller.driverRate} • ${controller.model}",
style: TextStyle(
fontSize: 12,
color: Colors.grey[700],
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
],
),
),
// أيقونة السيارة (أصغر)
_buildMicroCarIcon(controller, parseColor),
],
),
const SizedBox(height: 8),
// لوحة السيارة (شريط نحيف جداً)
_buildSlimLicensePlate(controller.licensePlate),
],
),
);
}
Widget _buildMicroCarIcon(
MapPassengerController controller, Color Function(String) parseColor) {
Color carColor = parseColor(controller.colorHex);
return Container(
height: 40, // تصغير من 50
width: 40,
decoration: BoxDecoration(
color: carColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(4),
child: ColorFiltered(
colorFilter: ColorFilter.mode(carColor, BlendMode.srcIn),
child: Image.asset(
box.read(BoxName.carType) == 'Scooter' ||
box.read(BoxName.carType) == 'Pink Bike'
? 'assets/images/moto.png'
: 'assets/images/car3.png',
fit: BoxFit.contain,
),
),
);
}
Widget _buildSlimLicensePlate(String plateNumber) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8),
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.grey.withOpacity(0.3)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
plateNumber,
style: const TextStyle(
fontFamily: 'RobotoMono',
fontSize: 18, // تصغير الرقم
fontWeight: FontWeight.w900,
color: Colors.black87,
letterSpacing: 1.5,
),
),
const Text("SYR",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.black54)),
],
),
);
}
// ---------------------------------------------------------------------------
// [MODIFIED] 3. أزرار الاتصال (Slim Buttons)
// ---------------------------------------------------------------------------
Widget _buildCompactButtonsRow(
BuildContext context, MapPassengerController controller) {
return SizedBox(
height: 40, // تحديد ارتفاع ثابت وصغير للأزرار
child: Row(
children: [
Expanded(
child: _buildSlimButton(
label: 'Message'.tr, // اختصار الكلمة
icon: Icons.chat_bubble_outline_rounded,
color: AppColor.blueColor,
bgColor: AppColor.blueColor.withOpacity(0.08),
onTap: () => _showContactOptionsDialog(context, controller),
),
),
const SizedBox(width: 10), // تقليل المسافة
Expanded(
child: _buildSlimButton(
label: 'Call'.tr, // اختصار الكلمة
icon: Icons.phone_rounded,
color: Colors.white,
bgColor: AppColor.greenColor,
onTap: () {
HapticFeedback.heavyImpact();
makePhoneCall(controller.driverPhone);
},
isPrimary: true,
),
),
],
),
);
}
Widget _buildSlimButton({
required String label,
required IconData icon,
required Color color,
required Color bgColor,
required VoidCallback onTap,
bool isPrimary = false,
}) {
return ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
backgroundColor: bgColor,
foregroundColor: color,
elevation: isPrimary ? 2 : 0,
padding: EdgeInsets.zero, // إزالة الحواشي الداخلية
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 18, color: color), // تصغير الأيقونة
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14, // تصغير الخط
color: color,
),
),
],
),
);
}
// --- النوافذ المنبثقة للرسائل (نفس الكود السابق مع تحسين بسيط) ---
void _showContactOptionsDialog(
BuildContext context, MapPassengerController controller) {
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Quick Message'.tr,
style: AppStyle.title.copyWith(fontSize: 16)),
const SizedBox(height: 15),
..._buildPredefinedMessages(controller),
const Divider(height: 20),
_buildCustomMessageInput(controller, context),
SizedBox(height: MediaQuery.of(context).viewInsets.bottom),
],
),
),
isScrollControlled: true,
);
}
List<Widget> _buildPredefinedMessages(MapPassengerController controller) {
const messages = [
'Hello, I\'m at the agreed-upon location',
'I\'m waiting for you',
"How much longer will you be?",
];
return messages
.map((message) => Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: InkWell(
onTap: () {
_sendMessage(controller, message.tr);
Get.back();
},
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.08),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.withOpacity(0.1)),
),
child: Row(
children: [
Icon(Icons.chat_bubble_outline,
size: 16, color: AppColor.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Text(message.tr,
style: AppStyle.subtitle.copyWith(fontSize: 13))),
],
),
),
),
))
.toList();
}
Widget _buildCustomMessageInput(
MapPassengerController controller, BuildContext context) {
return Row(
children: [
Expanded(
child: Container(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.08),
borderRadius: BorderRadius.circular(20),
),
child: Form(
key: controller.messagesFormKey,
child: TextFormField(
controller: controller.messageToDriver,
decoration: InputDecoration(
hintText: 'Type your message...'.tr,
hintStyle: TextStyle(color: Colors.grey[500], fontSize: 13),
border: InputBorder.none,
contentPadding: const EdgeInsets.only(bottom: 10),
),
),
),
),
),
const SizedBox(width: 8),
InkWell(
onTap: () {
if (controller.messagesFormKey.currentState!.validate()) {
_sendMessage(controller, controller.messageToDriver.text);
controller.messageToDriver.clear();
Get.back();
}
},
child: CircleAvatar(
backgroundColor: AppColor.primaryColor,
radius: 20,
child:
const Icon(Icons.send_rounded, color: Colors.white, size: 16),
),
),
],
);
}
void _sendMessage(MapPassengerController controller, String text) {
NotificationService.sendNotification(
category: 'MSG_FROM_PASSENGER',
target: controller.driverToken.toString(),
title: text.tr,
body: text.tr,
isTopic: false,
tone: 'ding',
driverList: [],
);
}
}
// -----------------------------------------------------------------------------
// مؤشرات الانتظار والوقت (مضغوطة)
// -----------------------------------------------------------------------------
class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
const DriverArrivePassengerAndWaitMinute({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Waiting...'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: Colors.orange)),
Text(
controller.stringRemainingTimeDriverWaitPassenger5Minute,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.orange,
fontSize: 12),
),
],
),
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
backgroundColor: Colors.orange.withOpacity(0.2),
color: Colors.orange,
minHeight: 4,
value:
controller.progressTimerDriverWaitPassenger5Minute.toDouble(),
),
),
],
);
});
}
}
class TimeDriverToPassenger extends StatelessWidget {
const TimeDriverToPassenger({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) {
if (controller.timeToPassengerFromDriverAfterApplied <= 0) {
return const SizedBox();
}
return Column(
children: [
// شريط التقدم فقط لأن الوقت والمسافة موجودان بالأعلى
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
color: AppColor.primaryColor,
minHeight: 4,
value: controller.progressTimerToPassengerFromDriverAfterApplied
.toDouble()
.clamp(0.0, 1.0),
),
),
],
);
});
}
}
@@ -0,0 +1,560 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/controller/payment/payment_controller.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
GetBuilder<MapPassengerController> buttomSheetMapPage() {
Get.put(PaymentController());
return GetBuilder<MapPassengerController>(
builder: (controller) =>
controller.isBottomSheetShown && controller.rideConfirm == false
? const Positioned(
left: 5,
bottom: 0,
right: 5,
child: Column(
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// double.parse(box.read(BoxName.passengerWalletTotal)) <
// 0 &&
// controller.data.isNotEmpty
// ? Container(
// decoration: AppStyle.boxDecoration
// .copyWith(color: AppColor.redColor),
// height: 50,
// width: Get.width * .94,
// child: Padding(
// padding:
// const EdgeInsets.symmetric(horizontal: 8),
// child: Text(
// 'Your trip cost is'.tr +
// ' ${controller.totalCostPassenger.toStringAsFixed(2)} '
// 'But you have a negative salary of'
// .tr +
// '${double.parse(box.read(BoxName.passengerWalletTotal)).toStringAsFixed(2)}'
// ' in your'
// .tr +
// ' ${AppInformation.appName}'
// ' wallet due to a previous trip.'
// .tr,
// style: AppStyle.subtitle,
// ),
// ))
// : const SizedBox(),
// ],
// ),
// const SizedBox(
// height: 5,
// ),
// AnimatedContainer(
// // clipBehavior: Clip.antiAliasWithSaveLayer,
// curve: Curves.easeInCirc,
// onEnd: () {
// controller.height = 250;
// },
// height: controller.heightBottomSheetShown,
// duration: const Duration(seconds: 2),
// child: Column(
// children: [
// controller.data.isEmpty
// ? const SizedBox()
// : Container(
// // width: Get.width * .9,
// height: 100,
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// boxShadow: [
// const BoxShadow(
// color: AppColor.accentColor,
// offset: Offset(2, 2)),
// BoxShadow(
// color: AppColor.accentColor
// .withOpacity(.4),
// offset: const Offset(-2, -2))
// ],
// borderRadius: const BorderRadius.all(
// Radius.circular(15))),
// child: ListView.builder(
// scrollDirection: Axis.horizontal,
// itemCount: controller
// .dataCarsLocationByPassenger.length -
// 1,
// itemBuilder:
// (BuildContext context, int index) {
// return Container(
// color: controller.gender == 'Female'
// ? const Color.fromARGB(
// 255, 246, 52, 181)
// : AppColor.secondaryColor,
// width: Get.width,
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceBetween,
// children: [
// SizedBox(
// width: Get.width * .15,
// child: Padding(
// padding:
// const EdgeInsets.all(8.0),
// child: Image.asset(
// 'assets/images/jeep.png',
// width: 50,
// fit: BoxFit.fill,
// repeat: ImageRepeat.repeatX,
// ),
// ),
// ),
// SizedBox(
// width: Get.width * .55,
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// mainAxisAlignment:
// MainAxisAlignment.spaceEvenly,
// children: [
// Text(
// controller.hours > 0
// ? '${'Your Ride Duration is '.tr}${controller.hours} ${'H and'.tr} ${controller.minutes} ${'m'.tr}'
// : '${'Your Ride Duration is '.tr} ${controller.minutes} m',
// style: AppStyle.subtitle,
// ),
// // Text(
// // '${'You will be thier in'.tr} ${DateFormat('h:mm a').format(controller.newTime)}',
// // style: AppStyle.subtitle,
// // ),
// Text(
// '${'Your trip distance is'.tr} ${controller.distance.toStringAsFixed(2)} ${'KM'.tr}',
// style: AppStyle.subtitle,
// )
// ],
// ),
// ),
// SizedBox(
// width: Get.width * .2,
// child: Padding(
// padding: const EdgeInsets.only(
// right: 5, left: 5),
// child: Column(
// crossAxisAlignment:
// CrossAxisAlignment.center,
// children: [
// Container(
// width: Get.width * .14,
// height: Get.height * .06,
// decoration: BoxDecoration(
// color: AppColor
// .secondaryColor,
// shape:
// BoxShape.rectangle,
// border: Border.all(
// width: 2,
// color: AppColor
// .greenColor)),
// child: Center(
// child: Text(
// '${'Fee is'.tr} \n${controller.totalPassenger.toStringAsFixed(2)}',
// style:
// AppStyle.subtitle,
// ),
// ),
// ),
// controller.promoTaken
// ? const Icon(
// Icons
// .filter_vintage_rounded,
// color:
// AppColor.redColor,
// )
// : const SizedBox(
// height: 0,
// )
// ],
// ),
// ),
// ),
// ],
// ),
// );
// },
// ),
// ),
// const SizedBox(
// height: 5,
// ),
// Container(
// // height: 130,
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// boxShadow: [
// const BoxShadow(
// color: AppColor.accentColor,
// offset: Offset(2, 2)),
// BoxShadow(
// color: AppColor.accentColor.withOpacity(.4),
// offset: const Offset(-2, -2))
// ],
// borderRadius:
// const BorderRadius.all(Radius.circular(15))),
// child: controller.data.isEmpty
// ? const SizedBox()
// : Center(
// child: Padding(
// padding: const EdgeInsets.symmetric(
// horizontal: 5),
// child: Column(
// children: [
// Row(
// children: [
// const Icon(
// Icons.location_on,
// color: AppColor.redColor,
// ),
// const SizedBox(
// width: 10,
// ),
// Text(
// 'From : '.tr,
// style: AppStyle.subtitle,
// ),
// Text(
// controller.data[0]
// ['start_address']
// .toString(),
// style: AppStyle.subtitle,
// )
// ],
// ),
// Row(
// children: [
// const Icon(Icons
// .location_searching_rounded),
// const SizedBox(
// width: 10,
// ),
// Text(
// 'To : '.tr,
// style: AppStyle.subtitle,
// ),
// Text(
// controller.data[0]['end_address'],
// style: AppStyle.subtitle,
// ),
// ],
// ),
// const Divider(
// color: AppColor.accentColor,
// thickness: 1,
// height: 2,
// indent: 1,
// ),
// SizedBox(
// height: 40,
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// Container(
// decoration: BoxDecoration(
// color:
// AppColor.secondaryColor,
// borderRadius:
// BorderRadius.circular(12),
// // border: Border.all(),
// ),
// child: Row(
// children: [
// Icon(
// Icons.monetization_on,
// color: Colors.green[400],
// ),
// InkWell(
// onTap: () async {
// controller
// .changeCashConfirmPageShown();
// Get.find<
// PaymentController>()
// .getPassengerWallet();
// },
// child: GetBuilder<
// PaymentController>(
// builder: (paymentController) =>
// paymentController
// .isCashChecked
// ? Text(
// 'CASH',
// style: AppStyle
// .title,
// )
// : Text(
// '${AppInformation.appName} Wallet',
// style: AppStyle
// .title,
// ),
// ),
// ),
// ],
// ),
// ),
// const SizedBox(
// width: 40,
// ),
// GetBuilder<PaymentController>(
// builder:
// (paymentController) =>
// Container(
// decoration:
// BoxDecoration(
// color: AppColor
// .secondaryColor,
// borderRadius:
// BorderRadius
// .circular(
// 12),
// ),
// child: Row(
// children: [
// Icon(
// Icons
// .qr_code_2_rounded,
// color: Colors
// .green[
// 400],
// ),
// InkWell(
// onTap: () {
// if (controller
// .promoTaken ==
// false) {
// Get.defaultDialog(
// title: 'Add Promo'.tr,
// titleStyle: AppStyle.title,
// content: Column(
// children: [
// SizedBox(
// width: Get.width * .7,
// child: TextFormField(
// controller: controller.promo,
// decoration: InputDecoration(
// labelText: 'Promo Code'.tr,
// hintText: 'Enter promo code'.tr,
// labelStyle: AppStyle.subtitle,
// hintStyle: AppStyle.subtitle,
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(10),
// ),
// filled: true,
// fillColor: Colors.grey[200],
// focusedBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: AppColor.primaryColor,
// width: 2.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// errorBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: Colors.red,
// width: 2.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// enabledBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: Colors.grey,
// width: 1.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// ),
// ),
// ),
// MyElevatedButton(
// title: 'Add Promo'.tr,
// onPressed: () async {
// controller.applyPromoCodeToPassenger();
// },
// )
// ],
// ));
// } else {
// Get.snackbar(
// 'You have promo!'
// .tr,
// '',
// backgroundColor:
// AppColor.redColor);
// }
// },
// child: Text(
// 'Add Promo'
// .tr,
// style: AppStyle
// .title,
// ),
// ),
// ],
// ),
// )),
// ],
// ),
// ),
// SizedBox(
// width: Get.width * .95,
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// controller.isCashSelectedBeforeConfirmRide ==
// false
// ? MyElevatedButton(
// title: 'Next'.tr,
// onPressed: () {
// controller
// .changeCashConfirmPageShown();
// },
// )
// :
// // controller.isPassengerChosen ==
// // false
// // ? MyElevatedButton(
// // title: 'Next'.tr,
// // onPressed: () {
// // controller
// // .onChangedPassengersChoose();
// // Get.defaultDialog(
// // barrierDismissible:
// // false,
// // title:
// // 'How Many Passengers?'
// // .tr,
// // titleStyle:
// // AppStyle
// // .title,
// // content:
// // Column(
// // children: [
// // Text(
// // 'Allowed up to 4 Passengers.'
// // .tr,
// // style: AppStyle
// // .title,
// // ),
// // SizedBox(
// // height:
// // 200, // Set the desired height here
// // child:
// // CupertinoPicker(
// // itemExtent:
// // 32,
// // onSelectedItemChanged:
// // (index) {
// // controller.onChangedPassengerCount(index +
// // 1);
// // },
// // children: [
// // Text('1 Passenger'.tr),
// // Text('2 Passengers'.tr),
// // Text('3 Passengers'.tr),
// // Text('4 Passengers'.tr),
// // ],
// // ),
// // ),
// // MyElevatedButton(
// // title:
// // 'Back',
// // onPressed:
// // () =>
// // Get.back(),
// // )
// // ],
// // ),
// // );
// // },
// // )
// // :
// MyElevatedButton(
// title: 'Confirm Selection'
// .tr,
// onPressed: () {
// controller
// .confirmRideForFirstDriver();
// },
// ),
// ],
// ),
// )
// ],
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// ],
),
)
: const SizedBox());
}
class Details extends StatelessWidget {
const Details({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (controller) => Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'${'Distance is'.tr} ${controller.distance.toStringAsFixed(2)} KM',
style: AppStyle.title,
),
Text(
'${'Duration is'.tr} ${controller.data[0]['duration']['text']}',
style: AppStyle.title,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'Cost for .21/km ${controller.costDistance.toStringAsFixed(2)} ',
style: AppStyle.title,
),
Text(
'${'Cost Duration'.tr} ${controller.averageDuration.toStringAsFixed(2)} is ${controller.costDuration.toStringAsFixed(2)} ',
style: AppStyle.title,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
'Total Driver ${controller.totalDriver.toStringAsFixed(2)}',
style: AppStyle.title,
),
Text(
'totaME ${controller.totalME.toStringAsFixed(2)} ',
style: AppStyle.title,
),
],
),
Text(
'Cost for passenger ${controller.totalPassenger.toStringAsFixed(2)} ',
style: AppStyle.title,
),
],
));
}
}
@@ -0,0 +1,258 @@
// import 'dart:async';
// import 'package:SEFER/constant/box_name.dart';
// import 'package:SEFER/controller/home/map_passenger_controller.dart';
// import 'package:SEFER/main.dart';
// import 'package:SEFER/views/widgets/my_scafold.dart';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:permission_handler/permission_handler.dart';
// import 'package:agora_rtc_engine/agora_rtc_engine.dart';
// import '../../../../constant/api_key.dart';
// import '../../../constant/colors.dart';
// import '../../../constant/style.dart';
// import '../../../controller/firebase/firbase_messge.dart';
// String appId = AK.agoraAppId;
// class PassengerCallPage extends StatefulWidget {
// const PassengerCallPage({
// super.key,
// required this.channelName,
// required this.token,
// required this.remoteID,
// });
// final String channelName, token, remoteID;
// @override
// State<PassengerCallPage> createState() => _PassengerCallPageState();
// }
// class _PassengerCallPageState extends State<PassengerCallPage> {
// int uid = 0;
// int? _remoteUid = 0; // uid of the remote user
// bool _isJoined = false; // Indicates if the local user has joined the channel
// late RtcEngine agoraEngine; // Agora engine instance
// String status = '';
// final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
// GlobalKey<ScaffoldMessengerState>(); // Global key to access the scaffold
// showMessage(String message) {
// scaffoldMessengerKey.currentState?.showSnackBar(SnackBar(
// content: Text(message),
// ));
// }
// initAgora() async {
// await setupVoiceSDKEngine();
// }
// @override
// void initState() {
// super.initState();
// _remoteUid = int.parse(widget.remoteID);
// uid = int.parse(box.read(BoxName.phone));
// // Set up an instance of Agora engine
// initAgora();
// }
// Future<void> setupVoiceSDKEngine() async {
// // retrieve or request microphone permission
// await [Permission.microphone].request();
// //create an instance of the Agora engine
// agoraEngine = createAgoraRtcEngine();
// await agoraEngine.initialize(RtcEngineContext(appId: AK.agoraAppId));
// // Register the event handler
// agoraEngine.registerEventHandler(
// RtcEngineEventHandler(
// onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
// showMessage(
// "Local user uid:${connection.localUid} joined the channel");
// setState(() {
// _isJoined = true;
// status = 'joined'.tr;
// });
// },
// onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
// showMessage("Driver joined the channel".tr);
// setState(() {
// status = "Driver joined the channel".tr;
// _remoteUid = remoteUid;
// });
// },
// onUserOffline: (RtcConnection connection, int? remoteUid,
// UserOfflineReasonType reason) {
// showMessage("Driver left the channel".tr);
// setState(() {
// status = "Driver left the channel".tr;
// _remoteUid = null;
// });
// },
// ),
// );
// }
// void join() async {
// // Set channel options including the client role and channel profile
// ChannelMediaOptions options = const ChannelMediaOptions(
// clientRoleType: ClientRoleType.clientRoleBroadcaster,
// channelProfile: ChannelProfileType.channelProfileCommunication,
// );
// await agoraEngine.joinChannel(
// token: widget.token,
// channelId: widget.channelName,
// options: options,
// uid: uid,
// );
// }
// //https://console.agora.io/invite?sign=5e9e22d06f22caeeada9954c9e908572%253A5ba8aed978a35eab5a5113742502ded2a41478b2a81cb19c71a30776e125b58a
// void leave() {
// setState(() {
// _isJoined = false;
// _remoteUid = null;
// });
// agoraEngine.leaveChannel();
// }
// // Clean up the resources when you leave
// @override
// void dispose() async {
// await agoraEngine.leaveChannel();
// super.dispose();
// }
// // Build UI
// @override
// Widget build(BuildContext context) {
// return MaterialApp(
// scaffoldMessengerKey: scaffoldMessengerKey,
// home: MyScafolld(
// // appBar: AppBar(
// // title: const Text('Get started with Voice Calling'),
// // ),
// title: 'Call Page'.tr,
// isleading: true,
// body: [
// Positioned(
// top: Get.height * .2,
// child: Container(
// height: 100, width: Get.width,
// decoration: AppStyle.boxDecoration,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// GestureDetector(
// onTap: () async {
// // await callController.initAgoraFull();
// // callController.join();
// // FirebaseMessagesController().sendNotificationToPassengerToken(
// // 'Call Income',
// // '${'You have call from driver'.tr} ${box.read(BoxName.nameDriver)}',
// // Get.find<MapDriverController>().tokenPassenger,
// // [
// // callController.token,
// // callController.channelName,
// // callController.uid.toString(),
// // callController.remoteUid.toString(),
// // ],
// // );
// join();
// // callController.fetchToken();
// },
// child: Container(
// width: 50,
// height: 50,
// decoration: const BoxDecoration(
// shape: BoxShape.circle,
// color: AppColor.greenColor),
// child: const Icon(
// Icons.phone,
// size: 35,
// color: AppColor.secondaryColor,
// )),
// ),
// Column(
// children: [
// Text(
// status,
// style: AppStyle.title,
// ),
// Text('Driver Name'),
// ],
// ),
// GestureDetector(
// onTap: () async {
// FirebaseMessagesController()
// .sendNotificationToPassengerToken(
// 'Call End'.tr,
// 'Call End',
// Get.find<MapPassengerController>().driverToken,
// [],
// 'iphone_ringtone.wav',
// );
// leave();
// Get.back();
// // },
// child: Container(
// width: 50,
// height: 50,
// decoration: const BoxDecoration(
// shape: BoxShape.circle, color: AppColor.redColor),
// child: const Icon(
// Icons.phone_disabled_sharp,
// size: 35,
// color: AppColor.secondaryColor,
// )),
// )
// ],
// ),
// // ignore: prefer_const_constructors
// ),
// ),
// // ListView(
// // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
// // children: [
// // // Status text
// // Container(height: 40, child: Center(child: _status())),
// // // Button Row
// // Row(
// // children: <Widget>[
// // Expanded(
// // child: ElevatedButton(
// // child: Text("Join".tr),
// // onPressed: () => {join()},
// // ),
// // ),
// // const SizedBox(width: 10),
// // Expanded(
// // child: ElevatedButton(
// // child: Text("Leave".tr),
// // onPressed: () => {leave()},
// // ),
// // ),
// // ],
// // ),
// // ],
// // ),
// ]),
// );
// }
// // Widget _status() {
// // String statusText;
// //
// // if (!_isJoined) {
// // statusText = 'Join a channel'.tr;
// // } else if (_remoteUid == null)
// // statusText = 'Waiting for a remote user to join...';
// // else
// // statusText = 'Connected to remote user, uid:$_remoteUid';
// //
// // return Text(
// // statusText,
// // );
// // }
// }
@@ -0,0 +1,160 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/home/map_passenger_controller.dart';
import '../../widgets/elevated_btn.dart';
// دالة لإظهار الشيت
void showCancelRideBottomSheet() {
Get.bottomSheet(
const CancelRidePageWidget(),
backgroundColor: Colors.transparent,
isScrollControlled: true,
);
}
// الويدجت مفصولة لترتيب الكود
class CancelRidePageWidget extends StatelessWidget {
const CancelRidePageWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// تأكد من وجود الكنترولر
final controller = Get.find<MapPassengerController>();
final List<String> reasons = [
"Changed my mind".tr,
"Found another transport".tr,
"Driver is taking too long".tr,
"Driver asked me to cancel".tr,
"Wrong pickup location".tr,
"Other".tr,
];
return Container(
height: Get.height * 0.7, // ارتفاع مناسب
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(25)),
),
child: GetBuilder<MapPassengerController>(
builder: (controller) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// مؤشر السحب
Center(
child: Container(
width: 50,
height: 5,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
),
const SizedBox(height: 20),
Text(
'Why do you want to cancel?'.tr,
style: AppStyle.title
.copyWith(fontSize: 18, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Expanded(
child: ListView.separated(
itemCount: reasons.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
bool isSelected = controller.selectedReasonIndex == index;
return Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
reasons[index],
style: TextStyle(
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
color: isSelected
? AppColor.primaryColor
: Colors.black87,
fontSize: 15),
),
trailing: isSelected
? Icon(Icons.radio_button_checked,
color: AppColor.primaryColor)
: Icon(Icons.radio_button_off, color: Colors.grey),
onTap: () {
controller.selectReason(index, reasons[index]);
},
),
// إظهار حقل النص فقط عند اختيار "أخرى"
if (isSelected && reasons[index] == "Other".tr)
Padding(
padding: const EdgeInsets.only(
bottom: 10, left: 10, right: 10),
child: TextField(
controller: controller.otherReasonController,
decoration: InputDecoration(
hintText: "Please write the reason...".tr,
filled: true,
fillColor: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 12),
),
maxLines: 2,
),
)
],
);
},
),
),
const SizedBox(height: 20),
// زر التأكيد
SizedBox(
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.redColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 0,
),
onPressed: () => controller.cancelRide(),
child: Text(
'Confirm Cancellation'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16),
),
),
),
const SizedBox(height: 10),
// زر التراجع
Center(
child: TextButton(
onPressed: () => Get.back(),
child: Text("Don't Cancel".tr,
style: TextStyle(color: Colors.grey[600])),
),
),
],
),
),
);
}
}
@@ -0,0 +1,872 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/main.dart';
import 'package:Tripz/views/home/profile/passenger_profile_page.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import 'package:Tripz/views/widgets/my_textField.dart';
import 'package:intl/intl.dart';
import 'dart:ui';
import '../../../constant/info.dart';
import '../../../controller/functions/tts.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../print.dart';
import '../../widgets/mydialoug.dart';
// --- CarType class (Unchanged) ---
class CarType {
final String carType;
final String carDetail;
final String image;
bool isSelected = false;
CarType(
{required this.carType, required this.carDetail, required this.image});
}
// --- List of Car Types (Unchanged) ---
List<CarType> carTypes = [
CarType(
carType: 'Fixed Price',
carDetail: 'Closest & Cheapest'.tr,
image: 'assets/images/carspeed.png'),
CarType(
carType: 'Comfort',
carDetail: 'Comfort choice'.tr,
image: 'assets/images/blob.png'),
CarType(
carType: 'Electric',
carDetail: 'Quiet & Eco-Friendly'.tr,
image: 'assets/images/electric.png'),
CarType(
carType: 'Lady',
carDetail: 'Lady Captain for girls'.tr,
image: 'assets/images/lady.png'),
CarType(
carType: 'Van',
carDetail: 'Van for familly'.tr,
image: 'assets/images/bus.png'),
CarType(
carType: 'Rayeh Gai',
carDetail: "Best choice for cities".tr,
image: 'assets/images/roundtrip.png'),
];
// --- Main Widget ---
class CarDetailsTypeToChoose extends StatelessWidget {
CarDetailsTypeToChoose({super.key});
final textToSpeechController = Get.put(TextToSpeechController());
void _prepareCarTypes(MapPassengerController controller) {
if (controller.distance > 23) {
if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) {
carTypes.add(CarType(
carType: 'Rayeh Gai',
carDetail: "Best choice for cities".tr,
image: 'assets/images/roundtrip.png'));
}
} else {
carTypes.removeWhere((car) => car.carType == 'Rayeh Gai');
}
}
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) {
_prepareCarTypes(controller);
if (!(controller.isBottomSheetShown) && controller.rideConfirm == false) {
return const SizedBox.shrink();
}
// Main Bottom Sheet Design
return Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: AppColor
.secondaryColor, // Solid background for better performance
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(0, -5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag Handle
Center(
child: Container(
width: 50,
height: 5,
margin: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
),
),
// Header (Title + Trip Info)
_buildModernHeader(controller),
// Warning Message (if any)
_buildNegativeBalanceWarning(controller),
// Car List
SizedBox(
height: 165, // Fixed height for consistency
child: ListView.separated(
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
itemCount: carTypes.length,
separatorBuilder: (context, index) =>
const SizedBox(width: 12),
itemBuilder: (context, index) {
final carType = carTypes[index];
final isSelected = controller.selectedIndex == index;
return _buildVerticalCarCard(
context, controller, carType, isSelected, index);
},
),
),
// Promo Code Button
_buildPromoButton(context, controller),
// Safe Area spacing
SizedBox(height: MediaQuery.of(context).padding.bottom + 10),
],
),
),
);
});
}
// --- UI Components ---
Widget _buildModernHeader(MapPassengerController controller) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Choose your ride'.tr,
style: AppStyle.headTitle.copyWith(
fontSize: 20,
fontWeight: FontWeight.w800,
letterSpacing: 0.5),
),
),
// Trip Info Pill
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColor.primaryColor.withOpacity(0.2)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.directions_car_filled_outlined,
size: 16, color: AppColor.primaryColor),
const SizedBox(width: 6),
Text(
'${controller.distance.toStringAsFixed(1)} ${'KM'.tr}',
style: AppStyle.subtitle.copyWith(
fontSize: 13,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor),
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 8),
height: 12,
width: 1,
color: Colors.grey.shade400),
Icon(Icons.access_time_filled_rounded,
size: 16, color: AppColor.primaryColor),
const SizedBox(width: 6),
Text(
controller.hours > 0
? '${controller.hours}h ${controller.minutes}m'
: '${controller.minutes} min',
style: AppStyle.subtitle.copyWith(
fontSize: 13,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor),
),
],
),
)
],
),
);
}
Widget _buildVerticalCarCard(
BuildContext context,
MapPassengerController controller,
CarType carType,
bool isSelected,
int index) {
return GestureDetector(
onTap: () {
controller.selectCarFromList(index);
_showCarDetailsDialog(
context, controller, carType, textToSpeechController);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
width: 110,
decoration: BoxDecoration(
color: isSelected
? AppColor.primaryColor.withOpacity(0.05)
: Colors.white,
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: isSelected
? AppColor.primaryColor
: Colors.grey.withOpacity(0.2),
width: isSelected ? 2.0 : 1.0,
),
boxShadow: isSelected
? [
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
)
]
: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
)
],
),
child: Stack(
alignment: Alignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Image with subtle scaling if selected
AnimatedScale(
scale: isSelected ? 1.1 : 1.0,
duration: const Duration(milliseconds: 250),
child: Image.asset(
carType.image,
height: 50,
fit: BoxFit.contain,
),
),
const SizedBox(height: 12),
// Car Type Text
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
carType.carType.tr,
style: AppStyle.subtitle.copyWith(
fontWeight:
isSelected ? FontWeight.w800 : FontWeight.w600,
fontSize: 14,
color:
isSelected ? AppColor.primaryColor : Colors.black87,
),
maxLines: 1,
),
),
const SizedBox(height: 6),
// Price Tag
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isSelected
? AppColor.primaryColor
: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: FittedBox(
child: Text(
'${_getPassengerPriceText(carType, controller)} ${'SYP'.tr}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: isSelected ? Colors.white : Colors.black87,
),
),
),
),
],
),
),
// Checkmark Badge for Selected Item
if (isSelected)
Positioned(
top: 8,
right: 8,
child: Container(
padding: const EdgeInsets.all(2),
decoration: const BoxDecoration(
color: AppColor.primaryColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.check, size: 12, color: Colors.white),
),
),
],
),
),
);
}
Widget _buildPromoButton(
BuildContext context, MapPassengerController controller) {
if (controller.promoTaken) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.fromLTRB(20, 10, 20, 5),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _showPromoCodeDialog(context, controller),
borderRadius: BorderRadius.circular(14),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.05),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.grey.withOpacity(0.2)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
shape: BoxShape.circle),
child: Icon(Icons.confirmation_number_outlined,
color: AppColor.primaryColor, size: 20),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Promo Code'.tr,
style: AppStyle.subtitle.copyWith(
fontSize: 14, fontWeight: FontWeight.bold),
),
Text(
'Have a promo code?'.tr,
style: AppStyle.subtitle.copyWith(
fontSize: 12, color: Colors.grey.shade600),
),
],
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 16, color: Colors.grey.shade400)
],
),
),
),
),
);
}
Widget _buildNegativeBalanceWarning(MapPassengerController controller) {
final passengerWallet =
double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ?? 0.0;
if (passengerWallet < 0.0) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.redColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.redColor.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.info_outline_rounded,
color: AppColor.redColor, size: 24),
const SizedBox(width: 12),
Expanded(
child: Text(
'${'You have a negative balance of'.tr} ${passengerWallet.toStringAsFixed(2)} ${'SYP'.tr}.',
style: AppStyle.subtitle.copyWith(
color: AppColor.redColor,
fontWeight: FontWeight.w600,
fontSize: 13),
),
),
],
),
);
}
return const SizedBox.shrink();
}
// --- Logic Helpers (Copied from your previous code to ensure functionality) ---
String _getPassengerPriceText(
CarType carType, MapPassengerController mapPassengerController) {
double rawPrice;
switch (carType.carType) {
case 'Comfort':
rawPrice = mapPassengerController.totalPassengerComfort;
break;
case 'Fixed Price':
rawPrice = mapPassengerController.totalPassengerSpeed;
break;
case 'Electric':
rawPrice = mapPassengerController.totalPassengerElectric;
break;
case 'Awfar Car':
rawPrice = mapPassengerController.totalPassengerBalash;
break;
case 'Scooter':
case 'Pink Bike':
rawPrice = mapPassengerController.totalPassengerScooter;
break;
case 'Van':
rawPrice = mapPassengerController.totalPassengerVan;
break;
case 'Lady':
rawPrice = mapPassengerController.totalPassengerLady;
break;
case 'Rayeh Gai':
rawPrice = mapPassengerController.totalPassengerRayehGai;
break;
default:
return '...';
}
final int roundedPrice = rawPrice.round();
final formatter = NumberFormat.decimalPattern();
return formatter.format(roundedPrice);
}
// --- Dialogs (Styled consistently) ---
void _showPromoCodeDialog(
BuildContext context, MapPassengerController controller) {
Get.dialog(
Dialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
backgroundColor: AppColor.secondaryColor,
elevation: 10,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: controller.promoFormKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Icon(Icons.local_activity_outlined,
size: 40, color: AppColor.primaryColor),
const SizedBox(height: 16),
Text(
'Apply Promo Code'.tr,
textAlign: TextAlign.center,
style: AppStyle.headTitle.copyWith(fontSize: 20),
),
const SizedBox(height: 8),
Text(
'Enter your code below to apply the discount.'.tr,
textAlign: TextAlign.center,
style: AppStyle.subtitle
.copyWith(color: Colors.grey.shade600, fontSize: 14),
),
const SizedBox(height: 24),
MyTextForm(
controller: controller.promo,
label: 'Promo Code'.tr,
hint: 'Enter your promo code'.tr,
type: TextInputType.text,
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
foregroundColor: Colors.grey,
),
child: Text('Cancel'.tr),
),
),
const SizedBox(width: 16),
Expanded(
child: MyElevatedButton(
title: 'Apply'.tr,
onPressed: () {
if (controller.promoFormKey.currentState!
.validate()) {
controller.applyPromoCodeToPassenger(context);
Get.back();
}
},
),
),
],
),
],
),
),
),
),
barrierColor: Colors.black.withOpacity(0.5),
);
}
void _showCarDetailsDialog(
BuildContext context,
MapPassengerController mapPassengerController,
CarType carType,
TextToSpeechController textToSpeechController) {
Get.dialog(
Dialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(28.0)),
backgroundColor: Colors.transparent,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.topCenter,
children: [
Container(
margin: const EdgeInsets.only(top: 60),
padding: const EdgeInsets.fromLTRB(24, 70, 24, 24),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(28.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
carType.carType.tr,
style: AppStyle.headTitle.copyWith(fontSize: 22),
),
const SizedBox(width: 8),
InkWell(
onTap: () => textToSpeechController.speakText(
_getCarDescription(
mapPassengerController, carType)),
borderRadius: BorderRadius.circular(20),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Icon(Icons.volume_up_rounded,
color: AppColor.primaryColor, size: 24),
),
)
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.05),
borderRadius: BorderRadius.circular(16)),
child: Text(
_getCarDescription(mapPassengerController, carType),
textAlign: TextAlign.center,
style: AppStyle.subtitle.copyWith(
color: Colors.black87,
fontSize: 15,
height: 1.4,
),
),
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Get.back(),
child: Text('Back'.tr,
style: TextStyle(color: Colors.grey.shade600)),
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: MyElevatedButton(
kolor: AppColor.greenColor,
title: 'Select This Ride'.tr,
onPressed: () {
Get.back();
_handleCarSelection(
context, mapPassengerController, carType);
},
),
),
],
),
],
),
),
Positioned(
top: 0,
child: Hero(
tag: 'car_${carType.carType}',
child: Image.asset(carType.image, height: 130),
),
),
],
),
),
barrierColor: Colors.black.withOpacity(0.6),
);
}
// --- Logic Helpers (Keep unchanged) ---
String _getCarDescription(
MapPassengerController mapPassengerController, CarType carType) {
switch (carType.carType) {
case 'Comfort':
return mapPassengerController.endNameAddress
.toLowerCase()
.contains("airport".tr)
? "Best choice for a comfortable car with a flexible route and stop points. This airport offers visa entry at this price."
.tr
: 'Best choice for comfort car and flexible route and stops point'
.tr;
case 'Fixed Price':
return 'This trip goes directly from your starting point to your destination for a fixed price. The driver must follow the planned route'
.tr;
case 'Electric':
return 'Travel in a modern, silent electric car. A premium, eco-friendly choice for a smooth ride.'
.tr;
case 'Scooter':
case 'Van':
return "Spacious van service ideal for families and groups. Comfortable, safe, and cost-effective travel together."
.tr;
case 'Pink Bike':
return 'This is for delivery or a motorcycle.'.tr;
case 'Mishwar Vip':
return "Perfect for passengers seeking the latest car models with the freedom to choose any route they desire"
.tr;
case 'Awfar Car':
return "Old and affordable, perfect for budget rides.".tr;
case 'Lady':
return "This trip is for women only".tr;
case 'Rayeh Gai':
return "Rayeh Gai: Round trip service for convenient travel between cities, easy and reliable."
.tr;
default:
return '';
}
}
void _handleCarSelection(BuildContext context,
MapPassengerController mapPassengerController, CarType carType) {
box.write(BoxName.carType, carType.carType);
mapPassengerController.totalPassenger =
_getOriginalPrice(carType, mapPassengerController);
if (carType.carType == 'Mishwar Vip') {
Get.back();
mapPassengerController.mishwariOption();
} else if (carType.carType == 'Lady') {
if (box.read(BoxName.gender).toString() != '') {
mapPassengerController.isBottomSheetShown = false;
mapPassengerController.update();
mapPassengerController.changeCashConfirmPageShown();
} else {
MyDialog().getDialog('Idintify gender',
'You should ideintify your gender for this type of trip!'.tr, () {
Get.to(() => PassengerProfilePage());
});
}
} else if (carType.carType == 'Rayeh Gai') {
Get.defaultDialog(
barrierDismissible: false,
title: "Select betweeen types".tr,
content:
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
_buildRayehGaiOption(mapPassengerController, 'Awfar Car',
mapPassengerController.totalPassengerRayehGaiBalash),
_buildRayehGaiOption(mapPassengerController, 'Fixed Price',
mapPassengerController.totalPassengerRayehGai),
_buildRayehGaiOption(mapPassengerController, 'Comfort',
mapPassengerController.totalPassengerRayehGaiComfort)
]),
cancel: MyElevatedButton(
kolor: AppColor.redColor,
title: 'Cancel'.tr,
onPressed: () => Get.back()),
confirm: MyElevatedButton(
kolor: AppColor.greenColor,
title: 'Next'.tr,
onPressed: () {
mapPassengerController.isBottomSheetShown = false;
mapPassengerController.update();
mapPassengerController.changeCashConfirmPageShown();
}));
} else {
mapPassengerController.isBottomSheetShown = false;
mapPassengerController.update();
mapPassengerController.changeCashConfirmPageShown();
}
}
double _getOriginalPrice(
CarType carType, MapPassengerController mapPassengerController) {
switch (carType.carType) {
case 'Comfort':
return mapPassengerController.totalPassengerComfort;
case 'Fixed Price':
return mapPassengerController.totalPassengerSpeed;
case 'Electric':
return mapPassengerController.totalPassengerElectric;
case 'Awfar Car':
return mapPassengerController.totalPassengerBalash;
case 'Van':
return mapPassengerController.totalPassengerVan;
case 'Lady':
return mapPassengerController.totalPassengerLady;
default:
return 0.0;
}
}
Widget _buildRayehGaiOption(MapPassengerController mapPassengerController,
String carTypeName, double price) {
return GestureDetector(
onTap: () {
Get.back();
mapPassengerController.totalPassenger = price;
mapPassengerController.isBottomSheetShown = false;
mapPassengerController.update();
mapPassengerController.changeCashConfirmPageShown();
},
child: Container(
decoration: AppStyle.boxDecoration1,
padding: const EdgeInsets.all(8.0),
child: Column(children: [
Text(carTypeName.tr),
Text(price.toStringAsFixed(0))
])),
);
}
}
// --- BurcMoney Widget (Unchanged) ---
class BurcMoney extends StatelessWidget {
const BurcMoney({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (mapPassengerController) {
final passengerWallet =
double.tryParse(box.read(BoxName.passengerWalletTotal) ?? '0.0') ??
0.0;
return mapPassengerController.data.isNotEmpty &&
mapPassengerController.isBottomSheetShown &&
!mapPassengerController.rideConfirm &&
passengerWallet < 0.0
? Positioned(
bottom: Get.height * .41,
left: 16,
right: 16,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.redColor.withOpacity(0.8),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
const Icon(
Icons.warning_amber_rounded,
color: Colors.white,
size: 24,
),
const SizedBox(width: 8),
Expanded(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '${'Negative Balance:'.tr} ',
style: AppStyle.subtitle.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
TextSpan(
text:
'${'You have a balance of'.tr} ${passengerWallet.toStringAsFixed(2)} ${box.read(BoxName.countryCode) == 'Egypt' ? 'LE'.tr : 'SYP'.tr} ${'due to a previous trip.'.tr}',
style: AppStyle.subtitle.copyWith(
color: Colors.white,
),
),
],
),
textAlign: TextAlign.start,
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: () async =>
await Get.find<TextToSpeechController>().speakText(
'${'you have a negative balance of'.tr}${passengerWallet.toStringAsFixed(2)}${' in your'.tr} ${AppInformation.appName}${' wallet due to a previous trip.'.tr}'),
child: const Icon(
Icons.headphones,
color: Colors.white,
),
),
],
),
),
)
: const SizedBox();
},
);
}
}
@@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/views/home/my_wallet/passenger_wallet.dart';
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/payment/payment_controller.dart';
import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
class CashConfirmPageShown extends StatelessWidget {
CashConfirmPageShown({super.key});
final PaymentController paymentController = Get.put(PaymentController());
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) {
// شرط الإظهار الرئيسي لم يتغير
return Positioned(
bottom: 0,
left: 0,
right: 0,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
// التحكم في ظهور اللوحة لم يتغير
transform: Matrix4.translationValues(
0, controller.isCashConfirmPageShown ? 0 : Get.height, 0),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
),
],
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// --- 1. رأس الصفحة ---
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Payment Method'.tr,
style: AppStyle.headTitle.copyWith(fontSize: 24),
),
// زر الإغلاق (كان معلقاً في الكود القديم، تم تفعيله هنا)
IconButton(
onPressed: () => controller.changeCashConfirmPageShown(),
icon: const Icon(Icons.close, color: AppColor.writeColor),
),
],
),
const SizedBox(height: 16),
// --- 2. بطاقات اختيار الدفع ---
GetBuilder<PaymentController>(builder: (paymentCtrl) {
// نفس منطق تغيير اللون للسيارات النسائية
final bool isLadyRide = box.read(BoxName.carType) == 'Lady' ||
box.read(BoxName.carType) == 'Pink Bike';
final Color selectedColor =
isLadyRide ? Colors.pink.shade300 : AppColor.primaryColor;
return Column(
children: [
// بطاقة المحفظة
_buildPaymentOptionCard(
icon: Icons.account_balance_wallet_outlined,
title: '${AppInformation.appName} Balance'.tr,
subtitle:
'${'Balance:'.tr} ${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
isSelected: paymentCtrl.isWalletChecked,
selectedColor: selectedColor,
onTap: () =>
paymentCtrl.onChangedPaymentMethodWallet(true),
),
const SizedBox(height: 12),
// بطاقة الكاش
_buildPaymentOptionCard(
icon: Icons.money_rounded,
title: 'Cash'.tr,
subtitle: 'Pay directly to the captain'.tr,
isSelected: paymentCtrl.isCashChecked,
selectedColor: selectedColor,
onTap: () =>
paymentCtrl.onChangedPaymentMethodCash(true),
),
],
);
}),
const SizedBox(height: 24),
// --- 3. أزرار التأكيد (بنفس منطقك القديم تماماً) ---
GetBuilder<PaymentController>(builder: (paymentCtrl) {
final bool isWalletSufficient = (double.tryParse(
box.read(BoxName.passengerWalletTotal) ?? '0') ??
0) >=
controller.totalPassenger;
// إذا تم اختيار المحفظة والرصيد غير كافٍ
if (paymentCtrl.isWalletChecked && !isWalletSufficient) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MyElevatedButton(
title: 'Top up Balance to continue'.tr,
onPressed: () =>
Get.to(() => const PassengerWallet()),
kolor: AppColor.redColor,
),
const SizedBox(height: 8),
TextButton(
onPressed: () =>
paymentCtrl.onChangedPaymentMethodCash(true),
child: Text("Or pay with Cash instead".tr,
style: TextStyle(color: AppColor.primaryColor)),
)
],
);
}
// في كل الحالات الأخرى (كاش، أو محفظة برصيد كافٍ)
else {
return MyElevatedButton(
title: 'Confirm & Find a Ride'.tr,
onPressed: () {
// --- نفس منطقك القديم بالضبط ---
controller.changeCashConfirmPageShown();
// controller.isSearchingWindow = true;
controller.startSearchingForDriver();
// controller.update();
},
);
}
}),
],
),
),
),
);
});
}
// --- ويدجت مساعدة لبناء بطاقة الدفع ---
Widget _buildPaymentOptionCard({
required IconData icon,
required String title,
required String subtitle,
required bool isSelected,
required VoidCallback onTap,
required Color selectedColor,
}) {
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isSelected
? selectedColor.withOpacity(0.1)
: AppColor.writeColor.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? selectedColor
: AppColor.writeColor.withOpacity(0.2),
width: isSelected ? 2.0 : 1.0,
),
),
child: Row(
children: [
Icon(icon,
color: isSelected ? selectedColor : AppColor.writeColor,
size: 28),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style:
AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
Text(subtitle,
style: AppStyle.subtitle.copyWith(
color: AppColor.writeColor.withOpacity(0.7))),
],
),
),
if (isSelected)
Icon(Icons.check_circle_rounded, color: selectedColor, size: 24),
],
),
),
);
}
}
@@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
import 'hexegone_clipper.dart';
GetBuilder<MapPassengerController> hexagonClipper() {
return GetBuilder<MapPassengerController>(
builder: ((controller) => controller.rideConfirm
? Positioned(
top: Get.height * .1,
left: Get.width * .1,
right: Get.width * .1,
child: ClipPath(
clipper:
HexagonClipper(), // CustomClipper to create a hexagon shape
child: AnimatedContainer(
duration: const Duration(microseconds: 300),
height: 250,
width: 250,
// decoration: AppStyle.boxDecoration,
// gradient: const LinearGradient(
// colors: [AppColor.greenColor, AppColor.secondaryColor],
// begin: Alignment.topLeft,
// end: Alignment.bottomCenter,
// ),
// border: Border.all(),
// color: AppColor.secondaryColor,
// borderRadius: BorderRadius.circular(15)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Waiting for Driver ...'.tr,
style: AppStyle.title,
),
// IconButton(
// onPressed: () {
// },
// icon: const Icon(Icons.add),
// ),
// Text(
// controller.dataCarsLocationByPassenger['message']
// [controller.carsOrder]['phone']
// .toString(),
// style: AppStyle.title,
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'${controller.dataCarsLocationByPassenger['message'][controller.carsOrder]['first_name']} ${controller.dataCarsLocationByPassenger['message'][controller.carsOrder]['last_name']}',
style: AppStyle.title,
),
Text(
'Age is '.tr +
controller
.dataCarsLocationByPassenger['message']
[controller.carsOrder]['age']
.toString(),
style: AppStyle.title,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['make']
.toString(),
style: AppStyle.title,
),
Text(
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['model']
.toString(),
style: AppStyle.title,
),
],
),
Text(
'Rating is '.tr +
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['ratingDriver']
.toString(),
style: AppStyle.title,
),
Container(
decoration: BoxDecoration(border: Border.all(width: 2)),
child: Text(
controller.dataCarsLocationByPassenger['message']
[controller.carsOrder]['car_plate']
.toString(),
style: AppStyle.title,
),
),
],
),
),
),
)
: const SizedBox()));
}
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
// import 'package:intl/intl.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
class DriverTimeArrivePassengerPage extends StatelessWidget {
const DriverTimeArrivePassengerPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (controller) {
return controller.remainingTime == 0
? Positioned(
bottom: Get.height * .35,
right: Get.width * .05,
child: Stack(
alignment: Alignment.center,
children: [
Container(
decoration: AppStyle.boxDecoration,
// width: 50,
// height: 50,
child: Padding(
padding: const EdgeInsetsDirectional.only(
start: 5, end: 5),
child: Column(
children: [
Text(
controller.durationByPassenger.toString() +
' to arrive you.'.tr,
style: AppStyle.title,
),
Text(
" ${DateFormat('h:mm a').format(controller.newTime)}",
style: AppStyle.title,
),
],
),
))
],
),
)
: const SizedBox();
},
);
}
}
@@ -0,0 +1,457 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/constant/table_names.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../main.dart';
// ---------------------------------------------------
// -- Widget for Destination Point Search (Optimized) --
// ---------------------------------------------------
/// A more optimized and cleaner implementation of the destination search form.
///
/// Improvements:
/// 1. **Widget Refactoring**: The UI is broken down into smaller, focused widgets
/// (_SearchField, _QuickActions, _SearchResults) to prevent unnecessary rebuilds.
/// 2. **State Management Scoping**: `GetBuilder` is used only on widgets that
/// actually need to update, not the entire form.
/// 3. **Reduced Build Logic**: Logic like reading from `box` is done once.
/// 4. **Readability**: Code is cleaner and easier to follow.
GetBuilder<MapPassengerController> formSearchPlacesDestenation() {
// --- [تحسين] قراءة القيم مرة واحدة في بداية البناء ---
// Store box values in local variables to avoid repeated calls inside the build method.
final String addWorkValue =
box.read(BoxName.addWork)?.toString() ?? 'addWork';
final String addHomeValue =
box.read(BoxName.addHome)?.toString() ?? 'addHome';
// --- [ملاحظة] تأكد من أن القيم الأولية موجودة ---
// This initialization can be moved to your app's startup logic or a splash screen controller.
if (addWorkValue.isEmpty || addHomeValue.isEmpty) {
box.write(BoxName.addWork, 'addWork');
box.write(BoxName.addHome, 'addHome');
}
return GetBuilder<MapPassengerController>(
id: 'destination_form', // Use an ID to allow targeted updates
builder: (controller) {
return Column(
children: [
// --- Widget for the search text field ---
_SearchField(controller: controller),
// --- Widget for "Add Work" and "Add Home" buttons ---
_QuickActions(
controller: controller,
addWorkValue: addWorkValue,
addHomeValue: addHomeValue,
),
// --- Widget for displaying search results, wrapped in its own GetBuilder ---
_SearchResults(),
],
);
},
);
}
// ---------------------------------------------------
// -- Private Helper Widgets for Cleaner Code --
// ---------------------------------------------------
/// A dedicated widget for the search input field.
class _SearchField extends StatefulWidget {
final MapPassengerController controller;
const _SearchField({required this.controller});
@override
State<_SearchField> createState() => _SearchFieldState();
}
class _SearchFieldState extends State<_SearchField> {
Timer? _debounce;
// --- [إصلاح] Listener لتحديث الواجهة عند تغيير النص لإظهار/إخفاء زر المسح ---
void _onTextChanged() {
if (mounted) {
setState(() {});
}
}
@override
void initState() {
super.initState();
// Add listener to update the suffix icon when text changes
widget.controller.placeDestinationController.addListener(_onTextChanged);
}
// --- [تحسين] إضافة Debouncer لتأخير البحث أثناء الكتابة ---
void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
if (query.length > 2) {
widget.controller.getPlaces();
widget.controller.changeHeightPlaces();
} else if (query.isEmpty) {
widget.controller.clearPlacesDestination();
widget.controller.changeHeightPlaces();
}
});
}
@override
void dispose() {
_debounce?.cancel();
// Remove the listener to prevent memory leaks
widget.controller.placeDestinationController.removeListener(_onTextChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: widget.controller.placeDestinationController,
onChanged: _onSearchChanged,
decoration: InputDecoration(
hintText: widget.controller.hintTextDestinationPoint,
hintStyle: AppStyle.subtitle.copyWith(color: Colors.grey[600]),
prefixIcon:
const Icon(Icons.search, color: AppColor.primaryColor),
// --- [إصلاح] تم استبدال Obx بشرط بسيط لأن `setState` يعيد بناء الواجهة الآن ---
suffixIcon: widget
.controller.placeDestinationController.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear, color: Colors.grey[400]),
onPressed: () {
widget.controller.placeDestinationController.clear();
// The listener will automatically handle the UI update
// And _onSearchChanged will handle clearing the results
},
)
: null, // Use null instead of SizedBox for better practice
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primaryColor),
),
filled: true,
fillColor: Colors.grey[50],
),
),
),
const SizedBox(width: 8.0),
IconButton(
onPressed: () {
widget.controller.changeMainBottomMenuMap();
widget.controller.changePickerShown();
},
icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30),
tooltip: widget.controller.isAnotherOreder
? 'Pick destination on map'.tr
: 'Pick on map'.tr,
),
],
),
);
}
}
/// A dedicated widget for the quick action buttons (Work/Home).
class _QuickActions extends StatelessWidget {
final MapPassengerController controller;
final String addWorkValue;
final String addHomeValue;
const _QuickActions({
required this.controller,
required this.addWorkValue,
required this.addHomeValue,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildQuickActionButton(
icon: Icons.work_outline,
text: addWorkValue == 'addWork' ? 'Add Work'.tr : 'To Work'.tr,
onTap: () {
if (addWorkValue == 'addWork') {
controller.workLocationFromMap = true;
controller.changeMainBottomMenuMap();
controller.changePickerShown();
} else {
_handleQuickAction(controller, BoxName.addWork, 'To Work');
}
},
onLongPress: () => _showChangeLocationDialog(controller, 'Work'),
),
_buildQuickActionButton(
icon: Icons.home_outlined,
text: addHomeValue == 'addHome' ? 'Add Home'.tr : 'To Home'.tr,
onTap: () {
if (addHomeValue == 'addHome') {
controller.homeLocationFromMap = true;
controller.changeMainBottomMenuMap();
controller.changePickerShown();
} else {
_handleQuickAction(controller, BoxName.addHome, 'To Home');
}
},
onLongPress: () => _showChangeLocationDialog(controller, 'Home'),
),
],
),
);
}
}
/// A dedicated widget for the search results list.
/// It uses its own `GetBuilder` to only rebuild when the list of places changes.
class _SearchResults extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
id: 'places_list', // Use a specific ID for targeted updates
builder: (controller) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: controller.placesDestination.isNotEmpty ? 300 : 0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
),
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: controller.placesDestination.length,
separatorBuilder: (context, index) =>
const Divider(height: 1, color: Colors.grey),
itemBuilder: (BuildContext context, int index) {
final res = controller.placesDestination[index];
final title = res['name_ar'] ?? res['name'] ?? 'Unknown Place';
final address = res['address'] ?? 'Details not available';
final latitude = res['latitude'];
final longitude = res['longitude'];
return ListTile(
leading: const Icon(Icons.place, size: 30, color: Colors.grey),
title: Text(
title,
style:
AppStyle.subtitle.copyWith(fontWeight: FontWeight.w500),
),
subtitle: Text(
address,
style: TextStyle(color: Colors.grey[600], fontSize: 12),
),
trailing: IconButton(
icon: const Icon(Icons.favorite_border, color: Colors.grey),
onPressed: () => _handleAddToFavorites(
context, latitude, longitude, title),
),
onTap: () => _handlePlaceSelection(
controller, latitude, longitude, title, index),
);
},
),
);
},
);
}
// --- [تحسين] استخراج المنطق المعقد إلى دوال مساعدة ---
Future<void> _handleAddToFavorites(BuildContext context, dynamic latitude,
dynamic longitude, String title) async {
if (latitude != null && longitude != null) {
await sql.insertMapLocation({
'latitude': latitude,
'longitude': longitude,
'name': title,
'rate': 'N/A',
}, TableName.placesFavorite);
Toast.show(
context,
'$title ${'Saved Successfully'.tr}',
AppColor.primaryColor,
);
} else {
Toast.show(
context,
'Invalid location data',
AppColor.redColor,
);
}
}
Future<void> _handlePlaceSelection(MapPassengerController controller,
dynamic latitude, dynamic longitude, String title, int index) async {
if (latitude == null || longitude == null) {
Toast.show(Get.context!, 'Invalid location data', AppColor.redColor);
return;
}
// Save to recent locations
await sql.insertMapLocation({
'latitude': latitude,
'longitude': longitude,
'name': title,
'rate': 'N/A',
'createdAt': DateTime.now().toIso8601String(),
}, TableName.recentLocations);
final destLatLng = LatLng(
double.parse(latitude.toString()), double.parse(longitude.toString()));
if (controller.isAnotherOreder) {
// **Another Order Flow**
await _handleAnotherOrderSelection(controller, destLatLng);
} else {
// **Regular Order Flow**
_handleRegularOrderSelection(controller, destLatLng, index);
}
}
Future<void> _handleAnotherOrderSelection(
MapPassengerController controller, LatLng destination) async {
controller.myDestination = destination;
controller.clearPlacesDestination(); // Helper method in controller
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}');
controller.isPickerShown = false;
controller.passengerStartLocationFromMap = false;
controller.changeMainBottomMenuMap();
controller.showBottomSheet1();
}
void _handleRegularOrderSelection(
MapPassengerController controller, LatLng destination, int index) {
controller.passengerLocation = controller.newMyLocation;
controller.myDestination = destination;
controller.convertHintTextDestinationNewPlaces(index);
controller.clearPlacesDestination(); // Helper method in controller
controller.changeMainBottomMenuMap();
controller.passengerStartLocationFromMap = true;
controller.isPickerShown = true;
}
}
// ---------------------------------------------------
// -- Helper Functions (kept from original code) --
// ---------------------------------------------------
Widget _buildQuickActionButton({
required IconData icon,
required String text,
VoidCallback? onTap,
VoidCallback? onLongPress,
}) {
return InkWell(
onTap: onTap,
onLongPress: onLongPress,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: AppColor.blueColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: AppColor.blueColor.withOpacity(0.3)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: AppColor.blueColor),
const SizedBox(height: 4.0),
Text(
text,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
color: AppColor.blueColor, fontWeight: FontWeight.w500),
),
],
),
),
);
}
void _showChangeLocationDialog(
MapPassengerController controller, String locationType) {
Get.defaultDialog(
title: 'Change $locationType location?'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'Yes'.tr,
onPressed: () {
Get.back();
if (locationType == 'Work') {
controller.workLocationFromMap = true;
} else {
controller.homeLocationFromMap = true;
}
controller.changeMainBottomMenuMap();
controller.changePickerShown();
},
),
);
}
void _handleQuickAction(
MapPassengerController controller, String boxName, String hintText) async {
// --- [تحسين] قراءة وتحويل الإحداثيات بأمان أكبر ---
try {
final locationString = box.read(boxName).toString();
final parts = locationString.split(',');
final latLng = LatLng(
double.parse(parts[0]),
double.parse(parts[1]),
);
controller.hintTextDestinationPoint = hintText;
controller.changeMainBottomMenuMap();
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${latLng.latitude},${latLng.longitude}',
);
controller.currentLocationToFormPlaces = false;
controller.clearPlacesDestination(); // Helper method in controller
controller.passengerStartLocationFromMap = false;
controller.isPickerShown = false;
controller.showBottomSheet1();
} catch (e) {
// Handle error if parsing fails
print("Error handling quick action: $e");
Toast.show(Get.context!, "Failed to get location".tr, AppColor.redColor);
}
}
@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
// ---------------------------------------------------
// -- Widget for Start Point Search (Updated) --
// ---------------------------------------------------
GetBuilder<MapPassengerController> formSearchPlacesStart() {
return GetBuilder<MapPassengerController>(
id: 'start_point_form', // إضافة معرف لتحديث هذا الجزء فقط عند الحاجة
builder: (controller) => Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: [
// --- حقل البحث النصي ---
Expanded(
child: TextFormField(
controller: controller.placeStartController,
onChanged: (value) {
if (controller.placeStartController.text.length > 2) {
controller.getPlacesStart();
} else if (controller.placeStartController.text.isEmpty) {
controller.clearPlacesStart();
}
},
decoration: InputDecoration(
hintText: 'Search for a starting point'.tr,
hintStyle:
AppStyle.subtitle.copyWith(color: Colors.grey[600]),
prefixIcon:
const Icon(Icons.search, color: AppColor.primaryColor),
suffixIcon: controller.placeStartController.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear, color: Colors.grey[400]),
onPressed: () {
controller.placeStartController.clear();
controller.clearPlacesStart();
},
)
: null,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primaryColor),
),
filled: true,
fillColor: Colors.grey[50],
),
),
),
const SizedBox(width: 8.0),
// --- أيقونة اختيار الموقع من الخريطة (الجزء المضاف) ---
IconButton(
onPressed: () {
// هذا السطر مهم جداً: نخبر الكونترولر أننا نحدد نقطة البداية الآن
controller.passengerStartLocationFromMap = true;
// إخفاء القائمة السفلية وفتح مؤشر الخريطة (Picker)
controller.changeMainBottomMenuMap();
controller.changePickerShown();
},
icon: Icon(Icons.location_on_outlined,
color: AppColor.accentColor, size: 30),
tooltip: 'Pick start point on map'.tr,
),
],
),
),
// --- قائمة نتائج البحث ---
AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: controller.placesStart.isNotEmpty ? 300 : 0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
),
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: controller.placesStart.length,
separatorBuilder: (context, index) =>
const Divider(height: 1, color: Colors.grey),
itemBuilder: (BuildContext context, int index) {
var res = controller.placesStart[index];
var title = res['name_ar'] ?? res['name'] ?? 'Unknown Place';
var address = res['address'] ?? 'Details not available';
return ListTile(
leading: const Icon(Icons.place, size: 30, color: Colors.grey),
title: Text(title,
style: AppStyle.subtitle
.copyWith(fontWeight: FontWeight.w500)),
subtitle: Text(address,
style: TextStyle(color: Colors.grey[600], fontSize: 12)),
onTap: () {
var latitude = res['latitude'];
var longitude = res['longitude'];
if (latitude != null && longitude != null) {
// تحديث موقع الراكب (نقطة الانطلاق) بناءً على الاختيار
controller.passengerLocation =
LatLng(double.parse(latitude), double.parse(longitude));
// تحديث النص في الحقل
controller.placeStartController.text = title;
// مسح النتائج
controller.clearPlacesStart();
// إغلاق القائمة والعودة للخريطة لرؤية النتيجة (اختياري حسب منطق تطبيقك)
controller.changeMainBottomMenuMap();
controller.update();
}
},
);
},
),
),
],
),
);
}
@@ -0,0 +1,167 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/table_names.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../main.dart';
GetBuilder<MapPassengerController> formSearchPlaces(int index) {
// DbSql sql = DbSql.instance;
return GetBuilder<MapPassengerController>(
builder: (controller) => Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Container(
decoration:
const BoxDecoration(color: AppColor.secondaryColor),
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(
borderRadius: BorderRadius.only(),
gapPadding: 4,
borderSide: BorderSide(
color: AppColor.redColor,
width: 2,
)),
suffixIcon: const Icon(Icons.search),
hintText: controller.hintTextwayPoint0.tr,
hintStyle: AppStyle.title,
hintMaxLines: 1,
prefixIcon: IconButton(
onPressed: () {
controller.allTextEditingPlaces[index].clear();
controller.clearPlaces(index);
},
icon: Icon(
Icons.clear,
color: Colors.red[300],
),
),
),
controller: controller.allTextEditingPlaces[index],
onChanged: (value) {
if (controller.allTextEditingPlaces[index].text.length >
5) {
controller.getPlacesListsWayPoint(index);
controller.changeHeightPlacesAll(index);
}
},
// onEditingComplete: () => controller.changeHeight(),
),
),
),
controller.placeListResponseAll[index].isEmpty
? InkWell(
onTap: () {
controller.startLocationFromMapAll[index] = true;
controller.wayPointIndex = index;
Get.back();
// controller.changeMainBottomMenuMap();
controller.changeWayPointStopsSheet();
controller.changePickerShown();
},
child: Text(
'Choose from Map'.tr + ' $index'.tr,
style:
AppStyle.title.copyWith(color: AppColor.blueColor),
),
)
: const SizedBox(),
Container(
height: controller.placeListResponseAll[index].isNotEmpty
? controller.height
: 0,
color: AppColor.secondaryColor,
child: ListView.builder(
itemCount: controller.placeListResponseAll[index].length,
itemBuilder: (BuildContext context, int i) {
var res = controller.placeListResponseAll[index][i];
return InkWell(
onTap: () async {
controller.changeHeightPlaces();
if (controller.currentLocationToFormPlacesAll[index] ==
true) {
controller.newStartPointLocation =
controller.passengerLocation;
} else {
controller.passengerLocation =
controller.newStartPointLocation;
}
controller.convertHintTextPlaces(index, res);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Image.network(
res['icon'],
width: 20,
),
IconButton(
onPressed: () async {
await sql.insertMapLocation({
'latitude': res['geometry']
['location']['lat'],
'longitude': res['geometry']
['location']['lng'],
'name': res['name'].toString(),
'rate': res['rating'].toString(),
}, TableName.placesFavorite);
Toast.show(
context,
'${res['name']} ${'Saved Sucssefully'.tr}',
AppColor.primaryColor);
},
icon: const Icon(Icons.favorite_border),
),
],
),
Column(
children: [
Text(
res['name'].toString(),
style: AppStyle.title,
),
Text(
res['vicinity'].toString(),
style: AppStyle.subtitle,
),
],
),
Column(
children: [
Text(
'rate',
style: AppStyle.subtitle,
),
Text(
res['rating'].toString(),
style: AppStyle.subtitle,
),
],
),
],
),
const Divider(
thickness: 1,
)
],
),
),
);
},
),
)
],
));
}
@@ -0,0 +1,157 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:Tripz/controller/home/points_for_rider_controller.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
// import '../../../controller/functions/location_controller.dart'; // Un-comment if needed
// import '../../../controller/home/device_tier.dart'; // Removed to rely on Controller logic
import '../../../controller/home/map_passenger_controller.dart';
import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart';
class GoogleMapPassengerWidget extends StatelessWidget {
GoogleMapPassengerWidget({super.key});
final WayPointController wayPointController = Get.put(WayPointController());
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (controller) => controller.isLoading
? const MyCircularProgressIndicator()
: Positioned(
bottom: Get.height * .2,
top: 0,
left: 0,
right: 0,
child: GoogleMap(
onMapCreated: controller.onMapCreated,
// ✅ Camera Bounds
cameraTargetBounds: CameraTargetBounds(controller.boundsdata),
// ✅ Performance: Smoother zoom limits for low-end devices
minMaxZoomPreference: controller.lowPerf
? const MinMaxZoomPreference(6, 17)
: const MinMaxZoomPreference(6, 18),
// ✅ Destination Selection on Long Press
onLongPress: (LatLng argument) {
MyDialog().getDialog('Are you want to go to this site'.tr, '',
() async {
controller.clearPolyline();
// Ensure we have car data available before routing
// if (controller.dataCarsLocationByPassenger != null) {
// i use this check if needed
if (controller.carsLocationByPassenger.isNotEmpty) {
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${argument.latitude},${argument.longitude}',
);
Get.back(); // Close Dialog
await controller.bottomSheet();
controller.showBottomSheet1();
} else {
Get.back();
Get.snackbar(
'We Are Sorry That we dont have cars in your Location!'
.tr,
'',
colorText: AppColor.redColor,
duration: const Duration(seconds: 5),
backgroundColor: AppColor.secondaryColor,
icon: const Icon(Icons.error, color: AppColor.redColor),
titleText: Text('Error'.tr,
style: const TextStyle(color: AppColor.redColor)),
messageText: Text(
'We Are Sorry That we dont have cars in your Location!'
.tr,
style: AppStyle.title),
);
}
});
},
// ✅ Hide UI elements on tap
onTap: (argument) {
controller.hidePlaces();
},
initialCameraPosition: CameraPosition(
target: controller.passengerLocation,
zoom: controller.lowPerf ? 14.5 : 15,
),
// ✅ Markers
markers: controller.markers.toSet(),
// ✅ Polygons (e.g., University/Country borders)
polygons: controller.polygons,
// ✅ Polylines: Switch to lighter version if lowPerf is detected
polylines: controller.lowPerf
? controller.polyLinesLight.toSet()
: controller.polyLines.toSet(),
// ✅ Map Type: Switch to Normal map on low-end devices to save RAM
mapType: controller.lowPerf
? MapType.normal
: (controller.mapType
? MapType.satellite
: MapType
.normal), // Changed terrain default to normal for better performance
// ✅ UI Settings for Performance
myLocationButtonEnabled: false,
mapToolbarEnabled: false,
tiltGesturesEnabled:
false, // Disable tilt to save GPU resources
// Lite Mode (Static image) only on very low-end Androids if needed,
// but usually handled by lowPerf logic in mapType/Traffic
liteModeEnabled: Platform.isAndroid && controller.lowPerf,
trafficEnabled: controller.mapTrafficON && !controller.lowPerf,
buildingsEnabled: !controller.lowPerf,
rotateGesturesEnabled:
!controller.lowPerf, // Disable rotation on low-end
// ✅ Camera Movement Logic
onCameraMove: (CameraPosition position) {
// 1. Always update current view target (for pickers)
controller.newMyLocation = position.target;
// 2. Handle Drag-to-Select for specific states
if (controller.startLocationFromMap == true) {
controller.newStartPointLocation = position.target;
} else if (controller.passengerStartLocationFromMap == true) {
controller.newStartPointLocation = position.target;
}
// 3. Handle Waypoints Dragging
int waypointsLength =
Get.find<WayPointController>().wayPoints.length;
if (waypointsLength > 0 &&
controller.wayPointIndex >= 0 &&
controller.wayPointIndex <
controller.placesCoordinate.length) {
controller.placesCoordinate[controller.wayPointIndex] =
'${position.target.latitude},${position.target.longitude}';
}
// 4. Throttle heavy calculations (Reverse Geocoding / API calls)
if (controller.lowPerf) {
controller.onCameraMoveThrottled(position);
}
},
myLocationEnabled: false,
),
),
);
}
}
@@ -0,0 +1,52 @@
import 'dart:math';
import 'package:flutter/material.dart';
class HexagonClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
final height = size.height;
final width = size.width;
final centerX = width / 2;
final centerY = height / 2;
final radius = width / 2;
const angle = 2 * pi / 10; // Angle between each side of the hexagon
// Start at the top right vertex of the hexagon
final startX = centerX + radius * cos(0);
final startY = centerY + radius * sin(0);
path.moveTo(startX, startY);
// Draw the remaining sides of the hexagon
for (int i = 1; i < 10; i++) {
final x = centerX + radius * cos(angle * i);
final y = centerY + radius * sin(angle * i);
path.lineTo(x, y);
}
path.close();
return path;
}
@override
bool shouldReclip(HexagonClipper oldClipper) => false;
}
class ArrowClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path();
path.moveTo(0, size.height / 2);
path.lineTo(size.width / 2, 0);
path.lineTo(size.width, size.height / 2);
path.lineTo(size.width / 2, size.height);
path.close();
return path;
}
@override
bool shouldReclip(ArrowClipper oldClipper) => false;
}
@@ -0,0 +1,213 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/colors.dart';
import '../../../controller/functions/tts.dart';
import '../../../controller/home/ios_live_activity_service.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/home/vip_waitting_page.dart';
import '../../../print.dart';
// --- الدالة الرئيسية بالتصميم الجديد ---
GetBuilder<MapPassengerController> leftMainMenuIcons() {
Get.put(TextToSpeechController());
return GetBuilder<MapPassengerController>(
builder: (controller) => Positioned(
// تم تعديل الموضع ليتناسب مع التصميم الجديد
top: Get.height * .01,
left: 0,
right: 0,
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0), // لإنشاء شكل الكبسولة
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 8.0, sigmaY: 8.0), // تأثير الزجاج المصنفر
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.4), // لون شبه شفاف
borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: AppColor.secondaryColor),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, // ليأخذ الشريط حجم الأزرار فقط
children: [
// --- تم استخدام دالة مساعدة جديدة للزر ---
_buildMapActionButton(
icon: Icons.satellite_alt_outlined,
tooltip: 'Toggle Map Type',
onPressed: () => controller.changeMapType(),
),
// _buildVerticalDivider(),
// _buildMapActionButton(
// icon: Icons.traffic_outlined,
// tooltip: 'Toggle Traffic',
// onPressed: () => controller.changeMapTraffic(),
// ),
_buildVerticalDivider(),
_buildMapActionButton(
icon: Icons.my_location_rounded,
tooltip: 'Go to My Location',
onPressed: () {
controller.mapController?.animateCamera(
CameraUpdate.newLatLng(
LatLng(
controller.passengerLocation.latitude,
controller.passengerLocation.longitude,
),
),
);
},
),
_buildVerticalDivider(),
_buildMapActionButton(
icon: Octicons.watch,
tooltip: 'VIP Waiting Page',
onPressed: () => Get.to(() => VipWaittingPage()),
),
// _buildMapActionButton(
// icon: Octicons.ellipsis,
// tooltip: 'test',
// onPressed: () => Get.to(() => TestPage()),
// ),
],
),
),
),
),
),
),
);
}
// --- دالة مساعدة جديدة لإنشاء الأزرار بشكل أنيق ---
Widget _buildMapActionButton({
required IconData icon,
required String tooltip,
required VoidCallback onPressed,
}) {
return IconButton(
onPressed: onPressed,
icon: Icon(icon, color: AppColor.writeColor, size: 22),
tooltip: tooltip,
splashRadius: 22,
padding: const EdgeInsets.all(12),
constraints: const BoxConstraints(), // لإزالة المساحات الافتراضية
);
}
// --- ويدجت للفاصل الرأسي بين الأزرار ---
Widget _buildVerticalDivider() {
return Container(
height: 20,
width: 1,
color: AppColor.writeColor.withOpacity(0.2),
);
}
// --- باقي الكود الخاص بك يبقى كما هو بدون تغيير ---
class TestPage extends StatelessWidget {
const TestPage({super.key});
@override
Widget build(BuildContext context) {
final random = Random();
return Scaffold(
appBar: AppBar(
title: const Text('iOS Live Activity Test'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// زر البدء
ElevatedButton(
onPressed: () async {
print("🍎 محاولة تشغيل Live Activity (Start)...");
try {
await IosLiveActivityService.startRideActivity(
rideId: "123",
driverName: "تجربة مبدئية",
carDetails: "تويوتا • أسود",
etaText: "5 دقائق",
progress: 0.2,
);
Log.print(
"✅ تم تشغيل Live Activity بنجاح! أغلق الشاشة لترى النتيجة.");
} catch (e) {
Log.print("❌ خطأ في Start Live Activity: $e");
}
},
child: const Text('Start Activity'),
),
const SizedBox(height: 16),
// زر التحديث العشوائي
ElevatedButton(
onPressed: () async {
Log.print("🔄 محاولة تحديث Live Activity (Update)...");
// توليد بيانات عشوائية للاختبار
final statuses = ['waiting', 'ongoing'];
final status = statuses[random.nextInt(statuses.length)];
final int minutes = random.nextInt(15) + 1; // 1–15
final String eta = "$minutes دقائق";
final double progress = (random.nextDouble() * 0.9) + 0.05;
// بين 0.05 و 0.95 تقريبًا
try {
await IosLiveActivityService.updateRideActivity(
status: status,
driverName:
status == 'waiting' ? 'السائق في الطريق' : 'السائق معك',
carDetails: "تويوتا • أسود",
etaText: eta,
progress: progress,
);
Log.print(
"✅ تم تحديث Live Activity: status=$status, eta=$eta, progress=$progress");
} catch (e) {
Log.print("❌ خطأ في Update Live Activity: $e");
}
},
child: const Text('Update (Random)'),
),
const SizedBox(height: 16),
// زر الإنهاء
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
onPressed: () async {
Log.print("🛑 محاولة إنهاء Live Activity (End)...");
try {
await IosLiveActivityService.endRideActivity();
Log.print("✅ تم إنهاء Live Activity.");
} catch (e) {
Log.print("❌ خطأ في End Live Activity: $e");
}
},
child: const Text('End Activity'),
),
],
),
),
);
}
}
@@ -0,0 +1,642 @@
import 'package:Tripz/views/widgets/my_textField.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/home/map_passenger_controller.dart';
import 'package:Tripz/main.dart';
import 'package:Tripz/views/home/map_widget.dart/form_search_places_destenation.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../constant/colors.dart';
import '../../../constant/table_names.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/functions/tts.dart';
import '../../widgets/error_snakbar.dart';
import '../../widgets/mydialoug.dart';
import 'form_search_start.dart';
class MainBottomMenuMap extends StatelessWidget {
const MainBottomMenuMap({super.key});
@override
Widget build(BuildContext context) {
Get.put(MapPassengerController());
return GetBuilder<MapPassengerController>(
builder: (controller) => Positioned(
bottom: Get.height * .04, // Increased bottom padding
left: 16,
right: 16,
child: GestureDetector(
onTap: controller
.changeMainBottomMenuMap, // Make the whole area tappable
child: AnimatedContainer(
duration: const Duration(
milliseconds: 300), // Reduced duration for smoother animation
curve: Curves.easeInOut, // Added animation curve
height: controller.mainBottomMenuMapHeight,
decoration: BoxDecoration(
color: AppColor.secondaryColor, // Use a solid background color
borderRadius: BorderRadius.circular(16), // More rounded corners
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
offset: Offset(0, 5),
),
],
),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(), // Add bouncing effect
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment
.stretch, // Stretch children to full width
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
controller.isMainBottomMenuMap
? 'Where are you going?'.tr
: 'Quick Actions'.tr,
style: AppStyle.title
.copyWith(fontWeight: FontWeight.bold),
),
IconButton(
onPressed: controller.changeMainBottomMenuMap,
icon: Icon(
controller.isMainBottomMenuMap
? Icons.keyboard_arrow_down_rounded
: Icons.keyboard_arrow_up_rounded,
size: 28,
color: AppColor.primaryColor,
),
),
],
),
),
if (controller.isMainBottomMenuMap) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: InkWell(
onTap: () => controller.changeMainBottomMenuMap(),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.primaryColor
.withOpacity(0.05), // Subtle background
borderRadius: BorderRadius.circular(12),
),
child: DefaultTextStyle(
style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor),
child: Center(
child: controller.isPickerShown
? clickPointPosition(controller, context)
: whereWidgetSmall(controller),
),
),
),
),
),
const SizedBox(height: 8),
if (controller.recentPlaces.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text('Recent Places'.tr, style: AppStyle.subtitle),
SizedBox(
height: 30,
child: Center(
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: controller.recentPlaces.length,
separatorBuilder: (context, index) =>
const SizedBox(width: 8),
itemBuilder: (context, index) =>
_buildRecentPlaceButton(
controller, context, index),
),
),
),
],
),
),
] else ...[
if (!controller.isAnotherOreder)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'${'From:'.tr} ${controller.currentLocationString}'
.tr,
style: AppStyle.subtitle,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: !controller.isAnotherOreder
? const SizedBox()
: formSearchPlacesStart(),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: formSearchPlacesDestenation(),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: GestureDetector(
onTap: () {
Get.dialog(
AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
title: Text('WhatsApp Location Extractor'.tr),
content: Form(
key: controller.sosFormKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
MyTextForm(
controller:
controller.whatsAppLocationText,
label: 'Location Link'.tr,
hint: 'Paste location link here'.tr,
type: TextInputType.url,
),
const SizedBox(height: 16),
MyElevatedButton(
title: 'Go to this location'.tr,
onPressed: () async {
controller.goToWhatappLocation();
},
),
],
),
),
),
);
},
child: Container(
decoration: BoxDecoration(
color: Colors.blue.shade100, // Lighter background
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.blue.shade400), // Add a border
),
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(Icons.link, color: Colors.blue.shade700),
const SizedBox(width: 8),
Expanded(
child: Text(
'Paste WhatsApp location link'.tr,
style: TextStyle(color: Colors.blue.shade700),
),
),
const Icon(Icons.arrow_forward_ios_rounded,
size: 16, color: Colors.blueGrey),
],
),
),
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: OutlinedButton(
onPressed: () {
showCupertinoModalPopup(
context: context,
builder: (BuildContext context) =>
CupertinoActionSheet(
title: Text('Select Order Type'.tr),
message: Text('Choose who this order is for'.tr),
actions: <Widget>[
CupertinoActionSheetAction(
child: Text('I want to order for myself'.tr),
onPressed: () {
controller.changeisAnotherOreder(false);
Navigator.pop(context);
},
),
CupertinoActionSheetAction(
child: Text(
'I want to order for someone else'.tr),
onPressed: () {
controller.changeisAnotherOreder(true);
Navigator.pop(context);
},
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
child: Text('Cancel'.tr),
),
),
);
},
child: Text(
!controller.isAnotherOreder
? 'Order for someone else'.tr
: 'Order for myself'.tr,
),
),
),
],
const SizedBox(height: 8),
],
),
),
),
),
),
);
}
Widget _buildRecentPlaceButton(
MapPassengerController controller, BuildContext context, int index) {
final textToSpeechController = Get.find<TextToSpeechController>();
return InkWell(
onTap: () {
MyDialog().getDialog('Are you want to go this site'.tr, ' ', () async {
Get.back();
await controller.getLocation();
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.recentPlaces[index]['latitude']},${controller.recentPlaces[index]['longitude']}',
);
controller.showBottomSheet1();
});
},
onLongPress: () {
MyDialog().getDialog(
"Are you sure to delete this location?".tr,
'',
() {
sql.deleteData(TableName.recentLocations,
controller.recentPlaces[index]['id']);
controller.getFavioratePlaces();
controller.update();
Get.back();
mySnackbarSuccess('deleted'.tr);
},
);
},
child: Container(
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.05), // Subtle background
borderRadius: BorderRadius.circular(12),
border: Border(
bottom: BorderSide(
color: AppColor.primaryColor.withOpacity(0.1), width: 1),
),
),
child: Text(controller.recentPlaces[index]['name'],
style: const TextStyle(fontSize: 14)),
),
);
}
Widget clickPointPosition(
MapPassengerController controller, BuildContext context) {
return TextButton(
onPressed: () async {
controller.clearPolyline();
controller.data = [];
if (controller.passengerStartLocationFromMap == true) {
controller.newMyLocation = controller.newStartPointLocation;
controller.changeMainBottomMenuMap();
await controller.getDirectionMap(
'${controller.newStartPointLocation.latitude},${controller.newStartPointLocation.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}',
);
controller.currentLocationToFormPlaces = false;
controller.placesDestination = [];
controller.clearPlacesStart();
controller.clearPlacesDestination();
controller.passengerStartLocationFromMap = false;
controller.isPickerShown = false;
controller.showBottomSheet1();
} else if (controller.startLocationFromMap == true) {
controller.newMyLocation = controller.newStartPointLocation;
controller.hintTextStartPoint =
'${controller.newStartPointLocation.latitude.toStringAsFixed(4)} , ${controller.newStartPointLocation.longitude.toStringAsFixed(4)}';
controller.startLocationFromMap = false;
controller.isPickerShown = false;
} else if (controller.workLocationFromMap == true) {
controller.hintTextDestinationPoint = 'To Work'.tr;
box.write(BoxName.addWork,
'${controller.newMyLocation.latitude.toStringAsFixed(4)} , ${controller.newMyLocation.longitude.toStringAsFixed(4)}');
controller.newMyLocation = controller.newMyLocation;
controller.isPickerShown = false;
controller.workLocationFromMap = false;
Get.snackbar('Work Saved'.tr, '',
backgroundColor: AppColor.greenColor);
} else if (controller.homeLocationFromMap == true) {
controller.hintTextDestinationPoint = 'To Home'.tr;
box.write(BoxName.addHome,
'${controller.newMyLocation.latitude.toStringAsFixed(4)} , ${controller.newMyLocation.longitude.toStringAsFixed(4)}');
controller.newMyLocation = controller.newMyLocation;
controller.isPickerShown = false;
controller.homeLocationFromMap = false;
controller.update();
Get.snackbar('Home Saved'.tr, '',
backgroundColor: AppColor.greenColor);
} else {
controller.hintTextDestinationPoint =
'${controller.newMyLocation.latitude.toStringAsFixed(4)} , ${controller.newMyLocation.longitude.toStringAsFixed(4)}';
controller.myDestination = controller.newMyLocation;
controller.isPickerShown = false;
controller.changeMainBottomMenuMap();
controller.passengerStartLocationFromMap = true;
controller.isPickerShown = true;
if (controller.isAnotherOreder == false) {
await controller.mapController?.animateCamera(
CameraUpdate.newLatLng(LatLng(
controller.passengerLocation.latitude,
controller.passengerLocation.longitude)));
Get.defaultDialog(
title: 'Destination selected'.tr,
titleStyle: AppStyle.title,
content: Text(
'Now select start pick'.tr,
style: AppStyle.title,
),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
}));
}
if (controller.isWhatsAppOrder == true) {
Get.defaultDialog(
title: 'Destination selected'.tr,
titleStyle: AppStyle.title,
content: Text(
'Now select start pick'.tr,
style: AppStyle.title,
),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
}));
}
}
controller.placesDestination = [];
controller.placeDestinationController.clear();
// controller.showBottomSheet1();
controller.changeMainBottomMenuMap();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
controller.passengerStartLocationFromMap
? Icons.location_on
: Icons.location_searching,
size: 20,
color: AppColor.primaryColor,
),
const SizedBox(width: 8),
Text(
controller.passengerStartLocationFromMap
? 'Confirm Pick-up Location'.tr
: "Set Location on Map".tr,
style: AppStyle.subtitle.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.primaryColor,
),
),
],
),
),
);
}
Widget whereWidgetSmall(MapPassengerController controller) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.location_searching, color: AppColor.primaryColor),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'Where to'.tr} ${(box.read(BoxName.name).toString().split(' ')[0])} ',
style: AppStyle.subtitle),
// if (controller.noCarString)
// Text('Nearest Car: ~'.tr,
// style: TextStyle(color: Colors.grey.shade600))
// else
// Text(
// controller.nearestCar != null
// ? 'Nearest Car: ${controller.nearestDistance.toStringAsFixed(0)} m'
// : 'No cars nearby'.tr,
// style: TextStyle(color: Colors.grey.shade600),
// ),
],
),
],
);
}
}
class FaviouratePlacesDialog extends StatelessWidget {
const FaviouratePlacesDialog({super.key});
@override
Widget build(BuildContext context) {
Get.put(MapPassengerController());
return GetBuilder<MapPassengerController>(
builder: (controller) => Center(
child: InkWell(
onTap: () async {
List favoritePlaces =
await sql.getAllData(TableName.placesFavorite);
Get.defaultDialog(
title: 'Favorite Places'.tr,
content: SizedBox(
width: Get.width * .8,
height: 300,
child: favoritePlaces.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.star_border_rounded,
size: 99,
color: AppColor.accentColor,
),
Text(
'No favorite places yet!'.tr,
style: AppStyle.title,
),
],
),
)
: ListView.separated(
itemCount: favoritePlaces.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading:
const Icon(Icons.star, color: Colors.amber),
title: Text(favoritePlaces[index]['name'],
style: AppStyle.title),
trailing: IconButton(
icon: const Icon(Icons.delete_outline,
color: Colors.redAccent),
onPressed: () async {
await sql.deleteData(TableName.placesFavorite,
favoritePlaces[index]['id']);
Get.back();
Toast.show(
context,
'${'Deleted'.tr} ${favoritePlaces[index]['name']} from your favorites',
AppColor.redColor);
},
),
onTap: () async {
Get.back();
await controller.getLocation();
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}',
);
controller.showBottomSheet1();
},
);
},
),
),
confirm: MyElevatedButton(
title: 'Back'.tr, onPressed: () => Get.back()),
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.star_border_rounded,
color: AppColor.accentColor),
const SizedBox(width: 8),
Text('Favorite Places'.tr, style: AppStyle.title),
],
),
),
),
);
}
}
@@ -0,0 +1,343 @@
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/main.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/views/home/my_wallet/passenger_wallet.dart';
import 'package:Tripz/views/home/profile/complaint_page.dart';
import 'package:Tripz/views/home/profile/order_history.dart';
import 'package:Tripz/views/home/profile/promos_passenger_page.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../notification/notification_page.dart';
import '../HomePage/contact_us.dart';
import '../HomePage/share_app_page.dart';
import '../setting_page.dart';
import '../profile/passenger_profile_page.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class MapMenuWidget extends StatelessWidget {
const MapMenuWidget({super.key});
@override
Widget build(BuildContext context) {
Get.lazyPut(() => MapPassengerController());
return GetBuilder<MapPassengerController>(
builder: (controller) => Stack(
children: [
// --- خلفية معتمة عند فتح القائمة ---
if (controller.widthMenu > 0)
GestureDetector(
onTap: controller.getDrawerMenu,
child: Container(
color: Colors.black.withOpacity(0.4),
),
),
// --- القائمة الجانبية المنزلقة ---
_buildSideMenu(controller),
// --- زر القائمة العائم ---
_buildMenuButton(controller),
],
),
);
}
// --- ويدجت لبناء زر القائمة ---
Widget _buildMenuButton(MapPassengerController controller) {
return Positioned(
top: 45,
left: 16,
child: SafeArea(
child: InkWell(
onTap: controller.getDrawerMenu,
borderRadius: BorderRadius.circular(50),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.8),
shape: BoxShape.circle,
border:
Border.all(color: AppColor.writeColor.withOpacity(0.2)),
),
child: Icon(
controller.widthMenu > 0 ? Icons.close : Icons.menu,
color: AppColor.writeColor,
size: 26,
),
),
),
),
),
),
);
}
// --- ويدجت لبناء القائمة الجانبية ---
Widget _buildSideMenu(MapPassengerController controller) {
return AnimatedPositioned(
duration: const Duration(milliseconds: 400),
curve: Curves.fastOutSlowIn,
top: 0,
bottom: 0,
left: controller.widthMenu > 0 ? 0 : -Get.width,
child: ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(
width: Get.width * 0.8,
constraints: const BoxConstraints(maxWidth: 320),
decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.95),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
)
],
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMenuHeader(),
_buildQuickActionButtons(),
const Divider(
color: Colors.white24,
indent: 16,
endIndent: 16,
height: 24),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 8),
children: [
MenuListItem(
title: 'My Balance'.tr,
icon: Icons.account_balance_wallet_outlined,
onTap: () => Get.to(() => const PassengerWallet())),
MenuListItem(
title: 'Order History'.tr,
icon: Icons.history_rounded,
onTap: () => Get.to(() => const OrderHistory())),
MenuListItem(
title: 'Promos'.tr,
icon: Icons.local_offer_outlined,
onTap: () =>
Get.to(() => const PromosPassengerPage())),
MenuListItem(
title: 'Contact Us'.tr,
icon: Icons.contact_support_outlined,
onTap: () => Get.to(() => ContactUsPage())),
MenuListItem(
title: 'Complaint'.tr,
icon: Icons.flag_outlined,
onTap: () => Get.to(() => ComplaintPage())),
MenuListItem(
title: 'Driver'.tr,
icon: Ionicons.car_sport_outline,
onTap: () => _launchDriverAppUrl()),
MenuListItem(
title: 'Share App'.tr,
icon: Icons.share_outlined,
onTap: () => Get.to(() => ShareAppPage())),
MenuListItem(
title: 'Privacy Policy'.tr,
icon: Icons.shield_outlined,
onTap: () => launchUrl(Uri.parse(
'${AppLink.server}/privacy_policy.php')),
),
],
),
),
const Divider(
color: Colors.white24,
indent: 16,
endIndent: 16,
height: 1),
Padding(
padding: const EdgeInsets.all(8.0),
child: MenuListItem(
title: 'Logout'.tr,
icon: Icons.logout_rounded,
onTap: () {
Get.defaultDialog(
title: "Logout".tr,
middleText: "Are you sure you want to logout?".tr,
textConfirm: "Logout".tr,
textCancel: "Cancel".tr,
onConfirm: () {
// controller.logout();
Get.back();
},
);
},
color: Colors.red.shade300,
),
),
],
),
),
),
),
),
);
}
// --- ويدجت رأس القائمة ---
Widget _buildMenuHeader() {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 30, 20, 16),
child: Row(
children: [
const CircleAvatar(
radius: 30,
backgroundColor: AppColor.primaryColor,
child: Icon(Icons.person, color: AppColor.writeColor, size: 35),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
box.read(BoxName.name) ?? 'Guest',
style: AppStyle.headTitle.copyWith(fontSize: 20),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
"Tripz Passenger".tr,
style: AppStyle.title.copyWith(
color: AppColor.writeColor.withOpacity(0.7),
fontSize: 14),
),
],
),
),
],
),
);
}
// --- ويدجت الأزرار السريعة ---
Widget _buildQuickActionButtons() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildSmallActionButton(
icon: Icons.person_outline_rounded,
label: 'Profile'.tr,
onTap: () => Get.to(() => PassengerProfilePage())),
_buildSmallActionButton(
icon: Icons.notifications_none_rounded,
label: 'Alerts'.tr,
onTap: () => Get.to(() => const NotificationPage())),
_buildSmallActionButton(
icon: Icons.settings_outlined,
label: 'Settings'.tr,
onTap: () => Get.to(() => const SettingPage())),
],
),
);
}
Widget _buildSmallActionButton(
{required IconData icon,
required String label,
required VoidCallback onTap}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: AppColor.writeColor.withOpacity(0.9), size: 24),
const SizedBox(height: 6),
Text(label,
style: AppStyle.subtitle.copyWith(
fontSize: 12, color: AppColor.writeColor.withOpacity(0.9))),
],
),
),
);
}
void _launchDriverAppUrl() async {
final String driverAppUrl;
if (defaultTargetPlatform == TargetPlatform.android) {
driverAppUrl =
'https://play.google.com/store/apps/details?id=com.Tripz_driver';
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
driverAppUrl = 'https://apps.apple.com/st/app/Tripz-driver/id6482995159';
} else {
return;
}
try {
final Uri url = Uri.parse(driverAppUrl);
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
Get.snackbar('Error', 'Could not launch driver app store.');
}
} catch (e) {
Get.snackbar('Error', 'Could not open the link.');
}
}
}
// --- ويدجت عناصر القائمة بتصميم محسن ---
class MenuListItem extends StatelessWidget {
const MenuListItem({
super.key,
required this.title,
required this.onTap,
required this.icon,
this.color,
});
final String title;
final IconData icon;
final VoidCallback onTap;
final Color? color;
@override
Widget build(BuildContext context) {
return ListTile(
onTap: onTap,
leading: Icon(
icon,
size: 26,
color: color ?? AppColor.writeColor.withOpacity(0.8),
),
title: Text(
title.tr,
style: AppStyle.title.copyWith(
fontSize: 16,
color: color ?? AppColor.writeColor,
),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
splashColor: AppColor.primaryColor.withOpacity(0.2),
);
}
}
@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../main.dart';
class MenuIconMapPageWidget extends StatelessWidget {
const MenuIconMapPageWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (controller) => Positioned(
top: Get.height * .008,
left: box.read(BoxName.lang) != 'ar' ? 5 : null,
right: box.read(BoxName.lang) == 'ar' ? 5 : null,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.accentColor)),
child: AnimatedCrossFade(
sizeCurve: Curves.bounceOut,
duration: const Duration(
milliseconds: 300), // Adjust the duration as needed
crossFadeState: controller.heightMenuBool
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: IconButton(
onPressed: () {
controller.getDrawerMenu();
},
icon: const Icon(
Icons.close,
color: AppColor.primaryColor,
),
),
secondChild: IconButton(
onPressed: () {
controller.getDrawerMenu();
},
icon: const Icon(
Icons.menu,
color: AppColor.accentColor,
),
),
),
),
));
}
}
@@ -0,0 +1,86 @@
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/main.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class NewMainBottomSheet extends StatelessWidget {
const NewMainBottomSheet({super.key});
@override
Widget build(BuildContext context) {
return Positioned(
bottom: 0,
left: 5,
right: 5,
child: Container(
decoration: AppStyle.boxDecoration,
width: Get.width,
height: Get.height * .15,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text('Home'.tr),
const Icon(Icons.home),
],
),
),
),
Container(
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Text('Work'.tr),
const Icon(Icons.work_outline),
],
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(15),
color: AppColor.blueColor.withOpacity(.5),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(Icons.search),
Text(
"${"Where you want go ".tr}${(box.read(BoxName.name).toString().split(' ')[0]).toString()} ?",
),
],
),
),
)
],
)
],
),
),
);
}
}
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class PassengerRideLocationWidget extends StatefulWidget {
const PassengerRideLocationWidget({super.key});
@override
State<PassengerRideLocationWidget> createState() =>
_PassengerRideLocationWidgetState();
}
class _PassengerRideLocationWidgetState
extends State<PassengerRideLocationWidget>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// --- إعداد الأنيميشن للأيقونة ---
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
)..repeat(reverse: true); // التكرار بشكل عكسي لإنشاء تأثير النبض
_animation = Tween<double>(begin: 0.9, end: 1.1).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) {
// --- نفس شرط الإظهار الخاص بك ---
return AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
bottom: controller.isPassengerRideLocationWidget
? 20
: -100, // حركة دخول وخروج ناعمة
left: 20,
right: 20,
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0), // حواف دائرية بالكامل
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), // تأثير زجاجي
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.85),
borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: AppColor.writeColor.withOpacity(0.2)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// --- أيقونة متحركة لجذب الانتباه ---
ScaleTransition(
scale: _animation,
child: Icon(
Icons.location_on,
color: AppColor.primaryColor,
size: 28,
),
),
const SizedBox(width: 12),
// --- نص إرشادي واضح ---
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Set pickup location".tr,
style: AppStyle.title
.copyWith(fontWeight: FontWeight.bold),
),
Text(
"Move the map to adjust the pin".tr,
style: AppStyle.subtitle.copyWith(
color: AppColor.writeColor.withOpacity(0.7),
),
),
],
),
],
),
),
),
),
);
});
}
}
@@ -0,0 +1,418 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/controller/functions/secure_storage.dart';
import 'package:Tripz/controller/home/payment/credit_card_controller.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/digit_obsecur_formate.dart';
import '../../../controller/home/map_passenger_controller.dart';
class PaymentMethodPage extends StatelessWidget {
const PaymentMethodPage({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(
builder: (controller) => Positioned(
right: 5,
bottom: 5,
left: 5,
child: AnimatedContainer(
duration: const Duration(milliseconds: 400),
height: controller.isPaymentMethodPageShown
? controller.paymentPageShown
: 0,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'My Card'.tr,
style: AppStyle.title.copyWith(fontSize: 22),
),
IconButton(
onPressed: () =>
controller.changePaymentMethodPageShown(),
icon: const Icon(Icons.close),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Add Card'.tr,
style: AppStyle.title,
),
// GetBuilder<CreditCardController>(
// builder: (controller) => IconButton(
// onPressed: () {
// // controller.scanCard();
// // Get.defaultDialog(content: OptionConfigureWidget(
// // initialOptions: scanOptions,
// // onScanOptionChanged: (newOptions) =>
// // scanOptions = newOptions,
// // ),
// // );
// },
// icon: const Icon(Icons.contact_emergency_sharp),
// ),
// )
],
),
const SizedBox(
height: 10,
),
const MyCreditCardWidget(),
const Spacer(),
GetBuilder<CreditCardController>(
builder: (controller) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyElevatedButton(
title: 'Add Credit Card'.tr,
onPressed: () async {
if (controller.formKey.currentState!
.validate()) {
SecureStorage().saveData(
BoxName.cardNumber,
controller
.cardNumberController.text);
SecureStorage().saveData(
BoxName.cardHolderName,
controller
.cardHolderNameController.text);
SecureStorage().saveData(
BoxName.cvvCode,
controller.cvvCodeController.text);
SecureStorage().saveData(
BoxName.expiryDate,
controller
.expiryDateController.text);
}
},
),
],
))
],
),
),
),
));
}
}
class MyCreditCardWidget extends StatelessWidget {
const MyCreditCardWidget({
super.key,
});
@override
Widget build(BuildContext context) {
Get.put(CreditCardController());
return GetBuilder<CreditCardController>(
builder: (controller) => Container(
height: Get.height * .4,
width: Get.width * .9,
decoration: const BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.all(Radius.circular(15)),
gradient: LinearGradient(colors: [
AppColor.secondaryColor,
// AppColor.blueColor,
// AppColor.greenColor,
AppColor.accentColor,
// AppColor.primaryColor,
// AppColor.redColor,
// AppColor.yellowColor
]),
boxShadow: [
BoxShadow(
spreadRadius: 3,
offset: Offset(3, 3),
blurRadius: 3,
color: AppColor.redColor),
BoxShadow(
offset: Offset(-3, -3),
blurRadius: 3,
spreadRadius: 3,
color: AppColor.redColor),
],
),
child: Form(
key: controller.formKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
child: Row(
children: [
getCardIcon(controller.cardNumberController
.text), // Dynamic credit card icon
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .25,
child: Text(
'Card Number'.tr,
style: AppStyle.title,
),
),
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .4,
height: 70,
child: TextFormField(
maxLength: 16,
keyboardType: TextInputType.number,
controller: controller.cardNumberController,
style: const TextStyle(
color: AppColor.blueColor,
fontFamily: 'digital-counter-7',
fontWeight: FontWeight.bold),
decoration: const InputDecoration(
helperStyle: TextStyle(
fontFamily: 'digital-counter-7'),
// labelText: 'Card Number',
),
// inputFormatters: [DigitObscuringFormatter()],
validator: (value) {
if (value!.isEmpty || value.length != 16) {
return 'Please enter a valid 16-digit card number'
.tr;
}
return null;
},
),
),
],
),
),
Row(
children: [
const Icon(Icons.person),
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .25,
child: Text(
'Holder Name',
style: AppStyle.title,
),
),
SizedBox(
width: Get.width * .03,
),
SizedBox(
width: Get.width * .3,
child: SizedBox(
// height: 50,
child: TextFormField(
style: AppStyle.title,
keyboardType: TextInputType.text,
// maxLength: 16,
controller: controller.cardHolderNameController,
decoration: const InputDecoration(
// labelText: 'Cardholder Name',
),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the cardholder name'
.tr;
}
return null;
},
),
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: Row(
children: [
const Icon(Icons.date_range_outlined),
SizedBox(
width: Get.width * .03,
),
Column(
children: [
SizedBox(
width: Get.width * .2,
child: Text(
'Expiry Date',
style: AppStyle.subtitle,
),
),
SizedBox(
width: Get.width * .1,
child: SizedBox(
height: 60,
child: TextFormField(
maxLength: 4,
keyboardType: TextInputType.datetime,
controller:
controller.expiryDateController,
style: AppStyle.title,
decoration: const InputDecoration(),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the expiry date'
.tr;
}
return null;
},
),
),
)
],
),
],
),
),
SizedBox(
width: Get.width * .4,
child: Row(
children: [
const Icon(Icons.security),
SizedBox(
width: Get.width * .021,
),
Column(
children: [
SizedBox(
width: Get.width * .2,
child: Text(
'CVV Code',
style: AppStyle.subtitle,
),
),
SizedBox(
width: Get.width * .2,
child: SizedBox(
height: 60,
child: TextFormField(
obscureText: true,
keyboardType: TextInputType.number,
style: const TextStyle(
color: AppColor.primaryColor,
fontFamily: 'digital-counter-7'),
maxLength: 3,
controller:
controller.cvvCodeController,
decoration: const InputDecoration(
// labelText: 'CVV Code',
),
validator: (value) {
if (value!.isEmpty &&
value.length != 3) {
return 'Please enter the CVV code'
.tr;
}
return null;
},
),
),
),
],
)
],
),
),
],
),
// const SizedBox(
// height: 20,
// ),
MyElevatedButton(
title: 'Save'.tr,
onPressed: () {
if (controller.formKey.currentState!.validate()) {
// final creditCard = CreditCardModel(
// cardNumber: controller.cardNumberController.text,
// cardHolderName:
// controller.cardHolderNameController.text,
// expiryDate: controller.expiryDateController.text,
// cvvCode: controller.cvvCodeController.text,
// );
// Process the credit card details
// You can use GetX to handle the logic here
if (controller.formKey.currentState!.validate()) {
SecureStorage().saveData(BoxName.cardNumber,
controller.cardNumberController.text);
SecureStorage().saveData(BoxName.cardHolderName,
controller.cardHolderNameController.text);
SecureStorage().saveData(BoxName.cvvCode,
controller.cvvCodeController.text);
SecureStorage().saveData(BoxName.expiryDate,
controller.expiryDateController.text);
}
}
},
),
],
),
))));
}
Widget getCardIcon(String cardNumber) {
String cardType = detectCardType(
cardNumber); // Function to detect card type based on the first digit
IconData iconData;
Color iconColor;
switch (cardType) {
case 'Visa':
iconData = Icons.credit_card_rounded;
iconColor = Colors.blue; // Change color for Visa cards
break;
case 'Mastercard':
iconData = Icons.credit_card_rounded;
iconColor = Colors.red; // Change color for Mastercard cards
break;
default:
iconData = Icons.credit_card_rounded;
iconColor = Colors.black; // Default color for other card types
break;
}
return Icon(
iconData,
color: iconColor,
);
}
String detectCardType(String cardNumber) {
if (cardNumber.startsWith('4')) {
return 'Visa';
} else if (cardNumber.startsWith('5')) {
return 'Mastercard';
} else {
return 'Other';
}
}
}
@@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/table_names.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
import 'form_search_places_destenation.dart';
class PickerAnimtionContainerFormPlaces extends StatelessWidget {
PickerAnimtionContainerFormPlaces({
super.key,
});
final controller = MapPassengerController();
@override
Widget build(BuildContext context) {
// DbSql sql = DbSql.instance;
return GetBuilder<MapPassengerController>(
builder: (controller) => Positioned(
bottom: 0,
left: 0,
right: 5,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: controller.heightPickerContainer,
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: AppColor.accentColor, offset: Offset(2, 2)),
BoxShadow(
color: AppColor.accentColor, offset: Offset(-2, -2))
],
color: AppColor.secondaryColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
)),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
controller.isPickerShown
? const SizedBox()
: Text(
'Hi, Where to '.tr,
style: AppStyle.title,
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
height: 5,
),
controller.isPickerShown
? InkWell(
onTapDown: (details) {
controller.changePickerShown();
controller.changeHeightPlaces();
},
child: Container(
height: 7,
width: Get.width * .3,
decoration: BoxDecoration(
color: AppColor.accentColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColor.accentColor,
)),
),
)
: const SizedBox(),
controller.isPickerShown
? InkWell(
onTap: () {},
child: formSearchPlacesDestenation(),
)
: Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
controller.changePickerShown();
},
child: Text(
"Pick your destination from Map".tr,
style: AppStyle.subtitle,
),
),
TextButton(
onPressed: () async {
List favoritePlaces = await sql
.getAllData(TableName.placesFavorite);
Get.defaultDialog(
title: 'Favorite Places'.tr,
content: SizedBox(
width: Get.width * .8,
height: 300,
child: favoritePlaces.isEmpty
? Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
const Icon(
Icons
.hourglass_empty_rounded,
size: 99,
color: AppColor
.primaryColor,
),
Text(
'You Dont Have Any places yet !'
.tr,
style: AppStyle.title,
),
],
),
)
: ListView.builder(
itemCount:
favoritePlaces.length,
itemBuilder:
(BuildContext context,
int index) {
return Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
TextButton(
onPressed: () async {
await controller
.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}',
);
controller
.changePickerShown();
controller
.changeBottomSheetShown(
forceValue:
true);
controller
.bottomSheet();
Get.back();
},
child: Text(
favoritePlaces[
index]['name'],
style:
AppStyle.title,
),
),
IconButton(
onPressed: () async {
await sql.deleteData(
TableName
.placesFavorite,
favoritePlaces[
index]
['id']);
Get.back();
Get.snackbar(
'Deleted ',
'${'You are Delete'.tr} ${favoritePlaces[index]['name']} from your list',
backgroundColor:
AppColor
.accentColor);
},
icon: const Icon(Icons
.favorite_outlined),
),
],
);
},
),
),
onCancel: () {},
);
},
child: Text(
"Go To Favorite Places".tr,
style: AppStyle.subtitle,
),
),
],
),
if (controller.isPickerShown &&
controller.placesDestination.isEmpty)
MyElevatedButton(
title: 'Go to this Target'.tr,
onPressed: () async {
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.newMyLocation.latitude},${controller.newMyLocation.longitude}',
);
controller.changePickerShown();
controller.changeBottomSheetShown(
forceValue: true);
controller.bottomSheet();
// await sql
// .getAllData(TableName.placesFavorite)
},
),
if (controller.isPickerShown &&
controller.placesDestination.isEmpty)
const SizedBox(),
],
),
],
),
),
));
}
}
@@ -0,0 +1,329 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/style.dart';
import '../../../constant/colors.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/home/points_for_rider_controller.dart';
class PointsPageForRider extends StatelessWidget {
PointsPageForRider({
super.key,
});
MapPassengerController mapPassengerController =
Get.put(MapPassengerController());
@override
Widget build(BuildContext context) {
Get.put(WayPointController());
return GetBuilder<MapPassengerController>(builder: (controller) {
return Positioned(
bottom: 2,
left: 2,
right: 2,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: controller.wayPointSheetHeight,
decoration: AppStyle.boxDecoration,
child: ListView(
children: [
// const AppBarPointsPageForRider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
mapPassengerController.downPoints();
},
icon: const Icon(Icons.arrow_drop_down_circle_outlined),
),
GetBuilder<WayPointController>(builder: (wayPointController) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () {
wayPointController.addWayPoints();
controller.isWayPointStopsSheetUtilGetMap = true;
},
child: const Text('Add Stops'),
),
wayPointController.wayPoints.length > 1
? ElevatedButton(
onPressed: () async {
mapPassengerController
.getMapPointsForAllMethods();
},
child: const Text('Get Direction'),
)
: const SizedBox()
],
);
}),
],
),
SizedBox(
height: Get.height * .36,
child: GetBuilder<WayPointController>(
builder: (wayPointController) {
return ReorderableListView(
// The children of the list are the text fields
children: wayPointController.wayPoints
.asMap()
.entries
.map((entry) {
final index = entry.key;
final wayPoint = entry.value;
return Padding(
key: ValueKey(index),
padding: const EdgeInsets.all(1),
child: ListTile(
leading: Container(
decoration: BoxDecoration(
color: AppColor.deepPurpleAccent,
border: Border.all(),
shape: BoxShape.rectangle),
child: Padding(
padding: const EdgeInsets.all(2),
child: Text(
index.toString(),
style: AppStyle.title,
),
)),
title: InkWell(
onTap: () {
// showAddLocationDialog(context);
Get.defaultDialog(
content: SizedBox(
width: Get.width,
height: 400,
child: mapPassengerController
.placeListResponse[index]),
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(),
color:
AppColor.accentColor.withOpacity(.5)),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(index > 0
? mapPassengerController
.currentLocationStringAll[index]
.toString()
: ''),
const Icon(
Icons.reorder,
size: 20,
),
],
),
),
),
trailing: index > 0
? IconButton(
icon: const Icon(Icons.close),
onPressed: () {
wayPointController.removeTextField(index);
},
)
: IconButton(
icon: const Icon(
Icons.close,
color: AppColor.secondaryColor,
),
onPressed: () {},
)),
);
}).toList(),
// The callback when the user reorders the text fields
onReorder: (int oldIndex, int newIndex) {
wayPointController.reorderTextFields(oldIndex, newIndex);
},
);
}),
),
],
),
),
);
});
}
// GetBuilder<PointsForRiderController>(
// builder: (controller) => Container(
// decoration: AppStyle.boxDecoration,
// height: Get.height *
// .5, // height: controller.heightPointsPageForRider,
// width: Get.width,
// child: Column(
// children: [
// SizedBox(
// height: 300,
// child: ReorderableListView(
// onReorder: (oldIndex, newIndex) {
// if (oldIndex < newIndex) {
// newIndex -= 1;
// }
// pointsForRiderController.locations.insert(
// newIndex,
// pointsForRiderController.locations
// .removeAt(oldIndex));
// },
// children: [
// for (int i = 0;
// i < pointsForRiderController.locations.length;
// i++)
// ListTile(
// key: Key('$i'),
// title: DragTarget<int>(
// onAccept: (int data) {
// pointsForRiderController.locations
// .insert(i, 'New Text Field');
// },
// builder: (context, candidateData, rejectedData) {
// return Row(
// children: [
// SizedBox(
// width: 300,
// child: TextField(
// controller: TextEditingController(
// text: pointsForRiderController
// .locations[i]),
// onChanged: (value) {
// pointsForRiderController
// .locations[i] = value;
// },
// decoration: InputDecoration(
// prefixIcon: IconButton(
// onPressed: () {
// pointsForRiderController
// .removeLocation(i);
// },
// icon: const Icon(Icons.delete),
// ),
// labelText: 'Text Field ${i + 1}',
// border: const OutlineInputBorder(),
// ),
// ),
// ),
// IconButton(
// onPressed: () {
// // pointsForRiderController.onReorder(
// // index, newIndex);
// },
// icon: const Icon(Icons.reorder),
// ),
// ],
// );
// },
// ),
// ),
// ],
// ),
// ),
// ElevatedButton(
// onPressed: () {
// pointsForRiderController.addLocation('location');
// },
// child: const Text('Add Text Field'),
// ),
// ],
// ),
// ));
}
void showAddLocationDialog(BuildContext context, int index) {
final TextEditingController locationController = TextEditingController();
// Get.put(WayPointController());
showDialog(
context: context,
builder: (context) {
return Dialog.fullscreen(
// title: const Text('Add Location'),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(
Icons.close,
size: 40,
),
),
Text(
'Add Location'.tr,
style: AppStyle.title,
),
const Icon(
Icons.clear,
color: AppColor.secondaryColor,
)
],
),
// SizedBox(
// width: Get.width,
// child: formSearchCaptain(),
// ),
],
),
);
},
);
}
class AppBarPointsPageForRider extends StatelessWidget {
const AppBarPointsPageForRider({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.arrow_back_ios_new_rounded,
color: AppColor.primaryColor,
),
),
Container(
child: Row(
children: [
const CircleAvatar(
backgroundColor: AppColor.primaryColor,
maxRadius: 15,
child: Icon(
Icons.person,
color: AppColor.secondaryColor,
),
),
TextButton(
onPressed: () {},
child: Text(
"Switch Rider".tr,
style: AppStyle.title,
),
),
],
),
),
const Icon(
Icons.clear,
color: AppColor.secondaryColor,
)
],
);
}
}
@@ -0,0 +1,332 @@
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
// تأكد من المسارات
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/audio_record1.dart';
import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/profile/profile_controller.dart';
import '../../../main.dart';
import '../../../views/home/profile/complaint_page.dart';
class RideBeginPassenger extends StatelessWidget {
const RideBeginPassenger({super.key});
@override
Widget build(BuildContext context) {
final ProfileController profileController = Get.put(ProfileController());
final AudioRecorderController audioController =
Get.put(AudioRecorderController());
return Obx(() {
final controller = Get.find<MapPassengerController>();
// شرط الإظهار
final bool isVisible =
controller.currentRideState.value == RideState.inProgress &&
controller.isStartAppHasRide == false;
return AnimatedPositioned(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,
// تم تقليل قيمة الإخفاء لأن الارتفاع الكلي للنافذة أصبح أصغر
bottom: isVisible ? 0 : -300,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
spreadRadius: 2,
offset: const Offset(0, -3),
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 1. مقبض السحب
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
),
const SizedBox(height: 12),
// 2. هيدر المعلومات (سائق + سيارة + سعر)
_buildCompactHeader(controller),
const SizedBox(height: 12),
// خط فاصل خفيف
const Divider(
height: 1, thickness: 0.5, color: Color(0xFFEEEEEE)),
const SizedBox(height: 12),
// 3. الأزرار (إجراءات)
_buildCompactActionButtons(
context, controller, profileController, audioController),
// إضافة هامش سفلي بسيط لرفع الأزرار عن حافة الشاشة
const SizedBox(height: 5),
],
),
),
),
);
});
}
// --- الهيدر (بدون تغيير، ممتاز) ---
Widget _buildCompactHeader(MapPassengerController controller) {
return Row(
children: [
// صورة السائق
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.5), width: 1.5),
),
child: CircleAvatar(
radius: 24,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (_, __) => const Icon(Icons.person),
),
),
const SizedBox(width: 10),
// الاسم ومعلومات السيارة
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
controller.driverName,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 4),
const Icon(Icons.star, color: Colors.amber, size: 14),
Text(
controller.driverRate,
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 2),
Row(
children: [
Flexible(
child: Text(
'${controller.model} • ',
style: TextStyle(fontSize: 12, color: Colors.grey[700]),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: Colors.grey[100],
border: Border.all(color: Colors.black12),
borderRadius: BorderRadius.circular(4),
),
child: Text(
controller.licensePlate,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 11,
fontWeight: FontWeight.w900,
),
),
),
],
),
],
),
),
// السعر
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
NumberFormat('#,###').format(controller.totalPassenger),
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 16,
color: AppColor.primaryColor,
),
),
Text('SYP',
style: TextStyle(fontSize: 9, color: Colors.grey[600])),
],
),
),
],
);
}
// --- الأزرار (بدون تغيير) ---
Widget _buildCompactActionButtons(
BuildContext context,
MapPassengerController controller,
ProfileController profileController,
AudioRecorderController audioController) {
return SizedBox(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_compactBtn(
icon: Icons.sos_rounded,
label: 'SOS'.tr,
color: AppColor.redColor,
bgColor: AppColor.redColor.withOpacity(0.1),
onTap: () async {
if (box.read(BoxName.sosPhonePassenger) == null) {
await profileController.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
} else {
makePhoneCall('112');
}
},
),
_compactBtn(
icon: FontAwesome.whatsapp,
label: 'WhatsApp'.tr,
color: const Color(0xFF25D366),
bgColor: const Color(0xFF25D366).withOpacity(0.1),
onTap: () async {
if (box.read(BoxName.sosPhonePassenger) == null) {
await profileController.updatField(
'sosPhone', TextInputType.phone);
} else {
final phone = controller.formatSyrianPhoneNumber(
box.read(BoxName.sosPhonePassenger).toString());
controller.sendWhatsapp(phone);
}
},
),
_compactBtn(
icon: Icons.share,
label: 'Share'.tr,
color: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1),
onTap: () async => await controller.shareTripWithFamily(),
),
GetBuilder<AudioRecorderController>(
init: audioController,
builder: (audioCtx) {
return _compactBtn(
icon: audioCtx.isRecording
? Icons.stop_circle_outlined
: Icons.mic_none_outlined,
label: audioCtx.isRecording ? 'Stop'.tr : 'Record'.tr,
color: audioCtx.isRecording
? AppColor.redColor
: AppColor.primaryColor,
bgColor: audioCtx.isRecording
? AppColor.redColor.withOpacity(0.1)
: AppColor.primaryColor.withOpacity(0.1),
onTap: () async {
if (!audioCtx.isRecording) {
await audioCtx.startRecording();
Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
} else {
await audioCtx.stopRecording();
Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
}
},
);
},
),
_compactBtn(
icon: Icons.info_outline_rounded,
label: 'Report'.tr,
color: Colors.grey[700]!,
bgColor: Colors.grey[200]!,
onTap: () => Get.to(() => ComplaintPage()),
),
],
),
);
}
Widget _compactBtn({
required IconData icon,
required String label,
required Color color,
required Color bgColor,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: bgColor,
shape: BoxShape.circle,
),
child: Icon(icon, size: 20, color: color),
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
color: Colors.grey[700],
fontWeight: FontWeight.w500),
),
],
),
);
}
}
@@ -0,0 +1,315 @@
import 'package:Tripz/controller/functions/launch.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
// ... استيراد ملفاتك الأخرى ...
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/profile/profile_controller.dart';
import '../../../main.dart';
class RideFromStartApp extends StatelessWidget {
const RideFromStartApp({super.key});
@override
Widget build(BuildContext context) {
final profileController = Get.put(ProfileController());
final MapPassengerController controller =
Get.find<MapPassengerController>();
return Obx(() {
final bool isRideActive =
controller.currentRideState.value == RideState.inProgress &&
controller.isStartAppHasRide == true;
if (!isRideActive) return const SizedBox();
// قراءة البيانات
final rideData = controller.rideStatusFromStartApp['data'] ?? {};
final driverId = rideData['driver_id'];
final driverName = rideData['driverName'] ?? 'Captain'.tr;
final driverRate = controller.driverRate;
final carType = rideData['carType'] ?? 'Car'.tr;
final carModel = controller.model ?? '';
final arrivalTime = box.read(BoxName.arrivalTime) ?? '--:--';
// تحديد البيانات للعرض
final displayTime = controller.stringRemainingTimeRideBegin.isNotEmpty
? controller.stringRemainingTimeRideBegin
: arrivalTime;
final displayDistance = rideData['distance']?.toStringAsFixed(1) ?? 'N/A';
final displayPrice = rideData['price']?.toString() ?? 'N/A';
return Positioned(
left: 0,
right: 0,
bottom: 0, // ملتصق بالأسفل تماماً
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: const BoxDecoration(
color: Colors.white, // خلفية بيضاء نظيفة
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 15.0,
spreadRadius: 5.0,
offset: Offset(0, -5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 1. مقبض صغير للدلالة على السحب (تصميم جمالي)
Center(
child: Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
),
// 2. حالة الرحلة + معلومات السائق
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// صورة السائق
Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border:
Border.all(color: AppColor.primaryColor, width: 2),
),
child: CircleAvatar(
radius: 28,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/$driverId.jpg'),
),
),
const SizedBox(width: 12),
// الاسم والسيارة
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
driverName,
style: AppStyle.title.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.star,
color: AppColor.yellowColor, size: 16),
const SizedBox(width: 4),
Text(
driverRate,
style: AppStyle.title.copyWith(
fontSize: 13, fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
Container(width: 1, height: 12, color: Colors.grey),
const SizedBox(width: 8),
Text(
"$carType - $carModel",
style: AppStyle.title.copyWith(
fontSize: 13, color: Colors.grey[600]),
),
],
),
],
),
),
// حالة الرحلة (نص ملون)
_buildStatusBadge(controller.currentRideState.value),
],
),
const SizedBox(height: 20),
// 3. شريط المعلومات (وقت، مسافة، سعر)
Container(
padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 10),
decoration: BoxDecoration(
color: AppColor.grayColor
.withOpacity(0.1), // خلفية رمادية خفيفة جداً
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoColumn(
Icons.access_time_filled, displayTime, "Time".tr),
_buildVerticalDivider(),
_buildInfoColumn(Icons.location_on, "$displayDistance KM",
"Distance".tr),
_buildVerticalDivider(),
_buildInfoColumn(
Icons.attach_money, "$displayPrice SYP", "Price".tr),
],
),
),
const SizedBox(height: 20),
// 4. أزرار التحكم (SOS & Share)
Row(
children: [
// زر المشاركة (يأخذ مساحة أكبر)
Expanded(
flex: 2,
child: ElevatedButton.icon(
onPressed: () => _checkAndCall(
controller.sendWhatsapp, profileController),
icon:
const Icon(FontAwesome.whatsapp, color: Colors.white),
label: Text("Share Trip".tr,
style: const TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
),
),
const SizedBox(width: 10),
// زر الاستغاثة SOS
Expanded(
flex: 1,
child: ElevatedButton.icon(
onPressed: () =>
makePhoneCall(box.read(BoxName.sosPhonePassenger)),
icon: const Icon(Icons.sos, color: Colors.white),
label: Text("SOS".tr,
style: const TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.redColor,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
),
),
],
),
],
),
),
);
});
}
// ------------------------- Widgets Helper Methods -------------------------
Widget _buildStatusBadge(RideState status) {
String text;
Color color;
if (status == RideState.inProgress) {
text = 'On Trip'.tr;
color = AppColor.primaryColor;
} else if (status == RideState.driverArrived) {
text = 'Arrived'.tr;
color = AppColor.greenColor;
} else {
text = 'Coming'.tr;
color = AppColor.yellowColor;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.5)),
),
child: Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
);
}
Widget _buildInfoColumn(IconData icon, String value, String label) {
return Column(
children: [
Icon(icon,
color: AppColor.secondaryColor,
size: 22), // افترضت أن السكندري لون داكن، أو استخدم Primary
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 15,
color: Colors.black87,
),
),
Text(
label,
style: TextStyle(
fontSize: 11,
color: Colors.grey[600],
),
),
],
);
}
Widget _buildVerticalDivider() {
return Container(
height: 30,
width: 1,
color: Colors.grey[300],
);
}
// دالة المساعدة للمنطق (بقيت كما هي ولكن تم تمرير البروفايل كونترولر)
Future<void> _checkAndCall(
Function(String) action, ProfileController profileController) async {
String? sosPhone = box.read(BoxName.sosPhonePassenger);
if (sosPhone == null || sosPhone == 'sos') {
await profileController.updatField('sosPhone', TextInputType.phone);
sosPhone = profileController.prfoileData['sosPhone'];
}
if (sosPhone != null && sosPhone != 'sos') {
action(sosPhone);
} else {
Get.snackbar('Warning'.tr, 'Please set a valid SOS phone number.'.tr,
backgroundColor: AppColor.redColor, colorText: Colors.white);
}
}
}
@@ -0,0 +1,469 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/home/map_passenger_controller.dart';
// --- الويدجت الرئيسية بالتصميم الجديد ---
class SearchingCaptainWindow extends StatefulWidget {
const SearchingCaptainWindow({super.key});
@override
State<SearchingCaptainWindow> createState() => _SearchingCaptainWindowState();
}
class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// [تعديل 1] نستخدم Obx للاستماع إلى التغييرات في حالة الرحلة
return Obx(() {
// ابحث عن الكنترولر مرة واحدة
final controller = Get.find<MapPassengerController>();
// [تعديل 2] شرط الإظهار يعتمد الآن على حالة الرحلة مباشرة
final bool isVisible =
controller.currentRideState.value == RideState.searching;
return AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
bottom: isVisible ? 0 : -Get.height * 0.45, // زيادة الارتفاع قليلاً
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, -5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// --- 1. أنيميشن الرادار ---
_buildRadarAnimation(controller),
const SizedBox(height: 20),
// --- 2. زر الإلغاء ---
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () {
// [تعديل 3] استدعاء دالة الإلغاء الموحدة
controller.changeCancelRidePageShow();
// ();
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.writeColor,
side:
BorderSide(color: AppColor.writeColor.withOpacity(0.3)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text('Cancel Search'.tr),
),
),
],
),
),
);
});
}
// --- ويدجت بناء أنيميشن الرادار ---
Widget _buildRadarAnimation(MapPassengerController controller) {
return SizedBox(
height: 180, // ارتفاع ثابت لمنطقة الأنيميشن
child: Stack(
alignment: Alignment.center,
children: [
// --- دوائر الرادار المتحركة (تبقى كما هي) ---
...List.generate(3, (index) {
return FadeTransition(
opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
),
),
child: ScaleTransition(
scale: Tween<double>(begin: 0.3, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
),
),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.7),
width: 2,
),
),
),
),
);
}),
// --- المحتوى في المنتصف ---
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
// [تعديل 4] النص يأتي مباشرة من الكنترولر
controller.driversStatusForSearchWindow,
style: AppStyle.headTitle.copyWith(fontSize: 20),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Searching for the nearest captain...'.tr,
style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withOpacity(0.7)),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// --- [!! تعديل جوهري !!] ---
// لم نعد بحاجة لـ buildTimerForIncrease
// المؤقت الرئيسي في الكنترولر هو من يقرر متى يعرض الحوار
// وهذا الجزء من الواجهة أصبح "غبياً" (لا يحتوي على منطق)
// الكنترولر سيستدعي _showIncreaseFeeDialog مباشرة
SizedBox(
height: 40,
width: 40,
child: Stack(
fit: StackFit.expand,
children: [
CircularProgressIndicator(
strokeWidth: 3,
color: AppColor.primaryColor,
backgroundColor: AppColor.primaryColor.withOpacity(0.2),
),
Center(
child: Icon(
Icons.search,
color: AppColor.writeColor.withOpacity(0.8),
),
),
],
),
),
],
),
],
),
);
}
}
// --- [!! تعديل جوهري !!] ---
// تم حذف دالة `buildTimerForIncrease` بالكامل.
// تم حذف دالة `_showIncreaseFeeDialog` من هذا الملف.
// لماذا؟ لأن الكنترولر الآن هو المسؤول الوحيد عن إظهار الحوار.
// دالة `_showIncreaseFeeDialog` موجودة بالفعل داخل `map_passenger_controller.dart`
// وسيتم استدعاؤها من `_handleRideState` عند انتهاء مهلة الـ 90 ثانية.
// // --- الويدجت الرئيسية بالتصميم الجديد ---
// class SearchingCaptainWindow extends StatefulWidget {
// const SearchingCaptainWindow({super.key});
// @override
// State<SearchingCaptainWindow> createState() => _SearchingCaptainWindowState();
// }
// class _SearchingCaptainWindowState extends State<SearchingCaptainWindow>
// with SingleTickerProviderStateMixin {
// late AnimationController _animationController;
// @override
// void initState() {
// super.initState();
// _animationController = AnimationController(
// vsync: this,
// duration: const Duration(seconds: 2),
// )..repeat();
// }
// @override
// void dispose() {
// _animationController.dispose();
// super.dispose();
// }
// @override
// Widget build(BuildContext context) {
// return GetBuilder<MapPassengerController>(
// builder: (controller) {
// return AnimatedPositioned(
// duration: const Duration(milliseconds: 300),
// curve: Curves.easeInOut,
// bottom: controller.isSearchingWindow ? 0 : -Get.height * 0.4,
// left: 0,
// right: 0,
// child: Container(
// padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// borderRadius: const BorderRadius.only(
// topLeft: Radius.circular(24),
// topRight: Radius.circular(24),
// ),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.2),
// blurRadius: 20,
// offset: const Offset(0, -5),
// ),
// ],
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// // --- 1. أنيميشن الرادار ---
// _buildRadarAnimation(controller),
// const SizedBox(height: 20),
// // --- 2. زر الإلغاء ---
// SizedBox(
// width: double.infinity,
// child: OutlinedButton(
// onPressed: () {
// // --- نفس منطقك للإلغاء ---
// controller.changeCancelRidePageShow();
// },
// style: OutlinedButton.styleFrom(
// foregroundColor: AppColor.writeColor,
// side: BorderSide(
// color: AppColor.writeColor.withOpacity(0.3)),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12)),
// padding: const EdgeInsets.symmetric(vertical: 12),
// ),
// child: Text('Cancel Search'.tr),
// ),
// ),
// ],
// ),
// ),
// );
// },
// );
// }
// // --- ويدجت بناء أنيميشن الرادار ---
// Widget _buildRadarAnimation(MapPassengerController controller) {
// return SizedBox(
// height: 180, // ارتفاع ثابت لمنطقة الأنيميشن
// child: Stack(
// alignment: Alignment.center,
// children: [
// // --- دوائر الرادار المتحركة ---
// ...List.generate(3, (index) {
// return FadeTransition(
// opacity: Tween<double>(begin: 1.0, end: 0.0).animate(
// CurvedAnimation(
// parent: _animationController,
// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
// ),
// ),
// child: ScaleTransition(
// scale: Tween<double>(begin: 0.3, end: 1.0).animate(
// CurvedAnimation(
// parent: _animationController,
// curve: Interval((index) / 3, 1.0, curve: Curves.easeInOut),
// ),
// ),
// child: Container(
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// border: Border.all(
// color: AppColor.primaryColor.withOpacity(0.7),
// width: 2,
// ),
// ),
// ),
// ),
// );
// }),
// // --- المحتوى في المنتصف ---
// Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text(
// controller.driversStatusForSearchWindow,
// style: AppStyle.headTitle.copyWith(fontSize: 20),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 8),
// Text(
// 'Searching for the nearest captain...'.tr,
// style: AppStyle.subtitle
// .copyWith(color: AppColor.writeColor.withOpacity(0.7)),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 16),
// // --- استدعاء نفس دالة المؤقت الخاصة بك ---
// buildTimerForIncrease(controller),
// ],
// ),
// ],
// ),
// );
// }
// }
// // --- نفس دالة المؤقت الخاصة بك مع تعديلات شكلية بسيطة ---
// Widget buildTimerForIncrease(MapPassengerController mapPassengerController) {
// return StreamBuilder<int>(
// stream: Stream.periodic(const Duration(seconds: 1))
// .map((_) => ++mapPassengerController.currentTimeSearchingCaptainWindow),
// initialData: 0,
// builder: (context, snapshot) {
// if (snapshot.hasData && snapshot.data! > 45) {
// // --- عرض زر زيادة الأجرة بنفس منطقك القديم ---
// return TextButton(
// onPressed: () =>
// _showIncreaseFeeDialog(context, mapPassengerController),
// child: Text(
// "No one accepted? Try increasing the fare.".tr,
// style: AppStyle.title.copyWith(
// color: AppColor.primaryColor,
// decoration: TextDecoration.underline),
// textAlign: TextAlign.center,
// ),
// );
// }
// final double progress = (snapshot.data ?? 0).toDouble() / 30.0;
// return SizedBox(
// height: 40,
// width: 40,
// child: Stack(
// fit: StackFit.expand,
// children: [
// CircularProgressIndicator(
// value: progress,
// strokeWidth: 3,
// color: AppColor.primaryColor,
// backgroundColor: AppColor.primaryColor.withOpacity(0.2),
// ),
// Center(
// child: Text(
// '${snapshot.data ?? 0}',
// style: AppStyle.title.copyWith(
// color: AppColor.writeColor, fontWeight: FontWeight.bold),
// ),
// ),
// ],
// ),
// );
// },
// );
// }
// // --- دالة لعرض نافذة زيادة الأجرة (مأخوذة من منطقك القديم) ---
// void _showIncreaseFeeDialog(
// BuildContext context, MapPassengerController mapPassengerController) {
// Get.defaultDialog(
// barrierDismissible: false,
// title: "Increase Your Trip Fee (Optional)".tr,
// titleStyle: AppStyle.title,
// content: Column(
// children: [
// Text(
// "We haven't found any drivers yet. Consider increasing your trip fee to make your offer more attractive to drivers."
// .tr,
// style: AppStyle.subtitle,
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 16),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// IconButton(
// onPressed: () {
// mapPassengerController.increasFeeFromPassenger.text =
// (mapPassengerController.totalPassenger + 3)
// .toStringAsFixed(1);
// mapPassengerController.update();
// },
// icon: const Icon(Icons.add_circle,
// size: 40, color: AppColor.greenColor),
// ),
// SizedBox(
// width: 100,
// child: Form(
// key: mapPassengerController.increaseFeeFormKey,
// child: MyTextForm(
// controller: mapPassengerController.increasFeeFromPassenger,
// label:
// mapPassengerController.totalPassenger.toStringAsFixed(2),
// hint:
// mapPassengerController.totalPassenger.toStringAsFixed(2),
// type: TextInputType.number,
// ),
// ),
// ),
// IconButton(
// onPressed: () {
// mapPassengerController.increasFeeFromPassenger.text =
// (mapPassengerController.totalPassenger - 3)
// .toStringAsFixed(1);
// mapPassengerController.update();
// },
// icon: const Icon(Icons.remove_circle,
// size: 40, color: AppColor.redColor),
// ),
// ],
// ),
// ],
// ),
// actions: [
// TextButton(
// child: Text("No, thanks".tr,
// style: const TextStyle(color: AppColor.redColor)),
// onPressed: () {
// Get.back();
// // mapPassengerController.cancelRide();
// mapPassengerController.changeCancelRidePageShow();
// },
// ),
// ElevatedButton(
// style: ElevatedButton.styleFrom(backgroundColor: AppColor.greenColor),
// child: Text("Increase Fee".tr),
// onPressed: () =>
// mapPassengerController.increaseFeeByPassengerAndReOrder(),
// ),
// ],
// );
// }
@@ -0,0 +1,340 @@
import 'package:Tripz/constant/colors.dart';
import 'package:Tripz/constant/style.dart';
import 'package:Tripz/controller/home/map_passenger_controller.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/links.dart';
import '../../../print.dart';
class CupertinoDriverListWidget extends StatelessWidget {
MapPassengerController mapPassengerController =
Get.put(MapPassengerController());
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Driver List'.tr), // Ensure text is properly localized
),
child: SafeArea(
child: mapPassengerController.driversForMishwari.isEmpty
? Center(
child: Text(
'No drivers available at the moment. Please try again later.'
.tr,
style: const TextStyle(
fontSize: 18, // Adjust the size as needed
fontWeight: FontWeight.w600,
color: CupertinoColors.inactiveGray, // Customize color
),
textAlign: TextAlign.center, // Center-align the text
),
)
: ListView.separated(
itemCount: mapPassengerController.driversForMishwari.length,
separatorBuilder: (context, index) =>
const Divider(height: 1),
itemBuilder: (context, index) {
var driver =
mapPassengerController.driversForMishwari[index];
return Container(
decoration: AppStyle.boxDecoration1,
child: CupertinoListTile(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 8),
leading: CircleAvatar(
radius: 25,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg',
),
child: Builder(
builder: (context) {
return Image.network(
'${AppLink.server}/portrate_captain_image/${driver['id']}.jpg',
fit: BoxFit.cover,
loadingBuilder: (BuildContext context,
Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child; // Image is loaded
} else {
return Center(
child: CircularProgressIndicator(
value: loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
(loadingProgress
.expectedTotalBytes ??
1)
: null,
),
);
}
},
errorBuilder: (BuildContext context,
Object error, StackTrace? stackTrace) {
return const Icon(
Icons
.person, // Icon to show when image fails to load
size: 25, // Adjust the size as needed
color: AppColor
.blueColor, // Color for the error icon
);
},
);
},
),
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${driver['NAME'].toString().split(' ')[0]} ${driver['NAME'].toString().split(' ')[1]}',
style:
const TextStyle(fontWeight: FontWeight.bold),
),
Text('${'Age'.tr}: ${driver['age'].toString()}'),
Row(
children: [
const Icon(CupertinoIcons.star_fill,
size: 16,
color: CupertinoColors.systemYellow),
const SizedBox(width: 4),
Text(driver['rating']?.toStringAsFixed(1) ??
'N/A'.tr),
const SizedBox(width: 8),
Text('${'Rides'.tr}: ${driver['ride_count']}'),
],
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'),
Text('${'Plate'.tr}: ${driver['car_plate']}'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
// width: Get.width * .3,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('${'Color'.tr}: ${driver['color']}'),
const SizedBox(width: 8),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: driver['color_hex']
.toString() ==
'null'
? Colors.amber
: hexToColor(driver['color_hex']
.toString()),
borderRadius:
BorderRadius.circular(4),
border: Border.all(),
),
),
],
),
),
],
),
],
),
onTap: () {
Log.print(' driver["id"]: ${driver['driver_id']}');
Get.find<MapPassengerController>().driverIdVip =
driver['driver_id'];
// Handle driver selection
Get.defaultDialog(
title:
'${'Selected driver'.tr}: ${driver['NAME']}',
content: Column(
children: [
Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'),
Text(
'${'Plate'.tr}: ${driver['car_plate']}'),
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
'${'Color'.tr}: ${driver['color']}'),
const SizedBox(width: 8),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: driver['color_hex']
.toString() ==
'null'
? Colors.amber
: hexToColor(
driver['color_hex']
.toString()),
borderRadius:
BorderRadius.circular(4),
border: Border.all(),
),
),
],
),
],
),
],
),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
showDateTimePickerDialog(driver);
}));
print('${'Selected driver'.tr}: ${driver['NAME']}');
// Get.back(); // Close the dialog
},
),
);
},
)),
);
}
Color hexToColor(String hexColor) {
hexColor = hexColor.replaceAll("#", "");
String colorString = "ff$hexColor";
return Color(int.parse(colorString, radix: 16));
}
void showDriverSelectionDialog(Map<String, dynamic> driver) {
Get.defaultDialog(
title: '${'Selected driver'.tr}: ${driver['name']}',
content: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Car'.tr}: ${driver['make']} ${driver['model']} (${driver['year']})'),
Text('${'Plate'.tr}: ${driver['car_plate']}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${'Color'.tr}: ${driver['color']}'),
const SizedBox(width: 8),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: driver['color_hex'].toString() == 'null'
? Colors.amber
: hexToColor(driver['color_hex'].toString()),
borderRadius: BorderRadius.circular(4),
border: Border.all(),
),
),
],
),
],
),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
showDateTimePickerDialog(driver);
},
),
);
}
void showDateTimePickerDialog(Map<String, dynamic> driver) {
DateTime selectedDateTime = DateTime.now();
Get.defaultDialog(
barrierDismissible: false,
title: "Select date and time of trip".tr,
content: SizedBox(
// height: 400, // Adjust height as needed
width: double.maxFinite,
child: Column(
children: [
DateTimePickerWidget(),
],
),
),
confirm: MyElevatedButton(
title: 'Confirm Trip'.tr,
onPressed: () async {
DateTime selectedDateTime =
mapPassengerController.selectedDateTime.value;
// Save trip data and set up notifications
Get.back();
await mapPassengerController.saveTripData(driver, selectedDateTime);
},
),
cancel: MyElevatedButton(
kolor: AppColor.redColor,
title: 'Cancel'.tr,
onPressed: () {
Get.back();
},
),
);
}
}
class DateTimePickerWidget extends StatelessWidget {
final MapPassengerController controller = Get.put(MapPassengerController());
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
automaticallyImplyLeading: false,
middle: Text('Date and Time Picker'.tr),
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() => Text(
'${'Selected Date and Time'.tr}: ${controller.selectedDateTime.value}',
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
)),
const SizedBox(height: 20),
SizedBox(
height: 200,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.dateAndTime,
initialDateTime: controller.selectedDateTime.value,
onDateTimeChanged: (newDateTime) {
controller.updateDateTime(newDateTime);
},
),
),
],
),
),
);
}
}
@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
GetBuilder<MapPassengerController> timerForCancelTripFromPassenger() {
return GetBuilder<MapPassengerController>(
builder: (controller) {
final isNearEnd =
controller.remainingTime <= 5; // Define a threshold for "near end"
return controller.remainingTime > 0 && controller.remainingTime != 25
? Positioned(
bottom: 5,
left: 10,
right: 10,
child: Container(
height: 180,
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Stack(
alignment: Alignment.center,
children: [
CircularProgressIndicator(
value: controller.progress,
// Set the color based on the "isNearEnd" condition
color: isNearEnd ? Colors.red : Colors.blue,
),
Text(
'${controller.remainingTime}',
style: AppStyle.number,
),
],
),
const SizedBox(
width: 30,
),
Text(
'You can cancel Ride now'.tr,
style: AppStyle.title,
)
],
),
Text(
'After this period\nYou can\'t cancel!'.tr,
style: AppStyle.title,
)
],
),
),
),
)
: const SizedBox();
},
);
}
@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Tripz/views/widgets/elevated_btn.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
import 'ride_begin_passenger.dart';
class TimerToPassengerFromDriver extends StatelessWidget {
const TimerToPassengerFromDriver({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>(builder: (controller) {
if (controller.remainingTime == 0 &&
(controller.isDriverInPassengerWay == true ||
controller.timeToPassengerFromDriverAfterApplied > 0)) {
// ) {
return Positioned(
left: 10,
right: 10,
bottom: 5,
child: Container(
decoration: AppStyle.boxDecoration,
height: controller.remainingTime == 0 &&
(controller.isDriverInPassengerWay == true ||
controller.timeToPassengerFromDriverAfterApplied > 0)
? 200
: 0,
// width: 100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
'You Can cancel Ride After Captain did not come in the time'
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
Stack(
children: [
LinearProgressIndicator(
backgroundColor: AppColor.accentColor,
color: controller
.remainingTimeToPassengerFromDriverAfterApplied <
60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 25,
borderRadius: BorderRadius.circular(15),
value: controller
.progressTimerToPassengerFromDriverAfterApplied
.toDouble(),
),
Center(
child: Text(
controller.stringRemainingTimeToPassenger,
style: AppStyle.title,
),
)
],
),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.phone,
color: AppColor.blueColor,
),
),
controller.remainingTimeToPassengerFromDriverAfterApplied < 60
? MyElevatedButton(
title: 'You can cancel trip'.tr,
onPressed: () async {
await controller
.calculateDistanceBetweenPassengerAndDriverBeforeCancelRide();
})
: const SizedBox()
],
),
),
),
);
} else if (controller.remainingTime == 0 &&
controller.isDriverArrivePassenger == true) {
return Positioned(
left: 10,
right: 10,
bottom: 5,
child: Container(
decoration: AppStyle.boxDecoration,
height: controller.remainingTime == 0 &&
controller.isDriverArrivePassenger == true
? 150
: 0,
// width: 100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
'The driver waiting you in picked location .'.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
Stack(
children: [
LinearProgressIndicator(
backgroundColor: AppColor.accentColor,
color:
controller.remainingTimeDriverWaitPassenger5Minute <
60
? AppColor.redColor
: AppColor.greenColor,
minHeight: 50,
borderRadius: BorderRadius.circular(15),
value: controller
.progressTimerDriverWaitPassenger5Minute
.toDouble(),
),
Center(
child: Text(
controller
.stringRemainingTimeDriverWaitPassenger5Minute,
style: AppStyle.title,
),
)
],
),
Text(
'Please go to Car now '.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
],
),
),
),
);
} else {
return const RideBeginPassenger();
}
});
}
}
@@ -0,0 +1,319 @@
import 'package:Tripz/constant/links.dart';
import 'package:Tripz/views/home/profile/complaint_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:Tripz/constant/box_name.dart';
import 'package:Tripz/controller/profile/profile_controller.dart';
import 'package:Tripz/main.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/audio_record1.dart';
import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.dart';
class VipRideBeginPassenger extends StatelessWidget {
const VipRideBeginPassenger({
super.key,
});
@override
Widget build(BuildContext context) {
ProfileController profileController = Get.put(ProfileController());
AudioRecorderController audioController =
Get.put(AudioRecorderController());
// Get.put(MapPassengerController());
return GetBuilder<MapPassengerController>(builder: (controller) {
if (controller.statusRideVip == 'Begin' ||
!controller.statusRideFromStart) {
return Positioned(
left: 10,
right: 10,
bottom: 10,
child: Container(
decoration: AppStyle.boxDecoration,
height: controller.statusRideVip == 'Begin' ? Get.height * .33 : 0,
// width: 100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg',
),
onBackgroundImageError: (_, __) {
// Handle error here
},
backgroundColor: Colors.grey,
child: const Icon(
Icons.person, // Default icon or placeholder
size: 30,
color: Colors.white,
), // Placeholder background color
),
Column(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
child: Text(
controller.driverName,
style: AppStyle.title,
),
),
),
const SizedBox(
height: 10,
),
Container(
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
controller.make,
style: AppStyle.title,
),
const SizedBox(
width: 10,
),
Text(
controller.model,
style: AppStyle.title,
),
],
),
),
),
],
),
Column(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.all(3),
child: Text(
'vip',
style: AppStyle.title,
),
),
),
Text(
'${controller.driverRate} 📈',
style: AppStyle.title,
),
],
),
],
),
// SizedBox(
// height: 5,
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
width: Get.width * .15,
decoration: AppStyle.boxDecoration,
child: IconButton(
onPressed: () => Get.to(
() => ComplaintPage(),
transition: Transition.downToUp,
),
icon: const Icon(
Icons.note_add,
color: AppColor.redColor,
),
tooltip: ' Add Note', // Optional tooltip for clarity
),
),
Container(
width: Get.width * .15,
decoration: AppStyle.boxDecoration,
child: audioController.isRecording == false
? IconButton(
onPressed: () async {
await audioController.startRecording();
Toast.show(context, 'Start Record'.tr,
AppColor.greenColor);
},
icon: const Icon(
Icons.play_circle_fill_outlined,
color: AppColor.greenColor,
),
tooltip:
' Add Note', // Optional tooltip for clarity
)
: IconButton(
onPressed: () async {
await audioController.stopRecording();
Toast.show(context, 'Record saved'.tr,
AppColor.greenColor);
},
icon: const Icon(
Icons.stop_circle,
color: AppColor.greenColor,
),
tooltip:
' Add Note', // Optional tooltip for clarity
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
decoration: AppStyle.boxDecoration,
width: Get.width * .15,
child: IconButton(
onPressed: () async {
if (box.read(BoxName.sosPhonePassenger) == null) {
{
await profileController.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
}
} else {
makePhoneCall('122');
// box.read(BoxName.sosPhonePassenger));
}
},
icon: const Icon(
Icons.sos_rounded,
color: AppColor.redColor,
),
),
),
Container(
decoration: AppStyle.boxDecoration,
width: Get.width * .15,
child: IconButton(
onPressed: () async {
if (box.read(BoxName.sosPhonePassenger) == null ||
box.read(BoxName.sosPhonePassenger) == 'sos') {
{
await profileController.updatField(
'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
}
} else {
String phoneNumber = box
.read(BoxName.sosPhonePassenger)
.toString();
// phoneNumber = phoneNumber.replaceAll('0', '');
var phone = box.read(BoxName.countryCode) ==
'Egypt'
? '+2${box.read(BoxName.sosPhonePassenger)}'
: '+962${box.read(BoxName.sosPhonePassenger)}';
controller.sendWhatsapp(phone);
}
},
icon: const Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
),
),
Container(
decoration: AppStyle.boxDecoration,
width: Get.width * .15,
child: IconButton(
onPressed: () async {
await controller.shareTripWithFamily();
},
icon: const Icon(
AntDesign.Safety,
color: AppColor.blueColor,
),
),
),
],
),
Stack(
children: [
// StreamCounter(),
LinearProgressIndicator(
backgroundColor: AppColor.accentColor,
color:
// controller.remainingTimeTimerRideBegin < 60
// ? AppColor.redColor
// :
AppColor.greenColor,
minHeight: 25,
borderRadius: BorderRadius.circular(15),
value:
24 //controller.progressTimerRideBegin.toDouble(),
),
Center(
child: Text(
controller.stringElapsedTimeRideBeginVip,
style: AppStyle.title,
),
)
],
)
],
),
),
),
);
} else {
return const SizedBox();
}
});
}
}
class StreamCounter extends StatelessWidget {
const StreamCounter({Key? key}) : super(key: key);
@override
// Build the UI based on the timer value
Widget build(BuildContext context) {
Get.put(MapPassengerController());
return GetBuilder<MapPassengerController>(builder: (controller) {
return StreamBuilder<int>(
initialData: 0,
stream: controller.timerController.stream,
builder: (context, snapshot) {
// Calculate the remaining time based on the current tick
final remainingTime = controller.durationToRide - snapshot.data!;
// Format the remaining time as a string
final formattedRemainingTime =
'${(remainingTime / 60).floor()}:${(remainingTime % 60).toString().padLeft(2, '0')}';
// Return the UI widgets based on the remaining time
return Column(
children: [
Text(formattedRemainingTime),
// ElevatedButton(
// onPressed: () {
// // Handle button press here
// },
// ),
],
);
},
);
});
}
}