26-1-21/1

This commit is contained in:
Hamza-Ayed
2026-01-21 17:01:45 +03:00
parent 11dfe94bbb
commit 3e89e1f1f0
32 changed files with 101957 additions and 12193 deletions

View File

@@ -47,8 +47,8 @@ android {
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 23 minSdk = 23
targetSdk = 36 targetSdk = 36
versionCode = 45 versionCode = 57
versionName = '1.0.45' versionName = '1.0.57'
multiDexEnabled = true multiDexEnabled = true
ndk { ndk {
abiFilters "armeabi-v7a", "arm64-v8a" abiFilters "armeabi-v7a", "arm64-v8a"

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

24
collect_code.py Normal file
View File

@@ -0,0 +1,24 @@
import os
# اسم الملف الناتج
output_filename = "full_project_code.txt"
# المجلد الذي تريد سحب الكود منه
source_folder = "./lib"
with open(output_filename, "w", encoding="utf-8") as outfile:
for root, dirs, files in os.walk(source_folder):
for file in files:
if file.endswith(".dart"):
file_path = os.path.join(root, file)
# كتابة فاصل واسم الملف ليعرف الذكاء الاصطناعي أين يبدأ الملف
outfile.write(f"\n\n{'='*50}\n")
outfile.write(f"FILE PATH: {file_path}\n")
outfile.write(f"{'='*50}\n\n")
try:
with open(file_path, "r", encoding="utf-8") as infile:
outfile.write(infile.read())
except Exception as e:
outfile.write(f"Error reading file: {e}\n")
print(f"تم تجميع الكود بنجاح في الملف: {output_filename}")

85287
full_project_code.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -33,11 +33,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>23</string> <string>30</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0.23</string> <string>1.1.30</string>
<key>FirebaseAppDelegateProxyEnabled</key> <key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string> <string>NO</string>
<key>GMSApiKey</key> <key>GMSApiKey</key>

194
lib/README.md Normal file
View File

@@ -0,0 +1,194 @@
إليك التقرير الفني الشامل لمشروع **Intaleq Passenger App**، مكتوباً بصيغة توثيق تقني (Technical Documentation) موجهة لفريق التطوير، بناءً على تحليل الكود المصدري.
---
# 📘 Intaleq Passenger App - Technical Documentation Report
**Prepared by:** CTO Office
**Target Audience:** Mobile Engineering Team
**Version:** 1.0
---
## 1. 🚀 بداية التشغيل (Initialization & Startup)
تعتمد مرحلة الإقلاع على تهيئة الخدمات الأساسية قبل عرض واجهة المستخدم لضمان استقرار التطبيق.
### أ. نقطة الدخول (`main.dart`)
- **المسار:** `lib/main.dart`
- **الوظيفة:**
- يستخدم `runZonedGuarded` لالتقاط الأخطاء العامة (Global Error Handling) وإرسالها للسيرفر عبر `CRUD.addError`.
- **تهيئة الخدمات:** يتم تهيئة `GetStorage` (للتخزين المحلي)، `WakelockPlus` (لمنع انطفاء الشاشة)، و `Firebase` (للإشعارات) قبل استدعاء `runApp`.
- **إعدادات التوجيه:** يتم تحديد الاتجاه العمودي فقط (`portraitUp`) للجهاز.
- **حقن التبعيات (DI):** يتم استدعاء `AppBindings` كـ `initialBinding` لتهيئة المتحكمات الأساسية.
### ب. إدارة التبعيات (`AppBindings`)
- **المسار:** `lib/app_bindings.dart`
- **الآلية:**
- يتم حقن `LocaleController` و `DeepLinkController` بشكل دائم (`permanent: true`) لضمان تواجدهم طوال دورة حياة التطبيق.
- يتم استخدام `Get.lazyPut` مع `fenix: true` للمتحكمات الأخرى (مثل `LoginController`) ليتم إنشاؤها عند الحاجة وإعادة إنشائها إذا تم التخلص منها.
### ج. شاشة البداية (`Splash Screen`)
- **المسار:** `lib/splash_screen_page.dart` و `lib/controller/home/splash_screen_controlle.dart`
- **المنطق:**
- يتم عرض انيميشن باستخدام `AnimatedTextKit` و `FadeTransition`.
- **العمليات الخلفية:** يقوم `SplashScreenController` بتنفيذ `_initializeBackgroundServices` لتهيئة خدمات التشفير (`EncryptionHelper`) والإشعارات.
- **التوجيه الذكي:** تتحقق الدالة `_performNavigationLogic` من وجود بيانات المستخدم في `BoxName`. إذا كان المستخدم مسجلاً ومفعلاً (`isVerified == '1'`)، يتم توجيهه تلقائياً للصفحة الرئيسية، وإلا يتم توجيهه لصفحة الدخول أو الترحيب (`OnBoarding`).
---
## 2. 🔐 دورة المصادقة (Authentication Cycle)
يعتمد النظام على مصادقة هجينة (Token-based + Social Auth) مع تخزين آمن.
### أ. التسجيل والدخول (`Sign Up & Login`)
- **المسار:** `lib/controller/auth/login_controller.dart` و `register_controller.dart`
- **الآلية:**
- **Social Login:** يتم استخدام `GoogleSignInHelper` أو `AuthController` (Apple). عند النجاح، يتم إرسال التوكن للسيرفر للتحقق.
- **Credentials:** في حالة الدخول التقليدي، يتم استدعاء `loginUsingCredentials` التي تتحقق من البيانات عبر API.
- **التحقق (Verification):** إذا رد السيرفر بأن الحساب غير مفعل، يتم تحويل المستخدم لصفحة `PhoneNumberScreen` للتحقق عبر OTP.
### ب. التحقق عبر الهاتف (OTP)
- **المسار:** `lib/controller/auth/otp_controller.dart`
- **المنطق:** يستخدم كلاس `PhoneAuthHelper` لإرسال OTP (غالباً عبر WhatsApp أو SMS حسب الدولة) ثم التحقق منه عبر الـ Endpoint `verifyOtp.php`.
### ج. إدارة الجلسة والتوكن
- **المخزن:** يتم تخزين الـ JWT في `GetStorage` تحت مفتاح `BoxName.jwt`.
- **التشفير:** يتم استخدام `EncryptionHelper` لتشفير البيانات الحساسة محلياً.
- **التجديد التلقائي:** في كلاس `CRUD`، إذا رد السيرفر بـ `401 Token expired`، يتم استدعاء `getJWT` تلقائياً لتجديد التوكن دون تسجيل خروج المستخدم.
---
## 3. 🗺️ الشاشة الرئيسية والخريطة (Home & Map Logic)
تعتبر `MapPagePassenger` هي الواجهة المركزية التي تديرها `MapPassengerController`.
### أ. تحميل الخريطة والموقع
- **المسار:** `lib/controller/home/map_passenger_controller.dart`
- **المتحكم:** `MapPassengerController`
- **المنطق:**
- يتم استخدام `location.getLocation()` لجلب موقع الراكب الحالي عند البدء.
- يتم تحديد المنطقة الجغرافية (سوريا، مصر، الأردن) عبر دالة `getLocationArea` التي تفحص وقوع الإحداثيات داخل مضلعات (Polygons) محددة مسبقاً.
### ب. السيارات القريبة (Real-time Updates)
- **الدالة:** `getCarsLocationByPassengerAndReloadMarker`.
- **الآلية:** تقوم بطلب API (مثل `getSpeed.php`) بناءً على نوع السيارة المختار (Speed, Comfort, Lady). يتم تحديث الـ `markers` على الخريطة، ويتم استخدام دالة `_smoothlyUpdateMarker` لتحريك أيقونة السيارة بسلاسة بدلاً من القفز المفاجئ.
### ج. القائمة الجانبية (Drawer)
- **المسار:** `lib/views/home/map_widget.dart/map_menu_widget.dart`
- **المتحكم:** `MyMenuController`
- **الوظيفة:** تدير حالة القائمة (مفتوحة/مغلقة) وتوفر روابط لصفحات الملف الشخصي، المحفظة، والسجل.
---
## 4. 🚕 دورة طلب الرحلة (Ride Request Flow)
هذا هو الجزء الأكثر تعقيداً في التطبيق، حيث يدار عبر آلة حالة (State Machine).
### أ. اختيار الوجهة ورسم المسار
- **المسار:** `lib/controller/home/map_passenger_controller.dart`
- **الوظيفة:** `getDirectionMap`.
- **المنطق:**
- يتم إرسال نقطة البداية والنهاية إلى خدمة التوجيه (OSRM/Google).
- يتم استلام نقاط المسار (Polyline Points) وفك تشفيرها في `Isolate` منفصل (`decodePolylineIsolate`) لتحسين الأداء.
- يتم رسم المسار على الخريطة وضبط الكاميرا لتشمل النقطتين.
### ب. اختيار نوع السيارة والسعر
- **المسار:** `lib/views/home/map_widget.dart/car_details_widget_to_go.dart`
- **الآلية:** يتم عرض قائمة أنواع السيارات (Fixed Price, Comfort, etc.). عند الاختيار، يتم حساب السعر المتوقع بناءً على المسافة والوقت والتعرفة الخاصة بكل نوع والمخزنة في المتحكم (`totalPassengerSpeed`, `totalPassengerComfort`).
### ج. إرسال الطلب (البحث عن سائق)
- **الدالة:** `startSearchingForDriver`.
- **العمليات:**
1. تغيير الحالة إلى `RideState.searching`.
2. استدعاء `postRideDetailsToServer` لإنشاء سجل الرحلة في قاعدة البيانات.
3. تشغيل المؤقت `_startMasterTimer` الذي يدير دورة البحث.
4. يتم توسيع نطاق البحث (Radius) تدريجياً (مراحل: 2400م -> 3000م -> 3100م) عبر `_findAndNotifyNearestDrivers`.
### د. انتظار السائق
- **الواجهة:** `SearchingCaptainWindow`.
- **المنطق:** يظهر رادار بحث. يتم التحقق دورياً (`Polling`) من حالة الرحلة في السيرفر. إذا مر الوقت المحدد (90 ثانية) دون قبول، يظهر خيار "زيادة السعر" (`_showIncreaseFeeDialog`).
---
## 5. 🚘 أثناء الرحلة (Active Ride)
يتم إدارة هذه المرحلة عبر تحديثات الحالة في `_handleRideState`.
### أ. قبول السائق
- **الحدث:** وصول إشعار FCM أو تغيير الحالة في السيرفر إلى `Apply`.
- **الإجراء:** يتم استدعاء `processRideAcceptance`.
- يتم جلب بيانات السائق وموقعه.
- يتم تفعيل تتبع السائق `startTimerFromDriverToPassengerAfterApplied`.
- تتغير الواجهة لعرض معلومات السائق والوقت المقدر للوصول.
### ب. بدء الرحلة وميزات الأمان
- **بدء الرحلة:** عند وصول حالة `Begin`، يتم استدعاء `processRideBegin`.
- **SOS:** زر الاستغاثة يستدعي `makePhoneCall` مع رقم الشرطة أو جهة اتصال الطوارئ المخزنة.
- **مشاركة الرحلة:** الدالة `shareTripWithFamily` تولد رابط تتبع مشفر وترسله عبر واتساب.
- **تسجيل الصوت:** يتم استخدام `AudioRecorderController` لتسجيل ما يدور في الرحلة لأغراض الأمان.
### ج. إلغاء الرحلة
- **الدالة:** `cancelRide`.
- **المنطق:**
- يتم إرسال طلب `cancel` للسيرفر لتحديث حالة الرحلة.
- يتم إرسال إشعار للسائق بالإلغاء.
- يتم تصفير الواجهة والعودة للخريطة.
---
## 6. 🏁 ما بعد الرحلة (Post-Ride)
### أ. شاشة الدفع
- **المسار:** `lib/controller/payment/payment_controller.dart`
- **المنطق:**
- يتم الخصم من المحفظة (`addPassengerWallet`) أو الدفع النقدي.
- يتم التعامل مع بوابات الدفع الخارجية (مثل Paymob, Stripe) إذا اختار العميل الدفع الإلكتروني.
### ب. التقييم
- **المسار:** `lib/views/Rate/rate_captain.dart` و `RateController`.
- **الآلية:** يقوم الراكب باختيار عدد النجوم وإضافة تعليق. يتم إرسال البيانات عبر `addRateToDriver`.
### ج. تقديم الشكاوى
- **المسار:** `lib/views/home/profile/complaint_page.dart`
- **المتحكم:** `ComplaintController`.
- **الميزة:** يمكن للراكب تسجيل رسالة صوتية وإرفاقها مع الشكوى، ثم يتم إرسالها للسيرفر عبر `submitComplaintToServer`.
---
## 7. ⚙️ الإعدادات والملف الشخصي
### أ. تعديل البيانات
- **المسار:** `lib/views/home/profile/passenger_profile_page.dart`.
- **المتحكم:** `ProfileController`.
- يسمح بتعديل الاسم، الجنس، ورقم الطوارئ.
### ب. المحفظة
- **المسار:** `lib/views/home/my_wallet/passenger_wallet.dart`.
- يعرض الرصيد الحالي وسجل المعاملات (`PassengerWalletHistoryController`).
### ج. اللغة
- **المتحكم:** `LocaleController`.
- يقوم بتغيير لغة التطبيق وتحديث `Get.updateLocale` وحفظ التفضيل في التخزين المحلي.

View File

@@ -6,13 +6,15 @@ class AppLink {
static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main'; static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main';
static String location = 'https://api.intaleq.xyz/intaleq/ride/location'; static String location = 'https://api.intaleq.xyz/intaleq/ride/location';
static String locationServer =
'https://location.intaleq.xyz/intaleq/ride/location';
static String seferPaymentServer0 = box.read('seferPaymentServer'); static String seferPaymentServer0 = box.read('seferPaymentServer');
static final String endPoint = 'https://api.intaleq.xyz/intaleq'; static final String endPoint = 'https://api.intaleq.xyz/intaleq';
static final String ride = 'https://rides.intaleq.xyz/intaleq'; static final String ride = 'https://rides.intaleq.xyz/intaleq';
// box.read(BoxName.serverChosen) ?? box.read(BoxName.basicLink); // box.read(BoxName.serverChosen) ?? box.read(BoxName.basicLink);
static final String server = 'https://api.intaleq.xyz/intaleq'; static final String server = 'https://api.intaleq.xyz/intaleq';
static final String serverSocket = 'https://rides.intaleq.xyz';
static String IntaleqSyriaServer = endPoint; static String IntaleqSyriaServer = endPoint;
static String IntaleqGizaServer = box.read('Giza'); static String IntaleqGizaServer = box.read('Giza');
static String IntaleqAlexandriaServer = box.read('Alexandria'); static String IntaleqAlexandriaServer = box.read('Alexandria');
@@ -244,7 +246,8 @@ class AppLink {
static String getLocationAreaLinks = static String getLocationAreaLinks =
'$server/ride/location/get_location_area_links.php'; '$server/ride/location/get_location_area_links.php';
static String addpassengerLocation = "$location/addpassengerLocation.php"; static String addpassengerLocation =
"$locationServer/addpassengerLocation.php";
static String getCarsLocationByPassengerSpeed = "$location/getSpeed.php"; static String getCarsLocationByPassengerSpeed = "$location/getSpeed.php";
static String getCarsLocationByPassengerComfort = "$location/getComfort.php"; static String getCarsLocationByPassengerComfort = "$location/getComfort.php";
static String getCarsLocationByPassengerBalash = "$location/getBalash.php"; static String getCarsLocationByPassengerBalash = "$location/getBalash.php";

View File

@@ -105,6 +105,9 @@ class FirebaseMessagesController extends GetxController {
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان // اقرأ "النوع" من حمولة البيانات، وليس من العنوان
String category = message.data['category'] ?? ''; String category = message.data['category'] ?? '';
final mapCtrl = Get.isRegistered<MapPassengerController>()
? Get.find<MapPassengerController>()
: null;
// اقرأ العنوان (للعرض) // اقرأ العنوان (للعرض)
String title = message.notification?.title ?? ''; String title = message.notification?.title ?? '';
String body = message.notification?.body ?? ''; String body = message.notification?.body ?? '';
@@ -119,17 +122,25 @@ class FirebaseMessagesController extends GetxController {
// ... داخل معالج الإشعارات في تطبيق الراكب ... // ... داخل معالج الإشعارات في تطبيق الراكب ...
else if (category == 'Accepted Ride') { else if (category == 'Accepted Ride') {
// <-- كان 'Accepted Ride' if (mapCtrl != null) {
var driverListJson = message.data['driverList']; Map<String, dynamic>? driverInfoMap;
if (driverListJson != null) {
var myList = jsonDecode(driverListJson) as List<dynamic>; // 2. معالجة driver_info (تأتي كـ String JSON من PHP)
final controller = Get.find<MapPassengerController>(); if (message.data['driver_info'] != null) {
// controller.currentRideState.value = RideState.driverApplied; try {
await controller.processRideAcceptance( String rawJson = message.data['driver_info'];
driverIdFromFCM: myList[0].toString(), // 🔥 فك التشفير: تحويل الـ String إلى Map
rideIdFromFCM: myList[3].toString()); driverInfoMap = jsonDecode(rawJson);
} else { } catch (e) {
Log.print('❌ خطأ: RIDE_ACCEPTED وصل بدون driverList'); print("❌ Error decoding FCM driver_info: $e");
}
}
// 3. تمرير البيانات الجاهزة للكنترولر
await mapCtrl.processRideAcceptance(
driverData: driverInfoMap,
source: "FCM",
);
} }
} else if (category == 'Promo') { } else if (category == 'Promo') {
// <-- كان 'Promo'.tr // <-- كان 'Promo'.tr
@@ -142,7 +153,7 @@ class FirebaseMessagesController extends GetxController {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'iphone_ringtone'); notificationController.showNotification(title, body, 'iphone_ringtone');
} }
var myListString = message.data['DriverList']; var myListString = message.data['passengerList'];
var myList = jsonDecode(myListString) as List<dynamic>; var myList = jsonDecode(myListString) as List<dynamic>;
Get.to(() => TripMonitor(), arguments: { Get.to(() => TripMonitor(), arguments: {
'rideId': myList[0].toString(), 'rideId': myList[0].toString(),
@@ -161,7 +172,7 @@ class FirebaseMessagesController extends GetxController {
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone1'); notificationController.showNotification(title, body, 'tone1');
} }
} else if (category == 'message From passenger') { } else if (category == 'MSG_FROM_PASSENGER') {
// <-- كان 'message From passenger' // <-- كان 'message From passenger'
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding'); notificationController.showNotification(title, body, 'ding');
@@ -178,78 +189,33 @@ class FirebaseMessagesController extends GetxController {
} else if (category == 'Trip is Begin') { } else if (category == 'Trip is Begin') {
// <-- كان 'Trip is Begin' // <-- كان 'Trip is Begin'
Log.print('[FCM] استقبل إشعار "TRIP_BEGUN".'); Log.print('[FCM] استقبل إشعار "TRIP_BEGUN".');
final controller = Get.find<MapPassengerController>(); // استدعاء الحارس
controller.processRideBegin(); mapCtrl!.processRideBegin(source: "FCM");
} else if (category == 'Hi ,I will go now') { } else if (category == 'Hi ,I will go now') {
// <-- كان 'Hi ,I will go now'.tr // <-- كان 'Hi ,I will go now'.tr
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding'); notificationController.showNotification(title, body, 'ding');
} }
update(); update();
} else if (category == 'Hi ,I Arrive your site') { } else if (category == "Arrive Ride") {
// <-- كان 'Hi ,I Arrive your site'.tr // استدعاء الحارس
final controller = Get.find<MapPassengerController>(); mapCtrl!.processDriverArrival("FCM");
// if (controller.currentRideState.value == RideState.driverApplied) {
Log.print('[FCM] السائق وصل. تغيير الحالة إلى driverArrived');
controller.currentRideState.value = RideState.driverArrived;
// }
} else if (category == 'Cancel Trip from driver') {
// <-- كان "Cancel Trip from driver"
Get.back();
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'cancel');
}
Get.defaultDialog(
title: "The driver canceled your ride.".tr, // العنوان المترجم للعرض
middleText: "We will look for a new driver.\nPlease wait.".tr,
confirm: MyElevatedButton(
kolor: AppColor.greenColor,
title: 'Ok'.tr,
onPressed: () async {
Get.back();
await Get.find<MapPassengerController>()
.reSearchAfterCanceledFromDriver();
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
kolor: AppColor.redColor,
onPressed: () {
Get.offAll(() => const MapPagePassenger());
},
));
} else if (category == 'Driver Finish Trip') { } else if (category == 'Driver Finish Trip') {
// <-- كان 'Driver Finish Trip'.tr
final rawData = message.data['DriverList'];
List<dynamic> driverList = []; List<dynamic> driverList = [];
if (rawData != null && rawData is String) {
// ✅ معالجة آمنة للبيانات
var rawData = message.data['DriverList'];
if (rawData != null && rawData.isNotEmpty) {
try { try {
driverList = jsonDecode(rawData); driverList = jsonDecode(rawData) as List<dynamic>;
} catch (e) { } catch (e) {
Log.print('Error decoding DriverList JSON: $e'); print("Error decoding DriverList: $e");
} }
} else {
Log.print('Error: DriverList data is null or not a String.');
} }
if (driverList.length >= 3) { if (driverList.isNotEmpty) {
if (Platform.isAndroid) { Get.find<MapPassengerController>()
notificationController.showNotification( .processRideFinished(driverList, source: "FCM");
title,
'${'you will pay to Driver'.tr} ${driverList[3].toString()} \$',
'tone1');
}
Get.find<AudioRecorderController>().stopRecording();
// ... (باقي كود المحفظة) ...
Get.find<MapPassengerController>().tripFinishedFromDriver();
// ... (إشعار "لا تنسى متعلقاتك") ...
Get.to(() => RateDriverFromPassenger(), arguments: {
'driverId': driverList[0].toString(),
'rideId': driverList[1].toString(),
'price': driverList[3].toString()
});
} else {
Log.print('Error: TRIP_FINISHED decoded list error.');
} }
} else if (category == 'Finish Monitor') { } else if (category == 'Finish Monitor') {
// <-- كان "Finish Monitor".tr // <-- كان "Finish Monitor".tr
@@ -262,19 +228,21 @@ class FirebaseMessagesController extends GetxController {
onPressed: () { onPressed: () {
Get.offAll(() => const MapPagePassenger()); Get.offAll(() => const MapPagePassenger());
})); }));
} else if (category == 'Driver Cancelled Your Trip') { } else if (category == 'Cancel Trip from driver') {
Log.print("🔔 FCM: Ride Cancelled by Driver received.");
// لا داعي لكتابة منطق التنظيف هنا، الكنترولر يتكفل بكل شيء
if (Get.isRegistered<MapPassengerController>()) {
// استدعاء الحارس (سيتجاهل الأمر إذا كان السوكيت قد سبقه)
Get.find<MapPassengerController>()
.processRideCancelledByDriver(message.data, source: "FCM");
}
// إشعار محلي (اختياري، لأن الديالوج سيظهر)
if (Platform.isAndroid) { if (Platform.isAndroid) {
notificationController.showNotification( notificationController.showNotification(
'Driver Cancelled Your Trip'.tr, 'Trip Cancelled'.tr, 'The driver cancelled the trip.'.tr, 'cancel');
'you will pay to Driver you will be pay the cost of driver time look to your Intaleq Wallet'
.tr,
'cancel');
} }
box.write(BoxName.parentTripSelected, false);
box.remove(BoxName.tokenParent);
Get.find<MapPassengerController>().restCounter();
Get.offAll(() => const MapPagePassenger());
} }
// ... (باقي الحالات مثل Call Income, Call End, إلخ) ... // ... (باقي الحالات مثل Call Income, Call End, إلخ) ...
// ... بنفس الطريقة ... // ... بنفس الطريقة ...
@@ -617,7 +585,7 @@ class FirebaseMessagesController extends GetxController {
Future<dynamic> passengerDialog(String message) { Future<dynamic> passengerDialog(String message) {
return Get.defaultDialog( return Get.defaultDialog(
barrierDismissible: false, barrierDismissible: false,
title: 'message From Driver'.tr, title: message.tr,
titleStyle: AppStyle.title, titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title, middleTextStyle: AppStyle.title,
middleText: message.tr, middleText: message.tr,

View File

@@ -1,41 +1,44 @@
import 'package:Intaleq/print.dart';
import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:get/get.dart'; // للترجمة .tr
class NotificationService { class NotificationService {
// استبدل هذا الرابط بالرابط الصحيح لملف PHP على السيرفر الخاص بك
static const String _serverUrl = static const String _serverUrl =
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm.php'; 'https://api.intaleq.xyz/intaleq/ride/firebase/send_fcm.php';
static const String _batchServerUrl =
'https://syria.intaleq.xyz/intaleq/fcm/send_fcm_batch.php';
static Future<void> sendNotification({ static Future<void> sendNotification({
required String target, required String target,
required String title, required String title,
required String body, required String body,
required String? category, // <-- [الإضافة الأولى] required String category, // إلزامي للتصنيف
String? tone, String? tone,
List<String>? driverList, // <-- [تعديل 1] : إضافة المتغير الجديد List<String>? driverList,
bool isTopic = false, bool isTopic = false,
}) async { }) async {
try { try {
final Map<String, dynamic> payload = { // 1. تجهيز البيانات المخصصة (Data Payload)
Map<String, dynamic> customData = {};
customData['category'] = category;
// إذا كان هناك قائمة سائقين/ركاب، نضعها هنا
if (driverList != null && driverList.isNotEmpty) {
// نرسلها كـ JSON String لأن FCM v1 يدعم String Values فقط في الـ data
customData['driverList'] = jsonEncode(driverList);
}
// 2. تجهيز الطلب الرئيسي للسيرفر
final Map<String, dynamic> requestPayload = {
'target': target, 'target': target,
'title': title, 'title': title,
'body': body, 'body': body,
'isTopic': isTopic, 'isTopic': isTopic,
'data':
customData, // 🔥🔥 التغيير الجوهري: وضعنا البيانات داخل "data" 🔥🔥
}; };
if (category != null) {
payload['category'] =
category; // <-- [الإضافة الثانية] (النص الثابت للتحكم)
}
// نضيف النغمة فقط إذا لم تكن فارغة
if (tone != null) {
payload['tone'] = tone;
}
// <-- [تعديل 2] : نضيف قائمة البيانات بعد تشفيرها إلى JSON if (tone != null) {
if (driverList != null) { requestPayload['tone'] = tone;
payload['driverList'] = jsonEncode(driverList);
} }
final response = await http.post( final response = await http.post(
@@ -43,71 +46,18 @@ class NotificationService {
headers: { headers: {
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
}, },
body: jsonEncode(payload), body: jsonEncode(requestPayload),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
print('✅ Notification sent successfully.'); print('✅ Notification sent successfully.');
print('Server Response: ${response.body}'); // print('Response: ${response.body}');
} else { } else {
print( print('❌ Failed to send notification. Code: ${response.statusCode}');
'❌ Failed to send notification. Status code: ${response.statusCode}'); print('Error Body: ${response.body}');
print('Server Error: ${response.body}');
} }
} catch (e) { } catch (e) {
print('An error occurred while sending notification: $e'); print('Error sending notification: $e');
}
}
/// [4] !! دالة جديدة مضافة !!
/// ترسل إشعاراً "مجمعاً" إلى قائمة من السائقين
static Future<void> sendBatchNotification({
required List<String> targets, // <-- قائمة التوكينز
required String title,
required String body,
String? tone,
List<String>? driverList, // <-- بيانات الرحلة (نفسها للجميع)
}) async {
// لا ترسل شيئاً إذا كانت القائمة فارغة
if (targets.isEmpty) {
Log.print('⚠️ [Batch] No targets to send to. Skipped.');
return;
}
try {
final Map<String, dynamic> payload = {
// "targets" بدلاً من "target"
'targets': jsonEncode(targets), // تشفير قائمة التوكينز
'title': title,
'body': body,
};
if (tone != null) {
payload['tone'] = tone;
}
// بيانات الرحلة (DriverList)
if (driverList != null) {
payload['driverList'] = jsonEncode(driverList);
}
final response = await http.post(
Uri.parse(_batchServerUrl), // <-- !! تستخدم الرابط الجديد
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(payload),
);
if (response.statusCode == 200) {
Log.print('✅ [Batch] Notifications sent successfully.');
Log.print('Server Response: ${response.body}');
} else {
Log.print('❌ [Batch] Failed to send. Status: ${response.statusCode}');
Log.print('Server Error: ${response.body}');
}
} catch (e) {
Log.print('❌ [Batch] An error occurred: $e');
} }
} }
} }

View File

@@ -78,7 +78,7 @@ class AudioRecorderController extends GetxController {
// Stop recording // Stop recording
Future<void> stopRecording() async { Future<void> stopRecording() async {
await recorder.stop(); recorder.stop();
isRecording = false; isRecording = false;
isPaused = false; isPaused = false;
update(); update();

View File

@@ -100,9 +100,8 @@ class CRUD {
} }
final sc = response.statusCode; final sc = response.statusCode;
Log.print('sc: ${sc}');
Log.print('request: ${response.request}');
final body = response.body; final body = response.body;
Log.print('request: ${response.request}');
Log.print('body: ${body}'); Log.print('body: ${body}');
// 2xx // 2xx
@@ -188,9 +187,9 @@ class CRUD {
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}' 'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}'
}, },
); );
Log.print('response.body: ${response.body}'); Log.print('request: ${response.request}');
Log.print('response.request: ${response.request}'); Log.print('body: ${response.body}');
Log.print('response.payload: ${payload}'); Log.print('payload: ${payload}');
if (response.statusCode == 200) { if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body); var jsonData = jsonDecode(response.body);

View File

@@ -11,19 +11,17 @@ Future<void> makePhoneCall(String phoneNumber) async {
// 1. تنظيف الرقم (إزالة المسافات والفواصل) // 1. تنظيف الرقم (إزالة المسافات والفواصل)
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), ''); String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
// 2. التحقق من طول الرقم لتحديد طريقة التنسيق // 2. منطق التنسيق (مع الحفاظ على الأرقام القصيرة مثل 112 كما هي)
if (formattedNumber.length > 6) { if (formattedNumber.length > 6) {
// --- التعديل المطلوب ---
if (formattedNumber.startsWith('09')) { if (formattedNumber.startsWith('09')) {
// إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي) // إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي) -> +963
// نحذف أول خانة (الصفر) ونضيف +963
formattedNumber = '+963${formattedNumber.substring(1)}'; formattedNumber = '+963${formattedNumber.substring(1)}';
} else if (!formattedNumber.startsWith('+')) { } else if (!formattedNumber.startsWith('+')) {
// إذا لم يكن يبدأ بـ + (ولم يكن يبدأ بـ 09)، نضيف + في البداية // إذا لم يكن دولياً ولا محلياً معروفاً -> إضافة + فقط
// هذا للحفاظ على منطقك القديم للأرقام الدولية الأخرى
formattedNumber = '+$formattedNumber'; formattedNumber = '+$formattedNumber';
} }
} }
// ملاحظة: الأرقام القصيرة (مثل 112) ستتجاوز الشرط أعلاه وتبقى "112" وهو الصحيح
// 3. التنفيذ (Launch) // 3. التنفيذ (Launch)
final Uri launchUri = Uri( final Uri launchUri = Uri(
@@ -31,8 +29,19 @@ Future<void> makePhoneCall(String phoneNumber) async {
path: formattedNumber, path: formattedNumber,
); );
if (await canLaunchUrl(launchUri)) { try {
await launchUrl(launchUri); // استخدام LaunchMode.externalApplication هو الحل الجذري لمشاكل الـ Intent
// لأنه يجبر النظام على تسليم الرابط لتطبيق الهاتف بدلاً من محاولة فتحه داخل تطبيقك
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri, mode: LaunchMode.externalApplication);
} else {
// في بعض الأجهزة canLaunchUrl تعود بـ false مع الـ tel ومع ذلك يعمل launchUrl
// لذا نجرب الإطلاق المباشر كاحتياط
await launchUrl(launchUri, mode: LaunchMode.externalApplication);
}
} catch (e) {
// طباعة الخطأ في حال الفشل التام
print("Error launching call: $e");
} }
} }

