26-1-21/1
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
BIN
assets/images/shamcashsend.png
Normal file
BIN
assets/images/shamcashsend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
24
collect_code.py
Normal file
24
collect_code.py
Normal 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
85287
full_project_code.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
194
lib/README.md
Normal 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` وحفظ التفضيل في التخزين المحلي.
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
127
lib/controller/home/device_performance.dart
Normal file
127
lib/controller/home/device_performance.dart
Normal 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
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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])),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
myLocationEnabled: true,
|
myLocationEnabled: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
20
pubspec.lock
20
pubspec.lock
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user