25-12-1/1
This commit is contained in:
@@ -39,18 +39,18 @@ class SettingsCaptain extends StatelessWidget {
|
||||
subtitle: 'Change the app language'.tr,
|
||||
onTap: () => Get.to(() => const Language()),
|
||||
),
|
||||
_buildListTile(
|
||||
icon: Icons.flag_outlined,
|
||||
title: 'Change Country'.tr,
|
||||
subtitle: 'Get features for your country'.tr,
|
||||
onTap: () => Get.to(
|
||||
() => MyScafolld(
|
||||
title: 'Change Country'.tr,
|
||||
body: [CountryPickerFromSetting()],
|
||||
isleading: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
// _buildListTile(
|
||||
// icon: Icons.flag_outlined,
|
||||
// title: 'Change Country'.tr,
|
||||
// subtitle: 'Get features for your country'.tr,
|
||||
// onTap: () => Get.to(
|
||||
// () => MyScafolld(
|
||||
// title: 'Change Country'.tr,
|
||||
// body: [CountryPickerFromSetting()],
|
||||
// isleading: true,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -6,6 +7,7 @@ import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
|
||||
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../controller/functions/location_controller.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../Rate/rate_passenger.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import 'mapDriverWidgets/driver_end_ride_bar.dart';
|
||||
@@ -14,114 +16,169 @@ import 'mapDriverWidgets/google_map_app.dart';
|
||||
import 'mapDriverWidgets/passenger_info_window.dart';
|
||||
import 'mapDriverWidgets/sos_connect.dart';
|
||||
|
||||
// Changed: تم إعادة بناء الصفحة بالكامل لتكون أكثر تنظيمًا
|
||||
class PassengerLocationMapPage extends StatelessWidget {
|
||||
PassengerLocationMapPage({super.key});
|
||||
final LocationController locationController = Get.put(LocationController());
|
||||
final MapDriverController mapDriverController =
|
||||
Get.put(MapDriverController());
|
||||
|
||||
// Helper function to show exit confirmation dialog
|
||||
Future<bool> showExitDialog() async {
|
||||
bool? result = await Get.defaultDialog(
|
||||
title: "Warning".tr,
|
||||
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
|
||||
middleText:
|
||||
"You are in an active ride. Leaving this screen might stop tracking. Are you sure you want to exit?"
|
||||
.tr,
|
||||
middleTextStyle: AppStyle.title,
|
||||
barrierDismissible: false,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Stay'.tr,
|
||||
kolor: AppColor.greenColor,
|
||||
onPressed: () => Get.back(result: false), // Return false (Don't pop)
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Exit'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () => Get.back(result: true), // Return true (Allow pop)
|
||||
),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// New: استخدام addPostFrameCallback لضمان أن تحميل البيانات يتم بعد بناء الواجهة
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
|
||||
mapDriverController.argumentLoading();
|
||||
mapDriverController.startTimerToShowPassengerInfoWindowFromDriver();
|
||||
} else {
|
||||
// في حال عدم وجود arguments، يتم التعامل مع هذا الخطأ
|
||||
Get.snackbar("Error", "No order data found.");
|
||||
Get.back();
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
// 1. الخريطة في الخلفية
|
||||
GoogleDriverMap(locationController: locationController),
|
||||
// ✅ Added PopScope to intercept back button
|
||||
return PopScope(
|
||||
canPop: false, // Prevents immediate popping
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (didPop) {
|
||||
return;
|
||||
}
|
||||
// Show dialog
|
||||
final shouldExit = await showExitDialog();
|
||||
if (shouldExit) {
|
||||
Get.back(); // Manually pop if confirmed
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
// 1. Map
|
||||
GoogleDriverMap(locationController: locationController),
|
||||
|
||||
// 2. شريط تعليمات الطريق في الأعلى
|
||||
const InstructionsOfRoads(),
|
||||
// 2. Instructions
|
||||
const InstructionsOfRoads(),
|
||||
|
||||
// 4. نافذة معلومات الراكب في الأسفل (تظهر قبل بدء الرحلة)
|
||||
// 3. Passenger Info
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: PassengerInfoWindow(),
|
||||
),
|
||||
|
||||
PassengerInfoWindow(),
|
||||
// 3. زر إلغاء الرحلة في الأعلى يسارًا
|
||||
// 4. Cancel Widget
|
||||
CancelWidget(mapDriverController: mapDriverController),
|
||||
|
||||
CancelWidget(mapDriverController: mapDriverController),
|
||||
// Changed: تم تعديل تصميم زر الإلغاء ليكون أيقونة بسيطة في الأعلى
|
||||
// 5. شريط معلومات وإنهاء الرحلة (يظهر بعد بدء الرحلة)
|
||||
driverEndRideBar(),
|
||||
// 5. End Ride Bar
|
||||
driverEndRideBar(),
|
||||
|
||||
// 6. أزرار الطوارئ والاتصال
|
||||
SosConnect(),
|
||||
// 6. SOS
|
||||
SosConnect(),
|
||||
|
||||
// 7. دائرة عرض السرعة
|
||||
speedCircle(),
|
||||
GoogleMapApp(),
|
||||
// 8. نافذة عرض السعر النهائي (تظهر بعد انتهاء الرحلة)
|
||||
const PricesWindow(),
|
||||
],
|
||||
),
|
||||
));
|
||||
// 7. Speed
|
||||
speedCircle(),
|
||||
|
||||
// 8. External Map
|
||||
Positioned(
|
||||
bottom: 100,
|
||||
right: 10,
|
||||
child: GoogleMapApp(),
|
||||
),
|
||||
|
||||
// 9. Prices Window
|
||||
const PricesWindow(),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// New: تصميم جديد لشريط تعليمات الطريق في أعلى الشاشة
|
||||
// ... The rest of your widgets (InstructionsOfRoads, CancelWidget, etc.) remain unchanged ...
|
||||
// ... Keep the code below exactly as you had it in the previous snippet ...
|
||||
|
||||
class InstructionsOfRoads extends StatelessWidget {
|
||||
const InstructionsOfRoads({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) =>
|
||||
// يتم إظهار التعليمات فقط إذا كانت متوفرة
|
||||
controller.currentInstruction.isNotEmpty
|
||||
? Positioned(
|
||||
bottom: 10,
|
||||
left: MediaQuery.of(context).size.width * 0.15,
|
||||
right: MediaQuery.of(context).size.width * 0.15,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
return Positioned(
|
||||
bottom: 10,
|
||||
left: MediaQuery.of(context).size.width * 0.15,
|
||||
right: MediaQuery.of(context).size.width * 0.15,
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) => controller.currentInstruction.isNotEmpty
|
||||
? AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.directions,
|
||||
color: AppColor.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
controller.currentInstruction,
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.directions, color: AppColor.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
controller.currentInstruction,
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(), // في حالة عدم وجود تعليمات، لا يظهر شيء
|
||||
const SizedBox(width: 10),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
controller.toggleTts();
|
||||
},
|
||||
child: Icon(
|
||||
controller.isTtsEnabled
|
||||
? Icons.volume_up
|
||||
: Icons.volume_off,
|
||||
color: controller.isTtsEnabled
|
||||
? AppColor.greenColor
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Changed: تم تعديل تصميم وموضع زر الإلغاء ليكون أيقونة بسيطة في الأعلى
|
||||
class CancelWidget extends StatelessWidget {
|
||||
const CancelWidget({
|
||||
super.key,
|
||||
@@ -137,7 +194,6 @@ class CancelWidget extends StatelessWidget {
|
||||
left: 10,
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
// يظهر زر الإلغاء فقط قبل انتهاء الرحلة
|
||||
if (controller.isRideFinished) return const SizedBox.shrink();
|
||||
|
||||
return GestureDetector(
|
||||
@@ -199,7 +255,6 @@ class CancelWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Changed: تم تعديل تصميم نافذة السعر لتكون أكثر وضوحًا
|
||||
class PricesWindow extends StatelessWidget {
|
||||
const PricesWindow({
|
||||
super.key,
|
||||
|
||||
@@ -621,45 +621,95 @@ class FloatingActionButtons extends StatelessWidget {
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
// هذا الكود يوضع داخل الـ Stack في ملف الواجهة (HomeCaptain View)
|
||||
|
||||
box.read(BoxName.rideStatus) == 'Applied' ||
|
||||
box.read(BoxName.rideStatus) == 'Begin'
|
||||
? Positioned(
|
||||
bottom: Get.height * .2,
|
||||
right: 6,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(microseconds: 200),
|
||||
width: homeCaptainController.widthMapTypeAndTraffic,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColor.blueColor),
|
||||
color: AppColor.secondaryColor,
|
||||
borderRadius: BorderRadius.circular(15)),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
box.write(BoxName.rideStatus, 'delete');
|
||||
homeCaptainController.update();
|
||||
},
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
box.read(BoxName.rideStatus) == 'Applied'
|
||||
? {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments:
|
||||
box.read(BoxName.rideArguments)),
|
||||
Get.put(MapDriverController())
|
||||
.changeRideToBeginToPassenger()
|
||||
}
|
||||
: {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments:
|
||||
box.read(BoxName.rideArguments)),
|
||||
Get.put(MapDriverController())
|
||||
.startRideFromStartApp()
|
||||
};
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.directions_rounded,
|
||||
size: 29,
|
||||
color: AppColor.blueColor,
|
||||
// جعلنا الزر يظهر في المنتصف أو يمتد ليكون واضحاً جداً
|
||||
right: 20,
|
||||
left: 20,
|
||||
child: Center(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(
|
||||
milliseconds:
|
||||
200), // تم تصحيح microseconds إلى milliseconds لحركة أنعم
|
||||
// أزلنا العرض الثابت homeCaptainController.widthMapTypeAndTraffic لكي يتسع للنص
|
||||
// width: homeCaptainController.widthMapTypeAndTraffic,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: AppColor.blueColor,
|
||||
width: 2), // تعريض الإطار قليلاً
|
||||
color: AppColor.secondaryColor, // لون الخلفية
|
||||
borderRadius: BorderRadius.circular(
|
||||
30), // تدوير الحواف ليشبه الأزرار الحديثة
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
)
|
||||
]),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
onLongPress: () {
|
||||
// وظيفة الحذف عند الضغط الطويل (للطوارئ)
|
||||
box.write(BoxName.rideStatus, 'delete');
|
||||
homeCaptainController.update();
|
||||
},
|
||||
onTap: () {
|
||||
// نفس منطقك الأصلي للانتقال
|
||||
if (box.read(BoxName.rideStatus) == 'Applied') {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments: box.read(BoxName.rideArguments));
|
||||
Get.put(MapDriverController())
|
||||
.changeRideToBeginToPassenger();
|
||||
} else {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments: box.read(BoxName.rideArguments));
|
||||
Get.put(MapDriverController())
|
||||
.startRideFromStartApp();
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min, // حجم الزر على قد المحتوى
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons
|
||||
.directions_car_filled_rounded, // تغيير الأيقونة لسيارة أو اتجاهات لتكون معبرة أكثر
|
||||
size: 24,
|
||||
color: AppColor.blueColor,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10), // مسافة بين الأيقونة والنص
|
||||
Text(
|
||||
"متابعة الرحلة", // النص الواضح للسائق
|
||||
style: const TextStyle(
|
||||
color: AppColor.blueColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily:
|
||||
'Cairo', // تأكد من نوع الخط المستخدم عندك
|
||||
),
|
||||
),
|
||||
if (box.read(BoxName.rideStatus) ==
|
||||
'Begin') ...[
|
||||
const SizedBox(width: 5),
|
||||
// إضافة مؤشر صغير (نقطة حمراء) إذا كانت الرحلة قد بدأت بالفعل (اختياري)
|
||||
const Icon(Icons.circle,
|
||||
size: 8, color: Colors.green)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -219,9 +219,10 @@ Future<void> checkForPendingOrderFromServer() async {
|
||||
link: AppLink.getArgumentAfterAppliedFromBackground,
|
||||
payload: {'driver_id': driverId},
|
||||
);
|
||||
Log.print('response: ${response}');
|
||||
|
||||
// Assuming the server returns order data if found, or 'failure'/'none' if not
|
||||
if (response != 'failure') {
|
||||
if (response['status'] == 'success') {
|
||||
final Map<String, dynamic> orderInfoFromServer = response['message'];
|
||||
final Map<String, dynamic> rideArguments =
|
||||
_transformServerDataToAppArguments(orderInfoFromServer);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:slide_to_act/slide_to_act.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
import 'dart:io';
|
||||
@@ -11,82 +12,88 @@ import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||
import '../../../widgets/elevated_btn.dart';
|
||||
|
||||
// Changed: إعادة تصميم كاملة للشريط ليصبح شريطًا علويًا عند بدء الرحلة
|
||||
GetBuilder<MapDriverController> driverEndRideBar() {
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) => AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
// New: يظهر الشريط من الأعلى عندما تبدأ الرحلة
|
||||
top: controller.isRideStarted ? 0 : -200,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 10,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// -- معلومات الرحلة --
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildInfoColumn(
|
||||
icon: Icons.social_distance,
|
||||
text: '${controller.distance} ${'KM'.tr}',
|
||||
label: 'Distance'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.timelapse,
|
||||
text: controller.hours > 1
|
||||
? '${controller.hours}h ${controller.minutes}m'
|
||||
: '${controller.minutes}m',
|
||||
label: 'Time'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.money_sharp,
|
||||
text: '${controller.paymentAmount} ${'SYP'.tr}',
|
||||
label: 'Price'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
const Divider(height: 20),
|
||||
// ملف: driver_end_ride_bar.dart
|
||||
|
||||
// -- مؤقت الرحلة المتبقي (إن وجد) --
|
||||
_builtTimerAndCarType(),
|
||||
Widget driverEndRideBar() {
|
||||
// 1. Positioned هي الوالد المباشر (لأنها داخل Stack في الصفحة الرئيسية)
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
// 2. GetBuilder يكون في الداخل
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) => AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
// 3. نستخدم التحريك (Translation) لإخفاء الشريط وإظهاره بدلاً من تغيير الـ top
|
||||
transform: Matrix4.translationValues(
|
||||
0, controller.isRideStarted ? 0 : -250, 0),
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 10,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildInfoColumn(
|
||||
icon: Icons.social_distance,
|
||||
text: '${controller.distance} ${'KM'.tr}',
|
||||
label: 'Distance'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.timelapse,
|
||||
text: controller.hours > 1
|
||||
? '${controller.hours}h ${controller.minutes}m'
|
||||
: '${controller.minutes}m',
|
||||
label: 'Time'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.money_sharp,
|
||||
text:
|
||||
'${NumberFormat('#,##0').format(double.tryParse(controller.paymentAmount.toString()) ?? 0)} ${'SYP'.tr}',
|
||||
label: 'Price'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// -- زر إنهاء الرحلة المنزلق --
|
||||
SlideAction(
|
||||
height: 55,
|
||||
borderRadius: 15,
|
||||
elevation: 4,
|
||||
text: 'Slide to End Trip'.tr,
|
||||
textStyle: AppStyle.title.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
// ... بقية الكود كما هو (الأزرار والمؤقت)
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
const Divider(height: 20),
|
||||
const _builtTimerAndCarType(),
|
||||
const SizedBox(height: 12),
|
||||
SlideAction(
|
||||
height: 55,
|
||||
borderRadius: 15,
|
||||
elevation: 4,
|
||||
text: 'Slide to End Trip'.tr,
|
||||
textStyle: AppStyle.title.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
outerColor: AppColor.redColor,
|
||||
innerColor: Colors.white,
|
||||
sliderButtonIcon: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.redColor,
|
||||
size: 24,
|
||||
),
|
||||
sliderRotate: false,
|
||||
onSubmit: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
controller.finishRideFromDriver();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
outerColor: AppColor.redColor,
|
||||
innerColor: Colors.white,
|
||||
sliderButtonIcon: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.redColor,
|
||||
size: 24,
|
||||
),
|
||||
sliderRotate: false,
|
||||
onSubmit: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
controller.finishRideFromDriver();
|
||||
return null; // New: onSubmit now returns null
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -116,100 +123,119 @@ class _builtTimerAndCarType extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.find<MapDriverController>();
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// -- نوع السيارة --
|
||||
Container(
|
||||
decoration: AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text(
|
||||
controller.carType,
|
||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
// -- مؤقت الرحلة --
|
||||
if (controller.carType != 'Comfort' &&
|
||||
controller.carType != 'Mishwar Vip' &&
|
||||
controller.carType != 'Lady') ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor.withOpacity(0.8)
|
||||
: AppColor.greenColor.withOpacity(0.8),
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor
|
||||
: AppColor.greenColor,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: Colors.transparent,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white.withOpacity(0.2)),
|
||||
minHeight: 40,
|
||||
value: controller.progressTimerRideBegin.toDouble(),
|
||||
),
|
||||
Text(
|
||||
controller.stringRemainingTimeRideBegin,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// نستخدم GetBuilder هنا لضمان تحديث العداد في كل ثانية
|
||||
return GetBuilder<MapDriverController>(builder: (controller) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// -- نوع السيارة --
|
||||
Container(
|
||||
decoration:
|
||||
AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text(
|
||||
controller.carType.tr,
|
||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
)
|
||||
),
|
||||
|
||||
// -- مؤقت الرحلة --
|
||||
if (controller.carType != 'Comfort' &&
|
||||
controller.carType != 'Mishwar Vip' &&
|
||||
controller.carType != 'Lady') ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor.withOpacity(0.8)
|
||||
: AppColor.greenColor.withOpacity(0.8),
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor
|
||||
: AppColor.greenColor,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: Colors.transparent,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white.withOpacity(0.2)),
|
||||
minHeight: 40,
|
||||
// تأكد من أن هذه القيمة بين 0.0 و 1.0 في الكونترولر
|
||||
value: controller.progressTimerRideBegin.toDouble(),
|
||||
),
|
||||
Text(
|
||||
controller.stringRemainingTimeRideBegin,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Changed: تم تعديل مكان ومظهر دائرة السرعة
|
||||
GetBuilder<MapDriverController> speedCircle() {
|
||||
if (Get.find<MapDriverController>().speed > 100) {
|
||||
if (Platform.isIOS) {
|
||||
HapticFeedback.selectionClick();
|
||||
} else {
|
||||
Vibration.vibrate(duration: 1000);
|
||||
}
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
titleStyle: AppStyle.title,
|
||||
title: 'Speed Over'.tr,
|
||||
middleText: 'Please slow down'.tr,
|
||||
middleTextStyle: AppStyle.title,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'I will slow down'.tr,
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
return controller.isRideStarted
|
||||
? Positioned(
|
||||
// New: تم وضع دائرة السرعة في الأسفل يمينًا
|
||||
bottom: 25,
|
||||
left: 3,
|
||||
child: Container(
|
||||
// غيرنا نوع الإرجاع إلى Widget بدلاً من GetBuilder
|
||||
Widget speedCircle() {
|
||||
// التحقق من السرعة يمكن أن يبقى هنا أو داخل الـ builder
|
||||
// لكن التنبيهات (Vibration/Dialog) يفضل أن تكون داخل الـ builder لتجنب تكرارها أثناء إعادة البناء الخارجية
|
||||
|
||||
return Positioned(
|
||||
// New: Positioned الآن هي الوالد المباشر (يجب وضع هذه الدالة داخل Stack في الصفحة الرئيسية)
|
||||
bottom: 25,
|
||||
left: 3,
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
// التحقق من التنبيهات هنا
|
||||
if (controller.speed > 100) {
|
||||
// نستخدم addPostFrameCallback لضمان عدم استدعاء الـ Dialog أثناء عملية البناء
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!Get.isDialogOpen!) {
|
||||
// تجنب فتح أكثر من نافذة
|
||||
if (Platform.isIOS) {
|
||||
HapticFeedback.selectionClick();
|
||||
} else {
|
||||
Vibration.vibrate(duration: 1000);
|
||||
}
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
titleStyle: AppStyle.title,
|
||||
title: 'Speed Over'.tr,
|
||||
middleText: 'Please slow down'.tr,
|
||||
middleTextStyle: AppStyle.title,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'I will slow down'.tr,
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return controller.isRideStarted
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
boxShadow: [BoxShadow(blurRadius: 5, color: Colors.black26)],
|
||||
boxShadow: const [
|
||||
BoxShadow(blurRadius: 5, color: Colors.black26)
|
||||
],
|
||||
border: Border.all(
|
||||
width: 4,
|
||||
color: controller.speed > 100
|
||||
@@ -227,13 +253,13 @@ GetBuilder<MapDriverController> speedCircle() {
|
||||
controller.speed.toStringAsFixed(0),
|
||||
style: AppStyle.number.copyWith(fontSize: 24),
|
||||
),
|
||||
Text("km/h", style: TextStyle(fontSize: 10)),
|
||||
const Text("km/h", style: TextStyle(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox();
|
||||
},
|
||||
)
|
||||
: const SizedBox(); // إذا لم تبدأ الرحلة نخفي العنصر وهو داخل الـ Positioned
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/info.dart';
|
||||
import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
|
||||
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
|
||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
||||
|
||||
import '../../../../constant/box_name.dart';
|
||||
import '../../../../constant/style.dart';
|
||||
import '../../../../controller/firebase/notification_service.dart';
|
||||
import '../../../../main.dart';
|
||||
import '../../../../print.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
|
||||
// Changed: إعادة تصميم كاملة لتصبح شريط معلومات علوي مدمج
|
||||
class PassengerInfoWindow extends StatelessWidget {
|
||||
PassengerInfoWindow({super.key});
|
||||
final fcm = Get.isRegistered<FirebaseMessagesController>()
|
||||
? Get.find<FirebaseMessagesController>()
|
||||
: Get.put(FirebaseMessagesController());
|
||||
|
||||
// Optimization: defining static styles here avoids rebuilding them every frame
|
||||
final TextStyle _labelStyle =
|
||||
AppStyle.title.copyWith(color: Colors.grey[600], fontSize: 13);
|
||||
final TextStyle _valueStyle =
|
||||
AppStyle.title.copyWith(fontWeight: FontWeight.bold, fontSize: 18);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Get safe area top padding (for Notches/Status bars)
|
||||
final double topPadding = MediaQuery.of(context).padding.top;
|
||||
final double topMargin = topPadding + 10; // Safe area + 10px spacing
|
||||
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) => AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
// Changed: تم تغيير الموضع من الأسفل إلى الأعلى
|
||||
top: controller.isPassengerInfoWindow ? 15.0 : -200.0,
|
||||
// FIX: Use calculated top margin to avoid hiding behind status bar
|
||||
top: controller.isPassengerInfoWindow ? topMargin : -250.0,
|
||||
left: 15.0,
|
||||
right: 15.0,
|
||||
child: Card(
|
||||
elevation: 8,
|
||||
shadowColor: Colors.black.withOpacity(0.3),
|
||||
// Optimization: Lower elevation slightly for smoother animation on cheap phones
|
||||
elevation: 4,
|
||||
shadowColor: Colors.black.withOpacity(0.2),
|
||||
color: Colors.white,
|
||||
surfaceTintColor: Colors.white, // Fix for Material 3 tinting
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
@@ -41,14 +47,12 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// New: صف علوي للمعلومات الأساسية
|
||||
_buildTopInfoRow(controller),
|
||||
const Divider(height: 16),
|
||||
|
||||
// Changed: الأزرار الآن في صف أفقي ومدمج
|
||||
if (!controller.isRideBegin) _buildActionButtons(controller),
|
||||
|
||||
// New: مؤشر انتظار الراكب المدمج
|
||||
// Optimization: Only render linear indicator if needed
|
||||
if (controller.remainingTimeInPassengerLocatioWait < 300 &&
|
||||
controller.remainingTimeInPassengerLocatioWait != 0 &&
|
||||
!controller.isRideBegin) ...[
|
||||
@@ -56,7 +60,6 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
_buildWaitingIndicator(controller),
|
||||
],
|
||||
|
||||
// زر الإلغاء بعد انتهاء وقت الانتظار
|
||||
if (controller.isdriverWaitTimeEnd &&
|
||||
!controller.isRideBegin) ...[
|
||||
const SizedBox(height: 10),
|
||||
@@ -70,35 +73,33 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// New: ودجت لعرض المعلومات العلوية بشكل مدمج
|
||||
Widget _buildTopInfoRow(MapDriverController controller) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start, // Align top
|
||||
children: [
|
||||
// معلومات الراكب
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Go to passenger:'.tr, style: _labelStyle),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Go to passenger:'.tr,
|
||||
style: AppStyle.title
|
||||
.copyWith(color: Colors.grey[600], fontSize: 13),
|
||||
),
|
||||
Text(
|
||||
controller.passengerName,
|
||||
style: AppStyle.title
|
||||
.copyWith(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
controller.passengerName ?? 'loading...',
|
||||
style: _valueStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// معلومات المسافة والزمن
|
||||
Row(
|
||||
const SizedBox(width: 10), // Spacing between name and chips
|
||||
Column(
|
||||
// Changed to Column for better layout on small screens
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
_buildInfoChip(Icons.map_outlined, '${controller.distance} km'),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(height: 6), // Vertical spacing
|
||||
_buildInfoChip(
|
||||
Icons.timer_outlined,
|
||||
controller.hours > 1
|
||||
@@ -111,10 +112,9 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// New: ودجت مخصص لعرض المعلومات بشكل أنيق
|
||||
Widget _buildInfoChip(IconData icon, String text) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -122,144 +122,164 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: AppColor.primaryColor, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(text,
|
||||
style: TextStyle(
|
||||
color: AppColor.primaryColor, fontWeight: FontWeight.bold)),
|
||||
Icon(icon, color: AppColor.primaryColor, size: 14), // Smaller icon
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: AppColor.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12 // Slightly smaller font for chips
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Changed: إعادة تصميم أزرار الإجراءات لتكون أكثر دمجًا
|
||||
Widget _buildActionButtons(MapDriverController controller) {
|
||||
return Row(
|
||||
children: [
|
||||
if (controller.isArrivedSend)
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.location_on, size: 18),
|
||||
label: Text('I Arrive'.tr),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.yellowColor,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () async {
|
||||
controller.getRoute(
|
||||
origin: controller.latLngPassengerLocation,
|
||||
destination: controller.latLngPassengerDestination,
|
||||
routeColor: Colors.blue // أو أي لون
|
||||
);
|
||||
if (await controller
|
||||
.calculateDistanceBetweenDriverAndPassengerLocation() <
|
||||
140) {
|
||||
// fcm.sendNotificationToDriverMAP(
|
||||
// 'Hi ,I Arrive your site',
|
||||
// 'I Arrive at your site'.tr,
|
||||
// controller.tokenPassenger,
|
||||
// [],
|
||||
// 'ding.wav',
|
||||
// );
|
||||
Log.print(
|
||||
'controller.tokenPassenger: ${controller.tokenPassenger}');
|
||||
flex: 1,
|
||||
child: SizedBox(
|
||||
height: 45, // Fixed height for consistency
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.yellowColor,
|
||||
foregroundColor: Colors.black,
|
||||
padding: EdgeInsets.zero, // Reduce padding to fit text
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () async {
|
||||
// LOGIC FIX: Check distance FIRST
|
||||
double distance = await controller
|
||||
.calculateDistanceBetweenDriverAndPassengerLocation();
|
||||
|
||||
NotificationService.sendNotification(
|
||||
target: controller.tokenPassenger.toString(),
|
||||
title: 'Hi ,I Arrive your site'.tr,
|
||||
body: 'I Arrive at your site'.tr,
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'ding',
|
||||
driverList: [], category: 'Hi ,I Arrive your site',
|
||||
);
|
||||
controller.startTimerToShowDriverWaitPassengerDuration();
|
||||
controller.isArrivedSend = false;
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'You are not near the passenger location'.tr,
|
||||
'Please go to the pickup location exactly'.tr,
|
||||
() => Get.back());
|
||||
}
|
||||
},
|
||||
if (distance < 140) {
|
||||
// Only draw route and send notif if close enough
|
||||
controller.getRoute(
|
||||
origin: controller.latLngPassengerLocation,
|
||||
destination: controller.latLngPassengerDestination,
|
||||
routeColor: Colors.blue);
|
||||
|
||||
NotificationService.sendNotification(
|
||||
target: controller.tokenPassenger.toString(),
|
||||
title: 'Hi ,I Arrive your site'.tr,
|
||||
body: 'I Arrive at your site'.tr,
|
||||
isTopic: false,
|
||||
tone: 'ding',
|
||||
driverList: [],
|
||||
category: 'Hi ,I Arrive your site',
|
||||
);
|
||||
controller.startTimerToShowDriverWaitPassengerDuration();
|
||||
controller.isArrivedSend = false;
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'You are not near'.tr, // Shortened title
|
||||
'Please go to the pickup location exactly'.tr,
|
||||
() => Get.back());
|
||||
}
|
||||
},
|
||||
// Using Row instead of .icon constructor for better control
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.location_on, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text('I Arrive'.tr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 12))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.isArrivedSend) const SizedBox(width: 8),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.play_arrow_rounded, size: 20),
|
||||
label: Text('Start the Ride'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.greenColor,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
flex: 2, // Give "Start" button more space
|
||||
child: SizedBox(
|
||||
height: 45,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.greenColor,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
MyDialog().getDialog(
|
||||
"Is the Passenger in your Car?".tr,
|
||||
"Don't start trip if passenger not in your car".tr,
|
||||
() async {
|
||||
await controller.startRideFromDriver();
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.play_arrow_rounded, size: 22),
|
||||
const SizedBox(width: 6),
|
||||
Flexible(
|
||||
child: Text('Start the Ride'.tr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
MyDialog().getDialog(
|
||||
"Is the Passenger in your Car?".tr,
|
||||
"Don't start trip if passenger not in your car".tr,
|
||||
() async {
|
||||
await controller.startRideFromDriver();
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Changed: مؤشر الانتظار الآن أكثر دمجًا
|
||||
Widget _buildWaitingIndicator(MapDriverController controller) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: AppColor.greyColor.withOpacity(0.3),
|
||||
return Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: LinearProgressIndicator(
|
||||
backgroundColor: AppColor.greyColor.withOpacity(0.2),
|
||||
// Ternary for color is fine
|
||||
color: controller.remainingTimeInPassengerLocatioWait < 60
|
||||
? AppColor.redColor
|
||||
: AppColor.greenColor,
|
||||
minHeight: 25,
|
||||
minHeight: 8, // Thinner looks more modern
|
||||
value: controller.progressInPassengerLocationFromDriver.toDouble(),
|
||||
),
|
||||
Text(
|
||||
controller.stringRemainingTimeWaitingPassenger,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13,
|
||||
shadows: [
|
||||
Shadow(color: Colors.black.withOpacity(0.5), blurRadius: 2)
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"${'Waiting'.tr}: ${controller.stringRemainingTimeWaitingPassenger}",
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.grey[700],
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// New: زر الإلغاء بعد انتهاء الانتظار
|
||||
Widget _buildCancelAfterWaitButton(MapDriverController controller) {
|
||||
return MyElevatedButton(
|
||||
title: 'You Can Cancel the Trip and get Cost From '.tr +
|
||||
AppInformation.appName.tr,
|
||||
title: 'Cancel Trip & Get Cost'.tr, // Shortened text
|
||||
kolor: AppColor.gold,
|
||||
onPressed: () {
|
||||
MyDialog().getDialog('Are you sure to cancel?'.tr, '', () async {
|
||||
NotificationService.sendNotification(
|
||||
target: controller.tokenPassenger.toString(),
|
||||
title: 'Driver Cancelled Your Trip'.tr,
|
||||
body:
|
||||
'You will need to pay the cost to the driver, or it will be deducted from your next trip',
|
||||
isTopic: false, // Important: this is a token
|
||||
body: 'You will need to pay the cost...',
|
||||
isTopic: false,
|
||||
tone: 'cancel',
|
||||
driverList: [], category: 'Driver Cancelled Your Trip',
|
||||
driverList: [],
|
||||
category: 'Driver Cancelled Your Trip',
|
||||
);
|
||||
box.write(BoxName.rideStatus, 'Cancel');
|
||||
await controller.addWaitingTimeCostFromPassengerToDriverWallet();
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:sefer_driver/constant/api_key.dart';
|
||||
import '../../../../constant/box_name.dart';
|
||||
@@ -213,13 +214,14 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
await _closeOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
var res = await CRUD().post(
|
||||
link: "${AppLink.ride}/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
List<String> bodyToPassenger = [
|
||||
_getData(6).toString(),
|
||||
_getData(8).toString(),
|
||||
@@ -340,16 +342,12 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
_log("Driver ID is null, cannot refuse order");
|
||||
return;
|
||||
}
|
||||
await _crud.post(link: AppLink.addDriverOrder, payload: {
|
||||
_crud.post(link: AppLink.addDriverOrder, payload: {
|
||||
'driver_id': driverId,
|
||||
'order_id': orderID,
|
||||
'status': 'Refused'
|
||||
});
|
||||
await _crud.post(link: AppLink.updateRides, payload: {
|
||||
'id': orderID,
|
||||
'status': 'Refused',
|
||||
'driver_id': driverId,
|
||||
});
|
||||
|
||||
_log("Order $orderID refused successfully");
|
||||
} catch (e) {
|
||||
_log("Error in _apiRefuseOrder for $orderID: $e");
|
||||
@@ -509,9 +507,16 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: _buildHighlightInfo("\$${order.price}", "السعر".tr,
|
||||
Icons.monetization_on_rounded, AppColors.priceHighlight,
|
||||
isLarge: true),
|
||||
child: _buildHighlightInfo(
|
||||
// التعديل هنا 👇
|
||||
"${NumberFormat('#,##0').format(order.price)} ل.س",
|
||||
// أو يمكنك استخدام "SYP" بدلاً من "ل.س"
|
||||
|
||||
"السعر".tr,
|
||||
Icons.monetization_on_rounded,
|
||||
AppColors.priceHighlight,
|
||||
isLarge: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
|
||||
@@ -248,38 +248,31 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
|
||||
kolor: AppColor.greenColor,
|
||||
title: 'Accept Order'.tr,
|
||||
onPressed: () async {
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
controller.endTimer();
|
||||
controller.changeApplied();
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.updateStausFromSpeed,
|
||||
payload: {
|
||||
'id': (controller.myList[16]),
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
CRUD().post(
|
||||
link:
|
||||
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php",
|
||||
"${AppLink.ride}/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
'id': (controller.myList[16]),
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
|
||||
if (res == 'failure') {
|
||||
MyDialog().getDialog(
|
||||
"This ride is already applied by another driver."
|
||||
.tr,
|
||||
'', () {
|
||||
Get.back();
|
||||
// Get.back();
|
||||
Get.back();
|
||||
});
|
||||
} else {
|
||||
await CRUD().postFromDialogue(
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
controller.endTimer();
|
||||
controller.changeApplied();
|
||||
|
||||
CRUD().postFromDialogue(
|
||||
link: AppLink.addDriverOrder,
|
||||
payload: {
|
||||
'driver_id':
|
||||
@@ -386,33 +379,26 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
|
||||
title: 'Refuse Order'.tr,
|
||||
onPressed: () async {
|
||||
controller.endTimer();
|
||||
List<String> bodyToPassenger = [
|
||||
box.read(BoxName.driverID).toString(),
|
||||
box.read(BoxName.nameDriver).toString(),
|
||||
box.read(BoxName.tokenDriver).toString(),
|
||||
];
|
||||
// List<String> bodyToPassenger = [
|
||||
// box.read(BoxName.driverID).toString(),
|
||||
// box.read(BoxName.nameDriver).toString(),
|
||||
// box.read(BoxName.tokenDriver).toString(),
|
||||
// ];
|
||||
|
||||
// FirebaseMessagesController()
|
||||
// .sendNotificationToPassengerToken(
|
||||
// 'Order Under Review'.tr,
|
||||
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||
// controller.myList[9].toString(),
|
||||
// bodyToPassenger,
|
||||
// 'notification');
|
||||
NotificationService.sendNotification(
|
||||
target: controller.myList[9].toString(),
|
||||
title: 'Order Under Review'.tr,
|
||||
body:
|
||||
'${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'start',
|
||||
driverList: [], category: 'Order Under Review',
|
||||
);
|
||||
// NotificationService.sendNotification(
|
||||
// target: controller.myList[9].toString(),
|
||||
// title: 'Order Under Review'.tr,
|
||||
// body:
|
||||
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||
// isTopic: false, // Important: this is a token
|
||||
// tone: 'start',
|
||||
// driverList: bodyToPassenger,
|
||||
// category: 'Order Under Review',
|
||||
// );
|
||||
|
||||
controller.refuseOrder(
|
||||
EncryptionHelper.instance.encryptData(
|
||||
controller.myList[16].toString()),
|
||||
);
|
||||
// controller.refuseOrder(
|
||||
// (controller.myList[16].toString()),
|
||||
// );
|
||||
controller.addRideToNotificationDriverString(
|
||||
controller.myList[16].toString(),
|
||||
controller.myList[29].toString(),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,48 +66,48 @@ class PointsCaptain extends StatelessWidget {
|
||||
color: AppColor.blueColor, size: 70),
|
||||
],
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: paymentController.formKey,
|
||||
child: MyTextForm(
|
||||
controller:
|
||||
paymentController.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963941234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (paymentController.formKey.currentState!
|
||||
.validate()) {
|
||||
box.write(
|
||||
BoxName.phoneWallet,
|
||||
paymentController
|
||||
.walletphoneController.text);
|
||||
await payWithMTNWallet(
|
||||
context, pricePoint.toString(), 'SYP');
|
||||
}
|
||||
}));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Pay by MTN Wallet'.tr),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/cashMTN.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
)),
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// Get.back();
|
||||
// Get.defaultDialog(
|
||||
// barrierDismissible: false,
|
||||
// title: 'Insert Wallet phone number'.tr,
|
||||
// content: Form(
|
||||
// key: paymentController.formKey,
|
||||
// child: MyTextForm(
|
||||
// controller:
|
||||
// paymentController.walletphoneController,
|
||||
// label: 'Insert Wallet phone number'.tr,
|
||||
// hint: '963941234567',
|
||||
// type: TextInputType.phone)),
|
||||
// confirm: MyElevatedButton(
|
||||
// title: 'OK'.tr,
|
||||
// onPressed: () async {
|
||||
// Get.back();
|
||||
// if (paymentController.formKey.currentState!
|
||||
// .validate()) {
|
||||
// box.write(
|
||||
// BoxName.phoneWallet,
|
||||
// paymentController
|
||||
// .walletphoneController.text);
|
||||
// await payWithMTNWallet(
|
||||
// context, pricePoint.toString(), 'SYP');
|
||||
// }
|
||||
// }));
|
||||
// },
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Text('Pay by MTN Wallet'.tr),
|
||||
// const SizedBox(width: 10),
|
||||
// Image.asset(
|
||||
// 'assets/images/cashMTN.png',
|
||||
// width: 70,
|
||||
// height: 70,
|
||||
// fit: BoxFit.fill,
|
||||
// ),
|
||||
// ],
|
||||
// )),
|
||||
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
@@ -153,51 +153,26 @@ class PointsCaptain extends StatelessWidget {
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: paymentController.formKey,
|
||||
child: MyTextForm(
|
||||
controller:
|
||||
paymentController.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963941234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (paymentController.formKey.currentState!
|
||||
.validate()) {
|
||||
box.write(
|
||||
BoxName.phoneWallet,
|
||||
paymentController
|
||||
.walletphoneController.text);
|
||||
// await payWithSyriaTelWallet(
|
||||
// context, pricePoint.toString(), 'SYP');
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication()
|
||||
.isDeviceSupported();
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate =
|
||||
await LocalAuthentication()
|
||||
.authenticate(
|
||||
localizedReason:
|
||||
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
if (!didAuthenticate) {
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
print(
|
||||
"❌ User did not authenticate with biometrics");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Get.to(() => PaymentScreenSmsProvider(
|
||||
amount: pricePoint));
|
||||
}
|
||||
}));
|
||||
// التحقق بالبصمة قبل أي شيء
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication().isDeviceSupported();
|
||||
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate =
|
||||
await LocalAuthentication().authenticate(
|
||||
localizedReason:
|
||||
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
|
||||
if (!didAuthenticate) {
|
||||
print("❌ User did not authenticate with biometrics");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
|
||||
Get.to(
|
||||
() => PaymentScreenSmsProvider(amount: pricePoint));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
@@ -113,9 +113,11 @@ class WalletCaptainRefactored extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'${'Total Points is'.tr} 💎',
|
||||
style: AppStyle.headTitle2
|
||||
.copyWith(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
'رصيد التشغيل 💎',
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
Reference in New Issue
Block a user