View File

@@ -0,0 +1,127 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
class DevicePerformanceManager {
/// القائمة البيضاء لموديلات الهواتف القوية (Flagships Only)
/// أي هاتف يبدأ موديله بأحد هذه الرموز سيعتبر قوياً
static const List<String> _highEndSamsungModels = [
'SM-S', // سلسلة Galaxy S21, S22, S23, S24 (S901, S908, S911...)
'SM-F', // سلسلة Fold و Flip (Z Fold, Z Flip)
'SM-N9', // سلسلة Note 9, Note 10, Note 20
'SM-G9', // سلسلة S10, S20 (G970, G980...)
];
static const List<String> _highEndGoogleModels = [
'Pixel 6',
'Pixel 7',
'Pixel 8',
'Pixel 9',
'Pixel Fold'
];
static const List<String> _highEndHuaweiModels = [
'ELS-', // P40 Pro
'ANA-', // P40
'HMA-', // Mate 20
'LYA-', // Mate 20 Pro
'VOG-', // P30 Pro
'ELE-', // P30
'NOH-', // Mate 40 Pro
'AL00', // Mate X series (some)
];
static const List<String> _highEndXiaomiModels = [
'2201122', // Xiaomi 12 series patterns often look like this
'2210132', // Xiaomi 13
'2304FPN', // Xiaomi 13 Ultra
'M2007J1', // Mi 10 series
'M2102K1', // Mi 11 Ultra
];
static const List<String> _highEndOnePlusModels = [
'GM19', // OnePlus 7
'HD19', // OnePlus 7T
'IN20', // OnePlus 8
'KB20', // OnePlus 8T
'LE21', // OnePlus 9
'NE22', // OnePlus 10
'PHB110', // OnePlus 11
'CPH', // Newer OnePlus models
];
/// دالة الفحص الرئيسية
static Future<bool> isHighEndDevice() async {
// 1. الآيفون دائماً قوي (نظام الرسوميات فيه متفوق حتى في الموديلات القديمة)
if (Platform.isIOS) return true;
if (Platform.isAndroid) {
try {
final androidInfo = await DeviceInfoPlugin().androidInfo;
String manufacturer = androidInfo.manufacturer.toLowerCase();
String model =
androidInfo.model.toUpperCase(); // نحوله لحروف كبيرة للمقارنة
String hardware = androidInfo.hardware.toLowerCase(); // المعالج
// --- الفحص العكسي (الحظر المباشر) ---
// إذا كان المعالج من الفئات الضعيفة جداً المشهورة في الهواتف المقلدة
// mt65xx, mt6735, sc77xx هي معالجات رخيصة جداً
if (hardware.contains('mt65') ||
hardware.contains('mt6735') ||
hardware.contains('sc77')) {
return false;
}
// --- فحص القائمة البيضاء (Whitelist) ---
// 1. Samsung Flagships
if (manufacturer.contains('samsung')) {
for (var prefix in _highEndSamsungModels) {
if (model.startsWith(prefix)) return true;
}
}
// 2. Google Pixel (6 and above)
if (manufacturer.contains('google')) {
for (var prefix in _highEndGoogleModels) {
if (model.contains(prefix.toUpperCase())) return true;
}
}
// 3. Huawei Flagships
if (manufacturer.contains('huawei')) {
for (var prefix in _highEndHuaweiModels) {
if (model.startsWith(prefix)) return true;
}
}
// 4. OnePlus Flagships
if (manufacturer.contains('oneplus')) {
for (var prefix in _highEndOnePlusModels) {
if (model.startsWith(prefix)) return true;
}
}
// 5. Xiaomi Flagships
if (manufacturer.contains('xiaomi') ||
manufacturer.contains('redmi') ||
manufacturer.contains('poco')) {
// شاومي تسميتها معقدة، لذا سنعتمد على فحص الرام كعامل مساعد هنا فقط
// لأن هواتف شاومي القوية عادة لا تزور الرام
// الرام يجب أن يكون أكبر من 6 جيجا (بايت)
double ramGB = (androidInfo.availableRamSize) / (1024 * 1024 * 1024);
if (ramGB > 7.5)
return true; // 8GB RAM or more is usually safe for Xiaomi high-end
}
// إذا لم يكن من ضمن القوائم أعلاه، نعتبره جهازاً متوسطاً/ضعيفاً ونعرض الرسم البسيط
return false;
} catch (e) {
// في حال حدوث خطأ في الفحص، نعود للوضع الآمن (الرسم البسيط)
return false;
}
}
return false;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ class RateController extends GetxController {
update(); update();
} }
void addRateToDriver() async { addRateToDriver() async {
if (selectedRateItemId < 1) { if (selectedRateItemId < 1) {
Get.defaultDialog( Get.defaultDialog(
title: 'You Should choose rate figure'.tr, title: 'You Should choose rate figure'.tr,

View File

@@ -1,3 +1,4 @@
import 'package:Intaleq/views/home/map_page_passenger.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -148,10 +149,11 @@ class RatingDriverBottomSheet extends StatelessWidget {
// 4. زر الإرسال // 4. زر الإرسال
MyElevatedButton( MyElevatedButton(
title: 'Submit Rating'.tr, title: 'Submit Rating'.tr,
onPressed: () { onPressed: () async {
controller.addRateToDriver(); await controller.addRateToDriver();
Get.find<MapPassengerController>() Get.offAll(() => MapPagePassenger());
.getRideStatusFromStartApp(); // Get.find<MapPassengerController>()
// .getRideStatusFromStartApp();
}) })
], ],
), ),

View File

@@ -35,6 +35,9 @@ class AuthScreen extends StatelessWidget {
final testerPasswordController = TextEditingController(); final testerPasswordController = TextEditingController();
final testerFormKey = GlobalKey<FormState>(); final testerFormKey = GlobalKey<FormState>();
// Brand Color for Logic (Cyan/Teal from the Arrow in the logo)
const Color brandColor = Color(0xFF00E5FF);
showDialog( showDialog(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
@@ -42,7 +45,8 @@ class AuthScreen extends StatelessWidget {
return BackdropFilter( return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: AlertDialog( child: AlertDialog(
backgroundColor: const Color(0xFF162232).withOpacity(0.85), // Updated background to match new theme (Dark Purple/Indigo)
backgroundColor: const Color(0xFF1A1A2E).withOpacity(0.90),
shape: shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: const Text( title: const Text(
@@ -73,7 +77,8 @@ class AuthScreen extends StatelessWidget {
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF00BFFF)), // Changed to Brand Cyan
borderSide: const BorderSide(color: brandColor),
), ),
), ),
validator: (value) => value == null || !value.contains('@') validator: (value) => value == null || !value.contains('@')
@@ -98,7 +103,8 @@ class AuthScreen extends StatelessWidget {
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF00BFFF)), // Changed to Brand Cyan
borderSide: const BorderSide(color: brandColor),
), ),
), ),
validator: (value) => value == null || value.isEmpty validator: (value) => value == null || value.isEmpty
@@ -116,13 +122,14 @@ class AuthScreen extends StatelessWidget {
), ),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00BFFF), backgroundColor: brandColor, // Updated Button Color
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: child: const Text('Login',
const Text('Login', style: TextStyle(color: Colors.black)), style: TextStyle(
color: Color(0xFF1A1A2E), fontWeight: FontWeight.bold)),
onPressed: () { onPressed: () {
if (testerFormKey.currentState!.validate()) { if (testerFormKey.currentState!.validate()) {
// Use the main controller to perform login // Use the main controller to perform login
@@ -149,39 +156,58 @@ class AuthScreen extends StatelessWidget {
return Scaffold( return Scaffold(
body: Container( body: Container(
// NEW: AI-inspired, brighter, and more dynamic color gradient // NEW DESIGN: Deep Purple/Indigo Gradient to match the "N" body
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Color(0xFF00122E), Color(0xFF00285F)], // Dark Indigo -> Deep Purple -> Dark Blue
begin: Alignment.topCenter, colors: [Color(0xFF2E1C59), Color(0xFF1A237E), Color(0xFF0D1117)],
end: Alignment.bottomCenter, begin: Alignment.topLeft,
end: Alignment.bottomRight,
), ),
), ),
child: Stack( child: Stack(
children: [ children: [
// Background shapes for a more dynamic feel // Background shapes updated to match the Logo accents
// Shape 1: The Orange/Red Swoosh color
Positioned( Positioned(
top: -100, top: -80,
left: -100, left: -80,
child: Container( child: Container(
width: 250, width: 250,
height: 250, height: 250,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: const Color(0xFF00BFFF).withOpacity(0.15), // Orange/Red from the swoosh lines
), color: const Color(0xFFFF5722).withOpacity(0.12),
boxShadow: [
BoxShadow(
color: const Color(0xFFFF5722).withOpacity(0.2),
blurRadius: 50,
spreadRadius: 10,
)
]),
), ),
), ),
// Shape 2: The Cyan/Teal Arrow color
Positioned( Positioned(
bottom: -150, bottom: -100,
right: -100, right: -80,
child: Container( child: Container(
width: 350, width: 350,
height: 350, height: 350,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: const Color(0xFF00BFFF).withOpacity(0.1), // Cyan/Teal from the arrow tip
), color: const Color(0xFF00E5FF).withOpacity(0.08),
boxShadow: [
BoxShadow(
color: const Color(0xFF00E5FF).withOpacity(0.15),
blurRadius: 60,
spreadRadius: 5,
)
]),
), ),
), ),
Center( Center(
@@ -199,10 +225,11 @@ class AuthScreen extends StatelessWidget {
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.white.withOpacity(0.1), color: Colors.white.withOpacity(0.05),
border: Border.all( border: Border.all(
color: Colors.white.withOpacity(0.2), // Gradient border for the logo container
width: 2)), color: Colors.white.withOpacity(0.1),
width: 1)),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(50), borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/logo.gif', child: Image.asset('assets/images/logo.gif',
@@ -219,9 +246,9 @@ class AuthScreen extends StatelessWidget {
color: Colors.white, color: Colors.white,
shadows: [ shadows: [
Shadow( Shadow(
blurRadius: 10.0, blurRadius: 15.0,
color: Colors.black26, color: Color(0xFF000000), // Darker shadow
offset: Offset(2, 2)), offset: Offset(0, 4)),
]), ]),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@@ -230,7 +257,7 @@ class AuthScreen extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Colors.white.withOpacity(0.8), color: Colors.white.withOpacity(0.75),
), ),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
@@ -239,15 +266,17 @@ class AuthScreen extends StatelessWidget {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(25.0), borderRadius: BorderRadius.circular(25.0),
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15), filter: ImageFilter.blur(
sigmaX: 20, sigmaY: 20), // Increased blur
child: Container( child: Container(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1), // Slightly darker tint for better contrast with Cyan inputs
color: const Color(0xFF1A237E).withOpacity(0.2),
borderRadius: BorderRadius.circular(25.0), borderRadius: BorderRadius.circular(25.0),
border: Border.all( border: Border.all(
color: Colors.white.withOpacity(0.2), color: Colors.white.withOpacity(0.1),
width: 1.5, width: 1.0,
), ),
), ),
child: child:
@@ -258,8 +287,7 @@ class AuthScreen extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
// A more distinct button for app testers // A more distinct button for app testers
Material( Material(
color: Colors.white.withOpacity(0.15), color: Colors.transparent,
borderRadius: BorderRadius.circular(12),
child: InkWell( child: InkWell(
onTap: () => onTap: () =>
_showTesterLoginDialog(context, loginController), _showTesterLoginDialog(context, loginController),
@@ -271,14 +299,14 @@ class AuthScreen extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.admin_panel_settings_outlined, Icon(Icons.admin_panel_settings_outlined,
color: Colors.white.withOpacity(0.8)), color: Colors.white.withOpacity(0.5)),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'For App Reviewers / Testers', 'For App Reviewers / Testers',
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.8), color: Colors.white.withOpacity(0.5),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w400,
), fontSize: 12),
), ),
], ],
), ),
@@ -310,6 +338,10 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
final _phoneController = TextEditingController(); final _phoneController = TextEditingController();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
bool _isLoading = false; bool _isLoading = false;
// Brand Color for Focus (Cyan/Teal)
final Color _focusColor = const Color(0xFF00E5FF);
static String formatSyrianPhone(String phone) { static String formatSyrianPhone(String phone) {
// Remove spaces, symbols, +, -, () // Remove spaces, symbols, +, -, ()
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim(); phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
@@ -404,7 +436,9 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
searchText: 'Search country'.tr, searchText: 'Search country'.tr,
languageCode: 'ar', languageCode: 'ar',
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
dropdownTextStyle: const TextStyle(color: Colors.black87), dropdownTextStyle: const TextStyle(
color: Colors
.white), // Changed to White for visibility on dark BG
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Phone Number'.tr, labelText: 'Phone Number'.tr,
hintText: 'witout zero'.tr, hintText: 'witout zero'.tr,
@@ -415,7 +449,8 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF00BFFF)), // Updated to Logo Cyan
borderSide: BorderSide(color: _focusColor, width: 2),
), ),
), ),
initialCountryCode: 'SY', initialCountryCode: 'SY',
@@ -444,8 +479,11 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _submit, onPressed: _submit,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00BFFF), // Updated to Logo Cyan
backgroundColor: _focusColor,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 5,
shadowColor: _focusColor.withOpacity(0.5),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
), ),
@@ -454,7 +492,8 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black), // Text is dark to contrast with bright Cyan
color: Color(0xFF1A1A2E)),
), ),
), ),
), ),
@@ -477,6 +516,9 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
final _otpController = TextEditingController(); final _otpController = TextEditingController();
bool _isLoading = false; bool _isLoading = false;
// Brand Color
final Color _brandColor = const Color(0xFF00E5FF);
void _submit() async { void _submit() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true); setState(() => _isLoading = true);
@@ -519,11 +561,17 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
counterText: "", counterText: "",
hintText: '-----', hintText: '-----',
hintStyle: TextStyle( hintStyle: TextStyle(
color: Colors.white.withOpacity(0.3), color: Colors.white.withOpacity(0.1),
letterSpacing: 18, letterSpacing: 18,
fontSize: 28), fontSize: 28),
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 10), contentPadding: const EdgeInsets.symmetric(vertical: 10),
// Add a subtle underline for the OTP area using brand color
enabledBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: Colors.white.withOpacity(0.2))),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: _brandColor)),
), ),
validator: (v) => v == null || v.length < 5 ? '' : null, validator: (v) => v == null || v.length < 5 ? '' : null,
), ),
@@ -536,8 +584,10 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _submit, onPressed: _submit,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00BFFF), backgroundColor: _brandColor, // Updated
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 5,
shadowColor: _brandColor.withOpacity(0.5),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
), ),
@@ -546,7 +596,7 @@ class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black), color: Color(0xFF1A1A2E)),
), ),
), ),
), ),
@@ -570,6 +620,9 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
final _emailController = TextEditingController(); final _emailController = TextEditingController();
bool _isLoading = false; bool _isLoading = false;
// Brand Color
final Color _brandColor = const Color(0xFF00E5FF);
void _submit() async { void _submit() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true); setState(() => _isLoading = true);
@@ -603,7 +656,8 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF00BFFF)), // Updated to Logo Cyan
borderSide: BorderSide(color: _brandColor, width: 2),
), ),
), ),
keyboardType: keyboardType, keyboardType: keyboardType,
@@ -646,8 +700,10 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _submit, onPressed: _submit,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00BFFF), backgroundColor: _brandColor, // Updated
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 5,
shadowColor: _brandColor.withOpacity(0.5),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)), borderRadius: BorderRadius.circular(12)),
), ),
@@ -656,7 +712,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black), color: Color(0xFF1A1A2E)),
), ),
), ),
), ),

View File

@@ -39,7 +39,7 @@ class MapPagePassenger extends StatelessWidget {
return Scaffold( return Scaffold(
body: SafeArea( body: SafeArea(
bottom: false, bottom: true,
child: Stack( child: Stack(
children: [ children: [
GoogleMapPassengerWidget(), GoogleMapPassengerWidget(),
@@ -89,17 +89,34 @@ class CancelRidePageShow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<MapPassengerController>( return GetBuilder<MapPassengerController>(
builder: (controller) => (controller.polyLines.isNotEmpty && builder: (controller) {
controller.statusRide != 'Begin') // نستخدم RideState Enum لأنه أدق، أو نصلح المنطق النصي
// || // الشرط:
// controller.timeToPassengerFromDriverAfterApplied == 0 // 1. يوجد خط مسار
// 2. الحالة ليست "بدأت"
// 3. الحالة ليست "انتهت"
// 4. الحالة ليست "قيد التنفيذ" (لزيادة التأكيد)
// bool showCancelButton = controller.polyLines.isNotEmpty &&
// controller.statusRide != 'Begin' && // استخدمنا &&
// controller.statusRide != 'inProgress' &&
// controller.statusRide != 'Finished';
// يمكنك أيضاً استخدام RideState ليكون أدق:
bool showCancelButton = controller.polyLines.isNotEmpty &&
controller.currentRideState.value != RideState.inProgress &&
controller.currentRideState.value != RideState.finished;
return showCancelButton
? Positioned( ? Positioned(
right: box.read(BoxName.lang) != 'ar' ? 10 : null, right: box.read(BoxName.lang) != 'ar' ? 10 : null,
left: box.read(BoxName.lang) == 'ar' ? 10 : null, left: box.read(BoxName.lang) == 'ar' ? 10 : null,
top: Get.height * .013, top: Get.height * .013,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
// استدعاء دالة الإلغاء
controller.changeCancelRidePageShow(); controller.changeCancelRidePageShow();
// ملاحظة: تأكد أن الدالة تظهر ديالوج للتأكيد أولاً ولا تلغي فوراً
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -115,7 +132,9 @@ class CancelRidePageShow extends StatelessWidget {
), ),
), ),
)) ))
: const SizedBox()); : const SizedBox();
},
);
} }
} }

View File

@@ -5,20 +5,18 @@ import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; // لتنسيق الأرقام import 'package:intl/intl.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../controller/firebase/notification_service.dart'; import '../../../controller/firebase/notification_service.dart';
import '../../../controller/functions/launch.dart'; import '../../../controller/functions/launch.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../widgets/my_textField.dart';
class ApplyOrderWidget extends StatelessWidget { class ApplyOrderWidget extends StatelessWidget {
const ApplyOrderWidget({super.key}); const ApplyOrderWidget({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// دالة لتحويل كود اللون الهيكس إلى لون
Color parseColor(String colorHex) { Color parseColor(String colorHex) {
if (colorHex.isEmpty) return Colors.grey; if (colorHex.isEmpty) return Colors.grey;
try { try {
@@ -39,57 +37,59 @@ class ApplyOrderWidget extends StatelessWidget {
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
curve: Curves.elasticOut, // تأثير حركي أجمل curve: Curves.elasticOut,
bottom: isVisible ? 0 : -Get.height * 0.6, // تغيير: جعلنا الإخفاء للأسفل أقل حدة ليكون التحريك أسرع
bottom: isVisible ? 0 : -400,
left: 0, left: 0,
right: 0, right: 0,
child: Container( child: Container(
// height: Get.height * 0.38, // زيادة الارتفاع قليلاً للتصميم الجديد
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).cardColor, color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 20, blurRadius: 20,
spreadRadius: 2, spreadRadius: 1,
color: Colors.black.withOpacity(0.15), color: Colors.black.withOpacity(0.1),
offset: const Offset(0, -2), offset: const Offset(0, -3),
) )
], ],
), ),
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20), // تغيير: تقليل الحواف الخارجية بشكل كبير
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: GetBuilder<MapPassengerController>( child: GetBuilder<MapPassengerController>(
builder: (c) { builder: (c) {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize:
MainAxisSize.min, // مهم جداً: يأخذ أقل مساحة ممكنة
children: [ children: [
// مقبض صغير في الأعلى // مقبض صغير
Container( Container(
width: 40, width: 40,
height: 5, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.3), color: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
), ),
const SizedBox(height: 15), const SizedBox(height: 10), // تقليل المسافة
// السعر والعنوان // 1. [تغيير جوهري] دمج السعر مع الحالة في صف واحد لتوفير المساحة
_buildPriceHeader(context, c), _buildCompactHeaderRow(context, c),
const SizedBox(height: 15), const SizedBox(height: 10), // مسافة مضغوطة
// كرت المعلومات الرئيسي (سائق + سيارة) // 2. كرت المعلومات المضغوط
_buildMainInfoCard(context, c, parseColor), _buildCompactInfoCard(context, c, parseColor),
const SizedBox(height: 15), const SizedBox(height: 10), // مسافة مضغوطة
// أزرار الاتصال // 3. أزرار الاتصال (Slim)
_buildContactButtonsRow(context, c), _buildCompactButtonsRow(context, c),
const SizedBox(height: 15), const SizedBox(height: 10), // مسافة مضغوطة
// شريط الوقت // 4. شريط الوقت
c.currentRideState.value == RideState.driverArrived c.currentRideState.value == RideState.driverArrived
? const DriverArrivePassengerAndWaitMinute() ? const DriverArrivePassengerAndWaitMinute()
: const TimeDriverToPassenger(), : const TimeDriverToPassenger(),
@@ -103,42 +103,90 @@ class ApplyOrderWidget extends StatelessWidget {
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// 1. قسم السعر (مع التنسيق الجديد) // [NEW] 1. صف الرأس المضغوط (يحتوي الحالة + الإحصائيات + السعر)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
Widget _buildPriceHeader( Widget _buildCompactHeaderRow(
BuildContext context, MapPassengerController controller) { BuildContext context, MapPassengerController controller) {
// تنسيق الرقم (مثلاً: 60,000) // تنسيق السعر
final formatter = NumberFormat("#,###"); final formatter = NumberFormat("#,###");
String formattedPrice = formatter.format(controller.totalPassenger); String formattedPrice = formatter.format(controller.totalPassenger);
return Column( // حساب الدقائق
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: [ children: [
Text( // القسم الأيسر: الحالة + Chips
'Driver Accepted Request'.tr, Expanded(
style: AppStyle.subtitle.copyWith(color: Colors.grey[600]), 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),
),
],
),
],
),
), ),
const SizedBox(height: 5),
Row( // القسم الأيمن: السعر (كبير وواضح في الزاوية)
mainAxisAlignment: MainAxisAlignment.center, Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( Text(
formattedPrice, formattedPrice,
style: AppStyle.title.copyWith( style: AppStyle.title.copyWith(
fontSize: 28, fontSize: 24, // تصغير من 32 إلى 24
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
color: AppColor.primaryColor, color: AppColor.primaryColor,
height: 1.0,
), ),
), ),
const SizedBox(width: 5), Text(
Padding( 'SYP'.tr,
padding: const EdgeInsets.only(top: 8.0), style: TextStyle(
child: Text( fontSize: 12,
'SYP'.tr, fontWeight: FontWeight.bold,
style: AppStyle.subtitle.copyWith( color: Colors.grey[600],
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
), ),
), ),
], ],
@@ -147,207 +195,251 @@ class ApplyOrderWidget extends StatelessWidget {
); );
} }
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, // تصغير الخط
),
),
],
),
);
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// 2. كرت المعلومات الرئيسي (السائق + السيارة 3D) // [MODIFIED] 2. كرت المعلومات المضغوط جداً
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
Widget _buildMainInfoCard(BuildContext context, Widget _buildCompactInfoCard(BuildContext context,
MapPassengerController controller, Color Function(String) parseColor) { MapPassengerController controller, Color Function(String) parseColor) {
return Container( return Container(
padding: const EdgeInsets.all(12), // تقليل الحواف الداخلية للكرت
padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor, // لون خلفية فاتح color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.withOpacity(0.1)), 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( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
// الجزء الأيسر: معلومات السائق Text(
Expanded( plateNumber,
child: Row( style: const TextStyle(
children: [ fontFamily: 'RobotoMono',
// صورة السائق fontSize: 18, // تصغير الرقم
Container( fontWeight: FontWeight.w900,
padding: const EdgeInsets.all(3), color: Colors.black87,
decoration: BoxDecoration( letterSpacing: 1.5,
shape: BoxShape.circle,
border: Border.all(color: AppColor.primaryColor, width: 2),
),
child: CircleAvatar(
radius: 26,
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (exception, stackTrace) =>
const Icon(Icons.person, size: 26, color: Colors.grey),
),
),
const SizedBox(width: 12),
// الاسم والتقييم والسيارة نص
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.driverName,
style: AppStyle.title.copyWith(
fontSize: 16, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.star, color: Colors.amber, size: 16),
const SizedBox(width: 4),
Text(
controller.driverRate,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
),
],
),
const SizedBox(height: 4),
Text(
'${controller.model}${controller.licensePlate}',
style: TextStyle(color: Colors.grey[600], fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
), ),
), ),
const Text("SYR",
// الجزء الأيمن: أيقونة السيارة الـ 3D style: TextStyle(
_build3DCarIcon(controller, parseColor), fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.black54)),
], ],
), ),
); );
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// 3. أيقونة السيارة الـ 3D (الدائرة والظلال والخلفية الذكية) // [MODIFIED] 3. أزرار الاتصال (Slim Buttons)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
Widget _build3DCarIcon( Widget _buildCompactButtonsRow(
MapPassengerController controller, Color Function(String) parseColor) {
Color carColor = parseColor(controller.colorHex);
// تحديد سطوع لون السيارة لتحديد لون الخلفية
// إذا كانت السيارة فاتحة (أكثر من 0.6)، الخلفية تكون غامقة، والعكس
bool isCarLight = carColor.computeLuminance() > 0.6;
// ألوان الخلفية للدائرة
Color bgGradientStart =
isCarLight ? Colors.blueGrey.shade700 : Colors.grey.shade100;
Color bgGradientEnd =
isCarLight ? Colors.blueGrey.shade900 : Colors.grey.shade300;
Color borderColor = isCarLight ? Colors.blueGrey.shade600 : Colors.white;
return Container(
width: 75,
height: 75,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
shape: BoxShape.circle,
// تدرج لوني للخلفية لتبدو 3D
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [bgGradientStart, bgGradientEnd],
),
border: Border.all(color: borderColor, width: 2),
// ظلال لرفع الدائرة عن السطح
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(4, 4),
),
BoxShadow(
color: Colors.white.withOpacity(isCarLight ? 0.1 : 0.8),
blurRadius: 10,
offset: const Offset(-4, -4),
),
],
),
child: Center(
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,
),
),
),
);
}
// ---------------------------------------------------------------------------
// 4. أزرار الاتصال (بتصميم جديد)
// ---------------------------------------------------------------------------
Widget _buildContactButtonsRow(
BuildContext context, MapPassengerController controller) { BuildContext context, MapPassengerController controller) {
return Row( return SizedBox(
children: [ height: 40, // تحديد ارتفاع ثابت وصغير للأزرار
Expanded( child: Row(
child: _buildActionButton( children: [
label: 'Message'.tr, Expanded(
icon: Icons.chat_bubble_outline_rounded, child: _buildSlimButton(
color: AppColor.blueColor, label: 'Message'.tr, // اختصار الكلمة
onTap: () => _showContactOptionsDialog(context, controller), icon: Icons.chat_bubble_outline_rounded,
color: AppColor.blueColor,
bgColor: AppColor.blueColor.withOpacity(0.08),
onTap: () => _showContactOptionsDialog(context, controller),
),
), ),
), const SizedBox(width: 10), // تقليل المسافة
const SizedBox(width: 15), Expanded(
Expanded( child: _buildSlimButton(
child: _buildActionButton( label: 'Call'.tr, // اختصار الكلمة
label: 'Call'.tr, icon: Icons.phone_rounded,
icon: Icons.phone_rounded, color: Colors.white,
color: AppColor.greenColor, bgColor: AppColor.greenColor,
onTap: () { onTap: () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
makePhoneCall(controller.driverPhone); makePhoneCall(controller.driverPhone);
}, },
isPrimary: true,
),
), ),
), ],
], ),
); );
} }
Widget _buildActionButton({ Widget _buildSlimButton({
required String label, required String label,
required IconData icon, required IconData icon,
required Color color, required Color color,
required Color bgColor,
required VoidCallback onTap, required VoidCallback onTap,
bool isPrimary = false,
}) { }) {
return ElevatedButton( return ElevatedButton(
onPressed: onTap, onPressed: onTap,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: color.withOpacity(0.1), backgroundColor: bgColor,
foregroundColor: color, foregroundColor: color,
elevation: 0, elevation: isPrimary ? 2 : 0,
padding: const EdgeInsets.symmetric(vertical: 12), padding: EdgeInsets.zero, // إزالة الحواشي الداخلية
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(icon, size: 20), Icon(icon, size: 18, color: color), // تصغير الأيقونة
const SizedBox(width: 8), const SizedBox(width: 6),
Text( Text(
label, label,
style: const TextStyle(fontWeight: FontWeight.bold), style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14, // تصغير الخط
color: color,
),
), ),
], ],
), ),
); );
} }
// --- النوافذ المنبثقة للرسائل (نفس المنطق القديم) --- // --- النوافذ المنبثقة للرسائل (نفس الكود السابق مع تحسين بسيط) ---
void _showContactOptionsDialog( void _showContactOptionsDialog(
BuildContext context, MapPassengerController controller) { BuildContext context, MapPassengerController controller) {
Get.bottomSheet( Get.bottomSheet(
@@ -361,13 +453,13 @@ class ApplyOrderWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Quick Message'.tr, style: AppStyle.title), Text('Quick Message'.tr,
style: AppStyle.title.copyWith(fontSize: 16)),
const SizedBox(height: 15), const SizedBox(height: 15),
..._buildPredefinedMessages(controller), ..._buildPredefinedMessages(controller),
const Divider(height: 30), const Divider(height: 20),
_buildCustomMessageInput(controller, context), _buildCustomMessageInput(controller, context),
SizedBox( SizedBox(height: MediaQuery.of(context).viewInsets.bottom),
height: MediaQuery.of(context).viewInsets.bottom), // للكيبورد
], ],
), ),
), ),
@@ -384,7 +476,7 @@ class ApplyOrderWidget extends StatelessWidget {
return messages return messages
.map((message) => Padding( .map((message) => Padding(
padding: const EdgeInsets.only(bottom: 10.0), padding: const EdgeInsets.only(bottom: 8.0),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
_sendMessage(controller, message.tr); _sendMessage(controller, message.tr);
@@ -392,18 +484,20 @@ class ApplyOrderWidget extends StatelessWidget {
}, },
child: Container( child: Container(
padding: padding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 15), const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1), color: Colors.grey.withOpacity(0.08),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.withOpacity(0.1)),
), ),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.quickreply_rounded, Icon(Icons.chat_bubble_outline,
size: 18, color: Colors.grey), size: 16, color: AppColor.primaryColor),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Text(message.tr, style: AppStyle.subtitle)), child: Text(message.tr,
style: AppStyle.subtitle.copyWith(fontSize: 13))),
], ],
), ),
), ),
@@ -418,10 +512,11 @@ class ApplyOrderWidget extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15), height: 40,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1), color: Colors.grey.withOpacity(0.08),
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(20),
), ),
child: Form( child: Form(
key: controller.messagesFormKey, key: controller.messagesFormKey,
@@ -429,24 +524,28 @@ class ApplyOrderWidget extends StatelessWidget {
controller: controller.messageToDriver, controller: controller.messageToDriver,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Type your message...'.tr, hintText: 'Type your message...'.tr,
hintStyle: TextStyle(color: Colors.grey[500], fontSize: 13),
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.only(bottom: 10),
), ),
), ),
), ),
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 8),
CircleAvatar( InkWell(
backgroundColor: AppColor.primaryColor, onTap: () {
child: IconButton( if (controller.messagesFormKey.currentState!.validate()) {
onPressed: () { _sendMessage(controller, controller.messageToDriver.text);
if (controller.messagesFormKey.currentState!.validate()) { controller.messageToDriver.clear();
_sendMessage(controller, controller.messageToDriver.text); Get.back();
controller.messageToDriver.clear(); }
Get.back(); },
} child: CircleAvatar(
}, backgroundColor: AppColor.primaryColor,
icon: const Icon(Icons.send_rounded, color: Colors.white, size: 20), radius: 20,
child:
const Icon(Icons.send_rounded, color: Colors.white, size: 16),
), ),
), ),
], ],
@@ -455,10 +554,10 @@ class ApplyOrderWidget extends StatelessWidget {
void _sendMessage(MapPassengerController controller, String text) { void _sendMessage(MapPassengerController controller, String text) {
NotificationService.sendNotification( NotificationService.sendNotification(
category: 'message From passenger', category: 'MSG_FROM_PASSENGER',
target: controller.driverToken.toString(), target: controller.driverToken.toString(),
title: 'Message From passenger'.tr, title: text.tr,
body: text, body: text.tr,
isTopic: false, isTopic: false,
tone: 'ding', tone: 'ding',
driverList: [], driverList: [],
@@ -467,7 +566,7 @@ class ApplyOrderWidget extends StatelessWidget {
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// مؤشرات الانتظار والوقت (نفس المنطق مع تحسين بسيط في التصميم) // مؤشرات الانتظار والوقت (مضغوطة)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
class DriverArrivePassengerAndWaitMinute extends StatelessWidget { class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
@@ -481,24 +580,27 @@ class DriverArrivePassengerAndWaitMinute extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Driver is waiting'.tr, Text('Waiting...'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: Colors.orange)),
Text( Text(
controller.stringRemainingTimeDriverWaitPassenger5Minute, controller.stringRemainingTimeDriverWaitPassenger5Minute,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, color: AppColor.redColor), fontWeight: FontWeight.bold,
color: Colors.orange,
fontSize: 12),
), ),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 4),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator( child: LinearProgressIndicator(
backgroundColor: Colors.grey[200], backgroundColor: Colors.orange.withOpacity(0.2),
color: controller.remainingTimeDriverWaitPassenger5Minute < 60 color: Colors.orange,
? AppColor.redColor minHeight: 4,
: AppColor.greenColor,
minHeight: 8,
value: value:
controller.progressTimerDriverWaitPassenger5Minute.toDouble(), controller.progressTimerDriverWaitPassenger5Minute.toDouble(),
), ),
@@ -520,25 +622,13 @@ class TimeDriverToPassenger extends StatelessWidget {
} }
return Column( return Column(
children: [ children: [
Row( // شريط التقدم فقط لأن الوقت والمسافة موجودان بالأعلى
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Driver arriving in'.tr,
style: const TextStyle(fontWeight: FontWeight.bold)),
Text(
controller.stringRemainingTimeToPassenger,
style: const TextStyle(
fontWeight: FontWeight.bold, color: AppColor.primaryColor),
),
],
),
const SizedBox(height: 6),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator( child: LinearProgressIndicator(
backgroundColor: Colors.grey[200], backgroundColor: AppColor.primaryColor.withOpacity(0.1),
color: AppColor.primaryColor, color: AppColor.primaryColor,
minHeight: 8, minHeight: 4,
value: controller.progressTimerToPassengerFromDriverAfterApplied value: controller.progressTimerToPassengerFromDriverAfterApplied
.toDouble() .toDouble()
.clamp(0.0, 1.0), .clamp(0.0, 1.0),

View File

@@ -5,97 +5,156 @@ import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart'; import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import '../../widgets/elevated_btn.dart'; import '../../widgets/elevated_btn.dart';
// دالة لإظهار الشيت
void showCancelRideBottomSheet() { void showCancelRideBottomSheet() {
Get.bottomSheet( Get.bottomSheet(
cancelRidePage(), const CancelRidePageWidget(),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
isScrollControlled: true, isScrollControlled: true,
); );
} }
GetBuilder<MapPassengerController> cancelRidePage() { // الويدجت مفصولة لترتيب الكود
Get.put(MapPassengerController()); class CancelRidePageWidget extends StatelessWidget {
const CancelRidePageWidget({Key? key}) : super(key: key);
final List<String> reasons = [ @override
"I don't need a ride anymore".tr, Widget build(BuildContext context) {
"I was just trying the application".tr, // تأكد من وجود الكنترولر
"No driver accepted my request".tr, final controller = Get.find<MapPassengerController>();
"I added the wrong pick-up/drop-off location".tr,
"I don't have a reason".tr,
"Other".tr,
];
return GetBuilder<MapPassengerController>( final List<String> reasons = [
builder: (controller) => controller.isCancelRidePageShown "Changed my mind".tr,
? Container( "Found another transport".tr,
height: Get.height * 0.6, "Driver is taking too long".tr,
padding: const EdgeInsets.all(20), "Driver asked me to cancel".tr,
decoration: BoxDecoration( "Wrong pickup location".tr,
color: Colors.white, "Other".tr,
boxShadow: [ ];
BoxShadow(
color: Colors.black.withOpacity(0.2), return Container(
offset: const Offset(0, 8), height: Get.height * 0.7, // ارتفاع مناسب
blurRadius: 16, 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),
), ),
], ),
borderRadius: BorderRadius.circular(20),
), ),
child: Column( const SizedBox(height: 20),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ Text(
Text( 'Why do you want to cancel?'.tr,
'Can we know why you want to cancel Ride ?'.tr, style: AppStyle.title
style: AppStyle.title .copyWith(fontSize: 18, fontWeight: FontWeight.bold),
.copyWith(fontSize: 18, fontWeight: FontWeight.bold), textAlign: TextAlign.center,
textAlign: TextAlign.center, ),
), const SizedBox(height: 10),
const SizedBox(height: 20),
Expanded( Expanded(
child: ListView.separated( child: ListView.separated(
itemCount: reasons.length, itemCount: reasons.length,
separatorBuilder: (context, index) => const Divider(), separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return ListTile( bool isSelected = controller.selectedReasonIndex == index;
return Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
title: Text( title: Text(
reasons[index], reasons[index],
style: AppStyle.title.copyWith(fontSize: 16), style: TextStyle(
), fontWeight: isSelected
leading: Radio( ? FontWeight.bold
value: index, : FontWeight.normal,
groupValue: controller.selectedReason, color: isSelected
onChanged: (int? value) { ? AppColor.primaryColor
controller.selectReason(value!, reasons[index]); : Colors.black87,
}, fontSize: 15),
activeColor: AppColor.primaryColor,
), ),
trailing: isSelected
? Icon(Icons.radio_button_checked,
color: AppColor.primaryColor)
: Icon(Icons.radio_button_off, color: Colors.grey),
onTap: () { onTap: () {
controller.selectReason(index, reasons[index]); controller.selectReason(index, reasons[index]);
}, },
); ),
},
), // إظهار حقل النص فقط عند اختيار "أخرى"
), if (isSelected && reasons[index] == "Other".tr)
const SizedBox(height: 20), Padding(
MyElevatedButton( padding: const EdgeInsets.only(
title: 'Cancel Ride'.tr, bottom: 10, left: 10, right: 10),
onPressed: () { child: TextField(
if (controller.selectedReason == -1) { controller: controller.otherReasonController,
Get.snackbar( decoration: InputDecoration(
'You Should be select reason.'.tr, hintText: "Please write the reason...".tr,
'', filled: true,
snackPosition: SnackPosition.BOTTOM, fillColor: Colors.grey[100],
backgroundColor: AppColor.redColor, border: OutlineInputBorder(
colorText: Colors.white, borderRadius: BorderRadius.circular(10),
); borderSide: BorderSide.none,
} else { ),
controller.cancelRide(); contentPadding: const EdgeInsets.symmetric(
} horizontal: 15, vertical: 12),
}, ),
), maxLines: 2,
], ),
)
],
);
},
),
), ),
)
: const SizedBox(), 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])),
),
),
],
),
),
);
}
} }

View File

@@ -16,7 +16,7 @@ import '../../../controller/home/map_passenger_controller.dart';
import '../../../print.dart'; import '../../../print.dart';
import '../../widgets/mydialoug.dart'; import '../../widgets/mydialoug.dart';
// --- CarType class (unchanged) --- // --- CarType class (Unchanged) ---
class CarType { class CarType {
final String carType; final String carType;
final String carDetail; final String carDetail;
@@ -27,21 +27,20 @@ class CarType {
{required this.carType, required this.carDetail, required this.image}); {required this.carType, required this.carDetail, required this.image});
} }
// --- List of Car Types (unchanged) --- // --- List of Car Types (Unchanged) ---
List<CarType> carTypes = [ List<CarType> carTypes = [
CarType( CarType(
carType: 'Speed', carType: 'Fixed Price',
carDetail: 'Closest & Cheapest'.tr, carDetail: 'Closest & Cheapest'.tr,
image: 'assets/images/carspeed.png'), // First choice image: 'assets/images/carspeed.png'),
CarType( CarType(
carType: 'Comfort', carType: 'Comfort',
carDetail: 'Comfort choice'.tr, carDetail: 'Comfort choice'.tr,
image: 'assets/images/blob.png'), // Second choice image: 'assets/images/blob.png'),
CarType( CarType(
carType: 'Electric', carType: 'Electric',
carDetail: 'Quiet & Eco-Friendly'.tr, carDetail: 'Quiet & Eco-Friendly'.tr,
image: image: 'assets/images/electric.png'),
'assets/images/electric.png'), // Third choice - NOTE: Use your actual image path
CarType( CarType(
carType: 'Lady', carType: 'Lady',
carDetail: 'Lady Captain for girls'.tr, carDetail: 'Lady Captain for girls'.tr,
@@ -62,8 +61,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
final textToSpeechController = Get.put(TextToSpeechController()); final textToSpeechController = Get.put(TextToSpeechController());
void _prepareCarTypes(MapPassengerController controller) { void _prepareCarTypes(MapPassengerController controller) {
// This logic remains the same if (controller.distance > 23) {
if (controller.distance > 33) {
if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) { if (!carTypes.any((car) => car.carType == 'Rayeh Gai')) {
carTypes.add(CarType( carTypes.add(CarType(
carType: 'Rayeh Gai', carType: 'Rayeh Gai',
@@ -83,110 +81,403 @@ class CarDetailsTypeToChoose extends StatelessWidget {
if (!(controller.isBottomSheetShown) && controller.rideConfirm == false) { if (!(controller.isBottomSheetShown) && controller.rideConfirm == false) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
// Added a BackdropFilter for a modern glassmorphism effect
// Main Bottom Sheet Design
return Positioned( return Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
child: ClipRRect( child: Container(
borderRadius: const BorderRadius.only( decoration: BoxDecoration(
topLeft: Radius.circular(30), color: AppColor
topRight: Radius.circular(30), .secondaryColor, // Solid background for better performance
), borderRadius: const BorderRadius.only(
child: BackdropFilter( topLeft: Radius.circular(30),
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), topRight: Radius.circular(30),
child: Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.9),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
border: Border.all(color: AppColor.writeColor.withOpacity(0.1)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Added a small handle for visual cue
Container(
width: 40,
height: 5,
margin: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: AppColor.writeColor.withOpacity(0.3),
borderRadius: BorderRadius.circular(10),
),
),
_buildHeader(controller),
_buildNegativeBalanceWarning(controller),
SizedBox(
height: 140, // Increased height for better spacing
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
itemCount: carTypes.length,
itemBuilder: (context, index) {
final carType = carTypes[index];
final isSelected = controller.selectedIndex == index;
return _buildHorizontalCarCard(
context, controller, carType, isSelected, index);
},
),
),
_buildPromoButton(context, controller),
const SizedBox(height: 8), // Added padding at the bottom
],
),
), ),
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),
],
), ),
), ),
); );
}); });
} }
// --- All other methods are here, with updated designs --- // --- 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( Widget _buildPromoButton(
BuildContext context, MapPassengerController controller) { BuildContext context, MapPassengerController controller) {
if (controller.promoTaken) { if (controller.promoTaken) return const SizedBox.shrink();
return const SizedBox.shrink();
}
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 16), padding: const EdgeInsets.fromLTRB(20, 10, 20, 5),
child: GestureDetector( child: Material(
onTap: () => _showPromoCodeDialog(context, controller), color: Colors.transparent,
child: Container( child: InkWell(
padding: const EdgeInsets.symmetric(vertical: 14), onTap: () => _showPromoCodeDialog(context, controller),
decoration: BoxDecoration( borderRadius: BorderRadius.circular(14),
color: AppColor.primaryColor.withOpacity(0.1), child: Container(
borderRadius: BorderRadius.circular(16), padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
border: Border.all( decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.5), color: Colors.grey.withOpacity(0.05),
width: 1, 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)
],
), ),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.local_offer_outlined,
color: AppColor.primaryColor, size: 22),
const SizedBox(width: 10),
Text(
'Have a promo code?'.tr,
style: AppStyle.title.copyWith(
fontSize: 16,
color: AppColor.primaryColor,
fontWeight: FontWeight.w600),
),
],
), ),
), ),
), ),
); );
} }
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( void _showPromoCodeDialog(
BuildContext context, MapPassengerController controller) { BuildContext context, MapPassengerController controller) {
Get.dialog( Get.dialog(
@@ -194,6 +485,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
shape: shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)), RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
backgroundColor: AppColor.secondaryColor, backgroundColor: AppColor.secondaryColor,
elevation: 10,
child: Padding( child: Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
child: Form( child: Form(
@@ -202,17 +494,20 @@ class CarDetailsTypeToChoose extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Icon(Icons.local_activity_outlined,
size: 40, color: AppColor.primaryColor),
const SizedBox(height: 16),
Text( Text(
'Apply Promo Code'.tr, 'Apply Promo Code'.tr,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: AppStyle.headTitle.copyWith(fontSize: 22), style: AppStyle.headTitle.copyWith(fontSize: 20),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Enter your code below to apply the discount.'.tr, 'Enter your code below to apply the discount.'.tr,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: AppStyle.subtitle style: AppStyle.subtitle
.copyWith(color: AppColor.writeColor.withOpacity(0.7)), .copyWith(color: Colors.grey.shade600, fontSize: 14),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
MyTextForm( MyTextForm(
@@ -225,15 +520,10 @@ class CarDetailsTypeToChoose extends StatelessWidget {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: OutlinedButton( child: TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
style: OutlinedButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: AppColor.writeColor, foregroundColor: Colors.grey,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
side: BorderSide(
color: AppColor.writeColor.withOpacity(0.3)),
), ),
child: Text('Cancel'.tr), child: Text('Cancel'.tr),
), ),
@@ -262,211 +552,6 @@ class CarDetailsTypeToChoose extends StatelessWidget {
); );
} }
Widget _buildHorizontalCarCard(
BuildContext context,
MapPassengerController controller,
CarType carType,
bool isSelected,
int index) {
return InkWell(
onTap: () {
controller.selectCarFromList(index);
_showCarDetailsDialog(
context, controller, carType, textToSpeechController);
},
borderRadius: BorderRadius.circular(20),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 120, // Increased width
margin: const EdgeInsets.only(right: 12),
padding: const EdgeInsets.all(8), // Added padding
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isSelected
? [
AppColor.primaryColor.withOpacity(0.3),
AppColor.primaryColor.withOpacity(0.1)
]
: [
AppColor.writeColor.withOpacity(0.05),
AppColor.writeColor.withOpacity(0.1)
],
),
borderRadius: BorderRadius.circular(20), // More rounded corners
border: Border.all(
color: isSelected
? AppColor.primaryColor
: AppColor.writeColor.withOpacity(0.2),
width: isSelected ? 2.5 : 1.0,
),
boxShadow: isSelected
? [
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.3),
blurRadius: 10,
spreadRadius: 1,
)
]
: [],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround, // Better alignment
children: [
Image.asset(carType.image, height: 55), // Slightly larger image
Text(
carType.carType.tr,
style: AppStyle.subtitle
.copyWith(fontWeight: FontWeight.bold, fontSize: 15),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
_buildPriceDisplay(controller, carType),
],
),
),
);
}
Widget _buildHeader(MapPassengerController controller) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Choose your ride'.tr,
style: AppStyle.headTitle.copyWith(fontSize: 24)),
const SizedBox(height: 8),
// Added icons for better visual representation
Row(
children: [
Icon(Icons.map_outlined,
color: AppColor.primaryColor, size: 16),
const SizedBox(width: 4),
Text(
'${controller.distance.toStringAsFixed(1)} ${'KM'.tr}',
style: AppStyle.subtitle.copyWith(
color: AppColor.primaryColor,
fontWeight: FontWeight.w600,
fontSize: 14),
),
const SizedBox(width: 12),
Icon(Icons.timer_outlined,
color: AppColor.primaryColor, size: 16),
const SizedBox(width: 4),
Text(
controller.hours > 0
? '${controller.hours}h ${controller.minutes}m'
: '${controller.minutes} min',
style: AppStyle.subtitle.copyWith(
color: AppColor.primaryColor,
fontWeight: FontWeight.w600,
fontSize: 14),
),
],
),
],
),
],
),
);
}
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.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: AppColor.redColor.withOpacity(0.9),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(Icons.error_outline, color: Colors.white, 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: Colors.white, fontWeight: FontWeight.w600))),
],
),
);
}
return const SizedBox.shrink();
}
Widget _buildPriceDisplay(
MapPassengerController mapPassengerController, CarType carType) {
return Text(
'${_getPassengerPriceText(carType, mapPassengerController)} ${'SYP'.tr}',
style: AppStyle.subtitle.copyWith(
fontSize: 15, // Increased font size
fontWeight: FontWeight.bold,
color: AppColor.primaryColor));
}
// --- LOGIC METHODS (UNCHANGED) ---
// 1. قم بإضافة هذا السطر في أعلى الملف
String _getPassengerPriceText(
CarType carType, MapPassengerController mapPassengerController) {
// الخطوة 1: احصل على السعر كـ double أولاً
double rawPrice;
switch (carType.carType) {
case 'Comfort':
rawPrice = mapPassengerController.totalPassengerComfort;
break;
case 'Speed':
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 '...'; // إذا كان نوع السيارة غير معروف
}
// الخطوة 2: قم بإزالة الكسور العشرية
// .round() ستحول 65000.00 إلى 65000
final int roundedPrice = rawPrice.round();
// الخطوة 3: أنشئ "مُنسّق" ليضيف فواصل الآلاف
// NumberFormat.decimalPattern() يستخدم إعدادات اللغة الافتراضية للجهاز
// لوضع الفاصلة (,) أو النقطة (.) حسب الدولة
final formatter = NumberFormat.decimalPattern();
// الخطوة 4: قم بتنسيق الرقم الصحيح
// سيحول 65000 إلى "65,000"
return formatter.format(roundedPrice);
}
void _showCarDetailsDialog( void _showCarDetailsDialog(
BuildContext context, BuildContext context,
MapPassengerController mapPassengerController, MapPassengerController mapPassengerController,
@@ -475,74 +560,75 @@ class CarDetailsTypeToChoose extends StatelessWidget {
Get.dialog( Get.dialog(
Dialog( Dialog(
shape: shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(24.0)), RoundedRectangleBorder(borderRadius: BorderRadius.circular(28.0)),
backgroundColor: backgroundColor: Colors.transparent,
Colors.transparent, // Make dialog background transparent
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
children: [ children: [
// Main content container
Container( Container(
margin: margin: const EdgeInsets.only(top: 60),
const EdgeInsets.only(top: 50), // Make space for the image padding: const EdgeInsets.fromLTRB(24, 70, 24, 24),
padding: const EdgeInsets.fromLTRB(20, 70, 20, 20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(24.0), borderRadius: BorderRadius.circular(28.0),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Text(
carType.carType.tr, carType.carType.tr,
style: AppStyle.headTitle.copyWith(fontSize: 24), style: AppStyle.headTitle.copyWith(fontSize: 22),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
IconButton( InkWell(
onPressed: () => textToSpeechController.speakText( onTap: () => textToSpeechController.speakText(
_getCarDescription( _getCarDescription(
mapPassengerController, carType)), mapPassengerController, carType)),
icon: Icon(Icons.volume_up_outlined, borderRadius: BorderRadius.circular(20),
color: AppColor.primaryColor, size: 26), child: Padding(
), padding: const EdgeInsets.all(4.0),
child: Icon(Icons.volume_up_rounded,
color: AppColor.primaryColor, size: 24),
),
)
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 16),
Text( Container(
_getCarDescription(mapPassengerController, carType), padding: const EdgeInsets.all(16),
textAlign: TextAlign.center, decoration: BoxDecoration(
style: AppStyle.subtitle.copyWith( color: Colors.grey.withOpacity(0.05),
color: AppColor.writeColor.withOpacity(0.7), borderRadius: BorderRadius.circular(16)),
fontSize: 16, child: Text(
height: 1.5, _getCarDescription(mapPassengerController, carType),
textAlign: TextAlign.center,
style: AppStyle.subtitle.copyWith(
color: Colors.black87,
fontSize: 15,
height: 1.4,
),
), ),
), ),
const SizedBox(height: 28), const SizedBox(height: 24),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TextButton( child: TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
style: TextButton.styleFrom( child: Text('Back'.tr,
foregroundColor: style: TextStyle(color: Colors.grey.shade600)),
AppColor.writeColor.withOpacity(0.8),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text('Cancel'.tr),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
flex: 2,
child: MyElevatedButton( child: MyElevatedButton(
kolor: AppColor.greenColor, kolor: AppColor.greenColor,
title: 'Next'.tr, title: 'Select This Ride'.tr,
onPressed: () { onPressed: () {
Get.back(); Get.back();
_handleCarSelection( _handleCarSelection(
@@ -555,10 +641,12 @@ class CarDetailsTypeToChoose extends StatelessWidget {
], ],
), ),
), ),
// Positioned car image
Positioned( Positioned(
top: -10, top: 0,
child: Image.asset(carType.image, height: 120), child: Hero(
tag: 'car_${carType.carType}',
child: Image.asset(carType.image, height: 130),
),
), ),
], ],
), ),
@@ -567,6 +655,8 @@ class CarDetailsTypeToChoose extends StatelessWidget {
); );
} }
// --- Logic Helpers (Keep unchanged) ---
String _getCarDescription( String _getCarDescription(
MapPassengerController mapPassengerController, CarType carType) { MapPassengerController mapPassengerController, CarType carType) {
switch (carType.carType) { switch (carType.carType) {
@@ -578,7 +668,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
.tr .tr
: 'Best choice for comfort car and flexible route and stops point' : 'Best choice for comfort car and flexible route and stops point'
.tr; .tr;
case 'Speed': 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' return 'This trip goes directly from your starting point to your destination for a fixed price. The driver must follow the planned route'
.tr; .tr;
case 'Electric': case 'Electric':
@@ -632,7 +722,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
_buildRayehGaiOption(mapPassengerController, 'Awfar Car', _buildRayehGaiOption(mapPassengerController, 'Awfar Car',
mapPassengerController.totalPassengerRayehGaiBalash), mapPassengerController.totalPassengerRayehGaiBalash),
_buildRayehGaiOption(mapPassengerController, 'Speed', _buildRayehGaiOption(mapPassengerController, 'Fixed Price',
mapPassengerController.totalPassengerRayehGai), mapPassengerController.totalPassengerRayehGai),
_buildRayehGaiOption(mapPassengerController, 'Comfort', _buildRayehGaiOption(mapPassengerController, 'Comfort',
mapPassengerController.totalPassengerRayehGaiComfort) mapPassengerController.totalPassengerRayehGaiComfort)
@@ -661,7 +751,7 @@ class CarDetailsTypeToChoose extends StatelessWidget {
switch (carType.carType) { switch (carType.carType) {
case 'Comfort': case 'Comfort':
return mapPassengerController.totalPassengerComfort; return mapPassengerController.totalPassengerComfort;
case 'Speed': case 'Fixed Price':
return mapPassengerController.totalPassengerSpeed; return mapPassengerController.totalPassengerSpeed;
case 'Electric': case 'Electric':
return mapPassengerController.totalPassengerElectric; return mapPassengerController.totalPassengerElectric;

View File

@@ -7,53 +7,81 @@ import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart'; import '../../../controller/home/map_passenger_controller.dart';
// --------------------------------------------------- // ---------------------------------------------------
// -- Widget for Start Point Search -- // -- Widget for Start Point Search (Updated) --
// --------------------------------------------------- // ---------------------------------------------------
GetBuilder<MapPassengerController> formSearchPlacesStart() { GetBuilder<MapPassengerController> formSearchPlacesStart() {
return GetBuilder<MapPassengerController>( return GetBuilder<MapPassengerController>(
id: 'start_point_form', // إضافة معرف لتحديث هذا الجزء فقط عند الحاجة
builder: (controller) => Column( builder: (controller) => Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: TextFormField( child: Row(
controller: controller.placeStartController, children: [
onChanged: (value) { // --- حقل البحث النصي ---
if (controller.placeStartController.text.length > 2) { Expanded(
controller.getPlacesStart(); child: TextFormField(
} else if (controller.placeStartController.text.isEmpty) { controller: controller.placeStartController,
controller.clearPlacesStart(); onChanged: (value) {
} if (controller.placeStartController.text.length > 2) {
}, controller.getPlacesStart();
decoration: InputDecoration( } else if (controller.placeStartController.text.isEmpty) {
hintText: 'Search for a starting point'.tr, controller.clearPlacesStart();
hintStyle: AppStyle.subtitle.copyWith(color: Colors.grey[600]), }
prefixIcon: },
const Icon(Icons.search, color: AppColor.primaryColor), decoration: InputDecoration(
suffixIcon: controller.placeStartController.text.isNotEmpty hintText: 'Search for a starting point'.tr,
? IconButton( hintStyle:
icon: Icon(Icons.clear, color: Colors.grey[400]), AppStyle.subtitle.copyWith(color: Colors.grey[600]),
onPressed: () { prefixIcon:
controller.placeStartController.clear(); const Icon(Icons.search, color: AppColor.primaryColor),
controller.clearPlacesStart(); suffixIcon: controller.placeStartController.text.isNotEmpty
}, ? IconButton(
) icon: Icon(Icons.clear, color: Colors.grey[400]),
: null, onPressed: () {
contentPadding: controller.placeStartController.clear();
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), controller.clearPlacesStart();
border: OutlineInputBorder( },
borderRadius: BorderRadius.circular(8.0), )
borderSide: BorderSide.none, : 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],
),
),
), ),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0), const SizedBox(width: 8.0),
borderSide: BorderSide(color: AppColor.primaryColor),
// --- أيقونة اختيار الموقع من الخريطة (الجزء المضاف) ---
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,
), ),
filled: true, ],
fillColor: Colors.grey[50],
),
), ),
), ),
// --- قائمة نتائج البحث ---
AnimatedContainer( AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
height: controller.placesStart.isNotEmpty ? 300 : 0, height: controller.placesStart.isNotEmpty ? 300 : 0,
@@ -84,11 +112,19 @@ GetBuilder<MapPassengerController> formSearchPlacesStart() {
var latitude = res['latitude']; var latitude = res['latitude'];
var longitude = res['longitude']; var longitude = res['longitude'];
if (latitude != null && longitude != null) { if (latitude != null && longitude != null) {
// تحديث موقع الراكب (نقطة الانطلاق) بناءً على الاختيار
controller.passengerLocation = controller.passengerLocation =
LatLng(double.parse(latitude), double.parse(longitude)); LatLng(double.parse(latitude), double.parse(longitude));
// تحديث النص في الحقل
controller.placeStartController.text = title; controller.placeStartController.text = title;
// مسح النتائج
controller.clearPlacesStart(); controller.clearPlacesStart();
// You might want to update the camera position on the map here
// إغلاق القائمة والعودة للخريطة لرؤية النتيجة (اختياري حسب منطق تطبيقك)
controller.changeMainBottomMenuMap();
controller.update(); controller.update();
} }
}, },

View File

@@ -147,7 +147,7 @@ class GoogleMapPassengerWidget extends StatelessWidget {
} }
}, },
myLocationEnabled: true, myLocationEnabled: false,
), ),
), ),
); );

View File

@@ -15,6 +15,7 @@ import '../../../controller/home/map_passenger_controller.dart';
import '../../../controller/home/vip_waitting_page.dart'; import '../../../controller/home/vip_waitting_page.dart';
import '../../../env/env.dart'; import '../../../env/env.dart';
import '../../../print.dart'; import '../../../print.dart';
import '../../auth/otp_page.dart';
// --- الدالة الرئيسية بالتصميم الجديد --- // --- الدالة الرئيسية بالتصميم الجديد ---
GetBuilder<MapPassengerController> leftMainMenuIcons() { GetBuilder<MapPassengerController> leftMainMenuIcons() {
@@ -35,9 +36,9 @@ GetBuilder<MapPassengerController> leftMainMenuIcons() {
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.secondaryColor.withOpacity(0.8), // لون شبه شفاف color: AppColor.secondaryColor.withOpacity(0.4), // لون شبه شفاف
borderRadius: BorderRadius.circular(50.0), borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: AppColor.writeColor.withOpacity(0.2)), border: Border.all(color: AppColor.secondaryColor),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -49,12 +50,12 @@ GetBuilder<MapPassengerController> leftMainMenuIcons() {
tooltip: 'Toggle Map Type', tooltip: 'Toggle Map Type',
onPressed: () => controller.changeMapType(), onPressed: () => controller.changeMapType(),
), ),
_buildVerticalDivider(), // _buildVerticalDivider(),
_buildMapActionButton( // _buildMapActionButton(
icon: Icons.traffic_outlined, // icon: Icons.traffic_outlined,
tooltip: 'Toggle Traffic', // tooltip: 'Toggle Traffic',
onPressed: () => controller.changeMapTraffic(), // onPressed: () => controller.changeMapTraffic(),
), // ),
_buildVerticalDivider(), _buildVerticalDivider(),
_buildMapActionButton( _buildMapActionButton(
icon: Icons.my_location_rounded, icon: Icons.my_location_rounded,
@@ -129,22 +130,9 @@ class TestPage extends StatelessWidget {
body: Center( body: Center(
child: TextButton( child: TextButton(
onPressed: () async { onPressed: () async {
// var token = (box.read(BoxName.tokenFCM).toString()); // print(box.read(BoxName.lowEndMode));
// Log.print( // box.read(BoxName.lowEndMode)
// 'box.read(BoxName.tokenFCM).toString(): ${box.read(BoxName.tokenFCM).toString()}'); Get.to(PhoneNumberScreen());
// // 'e-EE5Z5Fn0x5s6EYbtgT6f:APA91bHBTxkbdljuvDF0iPhso58r7fCwGh-WcYh3CYfUJEShUKFcQf496Xc5E6LHqRFKfOQBxYrWSdLO8d9gLbL-IdgyDuZ7jNUjzvrcV_YmagDtgz7-UNw';
// // 'fdN1o8akwURHj47wvShC4T:APA91bFm-mFfFjdCbHsDReN0MzPE1hiaHKtPJnzayMec6LiInjzk6YCX41SeF0T1FE7Z6d4Hjy1AkZhLIeebSgX4RrodzwSwZSH0kboTQEfqkrjrk4xw9aM';
// NotificationService.sendNotification(
// target:
// 'eznj5vRWRnqwKNtKJBaYNg:APA91bHhJ2DJ1KQa3KRx6wQtX8BkFHq6I_-dXGxT16p6pnV5AwI0bWOeiTJOI35VfTBaK4YSCKmAB4SsRnpARK0MTJ96xtpPmwAKfkvsZFga8OoGMeb3PmA',
// title: 'Order',
// body: 'endNameAddress',
// isTopic: false,
// tone: 'tone1',
// category: 'Order', // استخدام الفئة الثابتة
// driverList: []);
// RideState.driverApplied;
// Get.find<MapPassengerController>().Ride
}, },
child: Text( child: Text(
"Text Button", "Text Button",

View File

@@ -533,12 +533,12 @@ class MainBottomMenuMap extends StatelessWidget {
// else // else
Text( // Text(
controller.nearestCar != null // controller.nearestCar != null
? 'Nearest Car: ${controller.nearestDistance.toStringAsFixed(0)} m' // ? 'Nearest Car: ${controller.nearestDistance.toStringAsFixed(0)} m'
: 'No cars nearby'.tr, // : 'No cars nearby'.tr,
style: TextStyle(color: Colors.grey.shade600), // style: TextStyle(color: Colors.grey.shade600),
), // ),
], ],
), ),
], ],

View File

@@ -1,297 +1,10 @@
// import 'package:flutter/material.dart';
// import 'package:flutter_font_icons/flutter_font_icons.dart';
// import 'package:get/get.dart';
// import 'package:Intaleq/constant/box_name.dart';
// import 'package:Intaleq/controller/profile/profile_controller.dart';
// import 'package:Intaleq/main.dart';
// import 'package:Intaleq/views/home/profile/complaint_page.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';
// // --- الويدجت الرئيسية بالتصميم الجديد ---
// 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 GetBuilder<MapPassengerController>(builder: (controller) {
// // --- نفس شرط الإظهار الخاص بك ---
// if (controller.statusRide != 'Begin') {
// return const SizedBox.shrink();
// }
// return Positioned(
// left: 0,
// right: 0,
// bottom: 0,
// child: Container(
// 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: Padding(
// padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// // مقبض السحب (Handle)
// Container(
// width: 40,
// height: 5,
// decoration: BoxDecoration(
// color: AppColor.writeColor.withOpacity(0.3),
// borderRadius: BorderRadius.circular(12),
// ),
// ),
// const SizedBox(height: 12),
// // --- 1. قسم معلومات السائق ---
// _buildDriverInfo(controller),
// const Divider(height: 24, thickness: 0.5),
// // --- 2. قسم تقدم الرحلة ---
// _buildTripProgress(controller),
// const SizedBox(height: 16),
// // --- 3. قسم الإجراءات والأمان ---
// _buildActionButtons(
// context, controller, profileController, audioController),
// ],
// ),
// ),
// ),
// );
// });
// }
// // --- ويدجت مساعدة لعرض معلومات السائق بشكل منظم ---
// Widget _buildDriverInfo(MapPassengerController controller) {
// return Row(
// children: [
// CircleAvatar(
// radius: 28,
// backgroundImage: NetworkImage(
// '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
// ),
// const SizedBox(width: 12),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(controller.driverName,
// style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
// const SizedBox(height: 2),
// Text(
// '${controller.make} ${controller.model} • ${box.read(BoxName.carType)}',
// style: AppStyle.subtitle
// .copyWith(color: AppColor.writeColor.withOpacity(0.7)),
// ),
// ],
// ),
// ),
// const SizedBox(width: 12),
// Column(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
// decoration: BoxDecoration(
// color: AppColor.writeColor.withOpacity(0.1),
// borderRadius: BorderRadius.circular(6),
// ),
// child: Text(
// controller.licensePlate,
// style: AppStyle.subtitle
// .copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.5),
// ),
// ),
// const SizedBox(height: 4),
// Row(
// children: [
// Text(controller.driverRate,
// style: AppStyle.subtitle
// .copyWith(fontWeight: FontWeight.bold)),
// const SizedBox(width: 2),
// const Icon(Icons.star_rounded,
// color: AppColor.yellowColor, size: 16),
// ],
// ),
// ],
// )
// ],
// );
// }
// // --- ويدجت مساعدة لعرض شريط التقدم ---
// Widget _buildTripProgress(MapPassengerController controller) {
// return Column(
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('Time to Destination'.tr, style: AppStyle.subtitle),
// Text(controller.stringRemainingTimeRideBegin,
// style: AppStyle.subtitle.copyWith(
// fontWeight: FontWeight.bold, color: AppColor.primaryColor)),
// ],
// ),
// const SizedBox(height: 8),
// ClipRRect(
// borderRadius: BorderRadius.circular(10),
// child: LinearProgressIndicator(
// backgroundColor: AppColor.primaryColor.withOpacity(0.2),
// color: controller.remainingTimeTimerRideBegin < 60
// ? AppColor.redColor
// : AppColor.greenColor,
// minHeight: 10,
// value: controller.progressTimerRideBegin.toDouble(),
// ),
// ),
// ],
// );
// }
// // --- ويدجت مساعدة لعرض أزرار الإجراءات ---
// Widget _buildActionButtons(
// BuildContext context,
// MapPassengerController controller,
// ProfileController profileController,
// AudioRecorderController audioController) {
// return Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// _buildActionButton(
// icon: Icons.sos_rounded,
// label: 'SOS'.tr,
// color: AppColor.redColor,
// onTap: () async {
// // --- نفس منطقك القديم ---
// if (box.read(BoxName.sosPhonePassenger) == null) {
// await profileController.updatField(
// 'sosPhone', TextInputType.phone);
// box.write(BoxName.sosPhonePassenger,
// profileController.prfoileData['sosPhone']);
// } else {
// makePhoneCall('112');
// }
// }),
// _buildActionButton(
// icon: FontAwesome.whatsapp,
// label: 'WhatsApp'.tr,
// color: AppColor.greenColor,
// onTap: () 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 {
// final phoneNumber =
// box.read(BoxName.sosPhonePassenger).toString();
// final phone = controller.formatSyrianPhoneNumber(phoneNumber);
// controller.sendWhatsapp(phone); //
// }
// }),
// _buildActionButton(
// icon: Icons.share_location_outlined, // أيقونة جديدة ومناسبة
// label: 'Share'.tr, // اسم جديد وواضح
// color: AppColor.blueColor,
// onTap: () async {
// // نفس الوظيفة السابقة التي كانت تحت اسم "Video Call"
// await controller.getTokenForParent();
// }),
// _buildActionButton(
// icon: audioController.isRecording
// ? Icons.mic_off_rounded
// : Icons.mic_none_rounded,
// label: audioController.isRecording ? 'Stop'.tr : 'Record'.tr,
// color: AppColor.primaryColor,
// onTap: () async {
// // --- نفس منطقك القديم ---
// if (audioController.isRecording == false) {
// await audioController.startRecording();
// Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
// } else {
// await audioController.stopRecording();
// Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
// }
// },
// ),
// _buildActionButton(
// icon: Icons.note_add_outlined,
// label: 'Complaint'.tr,
// color: AppColor.yellowColor,
// onTap: () {
// // --- نفس منطقك القديم ---
// Get.to(() => ComplaintPage(), transition: Transition.downToUp);
// }),
// ],
// );
// }
// // --- ويدجت مساعدة لبناء زر إجراء فردي ---
// Widget _buildActionButton(
// {required IconData icon,
// required String label,
// required Color color,
// required VoidCallback onTap}) {
// return InkWell(
// onTap: onTap,
// borderRadius: BorderRadius.circular(12),
// child: Padding(
// padding: const EdgeInsets.all(4.0),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Container(
// padding: const EdgeInsets.all(12),
// decoration: BoxDecoration(
// color: color.withOpacity(0.1),
// shape: BoxShape.circle,
// ),
// child: Icon(icon, color: color, size: 26),
// ),
// const SizedBox(height: 6),
// Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
// ],
// ),
// ),
// );
// }
// }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/controller/profile/profile_controller.dart';
import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/profile/complaint_page.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
// تأكد من المسارات
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
@@ -299,6 +12,9 @@ import '../../../controller/functions/audio_record1.dart';
import '../../../controller/functions/launch.dart'; import '../../../controller/functions/launch.dart';
import '../../../controller/functions/toast.dart'; import '../../../controller/functions/toast.dart';
import '../../../controller/home/map_passenger_controller.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 { class RideBeginPassenger extends StatelessWidget {
const RideBeginPassenger({super.key}); const RideBeginPassenger({super.key});
@@ -312,70 +28,69 @@ class RideBeginPassenger extends StatelessWidget {
return Obx(() { return Obx(() {
final controller = Get.find<MapPassengerController>(); final controller = Get.find<MapPassengerController>();
// شرط الإظهار: تظهر فقط عندما تكون الرحلة جارية // شرط الإظهار
final bool isVisible = final bool isVisible =
controller.currentRideState.value == RideState.inProgress && controller.currentRideState.value == RideState.inProgress &&
controller.isStartAppHasRide == false; controller.isStartAppHasRide == false;
;
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
curve: Curves.easeOutBack, // حركة أكثر سلاسة curve: Curves.easeInOutCubic,
bottom: isVisible ? 0 : -Get.height * 0.6, // تم تقليل قيمة الإخفاء لأن الارتفاع الكلي للنافذة أصبح أصغر
bottom: isVisible ? 0 : -300,
left: 0, left: 0,
right: 0, right: 0,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, // خلفية بيضاء لنظافة التصميم color: Colors.white,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30), topLeft: Radius.circular(25),
topRight: Radius.circular(30), topRight: Radius.circular(25),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.15), color: Colors.black.withOpacity(0.1),
blurRadius: 25, blurRadius: 20,
spreadRadius: 5, spreadRadius: 2,
offset: const Offset(0, -5), offset: const Offset(0, -3),
), ),
], ],
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 20), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// مقبض السحب // 1. مقبض السحب
Container( Center(
width: 50, child: Container(
height: 5, width: 40,
decoration: BoxDecoration( height: 4,
color: Colors.grey[300], decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 12),
// الصف العلوي: معلومات السائق + السعر المثبت // 2. هيدر المعلومات (سائق + سيارة + سعر)
_buildDriverAndPriceSection(controller), _buildCompactHeader(controller),
const SizedBox(height: 20), const SizedBox(height: 12),
// الصف الأوسط: لوحة السيارة الواقعية + نوع السيارة // خط فاصل خفيف
_buildCarInfoSection(controller), const Divider(
height: 1, thickness: 0.5, color: Color(0xFFEEEEEE)),
const SizedBox(height: 20), const SizedBox(height: 12),
// شريط التقدم والوقت // 3. الأزرار (إجراءات)
_buildTripProgress(controller), _buildCompactActionButtons(
const SizedBox(height: 20),
const Divider(thickness: 1, color: Color(0xFFEEEEEE)),
const SizedBox(height: 10),
// الأزرار
_buildActionButtons(
context, controller, profileController, audioController), context, controller, profileController, audioController),
// إضافة هامش سفلي بسيط لرفع الأزرار عن حافة الشاشة
const SizedBox(height: 5),
], ],
), ),
), ),
@@ -384,56 +99,81 @@ class RideBeginPassenger extends StatelessWidget {
}); });
} }
// ويدجت معلومات السائق والسعر // --- الهيدر (بدون تغيير، ممتاز) ---
Widget _buildDriverAndPriceSection(MapPassengerController controller) { Widget _buildCompactHeader(MapPassengerController controller) {
return Row( return Row(
children: [ children: [
// صورة السائق // صورة السائق
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all(color: AppColor.primaryColor, width: 2), border: Border.all(
boxShadow: [ color: AppColor.primaryColor.withOpacity(0.5), width: 1.5),
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
), ),
child: CircleAvatar( child: CircleAvatar(
radius: 30, radius: 24,
backgroundImage: NetworkImage( backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'), '${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (_, __) => const Icon(Icons.person),
), ),
), ),
const SizedBox(width: 15), const SizedBox(width: 10),
// الاسم والتقييم // الاسم ومعلومات السيارة
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(
controller.driverName,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.w800,
fontSize: 18,
color: Colors.black87,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row( Row(
children: [ children: [
const Icon(Icons.star_rounded, Flexible(
color: AppColor.yellowColor, size: 18), 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 SizedBox(width: 4),
const Icon(Icons.star, color: Colors.amber, size: 14),
Text( Text(
controller.driverRate, controller.driverRate,
style: AppStyle.subtitle.copyWith( style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.grey[600]), 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,
),
),
), ),
], ],
), ),
@@ -441,27 +181,26 @@ class RideBeginPassenger extends StatelessWidget {
), ),
), ),
// السعر المثبت (تصميم كارد للسعر) // السعر
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1), color: AppColor.primaryColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.primaryColor.withOpacity(0.2)),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text('Total'.tr,
style: TextStyle(fontSize: 10, color: Colors.grey[600])),
Text( Text(
'${NumberFormat('#,###').format(controller.totalPassenger)} 💰', NumberFormat('#,###').format(controller.totalPassenger),
style: const TextStyle( style: const TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
fontSize: 18, fontSize: 16,
color: AppColor.primaryColor, color: AppColor.primaryColor,
), ),
), ),
Text('SYP',
style: TextStyle(fontSize: 9, color: Colors.grey[600])),
], ],
), ),
), ),
@@ -469,163 +208,22 @@ class RideBeginPassenger extends StatelessWidget {
); );
} }
// ويدجت معلومات السيارة ولوحة الأرقام // --- الأزرار (بدون تغيير) ---
Widget _buildCarInfoSection(MapPassengerController controller) { Widget _buildCompactActionButtons(
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF9F9F9),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[200]!),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// نوع السيارة وموديلها
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${controller.make} ${controller.model}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black87),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
child: Text(
box.read(BoxName.carType) == 'Speed'
? 'Fixed Price'
.tr // سيظهر "سعر ثابت" (تأكد من إضافتها لملف الترجمة)
: box.read(BoxName.carType) ?? 'Car',
style: const TextStyle(fontSize: 12, color: Colors.black54),
),
),
],
),
// -------------------------------------------
// تصميم لوحة السيارة الواقعي (Realistic Plate)
// -------------------------------------------
Container(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 2),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.black, width: 2), // إطار أسود
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(2, 2)),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// الشريط الأزرق الجانبي (مثل اللوحات الدولية)
Container(
width: 15,
height: 35, // ارتفاع اللوحة
decoration: const BoxDecoration(
color: Color(0xFF003399), // أزرق غامق
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2),
bottomLeft: Radius.circular(2),
),
),
child: const Center(
child: Text(
"SY", // رمز الدولة (مثال)
style: TextStyle(
color: Colors.white,
fontSize: 8,
fontWeight: FontWeight.bold),
),
),
),
const SizedBox(width: 8),
// رقم اللوحة
Text(
controller.licensePlate, // رقم اللوحة من الكونترولر
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w900,
letterSpacing: 2.0, // تباعد الأحرف لتبدو كأرقام محفورة
fontFamily: 'monospace', // خط ثابت العرض ليشبه اللوحات
color: Colors.black,
),
),
const SizedBox(width: 8),
],
),
),
],
),
);
}
Widget _buildTripProgress(MapPassengerController controller) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(Icons.access_time_filled,
size: 16, color: Colors.grey),
const SizedBox(width: 5),
Text('Arriving in'.tr,
style: TextStyle(color: Colors.grey[600], fontSize: 13)),
],
),
Text(
controller.stringRemainingTimeRideBegin,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: AppColor.primaryColor,
),
),
],
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
backgroundColor: Colors.grey[200],
color: controller.remainingTimeTimerRideBegin < 60
? AppColor.redColor
: AppColor.primaryColor,
minHeight: 8,
value: controller.progressTimerRideBegin.toDouble(),
),
),
],
);
}
Widget _buildActionButtons(
BuildContext context, BuildContext context,
MapPassengerController controller, MapPassengerController controller,
ProfileController profileController, ProfileController profileController,
AudioRecorderController audioController) { AudioRecorderController audioController) {
return Row( return SizedBox(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, height: 60,
children: [ child: Row(
// زر SOS بتصميم تحذيري mainAxisAlignment: MainAxisAlignment.spaceAround,
_buildActionButton( children: [
_compactBtn(
icon: Icons.sos_rounded, icon: Icons.sos_rounded,
label: 'SOS', label: 'SOS'.tr,
iconColor: Colors.white, color: AppColor.redColor,
bgColor: AppColor.redColor, bgColor: AppColor.redColor.withOpacity(0.1),
onTap: () async { onTap: () async {
if (box.read(BoxName.sosPhonePassenger) == null) { if (box.read(BoxName.sosPhonePassenger) == null) {
await profileController.updatField( await profileController.updatField(
@@ -635,123 +233,100 @@ class RideBeginPassenger extends StatelessWidget {
} else { } else {
makePhoneCall('112'); makePhoneCall('112');
} }
}), },
),
// زر واتساب _compactBtn(
_buildActionButton(
icon: FontAwesome.whatsapp, icon: FontAwesome.whatsapp,
label: 'WhatsApp', label: 'WhatsApp'.tr,
iconColor: Colors.white, color: const Color(0xFF25D366),
bgColor: const Color(0xFF25D366), bgColor: const Color(0xFF25D366).withOpacity(0.1),
onTap: () async { onTap: () async {
if (box.read(BoxName.sosPhonePassenger) == null || if (box.read(BoxName.sosPhonePassenger) == null) {
box.read(BoxName.sosPhonePassenger) == 'sos') {
await profileController.updatField( await profileController.updatField(
'sosPhone', TextInputType.phone); 'sosPhone', TextInputType.phone);
box.write(BoxName.sosPhonePassenger,
profileController.prfoileData['sosPhone']);
} else { } else {
final phoneNumber = final phone = controller.formatSyrianPhoneNumber(
box.read(BoxName.sosPhonePassenger).toString(); box.read(BoxName.sosPhonePassenger).toString());
final phone = controller.formatSyrianPhoneNumber(phoneNumber);
controller.sendWhatsapp(phone); controller.sendWhatsapp(phone);
} }
}), },
),
// زر المشاركة _compactBtn(
_buildActionButton( icon: Icons.share,
icon: Icons.share_location_rounded, label: 'Share'.tr,
label: 'Share', color: AppColor.primaryColor,
iconColor: AppColor.primaryColor,
bgColor: AppColor.primaryColor.withOpacity(0.1), bgColor: AppColor.primaryColor.withOpacity(0.1),
onTap: () async { onTap: () async => await controller.shareTripWithFamily(),
await controller.shareTripWithFamily(); ),
}), GetBuilder<AudioRecorderController>(
init: audioController,
// زر التسجيل builder: (audioCtx) {
GetBuilder<AudioRecorderController>( return _compactBtn(
init: audioController, icon: audioCtx.isRecording
builder: (audioCtx) { ? Icons.stop_circle_outlined
return _buildActionButton( : Icons.mic_none_outlined,
icon: audioCtx.isRecording ? Icons.stop : Icons.mic, label: audioCtx.isRecording ? 'Stop'.tr : 'Record'.tr,
label: audioCtx.isRecording ? 'Stop' : 'Record', color: audioCtx.isRecording
iconColor: ? AppColor.redColor
audioCtx.isRecording ? Colors.white : AppColor.primaryColor, : AppColor.primaryColor,
bgColor: audioCtx.isRecording bgColor: audioCtx.isRecording
? AppColor.redColor ? AppColor.redColor.withOpacity(0.1)
: AppColor.primaryColor.withOpacity(0.1), : AppColor.primaryColor.withOpacity(0.1),
isRecordingAnimation: audioCtx.isRecording, onTap: () async {
onTap: () async { if (!audioCtx.isRecording) {
if (audioCtx.isRecording == false) { await audioCtx.startRecording();
await audioCtx.startRecording(); Toast.show(context, 'Start Record'.tr, AppColor.greenColor);
Toast.show(context, 'Start Record'.tr, AppColor.greenColor); } else {
} else { await audioCtx.stopRecording();
await audioCtx.stopRecording(); Toast.show(context, 'Record saved'.tr, AppColor.greenColor);
Toast.show(context, 'Record saved'.tr, AppColor.greenColor); }
} },
}, );
); },
}, ),
), _compactBtn(
icon: Icons.info_outline_rounded,
// زر الشكوى
_buildActionButton(
icon: Icons.report_gmailerrorred_rounded,
label: 'Report'.tr, label: 'Report'.tr,
iconColor: Colors.grey[700]!, color: Colors.grey[700]!,
bgColor: Colors.grey[200]!, bgColor: Colors.grey[200]!,
onTap: () { onTap: () => Get.to(() => ComplaintPage()),
Get.to(() => ComplaintPage(), transition: Transition.downToUp); ),
}), ],
], ),
); );
} }
Widget _buildActionButton({ Widget _compactBtn({
required IconData icon, required IconData icon,
required String label, required String label,
required Color iconColor, required Color color,
required Color bgColor, required Color bgColor,
required VoidCallback onTap, required VoidCallback onTap,
bool isRecordingAnimation = false,
}) { }) {
return Column( return InkWell(
mainAxisSize: MainAxisSize.min, onTap: onTap,
children: [ borderRadius: BorderRadius.circular(10),
InkWell( child: Column(
onTap: onTap, mainAxisAlignment: MainAxisAlignment.center,
borderRadius: BorderRadius.circular(15), children: [
child: AnimatedContainer( Container(
duration: const Duration(milliseconds: 300), padding: const EdgeInsets.all(8),
width: 50,
height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: bgColor, color: bgColor,
borderRadius: BorderRadius.circular(15), shape: BoxShape.circle,
border: isRecordingAnimation
? Border.all(color: AppColor.redColor, width: 2)
: null,
boxShadow: [
BoxShadow(
color: bgColor.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 3),
),
],
), ),
child: Icon(icon, color: iconColor, size: 24), child: Icon(icon, size: 20, color: color),
), ),
), const SizedBox(height: 4),
const SizedBox(height: 6), Text(
Text( label,
label.tr, style: TextStyle(
style: const TextStyle( fontSize: 10,
fontSize: 11, color: Colors.grey[700],
fontWeight: FontWeight.w600, fontWeight: FontWeight.w500),
color: Colors.black54,
), ),
), ],
], ),
); );
} }
} }

View File

@@ -151,8 +151,8 @@ void showPaymentBottomSheet(BuildContext context) {
_buildPaymentOption( _buildPaymentOption(
context: context, context: context,
controller: controller, controller: controller,
amount: 10000, amount: 500,
bonusAmount: 0, bonusAmount: 30,
currency: 'SYP'.tr, currency: 'SYP'.tr,
), ),
@@ -160,8 +160,8 @@ void showPaymentBottomSheet(BuildContext context) {
_buildPaymentOption( _buildPaymentOption(
context: context, context: context,
controller: controller, controller: controller,
amount: 20000, amount: 1000,
bonusAmount: 500, bonusAmount: 70,
currency: 'SYP'.tr, currency: 'SYP'.tr,
), ),
@@ -169,8 +169,8 @@ void showPaymentBottomSheet(BuildContext context) {
_buildPaymentOption( _buildPaymentOption(
context: context, context: context,
controller: controller, controller: controller,
amount: 40000, amount: 2000,
bonusAmount: 2500, bonusAmount: 180,
currency: 'SYP'.tr, currency: 'SYP'.tr,
), ),
@@ -178,8 +178,8 @@ void showPaymentBottomSheet(BuildContext context) {
_buildPaymentOption( _buildPaymentOption(
context: context, context: context,
controller: controller, controller: controller,
amount: 100000, amount: 5000,
bonusAmount: 4000, bonusAmount: 700,
currency: 'SYP'.tr, currency: 'SYP'.tr,
), ),
@@ -466,18 +466,24 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
Get.to(() => PaymentScreenSmsProvider( Get.to(() => PaymentScreenSmsProvider(
amount: double.parse(controller.selectedAmount.toString()))); amount: double.parse(controller.selectedAmount.toString())));
}, },
child: Row( child: Padding(
mainAxisAlignment: MainAxisAlignment.spaceBetween, padding: const EdgeInsets.all(8.0),
children: [ child: Row(
Text('Pay by Sham Cash'.tr), mainAxisAlignment: MainAxisAlignment.spaceBetween,
const SizedBox(width: 10), children: [
Image.asset( Text(
'assets/images/shamCash.png', 'Pay by Sham Cash'.tr,
width: 70, style: AppStyle.title,
height: 70, ),
fit: BoxFit.fill, const SizedBox(width: 10),
), Image.asset(
], 'assets/images/shamCash.png',
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
),
)), )),
], ],
cancelButton: CupertinoActionSheetAction( cancelButton: CupertinoActionSheetAction(

View File

@@ -8,9 +8,8 @@ import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart'; import '../../../controller/functions/crud.dart';
import '../../../main.dart'; import '../../../main.dart';
// خدمة الدفع للراكب (تم تحديث المسارات) // --- خدمة الدفع (نفس المنطق السابق) ---
class PaymentService { class PaymentService {
// المسار الجديد لمجلد الركاب
final String _baseUrl = "${AppLink.paymentServer}/ride/shamcash/passenger"; final String _baseUrl = "${AppLink.paymentServer}/ride/shamcash/passenger";
Future<String?> createInvoice({required double amount}) async { Future<String?> createInvoice({required double amount}) async {
@@ -19,7 +18,7 @@ class PaymentService {
final response = await CRUD().postWallet( final response = await CRUD().postWallet(
link: url, link: url,
payload: { payload: {
'passengerID': box.read(BoxName.passengerID), // استخدام passengerID 'passengerID': box.read(BoxName.passengerID),
'amount': amount.toString(), 'amount': amount.toString(),
}, },
).timeout(const Duration(seconds: 15)); ).timeout(const Duration(seconds: 15));
@@ -83,21 +82,46 @@ class PaymentScreenSmsProvider extends StatefulWidget {
_PaymentScreenSmsProviderState(); _PaymentScreenSmsProviderState();
} }
class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> { class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider>
with SingleTickerProviderStateMixin {
final PaymentService _paymentService = PaymentService(); final PaymentService _paymentService = PaymentService();
Timer? _pollingTimer; Timer? _pollingTimer;
PaymentStatus _status = PaymentStatus.creatingInvoice; PaymentStatus _status = PaymentStatus.creatingInvoice;
String? _invoiceNumber; String? _invoiceNumber;
// العنوان الثابت للدفع (المستخرج من الصورة)
final String _paymentAddress = "80f23afe40499b02f49966c3340ae0fc";
// متحكم الأنيميشن للوميض
late AnimationController _blinkController;
late Animation<Color?> _colorAnimation;
late Animation<double> _shadowAnimation;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// إعداد الأنيميشن (وميض أحمر)
_blinkController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
)..repeat(reverse: true); // يكرر الحركة ذهاباً وإياباً
_colorAnimation = ColorTween(
begin: Colors.red.shade700,
end: Colors.red.shade100,
).animate(_blinkController);
_shadowAnimation = Tween<double>(begin: 2.0, end: 15.0).animate(
CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut),
);
_createAndPollInvoice(); _createAndPollInvoice();
} }
@override @override
void dispose() { void dispose() {
_pollingTimer?.cancel(); _pollingTimer?.cancel();
_blinkController.dispose();
super.dispose(); super.dispose();
} }
@@ -216,7 +240,7 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
// 1. المبلغ // 1. المبلغ
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade600]), colors: [Colors.blue.shade800, Colors.blue.shade600]),
@@ -224,97 +248,166 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.blue.withOpacity(0.25), color: Colors.blue.withOpacity(0.25),
blurRadius: 15, blurRadius: 10,
offset: const Offset(0, 8)) offset: const Offset(0, 5))
], ],
), ),
child: Column( child: Column(
children: [ children: [
const Text("المبلغ المطلوب", const Text("المبلغ المطلوب",
style: TextStyle(color: Colors.white70, fontSize: 14)), style: TextStyle(color: Colors.white70, fontSize: 14)),
const SizedBox(height: 8), const SizedBox(height: 5),
Text("${currencyFormat.format(widget.amount)} ل.س", Text("${currencyFormat.format(widget.amount)} ل.س",
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 32, fontSize: 28,
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
], ],
), ),
), ),
const SizedBox(height: 30), const SizedBox(height: 25),
// 2. التعليمات والنسخ (للراكب) // 2. رقم البيان (هام جداً - وميض أحمر)
Container( AnimatedBuilder(
padding: const EdgeInsets.all(20), animation: _blinkController,
decoration: BoxDecoration( builder: (context, child) {
color: Colors.white, return Container(
borderRadius: BorderRadius.circular(16), padding: const EdgeInsets.all(20),
border: Border.all(color: Colors.grey.shade200), decoration: BoxDecoration(
boxShadow: [ color: Colors.white,
BoxShadow( borderRadius: BorderRadius.circular(16),
color: Colors.grey.shade100, border: Border.all(
blurRadius: 10, color: _colorAnimation.value ?? Colors.red,
offset: const Offset(0, 4)) width: 3.0, // إطار سميك
], ),
), boxShadow: [
child: Column( BoxShadow(
children: [ color: (_colorAnimation.value ?? Colors.red)
Row( .withOpacity(0.4),
blurRadius: _shadowAnimation.value,
spreadRadius: 2,
)
],
),
child: Column(
children: [ children: [
Container( Row(
padding: const EdgeInsets.all(8), mainAxisAlignment: MainAxisAlignment.center,
decoration: BoxDecoration( children: [
color: Colors.orange.shade50, shape: BoxShape.circle), Icon(Icons.warning_rounded,
child: Icon(Icons.priority_high_rounded, color: Colors.red.shade800, size: 28),
color: Colors.orange.shade800, size: 20), const SizedBox(width: 8),
), Text(
const SizedBox(width: 12), "هام جداً: لا تنسَ!",
const Expanded(
child: Text(
"انسخ الرقم أدناه وضعه في خانة (الملاحظات) عند الدفع.",
style: TextStyle( style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w600)), color: Colors.red.shade900,
fontWeight: FontWeight.bold,
fontSize: 18),
),
],
),
const SizedBox(height: 10),
const Text(
"يجب نسخ (رقم البيان) هذا ووضعه في تطبيق شام كاش لضمان نجاح العملية.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Colors.black87,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 15),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: invoiceText));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text("تم نسخ رقم البيان ✅",
textAlign: TextAlign.center),
backgroundColor: Colors.red.shade700));
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.red.shade200, width: 1)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("رقم البيان (Invoice No)",
style: TextStyle(
fontSize: 12, color: Colors.grey)),
Text(invoiceText,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
color: Colors.red.shade900)),
],
),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.red.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.copy_rounded,
color: Colors.red.shade900, size: 24),
),
],
),
),
), ),
], ],
), ),
const SizedBox(height: 20), );
},
),
const SizedBox(height: 25),
// 3. عنوان الدفع (اختياري / عادي)
Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("عنوان الدفع (Payment Address)",
style: TextStyle(fontSize: 12, color: Colors.grey)),
const SizedBox(height: 8),
InkWell( InkWell(
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: invoiceText)); Clipboard.setData(ClipboardData(text: _paymentAddress));
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text("تم نسخ رقم البيان", content: const Text("تم نسخ عنوان الدفع",
textAlign: TextAlign.center), textAlign: TextAlign.center),
backgroundColor: Colors.green.shade600)); backgroundColor: Colors.green.shade600));
}, },
borderRadius: BorderRadius.circular(12), child: Row(
child: Container( children: [
padding: const EdgeInsets.symmetric( Expanded(
horizontal: 15, vertical: 12), child: Text(_paymentAddress,
decoration: BoxDecoration( style: const TextStyle(
color: Colors.grey.shade50, fontSize: 14,
borderRadius: BorderRadius.circular(12), fontWeight: FontWeight.bold,
border: Border.all( fontFamily: 'Courier',
color: Colors.blue.shade200, width: 1.5)), color: Colors.black87,
child: Row( ),
mainAxisAlignment: MainAxisAlignment.spaceBetween, overflow: TextOverflow.ellipsis),
children: [ ),
Column( const SizedBox(width: 8),
crossAxisAlignment: CrossAxisAlignment.start, const Icon(Icons.copy, size: 18, color: Colors.grey),
children: [ ],
const Text("رقم البيان (Invoice ID)",
style: TextStyle(
fontSize: 12, color: Colors.grey)),
Text(invoiceText,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
letterSpacing: 1.5)),
],
),
const Icon(Icons.copy_rounded,
color: Colors.blue, size: 24),
],
),
), ),
), ),
], ],
@@ -322,10 +415,10 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
// 3. QR Code // 4. QR Code
const Text("امسح الرمز للدفع", const Text("أو امسح الرمز للدفع",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 15), const SizedBox(height: 10),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
showDialog( showDialog(
@@ -336,30 +429,23 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
child: Image.asset(widget.qrImagePath)))); child: Image.asset(widget.qrImagePath))));
}, },
child: Container( child: Container(
padding: const EdgeInsets.all(15), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.grey.shade300)), border: Border.all(color: Colors.grey.shade300)),
child: Column( child: Image.asset(widget.qrImagePath,
children: [ width: 150,
Image.asset(widget.qrImagePath, height: 150,
width: 180, fit: BoxFit.contain,
height: 180, errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
fit: BoxFit.contain, size: 100, color: Colors.grey)),
errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
size: 100, color: Colors.grey)),
const SizedBox(height: 8),
const Text("اضغط للتكبير",
style: TextStyle(fontSize: 10, color: Colors.grey)),
],
),
), ),
), ),
const SizedBox(height: 40), const SizedBox(height: 30),
const LinearProgressIndicator(backgroundColor: Colors.white), const LinearProgressIndicator(backgroundColor: Colors.white),
const SizedBox(height: 10), const SizedBox(height: 10),
const Text("ننتظر إشعار الدفع تلقائياً...", const Text("جاري التحقق من الدفع تلقائياً...",
style: TextStyle(color: Colors.grey, fontSize: 12)), style: TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 20), const SizedBox(height: 20),
], ],
@@ -405,7 +491,7 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
const SizedBox(height: 15), const SizedBox(height: 15),
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 30), padding: EdgeInsets.symmetric(horizontal: 30),
child: Text("لم يصلنا إشعار الدفع.", child: Text("لم يصلنا إشعار الدفع خلال الوقت المحدد.",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))), style: TextStyle(color: Colors.grey))),
const SizedBox(height: 40), const SizedBox(height: 40),

View File

@@ -106,7 +106,7 @@ class MyDialog extends GetxController {
Get.back(); Get.back();
}, },
child: Text( child: Text(
'Cancel', 'Cancel'.tr,
style: TextStyle( style: TextStyle(
color: AppColor.redColor, color: AppColor.redColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View File

@@ -1212,10 +1212,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.2" version: "0.6.7"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
@@ -1828,6 +1828,22 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
socket_io_client:
dependency: "direct main"
description:
name: socket_io_client
sha256: "64bd271703db3682d4195dd813c555413d21a49bbaef7c3ed38932fd2a209a10"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
socket_io_common:
dependency: transitive
description:
name: socket_io_common
sha256: "469c7e6bb0c8d571a5158c1352112654f03aedc2f0a246533e1cbdb41efa4937"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:

View File

@@ -78,6 +78,7 @@ dependencies:
internet_connection_checker: ^3.0.1 internet_connection_checker: ^3.0.1
connectivity_plus: ^6.1.5 connectivity_plus: ^6.1.5
app_links: ^6.4.1 app_links: ^6.4.1
socket_io_client: ^1.0.2
# flutter_map: ^8.2.2 # flutter_map: ^8.2.2
# latlong2: ^0.9.1 # latlong2: ^0.9.1
# home_widget: ^0.7.0+1 # home_widget: ^0.7.0+1