refactor: إعادة تسمية التطبيقات الأربعة siro_* → intaleq_*

المجلدات وأسماء حزم dart واستيراداتها:
  siro_rider → intaleq_rider    siro_driver  → intaleq_driver
  siro_admin → intaleq_admin    siro_service → intaleq_service

شمل ذلك `name:` في كل pubspec وكل `package:siro_*` في الاستيرادات
(115 ملفاً في rider وحده)، وإشارات أسماء المجلدات في docs/ وتعليق في
backend/Admin/notifications/broadcast.php. لم تبقَ إشارة واحدة (تحقّقت).

+ ضُمّت حزم get و get_storage داخل intaleq_driver/packages/ وحُوّلت
مساراتها الثلاثة من `../../Intaleq/packages/*` إلى `./packages/*`. كانت
تُحل عرضاً إلى مجلد App/Intaleq القديم المجاور — تبعية خارج المستودع
تنكسر بأول نقل. لم يبقَ في أي تطبيق تبعية مسار خارج الشجرة.

⚠️ لم تُمسّ بعد: هويات المتاجر (applicationId · PRODUCT_BUNDLE_IDENTIFIER ·
shorebird app_id) ولا الألوان ولا النصوص المرئية ولا النطاقات — كل منها
كوميت منفصل، والهويات تحتاج قرار المالك.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 16:10:58 +03:00
co-authored by Claude Opus 5
parent 92dc6b3641
commit 0aab85c732
3137 changed files with 65294 additions and 1950 deletions
+194
View File
@@ -0,0 +1,194 @@
إليك التقرير الفني الشامل لمشروع **Siro Passenger App**، مكتوباً بصيغة توثيق تقني (Technical Documentation) موجهة لفريق التطوير، بناءً على تحليل الكود المصدري.
---
# 📘 Siro 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` وحفظ التفضيل في التخزين المحلي.
+68
View File
@@ -0,0 +1,68 @@
import 'package:get/get.dart';
import 'package:intaleq_rider/controller/auth/login_controller.dart';
import 'package:intaleq_rider/controller/firebase/firbase_messge.dart';
import 'package:intaleq_rider/controller/firebase/local_notification.dart';
import 'package:intaleq_rider/controller/home/deep_link_controller.dart';
import 'package:intaleq_rider/controller/local/local_controller.dart';
import 'package:intaleq_rider/controller/functions/tts.dart';
import 'package:intaleq_rider/controller/voice_call_controller.dart';
import 'package:intaleq_rider/controller/home/map/map_socket_controller.dart';
import 'package:intaleq_rider/controller/home/map/map_engine_controller.dart';
import 'package:intaleq_rider/controller/home/map/location_search_controller.dart';
import 'package:intaleq_rider/controller/home/map/nearby_drivers_controller.dart';
import 'package:intaleq_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:intaleq_rider/controller/home/map/ui_interactions_controller.dart';
import 'package:intaleq_rider/controller/home/menu_controller.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/controller/home/points_for_rider_controller.dart';
/// This is the central dependency injection file for the app.
/// It uses GetX Bindings to make the app start faster and manage memory better.
class AppBindings extends Bindings {
@override
void dependencies() {
// --- [Type 1: Permanent Controllers] ---
// Use Get.put() for controllers that need to be available immediately and ALWAYS.
// They are created right away and never destroyed.
// LocaleController is needed instantly to set the app's theme and language.
Get.put(LocaleController());
// DeepLinkController must always be listening for incoming links.
// `permanent: true` ensures it survives `Get.offAll()`.
Get.put(DeepLinkController(), permanent: true);
// --- [Type 2: Lazy Loaded "Phoenix" Controllers] ---
// Use Get.lazyPut() for controllers that are heavy or not needed immediately.
// They are only created in memory the first time `Get.find()` is called.
// `fenix: true` is the key: it allows the controller to be "reborn" after being
// destroyed (e.g., by Get.offAll), preserving its state and functionality.
// LoginController is only needed during the login process on the splash screen.
Get.lazyPut(() => LoginController(), fenix: true);
// NotificationController is initialized on the splash screen, but might be needed later.
Get.lazyPut(() => NotificationController(), fenix: true);
// FirebaseMessagesController is also initialized on splash, but must persist its token and listeners.
Get.lazyPut(() => FirebaseMessagesController(), fenix: true);
// TextToSpeechController for global accessibility
Get.lazyPut(() => TextToSpeechController(), fenix: true);
// VoiceCallController for WebRTC calls
Get.lazyPut(() => VoiceCallController(), fenix: true);
// Map & Ride controllers registered globally to prevent route-disposal race conditions.
Get.put(MapSocketController(), permanent: true);
Get.put(MapEngineController(), permanent: true);
Get.put(LocationSearchController(), permanent: true);
Get.put(NearbyDriversController(), permanent: true);
Get.put(RideLifecycleController(), permanent: true);
Get.put(UiInteractionsController(), permanent: true);
Get.put(MyMenuController(), permanent: true);
Get.put(CRUD(), permanent: true);
Get.put(WayPointController(), permanent: true);
}
}
+76
View File
@@ -0,0 +1,76 @@
// import 'package:intaleq_rider/main.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import '../env/env.dart';
import 'char_map.dart';
class AK {
static final String sss_pass = X.r(X.r(X.r(Env.sss_pass, cn), cC), cs);
static final String allowed = Env.allowed;
static final String allowedWallet = Env.allowedWallet;
static final String passnpassenger = X
.r(X.r(X.r(Env.passnpassenger, cn), cC), cs)
.toString()
.split(Env.addd)[0];
static final String newId = Env.newId;
static final String sss_encryptionSalt =
X.r(X.r(X.r(Env.sss_encryptionSalt, cn), cC), cs);
static final String secretKey = X.r(X.r(X.r(Env.secretKey, cn), cC), cs);
static final String basicAuthCredentials =
X.r(X.r(X.r(Env.basicAuthCredentials, cn), cC), cs);
static final String accountSIDTwillo =
X.r(X.r(X.r(Env.accountSIDTwillo, cn), cC), cs);
static final String serverAPI = X.r(X.r(X.r(Env.serverAPI, cn), cC), cs);
static final String mapAPIKEY = Env.mapAPIKEY;
static final String mapAPIKEYIOS = Env.mapAPIKEYIOS;
static final String twilloRecoveryCode =
X.r(X.r(X.r(Env.twilloRecoveryCode, cn), cC), cs);
static final String authTokenTwillo =
X.r(X.r(X.r(Env.authTokenTwillo, cn), cC), cs);
static final String chatGPTkey = X.r(X.r(X.r(Env.chatGPTkey, cn), cC), cs);
static final String transactionCloude =
X.r(X.r(X.r(Env.transactionCloude, cn), cC), cs);
static final String visionApi = X.r(X.r(X.r(Env.visionApi, cn), cC), cs);
static final String chatGPTkeySefer =
X.r(X.r(X.r(Env.chatGPTkeySefer, cn), cC), cs);
static final String chatGPTkeySeferNew =
X.r(X.r(X.r(Env.chatGPTkeySeferNew, cn), cC), cs);
static final String serverPHP = Env.serverPHP;
static final String llamaKey = X.r(X.r(X.r(Env.llamaKey, cn), cC), cs);
static final String cohere = X.r(X.r(X.r(Env.cohere, cn), cC), cs);
static final String claudeAiAPI = X.r(X.r(X.r(Env.claudeAiAPI, cn), cC), cs);
static final String geminiApi = X.r(X.r(X.r(Env.geminiApi, cn), cC), cs);
static final String agoraAppId = X.r(X.r(X.r(Env.agoraAppId, cn), cC), cs);
static final String agoraAppCertificate =
X.r(X.r(X.r(Env.agoraAppCertificate, cn), cC), cs);
static final String integrationIdPayMob =
X.r(X.r(X.r(Env.integrationIdPayMob, cn), cC), cs);
static final String passwordPayMob =
X.r(X.r(X.r(Env.passwordPayMob, cn), cC), cs);
static final String usernamePayMob =
X.r(X.r(X.r(Env.usernamePayMob, cn), cC), cs);
static final String payMobApikey =
X.r(X.r(X.r(Env.payMobApikey, cn), cC), cs);
static final String integrationIdPayMobWallet =
X.r(X.r(X.r(Env.integrationIdPayMobWallet, cn), cC), cs);
static final String apiKeyHere = Env.apiKeyHere;
static final String smsPasswordEgypt =
X.r(X.r(X.r(Env.smsPasswordEgypt, cn), cC), cs);
static final String ocpApimSubscriptionKey = Env.ocpApimSubscriptionKey;
static final String chatGPTkeySeferNew4 =
X.r(X.r(X.r(Env.chatGPTkeySeferNew4, cn), cC), cs);
static final String anthropicAIkeySeferNew =
X.r(X.r(X.r(Env.anthropicAIkeySeferNew, cn), cC), cs);
static final String llama3Key = X.r(X.r(X.r(Env.llama3Key, cn), cC), cs);
static final String payMobOutClientSecrret =
X.r(X.r(X.r(Env.payMobOutClientSecrret, cn), cC), cs);
static final String payMobOutClient_id =
X.r(X.r(X.r(Env.payMobOutClient_id, cn), cC), cs);
static final String payMobOutPassword =
X.r(X.r(X.r(Env.payMobOutPassword, cn), cC), cs);
static final String payMobOutUserName =
X.r(X.r(X.r(Env.payMobOutUserName, cn), cC), cs);
///////////
static final String keyOfApp = X.r(X.r(X.r(Env.keyOfApp, cn), cC), cs);
}
+116
View File
@@ -0,0 +1,116 @@
class BoxName {
static const String driverID = "driverID";
static const String countryCode = "countryCode";
static const String googlaMapApp = "googlaMapApp";
static const String tokenParent = "tokenParent";
static const String lang = "lang";
static const String serverChosen = "serverChosen";
static const String security_check = "security_check";
static const String gender = "gender";
static const String themeMode = "themeMode";
static const String jwt = "jwt";
static const String lowEndMode = "lowEndMode";
static const String deviceFpEncrypted = "deviceFpEncrypted";
static const String appVersionChecked = "appVersionChecked";
static const String lastName = "lastName";
static const String fingerPrint = "fingerPrint";
static const String payMobApikey = "payMobApikey";
static const String refreshToken = "refreshToken";
static const String serverLocations = "serverLocations";
static const String carType = "carType";
static const String carPlate = "carPlate";
static const String basicLink = "basicLink";
static const String packagInfo = "packagInfo";
static const String paymentLink = "paymentLink";
static const String locationName = "locationName";
static const String isVerified = 'isVerified';
static const String isFirstTime = 'isFirstTime';
static const String firstTimeLoadKey = 'firstTimeLoadKey';
static const String isSavedPhones = 'isSavedPhones';
static const String statusDriverLocation = "statusDriverLocation";
static const String isTest = "isTest";
static const String hmac = "hmac";
static const String password = "password";
static const String validity = "validity";
static const String promo = "promo";
static const String discount = "discount";
static const String arrivalTime = "arrivalTime";
static const String passwordDriver = "passwordDriver";
static const String agreeTerms = "agreeTerms";
static const String addWork = 'addWork';
static const String addHome = 'addHome';
static const String placesDestination = 'placesDestination';
static const String tipPercentage = 'tipPercentage';
static const String lastLocationPush = 'lastLocationPush';
static const String faceDetectTimes = "faceDetectTimes";
static const String sosPhonePassenger = "sosPhonePassenger";
static const String sosPhoneDriver = "sosPhoneDriver";
static const String passengerID = "pasengerID";
static const String phone = "phone";
static const String package = "package";
static const String isInstall = "isInstall";
static const String isGiftToken = "isGiftToken";
static const String isClaim = "isClaim";
static const String inviteCode = "inviteCode";
static const String phoneWallet = "phoneWallet";
static const String phoneDriver = "phoneDriver";
static const String dobDriver = "dobDriver";
static const String sexDriver = "sexDriver";
static const String lastNameDriver = "lastNameDriver";
static const String name = "name";
static const String locationPermission = "locationPermission";
static const String nameDriver = "nameDriver";
static const String driverPhotoUrl = "driverPhotoUrl";
static const String passengerPhotoUrl = "passengerPhotoUrl";
static const String email = "email";
static const String emailDriver = "emailDriver";
static const String tokens = "tokens";
static const String tokenFCM = "tokenFCM";
static const String tokenDriver = "tokenDriver";
static const String cardNumber = "cardNumber";
static const String cardNumberDriver = "cardNumberDriver";
static const String cardHolderName = "cardHolderName";
static const String cardHolderNameDriver = "cardHolderNameDriver";
static const String expiryDate = "expiryDate";
static const String expiryDateDriver = "expiryDateDriver";
static const String cvvCode = "cvvCode";
static const String cvvCodeDriver = "cvvCodeDriver";
static const String passengerWalletDetails = "passengerWalletDetails";
static const String passengerWalletTotal = "passengerWalletTotal";
static const String passengerWalletFound = "passengerWalletFound";
static const String periods = 'periods';
static const String onBoarding = 'onBoarding';
static const String apiKeyRun = 'apiKeyRun';
static const String keyOfApp = 'keyOfApp';
static const String initializationVector = 'initializationVector';
static const String serverAPI = 'serverAPI';
static const String secretKey = 'secretKey';
static const String basicAuthCredentials = 'basicAuthCredentials';
static const String mapAPIKEY = 'mapAPIKEY';
static const String twilloRecoveryCode = 'twilloRecoveryCode';
static const String accountSIDTwillo = 'accountSIDTwillo';
static const String authTokenTwillo = 'authTokenTwillo';
static const String chatGPTkey = 'chatGPTkey';
static const String chatGPTkeySefer = 'chatGPTkeySefer';
static const String transactionCloude = 'transactionCloude';
static const String visionApi = 'visionApi';
static const String vin = "vin";
static const String isvibrate = "isvibrate";
static const String make = "make";
static const String model = "model";
static const String year = "year";
static const String expirationDate = "expirationDate";
static const String color = "color";
static const String owner = "owner";
static const String registrationDate = "registrationDate";
static const String recentLocations = 'recentLocations';
static const String tripData = 'tripData';
static const String parentTripSelected = 'parentTripSelected';
static const String styleVersion = 'styleVersion';
static const String isDestinationMatch =
'isDestinationMatch'; // 🆕 AI Destination Matching
static const String isPrime = 'isPrime'; // 👑 Siro Prime subscription status
}
+75
View File
@@ -0,0 +1,75 @@
import '../env/env.dart';
Map<String, String> cs = {
"a": Env.a,
"b": Env.b,
"c": Env.c,
"d": Env.d,
"e": Env.e,
"f": Env.f,
"g": Env.g,
"h": Env.h,
"i": Env.i,
"j": Env.j,
"k": Env.k,
"l": Env.l,
"m": Env.m,
"n": Env.n,
"o": Env.o,
"p": Env.p,
"q": Env.q,
"r": Env.r,
"s": Env.s,
"t": Env.t,
"u": Env.u,
"v": Env.v,
"w": Env.w,
"x": Env.x,
"y": Env.y,
"z": Env.z,
};
Map<String, String> cC = {
"A": Env.A,
"B": Env.B,
"C": Env.C,
"D": Env.D,
"E": Env.E,
"F": Env.F,
"G": Env.G,
"H": Env.H,
"I": Env.I,
"J": Env.J,
"K": Env.K,
"L": Env.L,
"M": Env.M,
"N": Env.N,
"O": Env.O,
"P": Env.P,
"Q": Env.Q,
"R": Env.R,
"S": Env.S,
"T": Env.T,
"U": Env.U,
"V": Env.V,
"W": Env.W,
"X": Env.X,
"Y": Env.Y,
"Z": Env.Z
};
//
//
Map<String, String> cn = {
"0": "3",
"1": "7",
"2": "1",
"3": "9",
"4": "0",
"5": "5",
"6": "2",
"7": "6",
"8": "4",
"9": "8"
};
+34
View File
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
/// A class that holds the color palette for the 'Siro' app.
/// The palette is professionally designed to be modern, cohesive, and culturally
/// relevant, inspired by the Syrian flag and the app's brand identity.
class AppColor {
// --- Core Brand Colors ---
static const Color primaryColor = Color(0xFF1A2340); // Navy Blue
static const Color secondaryColorStatic = Color(0xFF8C9CF8); // Periwinkle Blue
static const Color accentColor = Color(0xFF8C9CF8);
// --- Dynamic Colors (Light / Dark Mode Support) ---
static Color get writeColor => Get.isDarkMode ? Colors.white : const Color(0xFF0F172A); // Primary Text
static Color get secondaryColor => Get.isDarkMode ? const Color(0xFF1E1E1E) : const Color(0xFFF8FAFC); // Background
static Color get cardColor => Get.isDarkMode ? const Color(0xFF2C2C2E) : const Color(0xFFFFFFFF); // Card Background
// --- Neutral & Status Colors ---
static Color get grayColor => Get.isDarkMode ? Colors.grey[400]! : const Color(0xFF64748B); // Secondary Text
static Color get borderColor => Get.isDarkMode ? const Color(0xFF3A3A3C) : const Color(0xFFE2E8F0); // Border
static const Color redColor = Color(0xFFEF4444); // Error
static const Color greenColor = Color(0xFF22C55E); // Success
static const Color yellowColor = Color(0xFFF59E0B); // Warning
// --- Legacy / Maps Colors (Kept for compatibility) ---
static const Color blueColor = Color(0xFF1A2340);
static const Color gold = Color(0xFFFFD700);
static const Color bronze = Color(0xFFCD7F32);
static const Color goldenBronze = Color(0xFFB87333);
static Color get cyanBlue => const Color(0xFF1A2340);
static Color get cyanAccent => const Color(0xFF1A2340).withOpacity(0.12);
static Color get deepPurpleAccent => const Color(0xFF8C9CF8).withOpacity(0.1);
}
@@ -0,0 +1,97 @@
// في ملف: constant/country_polygons.dart
import 'package:intaleq_maps/intaleq_maps.dart';
class CountryPolygons {
// ==========================================================
// 1. الأردن: عمان، الزرقاء، إربد، السلط ومساحة كبيرة داخل الأردن
// ==========================================================
static final List<LatLng> jordanBoundary = [
const LatLng(32.70, 35.80), // شمال إربد
const LatLng(32.35, 36.30), // المفرق
const LatLng(32.10, 36.30), // الزرقاء
const LatLng(31.85, 36.20), // شرق عمان
const LatLng(31.50, 36.00), // مادبا
const LatLng(30.80, 35.80), // الكرك / الطفيلة
const LatLng(30.20, 35.60), // معان
const LatLng(29.50, 35.00), // العقبة
const LatLng(29.50, 34.90), // الحدود الغربية للعقبة
const LatLng(30.50, 35.20), // وادي عربة
const LatLng(31.50, 35.50), // البحر الميت
const LatLng(32.05, 35.60), // السلط
const LatLng(32.70, 35.60), // غرب إربد
];
// ==========================================================
// 2. سوريا: دمشق، ريف دمشق، حمص، حلب، اللاذقية (استثناء الصحراء)
// ==========================================================
static final List<LatLng> syriaBoundary = [
const LatLng(32.60, 36.00), // درعا
const LatLng(32.70, 36.60), // السويداء
const LatLng(33.50, 36.50), // ريف دمشق الشرقي
const LatLng(34.10, 36.80), // النبك
const LatLng(34.70, 36.80), // حمص
const LatLng(35.10, 36.80), // حماة
const LatLng(36.20, 37.30), // حلب الشرقية
const LatLng(36.60, 37.00), // شمال حلب
const LatLng(36.00, 36.00), // إدلب / الجبال
const LatLng(35.70, 35.80), // اللاذقية
const LatLng(34.80, 35.80), // طرطوس
const LatLng(34.50, 36.20), // تلكلخ
const LatLng(33.50, 36.00), // دمشق الغربية
];
// ==========================================================
// 3. مصر: القاهرة كاملة، الإسكندرية، ومراكز المحافظات
// ==========================================================
static final List<LatLng> egyptBoundary = [
const LatLng(29.50, 30.80), // جنوب الفيوم
const LatLng(29.80, 31.40), // شرق القاهرة الكبرى
const LatLng(30.10, 31.80), // العاشر من رمضان
const LatLng(30.60, 32.30), // الإسماعيلية
const LatLng(31.30, 31.80), // بورسعيد
const LatLng(31.50, 31.25), // دمياط
const LatLng(31.30, 29.80), // الإسكندرية
const LatLng(30.80, 29.80), // وادي النطرون
const LatLng(30.00, 30.80), // 6 أكتوبر / غرب القاهرة
];
/// دالة للتحقق مما إذا كان الموقع ضمن نطاق الخدمة المدعوم
static bool isServiceSupported(LatLng? point) {
if (point == null) return false;
if (_isPointInPolygon(point, jordanBoundary)) return true;
if (_isPointInPolygon(point, syriaBoundary)) return true;
if (_isPointInPolygon(point, egyptBoundary)) return true;
return false;
}
/// دالة تحدد اسم الدولة (باللغة الإنجليزية للـ API) بناءً على الإحداثيات
static String getCountryName(LatLng? point) {
if (point == null) return "unsupported";
if (_isPointInPolygon(point, jordanBoundary)) return "jordan";
if (_isPointInPolygon(point, syriaBoundary)) return "syria";
if (_isPointInPolygon(point, egyptBoundary)) return "egypt";
return "unsupported";
}
/// خوارزمية Ray Casting للتحقق من وقوع نقطة داخل مضلع
static bool _isPointInPolygon(LatLng p, List<LatLng> polygon) {
bool isInside = false;
int j = polygon.length - 1;
for (int i = 0; i < polygon.length; i++) {
if (((polygon[i].latitude > p.latitude) !=
(polygon[j].latitude > p.latitude)) &&
(p.longitude <
(polygon[j].longitude - polygon[i].longitude) *
(p.latitude - polygon[i].latitude) /
(polygon[j].latitude - polygon[i].latitude) +
polygon[i].longitude)) {
isInside = !isInside;
}
j = i;
}
return isInside;
}
}
+145
View File
@@ -0,0 +1,145 @@
import 'package:intaleq_rider/print.dart';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import '../controller/functions/crud.dart';
import '../main.dart';
import 'box_name.dart';
import 'char_map.dart';
import 'links.dart';
class AC {
gAK() async {
if (box.read(BoxName.apiKeyRun).toString() != 'run') {
var res = await CRUD().get(link: AppLink.getApiKey, payload: {});
var decod = res;
Log.print(decod);
Map<String, dynamic> jsonData = {};
for (var i = 0; i < decod['message'].length; i++) {
String h = decod['message'][i]['hashed_key'].toString();
String retrievedString = r(r(r(h, cn), cC), cs);
await storage.write(
key: decod['message'][i]['name'].toString(),
value: retrievedString.toString(),
);
//
String name = decod['message'][i]['name'].toString();
String value = decod['message'][i]['hashed_key'].toString();
jsonData[name] = value;
}
String jsonString = json.encode(jsonData);
Log.print(jsonString);
box.write(BoxName.apiKeyRun, 'run');
}
}
String q(String b, String c) {
final d = utf8.encode(c);
final e = utf8.encode(b);
final f = Hmac(sha256, d);
final g = f.convert(e);
final h = g.bytes;
final i = base64Url.encode(h);
return i;
}
String j(String k, String l) {
final m = utf8.encode(l);
final n = base64Url.decode(k);
final o = Hmac(sha256, m);
final p = o.convert(n);
final q = utf8.decode(p.bytes);
return q;
}
String a(String b, String c) {
int d = b.length;
int e = d ~/ 4;
List<String> f = [];
for (int g = 0; g < d; g += e) {
int h = g + e;
if (h > d) {
h = d;
}
String i = b.substring(g, h);
f.add(i);
}
// print(f);
Map<String, String> j = {};
j['birinci'] = f[4];
j['ikinci'] = f[2];
j['üçüncü'] = c + f[1];
j['dördüncü'] = f[0];
j['beş'] = f[3];
String k = '';
j.forEach((l, m) {
k += m;
});
return k;
}
Map<String, String> n(String o, String c) {
String p = o.replaceAll(c, '');
Map<String, String> q = {};
q['birinci'] = p[p.length - 5] + p[p.length - 3];
q['ikinci'] = p[p.length - 1] + p[p.length - 15];
q['üçüncü'] = p[p.length - 9] + p[p.length - 12];
q['dördüncü'] = p[p.length - 11] + p[p.length - 6];
q['beş'] = p[p.length - 2] + p[p.length - 8];
return q;
}
String c(String a, Map<String, String> b) {
StringBuffer c = StringBuffer();
c.write(a);
String d = "Bl";
c.write(b[d] ?? d);
StringBuffer e = StringBuffer();
String f = c.toString();
for (int g = 0; g < f.length; g++) {
String h = f[g];
e.write(b[h] ?? h);
}
return e.toString();
}
String r(String a, Map<String, String> b) {
StringBuffer c = StringBuffer();
String d = "Bl";
int e = d.length;
for (int f = 0; f < a.length; f++) {
String g = a[f];
String h = b.keys.firstWhere(
(i) => b[i] == g,
orElse: () => g,
);
c.write(h);
}
String j = c.toString();
if (j.endsWith(d)) {
j = j.substring(0, j.length - e);
}
return j;
}
}
+15
View File
@@ -0,0 +1,15 @@
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:get/get.dart';
class CurrencyHelper {
static String get currency {
String country = box.read(BoxName.countryCode) ?? 'Jordan';
switch (country) {
case 'Syria': return 'SYP'.tr;
case 'Egypt': return 'EGP'.tr;
case 'Jordan':
default: return 'JOD'.tr;
}
}
}
+172
View File
@@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'colors.dart';
/// -----------------------------------------------------------------------------
/// Siro Design Tokens
/// -----------------------------------------------------------------------------
/// A single source of truth for spacing, radii, shadows, durations and the
/// shared bottom-sheet surface used across the passenger map overlays.
///
/// Goal: kill the "magic numbers" scattered across every map widget
/// (radii of 15/24/25, ad-hoc shadows, handles that appear only sometimes)
/// so all sheets read as ONE consistent product — the way Uber / Careem / Bolt
/// feel like a single system rather than a pile of separate screens.
///
/// Everything here is additive. Existing widgets keep working untouched; new
/// and refactored widgets opt in.
/// 4pt spacing scale. Use these instead of raw `SizedBox(height: 10)`.
class AppSpacing {
AppSpacing._();
static const double xs = 4;
static const double sm = 8;
static const double md = 12;
static const double lg = 16;
static const double xl = 20;
static const double xxl = 24;
// Convenience gaps (const so they can live in const child lists).
static const Widget gapXs = SizedBox(height: xs, width: xs);
static const Widget gapSm = SizedBox(height: sm, width: sm);
static const Widget gapMd = SizedBox(height: md, width: md);
static const Widget gapLg = SizedBox(height: lg, width: lg);
static const Widget gapXl = SizedBox(height: xl, width: xl);
static const Widget vGapXs = SizedBox(height: xs);
static const Widget vGapSm = SizedBox(height: sm);
static const Widget vGapMd = SizedBox(height: md);
static const Widget vGapLg = SizedBox(height: lg);
static const Widget vGapXl = SizedBox(height: xl);
}
/// Corner radii. `sheet` is THE radius for every bottom sheet so they all match.
class AppRadii {
AppRadii._();
static const double sm = 8;
static const double md = 12;
static const double lg = 16;
static const double sheet = 24;
static const double pill = 999;
static const BorderRadius sheetTop = BorderRadius.only(
topLeft: Radius.circular(sheet),
topRight: Radius.circular(sheet),
);
static BorderRadius all(double r) => BorderRadius.circular(r);
}
/// Shared elevation. One shadow for sheets, one for cards — no more per-widget
/// hand-tuned `BoxShadow`s (and no neumorphic accent-color shadows).
class AppShadows {
AppShadows._();
static List<BoxShadow> get sheet => [
BoxShadow(
color: Colors.black.withValues(
alpha: Get.isDarkMode ? 0.45 : 0.12,
),
blurRadius: 24,
spreadRadius: 1,
offset: const Offset(0, -4),
),
];
static List<BoxShadow> get card => [
BoxShadow(
color: Colors.black.withValues(
alpha: Get.isDarkMode ? 0.30 : 0.06,
),
blurRadius: 12,
offset: const Offset(0, 4),
),
];
}
/// Motion durations. Keep sheet slide-in/out consistent everywhere.
class AppDurations {
AppDurations._();
static const Duration fast = Duration(milliseconds: 200);
static const Duration base = Duration(milliseconds: 300);
static const Duration slow = Duration(milliseconds: 450);
}
/// Minimum touch target (Material / iOS HIG guidance is 44–48px).
class AppSizes {
AppSizes._();
static const double minTouch = 48;
}
/// -----------------------------------------------------------------------------
/// Shared bottom-sheet surface
/// -----------------------------------------------------------------------------
/// The rounded-top card + shadow + optional drag handle that every map sheet
/// draws by hand today. Wrap your content in this and all sheets instantly
/// share the same radius, shadow, handle and horizontal rhythm.
///
/// It does NOT own show/hide animation — each caller keeps its own
/// AnimatedPositioned / transform logic so behaviour is unchanged.
class SiroSheetSurface extends StatelessWidget {
const SiroSheetSurface({
super.key,
required this.child,
this.padding =
const EdgeInsets.fromLTRB(AppSpacing.xl, AppSpacing.md, AppSpacing.xl, AppSpacing.lg),
this.showHandle = true,
this.color,
});
final Widget child;
final EdgeInsets padding;
final bool showHandle;
final Color? color;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: color ?? AppColor.cardColor,
borderRadius: AppRadii.sheetTop,
boxShadow: AppShadows.sheet,
),
padding: padding,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (showHandle) ...[
const SiroSheetHandle(),
AppSpacing.vGapMd,
],
child,
],
),
);
}
}
/// The little grab handle at the top of a sheet. Consistent size everywhere.
class SiroSheetHandle extends StatelessWidget {
const SiroSheetHandle({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColor.grayColor.withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(AppRadii.pill),
),
),
);
}
}
+288
View File
@@ -0,0 +1,288 @@
class AppInformation {
static const String companyName = 'Siro llc';
static const String appName = 'Siro';
static const String phoneNumber = '';
static const String linkedInProfile =
'https://www.linkedin.com/in/hamza-ayed/';
static const String website = 'https://intaleqapp.com';
static const String email = 'support@intaleqapp.com';
static const String addd = 'BlBlNl';
static const String privacyPolicy = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Siro - Privacy Policy & Terms of Use</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.7;
color: #333;
padding: 10px;
}
h1, h2, h3 {
color: #2c3e50;
}
h1 {
font-size: 1.8em;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
}
h2 {
font-size: 1.5em;
margin-top: 30px;
color: #2980b9;
}
h3 {
font-size: 1.2em;
margin-top: 20px;
color: #16a085;
}
strong {
color: #2c3e50;
}
ul {
padding-left: 20px;
list-style-position: outside;
}
.highlight {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #3498db;
margin: 20px 0;
border-radius: 4px;
}
a {
color: #3498db;
text-decoration: none;
}
</style>
</head>
<body>
<h1>Privacy Policy & Terms of Use</h1>
<div class="highlight">
<p><strong>Effective Date:</strong> August 9, 2025</p>
<p><strong>Last Updated:</strong> August 9, 2025</p>
</div>
<h2>1. Introduction and Acceptance</h2>
<p>By downloading, registering, or using the Siro application ("App"), you agree to be bound by this Privacy Policy and our Terms of Use. If you do not agree, you must stop using the App immediately. Your continued use constitutes acceptance of these terms and any future updates.</p>
<h2>2. Definitions</h2>
<ul>
<li><strong>"Siro", "we", "us":</strong> Refers to the Siro for Ride Hailing company, Damascus – Syria (Owner & operator), which provides the technology platform.</li>
<li><strong>"Driver":</strong> An independent service provider who uses the App to offer transportation services.</li>
<li><strong>"Passenger", "you":</strong> An individual who uses the App to request transportation services.</li>
<li><strong>"Services":</strong> The connection between Passengers and Drivers facilitated by our App.</li>
</ul>
<h2>3. Privacy Policy</h2>
<h3>3.1 Information We Collect</h3>
<p>We collect information necessary to provide and improve our Services.</p>
<h4>A. Information You Provide:</h4>
<ul>
<li><strong>For Drivers:</strong> To ensure safety and compliance, we collect identity information, including your full name, phone number, personal photo, and official documents (e.g., driver's license, vehicle registration).</li>
<li><strong>For Passengers:</strong> We only require a phone number for registration and communication. We are not authorized to request or view official identity documents for passengers.</li>
</ul>
<h4>B. Information Collected Automatically:</h4>
<ul>
<li><strong>Location Data:</strong> We collect precise location data when the App is in use to facilitate ride matching, navigation, and for safety purposes.</li>
<li><strong>Device Data:</strong> We collect information about your device, such as model, operating system, and unique identifiers, to ensure App functionality and for security verification.</li>
<li><strong>Usage Data:</strong> We log how you interact with our App, including trip history and features used, to improve our services.</li>
</ul>
<h3>3.2 Payment Information</h3>
<p><strong>We do not collect, process, or store any sensitive payment information like credit/debit card numbers.</strong> We facilitate payments by connecting you to licensed, local third-party providers:</p>
<ul>
<li><strong>Mobile Carrier Billing:</strong> Payments via MTN and Syriatel are processed directly by them based on your registered phone number. A one-time password (OTP) sent by the carrier is required to confirm the transaction.</li>
<li><strong>Bank Card Payments:</strong> We connect you with the Syrian company "eCash" to process card payments. They handle the transaction, and your bank will send an OTP to your phone to authorize it.</li>
</ul>
<h3>3.3 How We Use Your Information</h3>
<ul>
<li>To operate and maintain the Services (e.g., connect Drivers and Passengers).</li>
<li>To verify Driver identity and eligibility.</li>
<li>To improve App security and prevent fraud.</li>
<li>To provide customer support.</li>
<li>To comply with legal obligations.</li>
</ul>
<h3>3.4 Data Sharing</h3>
<p>We do not sell your personal data. We only share it in the following limited circumstances:</p>
<ul>
<li><strong>Between Passenger and Driver:</strong> To facilitate a ride, we share necessary information like name, photo, and real-time location.</li>
<li><strong>With Service Providers:</strong> For services like payment processing and mapping. These providers are contractually obligated to protect your data.</li>
<li><strong>For Legal Reasons:</strong> If required by law or a valid legal order.</li>
</ul>
<h3>3.5 Policy for Minors</h3>
<p>Our services are intended for individuals over the age of 18.
<strong>For Drivers:</strong> We strictly verify the identity and age of all drivers to ensure no minors are operating on our platform.
<strong>For Passengers:</strong> While we do not verify passenger identity, the service is not directed at children under 18. If a parent or guardian becomes aware that their child has provided us with information without their consent, they should contact us immediately.</p>
<h2>4. User Obligations & Conduct</h2>
<ul>
<li>You must provide accurate and current information during registration.</li>
<li>You are responsible for maintaining the security of your account.</li>
<li>You agree not to use the App for any illegal activities, to harass others, or to cause damage to a Driver's vehicle.</li>
</ul>
<h2>5. Disclaimer of Liability</h2>
<p>The App is provided "as is". Siro is an intermediary platform and is not liable for the actions of Drivers or Passengers, accidents, delays, or any disputes between users. Our liability is limited to the fullest extent permitted by law.</p>
<h2>6. Policy Updates</h2>
<p>We may update these terms. If we make significant changes, we will notify you within the App. You will be required to review and accept the new terms to continue using the Services, ensuring your consent is active and informed.</p>
<h2>7. Account Deletion & Contact</h2>
<p>You have the right to request the deletion of your account and personal data. To do so, or for any other questions, please contact us. We will respond to deletion requests within 30 days.</p>
<p><strong>Email:</strong> <a href="mailto:support@intaleqapp.com">support@intaleqapp.com</a></p>
</body>
</html>
''';
static const String privacyPolicyArabic = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>سيرو - سياسة الخصوصية وشروط الاستخدام</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.7;
color: #333;
padding: 10px;
text-align: right;
}
h1, h2, h3 {
color: #2c3e50;
}
h1 {
font-size: 1.8em;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
}
h2 {
font-size: 1.5em;
margin-top: 30px;
color: #2980b9;
}
h3 {
font-size: 1.2em;
margin-top: 20px;
color: #16a085;
}
strong {
color: #2c3e50;
}
ul {
padding-right: 20px;
list-style-position: outside;
}
.highlight {
background-color: #f8f9fa;
padding: 15px;
border-right: 4px solid #3498db;
margin: 20px 0;
border-radius: 4px;
}
a {
color: #3498db;
text-decoration: none;
}
</style>
</head>
<body>
<h1>سياسة الخصوصية وشروط الاستخدام</h1>
<div class="highlight">
<p><strong>تاريخ النفاذ:</strong> 9 أغسطس 2025</p>
<p><strong>آخر تحديث:</strong> 9 أغسطس 2025</p>
</div>
<h2>1. المقدمة والقبول</h2>
<p>عبر تحميل أو تسجيل أو استخدام تطبيق "سيرو" ("التطبيق")، فإنك توافق على الالتزام بسياسة الخصوصية وشروط الاستخدام هذه. إذا كنت لا توافق، يجب عليك التوقف فورًا عن استخدام التطبيق. استمرارك في الاستخدام يُعد قبولاً لهذه الشروط وأي تحديثات مستقبلية لها.</p>
<h2>2. التعريفات</h2>
<ul>
<li><strong>"سيرو"، "نحن":</strong> تشير إلى شركة سيرو لنقل الركاب، دمشق – سوريا (مالك ومشغل التطبيق)، التي توفر المنصة التقنية.</li>
<li><strong>"السائق":</strong> مقدم خدمة مستقل يستخدم التطبيق لتقديم خدمات النقل.</li>
<li><strong>"الراكب"، "أنت":</strong> الفرد الذي يستخدم التطبيق لطلب خدمات النقل.</li>
<li><strong>"الخدمات":</strong> عملية الربط بين الركاب والسائقين التي يسهلها تطبيقنا.</li>
</ul>
<h2>3. سياسة الخصوصية</h2>
<h3>3.1 المعلومات التي نجمعها</h3>
<p>نحن نجمع المعلومات الضرورية لتقديم خدماتنا وتحسينها.</p>
<h4>أ. المعلومات التي تقدمها بنفسك:</h4>
<ul>
<li><strong>بالنسبة للسائقين:</strong> لضمان السلامة والامتثال للقوانين، نجمع بيانات الهوية الشخصية، بما في ذلك الاسم الكامل، رقم الهاتف، صورة شخصية، والوثائق الرسمية (مثل رخصة القيادة وتسجيل المركبة).</li>
<li><strong>بالنسبة للركاب:</strong> نطلب فقط رقم هاتف للتسجيل والتواصل. نحن غير مخولين بطلب أو الاطلاع على وثائق الهوية الرسمية للركاب.</li>
</ul>
<h4>ب. المعلومات التي تُجمع تلقائيًا:</h4>
<ul>
<li><strong>بيانات الموقع:</strong> نجمع بيانات الموقع الجغرافي الدقيقة عند استخدام التطبيق لتسهيل تحديد أماكن الانطلاق والوصول، الملاحة، ولأغراض السلامة.</li>
<li><strong>بيانات الجهاز:</strong> نجمع معلومات عن جهازك (طراز، نظام تشغيل، معرفات فريدة) لضمان عمل التطبيق وللتحقق الأمني.</li>
<li><strong>بيانات الاستخدام:</strong> نسجل كيفية تفاعلك مع التطبيق، بما في ذلك سجل الرحلات والميزات المستخدمة، بهدف تحسين خدماتنا.</li>
</ul>
<h3>3.2 معلومات الدفع</h3>
<p><strong>نحن لا نجمع أو نعالج أو نخزن أي معلومات دفع حساسة</strong> مثل أرقام بطاقات الائتمان/الخصم. نحن نسهل عمليات الدفع عبر ربطك بمزودي خدمات محليين مرخصين:</p>
<ul>
<li><strong>الدفع عبر رصيد الهاتف المحمول:</strong> تتم معالجة الدفعات عبر شركتي MTN و Syriatel مباشرة من خلالهما بناءً على رقم هاتفك المسجل لديهم. يتطلب تأكيد العملية إدخال رمز تحقق (OTP) يُرسل من قبل شركة الاتصالات.</li>
<li><strong>الدفع عبر البطاقات البنكية:</strong> نربطك بشركة "eCash" السورية لمعالجة الدفعات بالبطاقات. هي التي تتولى المعاملة، وسيقوم البنك الذي تتعامل معه بإرسال رمز تحقق (OTP) إلى هاتفك لتفويض العملية.</li>
</ul>
<h3>3.3 كيف نستخدم معلوماتك</h3>
<ul>
<li>لتشغيل وصيانة الخدمات (مثل الربط بين السائقين والركاب).</li>
<li>للتحقق من هوية السائقين وأهليتهم.</li>
<li>لتحسين أمان التطبيق ومنع الاحتيال.</li>
<li>لتقديم الدعم الفني للعملاء.</li>
<li>للامتثال للالتزامات القانونية.</li>
</ul>
<h3>3.4 مشاركة البيانات</h3>
<p>نحن لا نبيع بياناتك الشخصية. نشاركها فقط في الحالات المحدودة التالية:</p>
<ul>
<li><strong>بين الراكب والسائق:</strong> لتسهيل الرحلة، نشارك المعلومات الضرورية مثل الاسم، الصورة، والموقع المباشر.</li>
<li><strong>مع مزودي الخدمات:</strong> مثل معالجي الدفع وخدمات الخرائط. هؤلاء المزودون ملزمون تعاقديًا بحماية بياناتك.</li>
<li><strong>لأسباب قانونية:</strong> إذا طُلب ذلك بموجب القانون أو أمر قضائي ساري المفعول.</li>
</ul>
<h3>3.5 سياسة القاصرين</h3>
<p>خدماتنا موجهة للأفراد الذين تزيد أعمارهم عن 18 عامًا.
<strong>بالنسبة للسائقين:</strong> نحن نتحقق بدقة من هوية وعمر جميع السائقين لضمان عدم وجود قاصرين يعملون على منصتنا.
<strong>بالنسبة للركاب:</strong> على الرغم من أننا لا نتحقق من هوية الركاب، فإن الخدمة غير موجهة للأطفال دون سن 18. إذا علم ولي الأمر أن طفله قد زودنا بمعلومات دون موافقته، فيجب عليه الاتصال بنا على الفور.</p>
<h2>4. التزامات المستخدم وسلوكه</h2>
<ul>
<li>يجب عليك تقديم معلومات دقيقة وحديثة عند التسجيل.</li>
<li>أنت مسؤول عن الحفاظ على أمان حسابك.</li>
<li>أنت توافق على عدم استخدام التطبيق لأي أنشطة غير قانونية، أو لمضايقة الآخرين، أو التسبب في ضرر لمركبة السائق.</li>
</ul>
<h2>5. إخلاء المسؤولية</h2>
<p>يتم تقديم التطبيق "كما هو". "سيرو" هي منصة وسيطة وليست مسؤولة عن تصرفات السائقين أو الركاب، أو الحوادث، أو التأخير، أو أي نزاعات بين المستخدمين. مسؤوليتنا محدودة إلى أقصى حد يسمح به القانون.</p>
<h2>6. تحديثات السياسة</h2>
<p>قد نقوم بتحديث هذه الشروط. في حال إجراء تغييرات جوهرية، سنقوم بإعلامك داخل التطبيق. سيُطلب منك مراجعة الشروط الجديدة وقبولها لمواصلة استخدام الخدمات، لضمان أن موافقتك فعالة ومبنية على معرفة.</p>
<h2>7. حذف الحساب والتواصل</h2>
<p>لديك الحق في طلب حذف حسابك وبياناتك الشخصية. للقيام بذلك، أو لأي استفسارات أخرى، يرجى التواصل معنا. سنرد على طلبات الحذف في غضون 30 يومًا.</p>
<p><strong>البريد الإلكتروني:</strong> <a href="mailto:support@intaleqapp.com">support@intaleqapp.com</a></p>
</body>
</html>
''';
}
+547
View File
@@ -0,0 +1,547 @@
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/main.dart';
class AppLink {
static const String appDomain = 'siromove.com';
static String get inviteRedirectUrl {
if (currentCountry == 'Syria') {
return "https://siromove.com/inviteSyria.php";
}
return "https://siromove.com/invite.php";
}
static String get currentCountry => box.read(BoxName.countryCode) ?? 'Jordan';
static String get paymentServer {
switch (currentCountry) {
case 'Syria':
return 'https://wallet-syria.siromove.com/v1/main';
case 'Egypt':
return 'https://wallet-egypt.siromove.com/v1/main';
case 'Jordan':
return 'https://walletintaleq.intaleq.xyz/v2/main';
default:
return 'https://wallet.siromove.com/v1/main';
}
}
static String get location {
switch (currentCountry) {
case 'Syria':
return 'https://api-syria.siromove.com/siro_v3/ride/location';
case 'Egypt':
return 'https://api-egypt.siromove.com/siro_v3/ride/location';
case 'Jordan':
return 'https://jordan-siro.intaleqapp.com/backend/ride/location';
default:
return 'https://api.siromove.com/siro_v3/ride/location';
}
}
static String get locationServerSide {
switch (currentCountry) {
case 'Syria':
return 'https://location-syria.siromove.com/siro/ride/location';
case 'Egypt':
return 'https://location-egypt.siromove.com/siro/ride/location';
case 'Jordan':
return 'https://jordan-siro.intaleqapp.com/backend/ride/location';
default:
return 'https://location.siromove.com/siro/ride/location';
}
}
static String get routesOsm {
switch (currentCountry) {
case 'Syria':
return 'https://routes-syria.siromove.com';
case 'Egypt':
return 'https://routes-egypt.siromove.com';
case 'Jordan':
return 'https://routesjo.intaleq.xyz';
default:
return 'https://routes.siromove.com';
}
}
static String get mapSaasRoute {
switch (currentCountry) {
case 'Syria':
return 'https://map-syria.siromove.com/api/maps/route';
case 'Egypt':
return 'https://map-egypt.siromove.com/api/maps/route';
case 'Jordan':
return 'https://map-saas.intaleqapp.com/api/maps/route';
default:
return 'https://map-saas.intaleqapp.com/api/maps/route';
}
}
static String get reverseGeocoding {
switch (currentCountry) {
case 'Syria':
return 'https://map-syria.siromove.com/api/geocoding/reverse';
case 'Egypt':
return 'https://map-egypt.siromove.com/api/geocoding/reverse';
case 'Jordan':
return 'https://map-saas.intaleqapp.com/api/geocoding/reverse';
default:
return 'https://map-saas.intaleqapp.com/api/geocoding/reverse';
}
}
static String get searchGeocoding {
switch (currentCountry) {
case 'Syria':
return 'https://map-syria.siromove.com/api/geocoding/search';
case 'Egypt':
return 'https://map-egypt.siromove.com/api/geocoding/search';
case 'Jordan':
return 'https://map-saas.intaleqapp.com/api/geocoding/search';
default:
return 'https://map-saas.intaleqapp.com/api/geocoding/search';
}
}
static String get mapSaasPlaces {
switch (currentCountry) {
case 'Syria':
return 'https://map-syria.siromove.com/api/geocoding/places';
case 'Egypt':
return 'https://map-egypt.siromove.com/api/geocoding/places';
case 'Jordan':
return 'https://map-saas.intaleqapp.com/api/geocoding/places';
default:
return 'https://map-saas.intaleqapp.com/api/geocoding/places';
}
}
static String get endPoint => server;
static String get rideServerSide {
switch (currentCountry) {
case 'Syria':
return 'https://ride-syria.siromove.com/siro';
case 'Egypt':
return 'https://ride-egypt.siromove.com/siro';
case 'Jordan':
return 'https://jordan-siro.intaleqapp.com/backend';
default:
return 'https://jordan-siro.intaleqapp.com/backend';
}
}
static String get server {
switch (currentCountry) {
case 'Syria':
return 'https://api-syria.siromove.com/siro_v3';
case 'Egypt':
return 'https://api-egypt.siromove.com/siro_v3';
case 'Jordan':
return 'https://jordan-siro.intaleqapp.com/backend';
default:
return 'https://api.siromove.com/siro_v3';
}
}
static String get serverSocket {
switch (currentCountry) {
case 'Syria':
return 'https://api-syria.siromove.com';
case 'Egypt':
return 'https://api-egypt.siromove.com';
case 'Jordan':
return 'https://jordan-siro.intaleqapp.com:3030';
default:
return 'https://jordan-siro.intaleqapp.com:3030';
}
}
///
static String get googleMapsLink => 'https://maps.googleapis.com/maps/api/';
/// here map link for searching for places
static String get searcMaps =>
'https://autosuggest.search.hereapi.com/v1/autosuggest';
static String get test => "$server/test.php";
//===============firebase==========================
static String get getTokens => "$server/ride/firebase/getTokensPassenger.php";
static String get getTokenParent =>
"$server/ride/firebase/getTokenParent.php";
static String get addTokens => "$server/ride/firebase/add.php";
static String get addFingerPrint => "$paymentServer/ride/firebase/add.php";
static String get addTokensDriver => "$server/ride/firebase/addDriver.php";
static String get packageInfo => "$server/auth/packageInfo.php";
//=======================Wallet===================
static String get wallet => '$paymentServer/ride/passengerWallet';
static String get walletDriver => '$paymentServer/ride/driverWallet';
static String get getAllPassengerTransaction =>
"$wallet/getAllPassengerTransaction.php";
static String get getWalletByPassenger => "$wallet/getWalletByPassenger.php";
static String get getPassengersWallet => "$wallet/get.php";
static String get payWithPayMobWalletPasenger =>
'$paymentServer/ride/payMob/wallet/payWithPayMob.php';
static String get payWithPayMobCardPassenger =>
'$paymentServer/ride/payMob/payWithPayMob.php';
static String get payWithEcash => "$paymentServer/ecash/payWithEcash.php";
static String get paymetVerifyPassenger =>
"$paymentServer/ride/payMob/paymet_verfy.php";
static String get getPassengerWalletArchive =>
"$wallet/getPassengerWalletArchive.php";
static String get addDrivePayment => "$paymentServer/ride/payment/add.php";
static String get addSeferWallet => "$paymentServer/ride/seferWallet/add.php";
static String get addPassengersWallet => "$wallet/add.php";
static String get deletePassengersWallet => "$wallet/delete.php";
static String get updatePassengersWallet => "$wallet/update.php";
//=======================Siro Prime===================
static String get initiatePrime => "$server/api/payments/initiate_prime.php";
static String get getPrimeStatus =>
"$server/api/payments/get_prime_status.php";
static String get getWalletByDriver => "$walletDriver/getWalletByDriver.php";
static String get getDriversWallet => "$walletDriver/get.php";
static String get addDriversWalletPoints => "$walletDriver/add.php";
static String get deleteDriversWallet => "$walletDriver/delete.php";
static String get updateDriversWallet => "$walletDriver/update.php";
//=======================promo===================ride.mobile-app.store/ride/promo/get.php
static String get promo => '$server/ride/promo';
static String get getPassengersPromo => "$promo/get.php";
static String get getPromoFirst => "$promo/getPromoFirst.php";
static String get getPromoBytody => "$promo/getPromoBytody.php";
static String get addPassengersPromo => "$promo/add.php";
static String get deletePassengersPromo => "$promo/delete.php";
static String get updatePassengersPromo => "$promo/update.php";
//===============contact==========================
static String get savePhones => "$server/ride/egyptPhones/add.php";
static String get getPhones => "$server/ride/egyptPhones/get.php";
////=======================cancelRide===================
// static String ride = '$server/ride';
static String get addCancelRideFromPassenger =>
"$rideServerSide/cancelRide/add.php";
static String get cancelRide => "$rideServerSide/cancelRide/get.php";
//-----------------ridessss------------------
static String get addRides => "$rideServerSide/ride/rides/add.php";
static String get getRides => "$rideServerSide/ride/rides/get.php";
static String get getRideOrderID =>
"$rideServerSide/ride/rides/getRideOrderID.php";
static String get getRideStatus =>
"$rideServerSide/ride/rides/getRideStatus.php";
static String get getRideStatusBegin =>
"$rideServerSide/ride/rides/getRideStatusBegin.php";
static String get getRideStatusFromStartApp =>
"$server/ride/rides/getRideStatusFromStartApp.php";
static String get updateRides => "$rideServerSide/ride/rides/update.php";
static String get updateStausFromSpeed =>
"$rideServerSide/ride/rides/updateStausFromSpeed.php";
static String get deleteRides => "$rideServerSide/ride/rides/delete.php";
//-----------------DriverPayment------------------
static String get adddriverScam => "$server/driver_scam/add.php";
static String get getdriverScam => "$server/ride/driver_scam/get.php";
/////////---getKazanPercent===////////////
static String get getKazanPercent => "$server/ride/kazan/get.php";
static String get addKazanPercent => "$server/ride/kazan/add.php";
////-----------------DriverPayment------------------
static String get addDriverpayment => "$paymentServer/ride/payment/add.php";
static String get addDriverPaymentPoints =>
"$paymentServer/ride/driverPayment/add.php";
static String get addPaymentTokenPassenger =>
"$paymentServer/ride/passengerWallet/addPaymentTokenPassenger.php";
static String get addPaymentTokenDriver =>
"$paymentServer/ride/driverWallet/addPaymentToken.php";
static String get getDriverPaymentPoints =>
"$paymentServer/ride/driverWallet/get.php";
static String get payWithEcashPassenger =>
"$paymentServer/ride/ecash/passenger/payWithEcash.php";
static String get payWithMTNConfirm =>
"$paymentServer/ride/mtn/passenger/mtn_confirm.php";
static String get payWithMTNStart =>
"$paymentServer/ride/mtn/passenger/mtn_start.php";
static String get payWithSyriatelConfirm =>
"$paymentServer/ride/syriatel/passenger/confirm_payment.php";
static String get payWithSyriatelStart =>
"$paymentServer/ride/syriatel/passenger/start_payment.php";
static String get getDriverpaymentToday =>
"$paymentServer/ride/payment/get.php";
static String get getCountRide =>
"$paymentServer/ride/payment/getCountRide.php";
static String get getAllPaymentFromRide =>
"$paymentServer/ride/payment/getAllPayment.php";
static String get getAllPaymentVisa =>
"$paymentServer/ride/payment/getAllPaymentVisa.php";
static String get createMtnInvoice =>
"$paymentServer/ride/mtn_new/create_mtn_invoice.php";
static String get createCliqInvoice =>
"$paymentServer/ride/cliq/create_cliq_invoice.php";
static String get checkMtnStatus =>
"$paymentServer/ride/mtn_new/check_status.php";
static String get checkCliqStatus =>
"$paymentServer/ride/cliq/check_status.php";
static String get uploadMtnProof =>
"$paymentServer/ride/mtn_new/verify_payment_ai.php";
static String get uploadCliqProof =>
"$paymentServer/ride/cliq/verify_payment_ai.php";
//-----------------Passenger NotificationCaptain------------------
static String get addNotificationPassenger =>
"$server/ride/notificationPassenger/add.php";
static String get getNotificationPassenger =>
"$server/ride/notificationPassenger/get.php";
static String get updateNotificationPassenger =>
"$server/ride/notificationPassenger/update.php";
//-----------------Driver NotificationCaptain------------------
static String get addNotificationCaptain =>
"$server/ride/notificationCaptain/add.php";
static String get addWaitingRide =>
"$server/ride/notificationCaptain/addWaitingRide.php";
static String get updateWaitingTrip =>
"$server/ride/notificationCaptain/updateWaitingTrip.php";
static String get getRideWaiting =>
"$endPoint/ride/notificationCaptain/getRideWaiting.php";
static String get getNotificationCaptain =>
"$server/ride/notificationCaptain/get.php";
static String get updateNotificationCaptain =>
"$server/ride/notificationCaptain/update.php";
static String get deleteNotificationCaptain =>
"$server/ride/notificationCaptain/delete.php";
//-----------------invitor------------------
static String get getUnifiedCode =>
"$server/ride/invitor/get_unified_code.php";
static String get addUnifiedInvite =>
"$server/ride/invitor/add_unified_invite.php";
static String get getPassengerReferrals =>
"$server/ride/invitor/get_passenger_referrals.php";
static String get addInviteDriver => "$server/ride/invitor/add.php";
static String get addInvitationPassenger =>
"$server/ride/invitor/addInvitationPassenger.php";
static String get getInviteDriver => "$server/ride/invitor/get.php";
static String get getDriverInvitationToPassengers =>
"$server/ride/invitor/getDriverInvitationToPassengers.php";
static String get updateInviteDriver => "$server/ride/invitor/update.php";
static String get updatePassengerGift =>
"$server/ride/invitor/updatePassengerGift.php";
static String get claimInviteReward => "$server/ride/invitor/claim.php";
//-----------------Api Key------------------
static String get addApiKey => "$server/ride/apiKey/add.php";
static String get getApiKey => "$server/ride/apiKey/get.php";
static String get getCnMap => "$server/auth/cnMap.php";
static String get updateApiKey => "$server/ride/apiKey/update.php";
static String get deleteApiKey => "$server/ride/apiKey/delete.php";
static String get getPlacesSyria => "$server/ride/places_syria/get.php";
//-----------------Feed Back------------------
static String get addFeedBack => "$server/ride/feedBack/add.php";
static String get add_solve_all => "$server/ride/feedBack/add_solve_all.php";
static String get uploadAudio => "$server/ride/feedBack/upload_audio.php";
static String get getFeedBack => "$server/ride/feedBack/get.php";
static String get updateFeedBack =>
"$server/ride/feedBack/updateFeedBack.php";
//-----------------Tips------------------
static String get addTips => "$server/ride/tips/add.php";
static String get getTips => "$server/ride/tips/get.php";
static String get updateTips => "$server/ride/tips/update.php";
//-----------------Help Center------------------
static String get addhelpCenter => "$server/ride/helpCenter/add.php";
static String get gethelpCenter => "$server/ride/helpCenter/get.php";
static String get getByIdhelpCenter => "$server/ride/helpCenter/getById.php";
static String get updatehelpCenter => "$server/ride/helpCenter/update.php";
static String get deletehelpCenter => "$server/ride/helpCenter/delete.php";
//-----------------license------------------
static String get addLicense => "$server/ride/license/add.php";
static String get getLicense => "$server/ride/license/get.php";
static String get updateLicense => "$server/ride/license/updateFeedBack.php";
//-----------------RegisrationCar------------------
static String get addRegisrationCar => "$server/ride/RegisrationCar/add.php";
static String get getRegisrationCar =>
"${box.read(BoxName.serverChosen)}/server/ride/RegisrationCar/get.php";
static String get selectDriverAndCarForMishwariTrip =>
"$server/ride/RegisrationCar/selectDriverAndCarForMishwariTrip.php";
static String get updateRegisrationCar =>
"$server/ride/RegisrationCar/update.php";
//-----------------mishwari------------------
static String get addMishwari => "$server/ride/mishwari/add.php";
static String get cancelMishwari => "$server/ride/mishwari/cancel.php";
static String get getMishwari => "$server/ride/mishwari/get.php";
static String get sendChatMessage => "$server/ride/chat/send_message.php";
//-----------------DriverOrder------------------
static String get addDriverOrder => "$server/ride/driver_order/add.php";
static String get getDriverOrder => "$server/ride/driver_order/get.php";
static String get getOrderCancelStatus =>
"$server/ride/driver_order/getOrderCancelStatus.php";
static String get updateDriverOrder => "$server/ride/driver_order/update.php";
static String get deleteDriverOrder => "$server/ride/driver_order/delete.php";
// =====================================
static String get addRateToPassenger => "$server/ride/rate/add.php";
static String get savePlacesServer => "$server/ride/places/add.php";
static String get getapiKey => "$server/ride/apiKey/get.php";
// Endpoint لجلب التسعيرة من السيرفر (Server-Side Pricing)
static String get getPrices => "$server/ride/pricing/get.php";
static String get getCompetitorContext =>
"$server/api/ride/get_competitor_context.php";
static String get addRateToDriver => "$server/ride/rate/addRateToDriver.php";
static String get getDriverRate => "$server/ride/rate/getDriverRate.php";
static String get getPassengerRate =>
"$server/ride/rate/getPassengerRate.php";
////////////////emails ============//
static String get sendEmailToPassengerForTripDetails =>
"$server/ride/rides/emailToPassengerTripDetail.php";
// ===========================================
static String get pathImage => "$server/upload/types/";
static String get uploadImage => "$server/uploadImage.php";
static String get uploadImage1 => "$server/uploadImage1.php";
static String get uploadImagePortrate => "$server/uploadImagePortrate.php";
static String get uploadImageType => "$server/uploadImageType.php";
//=============egypt documents ==============
static String get uploadEgyptidFront =>
"$server/EgyptDocuments/uploadEgyptidFront.php";
static String get uploadEgypt => "$server/uploadEgypt.php";
//==================certifcate==========
// static String location = '${box.read(BoxName.serverChosen)}/ride/location';
static String get getCarsLocationByPassenger => "$location/get.php";
static String get getLocationAreaLinks =>
'$server/ride/location/get_location_area_links.php';
static String get addpassengerLocation =>
"$locationServerSide/addpassengerLocation.php";
static String get getCarsLocationByPassengerSpeed => "$location/getSpeed.php";
static String get getCarsLocationByPassengerComfort =>
"$location/getComfort.php";
static String get getCarsLocationByPassengerBalash =>
"$location/getBalash.php";
static String get getCarsLocationByPassengerElectric =>
"$location/getElectric.php";
static String get getCarsLocationByPassengerPinkBike =>
"$location/getPinkBike.php";
static String get getCarsLocationByPassengerVan =>
"$location/getCarsLocationByPassengerVan.php";
static String get getCarsLocationByPassengerDelivery =>
"$location/getDelivery.php";
static String get getLocationParents => "$location/getLocationParents.php";
static String get getFemalDriverLocationByPassenger =>
"$location/getFemalDriver.php";
static String get getDriverCarsLocationToPassengerAfterApplied =>
"$location/getDriverCarsLocationToPassengerAfterApplied.php";
// static String addCarsLocationByPassenger = "$location/add.php";
// static String deleteCarsLocationByPassenger = "$location/delete.php";
// static String updateCarsLocationByPassenger = "$location/update.php";
// static String getTotalDriverDuration = "$location/getTotalDriverDuration.php";
// static String getTotalDriverDurationToday =
// "$location/getTotalDriverDurationToday.php";
//==================Blog=============
static String get profile => '$server/ride/profile';
static String get getprofile => "$profile/get.php";
static String get getCaptainProfile => "$profile/getCaptainProfile.php";
static String get addprofile => "$profile/add.php";
static String get deleteprofile => "$profile/delete.php";
static String get updateprofile => "$profile/update.php";
//===================Auth============
static String get auth => '$server/auth';
static String get login => "$auth/login.php";
static String get loginJwtRider => "$server/login.php";
static String get loginJwtWalletRider => "$server/loginWallet.php";
static String get loginFirstTime => "$server/loginFirstTime.php";
static String get getTesterApp => "$auth/Tester/getTesterApp.php";
static String get updateTesterApp => "$auth/Tester/updateTesterApp.php";
static String get signUp => "$auth/passenger/register.php";
static String get savePassengerLocation =>
"$auth/passenger/save_location.php";
static String get sendVerifyEmail => "$auth/sendVerifyEmail.php";
static String get loginFromGooglePassenger =>
"$auth/passenger/loginFromGoogle.php";
static String get loginUsingCredentialsWithoutGooglePassenger =>
"$auth/passenger/loginUsingCredentialsWithoutGoogle.php";
static String get checkPhoneNumberISVerfiedPassenger =>
"$auth/checkPhoneNumberISVerfiedPassenger.php";
static String get passengerRemovedAccountEmail =>
"$auth/passenger/remove_account.php";
static String get verifyEmail => "$auth/verifyEmail.php";
//===================Auth Captin============
static String get authCaptin => '$server/auth/driver';
static String get loginCaptin => "$authCaptin/login.php";
static String get loginFromGoogleCaptin => "$authCaptin/loginFromGoogle.php";
static String get signUpCaptin => "$authCaptin/register.php";
static String get sendVerifyEmailCaptin => "$authCaptin/sendVerifyEmail.php";
static String get sendVerifyOtpMessage => "$server/auth/otp/request.php";
static String get verifyOtpMessage => "$server/auth/otp/verify.php";
static String get verifyEmailCaptin => "$authCaptin/verifyEmail.php";
static String get removeUser => "$authCaptin/removeAccount.php";
static String get deletecaptainAccounr =>
"$authCaptin/deletecaptainAccounr.php";
static String get updateAccountBank => "$authCaptin/updateAccountBank.php";
static String get getAccount => "$authCaptin/getAccount.php";
static String get updatePassengersInvitation =>
"$server/ride/invitor/updatePassengersInvitation.php";
static String get updateDriverInvitationDirectly =>
"$server/ride/invitor/updateDriverInvitationDirectly.php";
//===================Admin Captin============
static String get getPassengerDetailsByPassengerID =>
"$server/Admin/getPassengerDetailsByPassengerID.php";
static String get getPassengerDetails =>
"$server/Admin/getPassengerDetails.php";
static String get getPassengerbyEmail =>
"$server/Admin/getPassengerbyEmail.php";
static String get addAdminUser => "$server/Admin/adminUser/add.php";
static String get getAdminUser => "$server/Admin/adminUser/get.php";
static String get addError => "$server/Admin/errorApp.php";
static String get getCaptainDetailsByEmailOrIDOrPhone =>
"$server/Admin/AdminCaptain/getCaptainDetailsByEmailOrIDOrPhone.php";
static String get getCaptainDetails => "$server/Admin/AdminCaptain/get.php";
static String get getRidesPerMonth =>
"$server/Admin/AdminRide/getRidesPerMonth.php";
static String get getRidesDetails => "$server/Admin/AdminRide/get.php";
//////////Sms egypt///////////
static String get sendSms => "https://sms.kazumi.me/api/sms/send-sms";
static String get sendSmsFromPHP => '$server/auth/otp/request.php';
static String get verifyOtpPassenger => '$server/auth/otp/verify.php';
static String get senddlr => "https://sms.kazumi.me/api/sms/send-dlr";
static String get sendvalidity =>
"https://sms.kazumi.me/api/sms/send-validity";
static String get sendmany => "https://sms.kazumi.me/api/sms/send-many";
static String get checkCredit => "https://sms.kazumi.me/api/sms/check-credit";
static String get getSender => "$server/auth/sms/getSender.php";
static String get checkStatus => "https://sms.kazumi.me/api/sms/check-status";
static String get updatePhoneInvalidSMSPassenger =>
"$server/auth/sms/updatePhoneInvalidSMSPassenger.php";
static String detectCountryFromPhone(String phone) {
final clean = phone.replaceAll(RegExp(r'[^0-9]'), '');
if (clean.startsWith('962')) return 'Jordan';
if (clean.startsWith('963')) return 'Syria';
if (clean.startsWith('20')) return 'Egypt';
if (clean.startsWith('07') || clean.startsWith('7')) return 'Jordan';
if (clean.startsWith('09') || clean.startsWith('9')) return 'Syria';
if (clean.startsWith('01') || clean.startsWith('1')) return 'Egypt';
return '';
}
}
@@ -0,0 +1,31 @@
List<String> passengerMessages = [
// --- رسائل العروض والتوفير ---
"وفر على حالك: 🚗 أسعار سيرو نازلة كتير! شوف العروض الجديدة وفوت هلأ قبل ما تخلص. 🌟",
"خصم اليوم: 🤔 لا تفوّت الفرصة! افتح تطبيق سيرو وشوف الأسعار يلي ما بتنعاد.",
"عروض نارية: 🎁 اليوم خصم خاص إلك، احجز مشوارك الجاي بسعر ولا أروع!",
"مشاوير اقتصادية: 💸 مع سيرو بتتحرك براحتك وبتدفع أقل، جرب وشوف الفرق!",
// --- رسائل السهولة والراحة ---
"مشوار بكبسة زر: 📲 افتح تطبيق سيرو، وخلِّي السيارة توصلك لعندك بثواني.",
"ارتاح من المواصلات: 🤔 خلّي سيرو يريحك من الانتظار والزحمة، وسيرو وين ما بدك.",
"سيارتك جاهزة: 🚕 الكابتن ناطر طلبك، حدد وجهتك وخلّي الطريق علينا.",
"رحلة مريحة: 🛣️ ارتاح بالكرسي، نحنا منهتم بكل التفاصيل من الباب للباب.",
// --- رسائل الأمان والثقة ---
"رحلتك بأمان: 🙏 كل كباتنّا مدرّبين وملتزمين، وسلامتك أولويتنا.",
"سافر وانت مطمّن: 🔒 مع سيرو كل شي موثوق ومسجّل لتكون مرتاح البال.",
"شارك رحلتك: ❤️ فيك تبعت تفاصيل المشوار لأهلك أو رفقاتك بخطوة وحدة.",
"سيرو بثقة: ✅ كل الرحلات مراقبة لتضمن تجربة آمنة ومريحة 100%.",
// --- رسائل تفاعلية ومناسبات ---
"الويكند بلّش: 🥳 خلي مشاويرك مع الأصحاب علينا، وفر وقتك وفلوسك مع سيرو.",
"رايح عالشغل: 💼 لا تتأخر، افتح التطبيق وخلي الكابتن يوصلك بلا تعب.",
"الشمس مولّعة: ☀️ لا تمشي تحت الحر، خلي السيارة تجي لعندك.",
"مستعجل: 🏃‍♂️ لا تقلق، سيرو أسرع طريق لتوصل عموعدك.",
// --- رسائل تشجيعية عامة ---
"وين رايح اليوم؟ 🗺️ وين ما كانت وجهتك، سيرو بيخدمك بكل مكان وبأي وقت.",
"جرب شي جديد: 🚘 شوف فئات السيارات الجديدة وخلي رحلتك أريح وأجمل.",
"شكراً لاختيارك: ⭐ وجودك معنا بيفرحنا، ونتمنى دايماً تكون رحلتك مريحة وسعيدة.",
"كل يوم جديد: ✨ فوت عالتطبيق وتابع آخر التحديثات والعروض يلي نازلة خصيصاً إلك."
];
@@ -0,0 +1,66 @@
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/main.dart';
class PaymentTier {
final double amount;
final double bonus;
const PaymentTier(this.amount, this.bonus);
}
class PaymentTierConfig {
final String currencyCode;
final List<PaymentTier> tiers;
const PaymentTierConfig({required this.currencyCode, required this.tiers});
static const Map<String, PaymentTierConfig> _configs = {
'Jordan': PaymentTierConfig(
currencyCode: 'JOD',
tiers: [
PaymentTier(5, 0),
PaymentTier(10, 0.5),
PaymentTier(20, 1),
PaymentTier(50, 3),
],
),
'Egypt': PaymentTierConfig(
currencyCode: 'EGP',
tiers: [
PaymentTier(100, 0),
PaymentTier(200, 10),
PaymentTier(400, 20),
PaymentTier(1000, 50),
],
),
'Syria': PaymentTierConfig(
currencyCode: 'SYP',
tiers: [
PaymentTier(100, 0),
PaymentTier(200, 10),
PaymentTier(400, 20),
PaymentTier(1000, 50),
],
),
};
static PaymentTierConfig get current {
final country = box.read(BoxName.countryCode) ?? 'Jordan';
return _configs[country] ?? _configs['Jordan']!;
}
static List<PaymentTier> get currentTiers => current.tiers;
static String get currentCurrency => current.currencyCode;
static String formatAmount(double v) {
return v == v.roundToDouble() ? v.toInt().toString() : v.toStringAsFixed(1);
}
static String tierLabel(int index) {
final t = currentTiers[index];
final cur = currentCurrency;
if (t.bonus > 0) {
return '${'Pay'.tr} ${formatAmount(t.amount)} $cur, ${'Get'.tr} ${formatAmount(t.amount + t.bonus)} $cur';
}
return '${formatAmount(t.amount)} $cur';
}
}
@@ -0,0 +1,198 @@
sefer cairo server
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
{{ssl_certificate_key}}
{{ssl_certificate}}
server_name www.sefer.click;
return 301 https://sefer.click$request_uri;
}
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
{{ssl_certificate_key}}
{{ssl_certificate}}
server_name sefer.click www1.sefer.click;
{{root}}
{{nginx_access_log}}
{{nginx_error_log}}
# Set the maximum request body size
client_max_body_size 10m;
if ($scheme != "https") {
rewrite ^ https://$host$uri permanent;
}
location ~ /.well-known {
auth_basic off;
allow all;
}
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline';" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
{{settings}}
location / {
{{varnish_proxy_pass}}
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_hide_header X-Varnish;
proxy_redirect off;
proxy_max_temp_file_size 0;
proxy_connect_timeout 720;
proxy_send_timeout 720;
proxy_read_timeout 720;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;
}
location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf|map|mjs)$ {
add_header Access-Control-Allow-Origin "*";
expires max;
access_log off;
}
location ~ /\.(ht|svn|git) {
deny all;
}
if (-f $request_filename) {
break;
}
}
server {
listen 8080;
listen [::]:8080;
server_name sefer.click www1.sefer.click;
{{root}}
try_files $uri $uri/ /index.php?$args;
index index.php index.html;
location ~ \.php$ {
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files $uri =404;
fastcgi_read_timeout 3600;
fastcgi_send_timeout 3600;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
fastcgi_param HTTPS "on";
fastcgi_param SERVER_PORT 443;
fastcgi_pass 127.0.0.1:{{php_fpm_port}};
fastcgi_param PHP_VALUE "{{php_settings}}";
}
if (-f $request_filename) {
break;
}
}
wallet server
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
{{ssl_certificate_key}}
{{ssl_certificate}}
server_name www.seferpw.shop;
return 301 https://seferpw.shop$request_uri;
}
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
{{ssl_certificate_key}}
{{ssl_certificate}}
server_name seferpw.shop www1.seferpw.shop 156.67.82.188;
{{root}}
{{nginx_access_log}}
{{nginx_error_log}}
# Set the maximum request body size
client_max_body_size 10m;
if ($scheme != "https") {
rewrite ^ https://$host$uri permanent;
}
location ~ /.well-known {
auth_basic off;
allow all;
}
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline';" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
{{settings}}
location / {
{{varnish_proxy_pass}}
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_hide_header X-Varnish;
proxy_redirect off;
proxy_max_temp_file_size 0;
proxy_connect_timeout 720;
proxy_send_timeout 720;
proxy_read_timeout 720;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_temp_file_write_size 256k;
}
location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf|map|mjs)$ {
add_header Access-Control-Allow-Origin "*";
expires max;
access_log off;
}
location ~ /\.(ht|svn|git) {
deny all;
}
if (-f $request_filename) {
break;
}
}
server {
listen 8080;
listen [::]:8080;
server_name seferpw.shop www1.seferpw.shop 156.67.82.188;
{{root}}
try_files $uri $uri/ /index.php?$args;
index index.php index.html;
location ~ \.php$ {
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files $uri =404;
fastcgi_read_timeout 3600;
fastcgi_send_timeout 3600;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
fastcgi_param HTTPS "on";
fastcgi_param SERVER_PORT 443;
fastcgi_pass 127.0.0.1:{{php_fpm_port}};
fastcgi_param PHP_VALUE "{{php_settings}}";
}
if (-f $request_filename) {
break;
}
}
+74
View File
@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../main.dart';
import 'box_name.dart';
import 'colors.dart';
class AppStyle {
static TextStyle get headTitle => TextStyle(
fontWeight: FontWeight.bold,
fontSize: box.read(BoxName.lang) == 'ar' ? 36 : 28,
color: AppColor.accentColor,
fontFamily: box.read(BoxName.lang) == 'ar'
// ?GoogleFonts.markaziText().fontFamily
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get headTitle2 => TextStyle(
fontWeight: FontWeight.bold,
fontSize: box.read(BoxName.lang) == 'ar' ? 24 : 20,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get title => TextStyle(
fontWeight: FontWeight.normal,
fontSize: box.read(BoxName.lang) == 'ar' ? 16 : 14,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get subtitle => TextStyle(
fontWeight: FontWeight.bold,
fontSize: box.read(BoxName.lang) == 'ar' ? 12 : 11,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get number => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: AppColor.writeColor,
fontFamily: 'digit');
static BoxDecoration get boxDecoration => BoxDecoration(
boxShadow: [
BoxShadow(
color: AppColor.accentColor.withOpacity(0.3),
blurRadius: 5,
offset: const Offset(2, 4)),
BoxShadow(
color: AppColor.accentColor.withOpacity(0.1),
blurRadius: 5,
offset: const Offset(-2, -2))
],
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30),
));
static BoxDecoration get boxDecoration1 => BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4)),
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.02),
blurRadius: 5,
offset: const Offset(-2, -2))
],
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30),
),
);
}
@@ -0,0 +1,19 @@
// import 'package:intaleq_rider/env/env.dart';
class TableName {
static const String placesFavorite = "placesFavorite";
static const String recentLocations = "recentLocations";
static const String carLocations = "carLocations";
static const String driverOrdersRefuse = "driverOrdersRefuse";
static const String rideLocation = "rideLocation";
static const String faceDetectTimes = "faceDetectTimes";
static const String captainNotification = "captainNotification";
}
class Pasenger {
static const String pasengerpas = 'MG6DEJZSczBT6Rx0jOlehQ==';
static const String payMobApikey = 'payMobApikey';
static const String initializationVector = 'initializationVector';
static const String keyOfApp = 'keyOfApp';
static const String FCM_PRIVATE_KEY_SIRO = 'FCM_PRIVATE_KEY_SIRO';
}
@@ -0,0 +1,83 @@
import 'package:intaleq_maps/intaleq_maps.dart';
class UniversitiesPolygons {
// AUC polygon points
static const List<List<LatLng>> universityPolygons = [
// AUC Polygon
[
LatLng(30.013431, 31.502572),
LatLng(30.018469, 31.497478),
LatLng(30.023158, 31.495870),
LatLng(30.025084, 31.496781),
LatLng(30.018701, 31.511393),
LatLng(30.015312, 31.508310),
],
// Example polygon for University 'German University in Cairo (GUC)'
[
LatLng(29.984554, 31.437829),
LatLng(29.990363, 31.438390),
LatLng(29.990560, 31.445643),
LatLng(29.984436, 31.445825),
],
//Future University in Egypt (FUE)
[
LatLng(30.025794, 31.490946),
LatLng(30.028341, 31.491014),
LatLng(30.028341, 31.492586),
LatLng(30.025844, 31.492491),
],
//'British University in Egypt (BUE)'
[
LatLng(30.117423, 31.605834),
LatLng(30.118224, 31.605543),
LatLng(30.118649, 31.607361),
LatLng(30.118932, 31.608033),
LatLng(30.119592, 31.612159),
LatLng(30.119372, 31.612958),
LatLng(30.120017, 31.617102),
LatLng(30.119435, 31.617193),
],
//Misr International University (MIU)
[
LatLng(30.166498, 31.491663),
LatLng(30.171956, 31.491060),
LatLng(30.172212, 31.495754),
LatLng(30.167112, 31.496108),
],
// Canadian International College (CIC)
[
LatLng(30.034312, 31.428963),
LatLng(30.035661, 31.429037),
LatLng(30.036074, 31.430522),
LatLng(30.036017, 31.431146),
LatLng(30.034580, 31.431117),
],
// October 6 University (O6U)
[
LatLng(29.974102, 30.946934),
LatLng(29.976620, 30.944925),
LatLng(29.979848, 30.949832),
LatLng(29.977372, 30.951950),
],
[
LatLng(30.029312, 31.210046),
LatLng(30.027124, 31.201393),
LatLng(30.014523, 31.205087),
LatLng(30.015416, 31.212218),
LatLng(30.027325, 31.210661),
],
// Add polygons for 8 more universities...
];
static const List<String> universityNames = [
"American University in Cairo (AUC)",
'German University in Cairo (GUC)',
'Future University in Egypt (FUE)',
'British University in Egypt (BUE)',
'Misr International University (MIU)',
'Canadian International College (CIC)',
'October 6 University (O6U)',
"Cairo University",
// Add names for 8 more universities...
];
}
@@ -0,0 +1,57 @@
// import 'package:home_widget/home_widget.dart';
// class TripzHomeWidgetProvider {
// static const String widgetName = 'TripzHomeWidget';
// // Initialize Home Widget
// static Future<void> initHomeWidget() async {
// await HomeWidget.registerInteractivityCallback(backgroundCallback);
// }
// // Background Callback for Widget Updates
// static Future<void> backgroundCallback(Uri? uri) async {
// if (uri?.host == 'updateWidget') {
// // Logic to update widget data
// await updateWidgetData();
// }
// }
// // Update Widget Data Method
// static Future<void> updateWidgetData() async {
// // Fetch current ride details
// final rideData = await _fetchCurrentRideDetails();
// // Update Widget with Ride Information
// await HomeWidget.saveWidgetData<String>(
// 'ride_destination', rideData.destination);
// await HomeWidget.saveWidgetData<String>(
// 'ride_estimated_time', rideData.estimatedTime);
// await HomeWidget.saveWidgetData<double>('ride_fare', rideData.fare);
// // Trigger Widget Update
// await HomeWidget.updateWidget(
// name: widgetName,
// iOSName: 'TripzWidgetProvider',
// androidName: 'com.mobileapp.store.ride.HomeWidgetProvider',
// );
// }
// // Mock method to fetch ride details (replace with actual implementation)
// static Future<RideData> _fetchCurrentRideDetails() async {
// // Implement actual data fetching logic
// return RideData(
// destination: 'Downtown Office', estimatedTime: '25 mins', fare: 15.50);
// }
// }
// // Ride Data Model
// class RideData {
// final String destination;
// final String estimatedTime;
// final double fare;
// RideData(
// {required this.destination,
// required this.estimatedTime,
// required this.fare});
// }
@@ -0,0 +1,41 @@
import 'package:intaleq_rider/print.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
Future<bool> verifyCertificateManually(
String host, int port, String expectedPin) async {
try {
final socket = await SecureSocket.connect(host, port,
timeout: const Duration(seconds: 5));
final certificate = socket.peerCertificate;
if (certificate == null) {
Log.print("❌ لا يوجد شهادة.");
return false;
}
final der = certificate.der;
final actualPin = base64.encode(sha256.convert(der).bytes);
Log.print("📛 HOST: $host");
Log.print("📜 Subject: ${certificate.subject}");
Log.print("📜 Issuer: ${certificate.issuer}");
Log.print("📅 Valid From: ${certificate.startValidity}");
Log.print("📅 Valid To: ${certificate.endValidity}");
Log.print(
"🔐 Server Pin: $actualPin → ${actualPin == expectedPin ? '✅ MATCH' : '❌ MISMATCH'}");
socket.destroy();
return actualPin == expectedPin;
} catch (e) {
Log.print("❌ خطأ أثناء الاتصال أو الفحص: $e");
return false;
}
}
/// تحويل المفتاح العام إلى بصمة SHA-256
List<int> sha256Convert(Uint8List der) {
return sha256.convert(der).bytes;
}
@@ -0,0 +1,685 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:intaleq_rider/services/geofencing_service.dart';
import 'package:geolocator/geolocator.dart';
import 'package:intaleq_rider/constant/api_key.dart';
import 'package:intaleq_rider/controller/firebase/firbase_messge.dart';
import 'package:intaleq_rider/views/auth/otp_page.dart';
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
import 'package:http/http.dart' as http;
import 'package:intaleq_rider/constant/info.dart';
import 'package:intaleq_rider/controller/functions/add_error.dart';
import 'package:intaleq_rider/views/auth/login_page.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/views/home/map_page_passenger.dart';
import 'package:location/location.dart' as loc;
import '../../env/env.dart';
import '../../print.dart';
import '../../views/auth/otp_token_page.dart';
import '../functions/country_logic.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/package_info.dart';
class LoginController extends GetxController {
final formKey = GlobalKey<FormState>();
final formKeyAdmin = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController adminPasswordController = TextEditingController();
TextEditingController adminNameController = TextEditingController();
bool isAgreeTerms = false;
bool isloading = false;
late int isTest = 1;
void changeAgreeTerm() {
isAgreeTerms = !isAgreeTerms;
update();
}
var dev = '';
@override
void onInit() async {
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
// كشف الدولة تلقائياً عبر الموقع الجغرافي
await CountryLogic.initializeCountry();
FirebaseMessagesController().getToken();
super.onInit();
}
void saveAgreementTerms() {
box.write(BoxName.agreeTerms, 'agreed');
update();
}
void saveCountryCode(String countryCode) {
box.write(BoxName.countryCode, countryCode);
update();
}
// ═══════════════════════════════════════════════════════════════
// LoginController — دوال إدارة الـ JWT
// ───────────────────────────────────────────────────────────────
// لا تغيير على اسم الكلاس أو أسماء الدوال
// ═══════════════════════════════════════════════════════════════
// داخل class LoginController
// ─────────────────────────────────────────────────────────────
// getJWT: الحصول على توكن للراكب
// ─────────────────────────────────────────────────────────────
// المنطق:
// • firstTimeLoadKey != false ← أول مرة يفتح التطبيق → loginFirstTime
// • firstTimeLoadKey == false ← مستخدم موجود → loginJwtRider
// ─────────────────────────────────────────────────────────────
Future<void> getJWT({bool force = false}) async {
// إذا كان التوكن الحالي لا يزال صالحاً، لا داعي لطلب واحد جديد
if (!force && isTokenValid()) {
Log.print("JWT is still valid. Skipping request.");
return;
}
try {
dev = Platform.isAndroid ? 'android' : 'ios';
// تأكد إن البصمة محدّثة قبل أي طلب
await DeviceHelper.getDeviceFingerprint();
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
// ── أول تسجيل ─────────────────────────────────────────
// نرسل البصمة المشفرة مع باقي البيانات
// السيرفر سيعمل hash لها ويخزنها في JWT payload
var payload = {
'id': box.read(BoxName.passengerID) ?? AK.newId,
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': fp,
};
var response = await http.post(
Uri.parse(AppLink.loginFirstTime),
body: payload,
);
Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}');
Log.print('payload: $payload');
Log.print('response code: ${response.statusCode}');
Log.print('response body: ${response.body}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final String? jwt = decoded['data'] != null
? decoded['data']['jwt']
: (decoded['message'] != null
? decoded['message']['jwt']
: decoded['jwt']);
if (jwt != null) {
// نشفر الـ JWT بالتشفير الثلاثي قبل التخزين في GetStorage
box.write(BoxName.jwt, c(jwt));
storage.write(key: BoxName.jwt, value: c(jwt));
}
await EncryptionHelper.initialize();
}
} else {
// ── مستخدم موجود: تجديد التوكن
var payload = {
'id': box.read(BoxName.passengerID),
'fingerPrint': fp,
'aud': '${AK.allowed}$dev',
};
var response = await http.post(
Uri.parse(AppLink.loginJwtRider),
body: payload,
);
Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}');
Log.print('payload: $payload');
Log.print('response: ${response.body}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
final String? jwt = decoded['data'] != null
? decoded['data']['jwt']
: (decoded['message'] != null
? decoded['message']['jwt']
: decoded['jwt']);
if (jwt != null) {
box.write(BoxName.jwt, c(jwt));
storage.write(key: BoxName.jwt, value: c(jwt));
}
}
}
} catch (e) {
Log.print('Error in getJWT: $e');
}
}
// ─────────────────────────────────────────────────────────────
// التحقق من صلاحية التوكن يدوياً (بدون مكاتب خارجية)
// ─────────────────────────────────────────────────────────────
bool isTokenValid() {
try {
final String? encryptedJwt = box.read(BoxName.jwt);
if (encryptedJwt == null || encryptedJwt.isEmpty) {
Log.print("isTokenValid: No token found in storage.");
return false;
}
// 1. فك تشفير التوكن المخزن
final String jwtFull = r(encryptedJwt).toString();
final String jwt = jwtFull.split(Env.addd)[0];
final parts = jwt.split('.');
if (parts.length != 3) {
Log.print("isTokenValid: Invalid JWT format (parts: ${parts.length})");
return false;
}
// 2. فك Base64 للجزء الأوسط (Payload)
String payloadPart = parts[1];
while (payloadPart.length % 4 != 0) {
payloadPart += '=';
}
final String decodedPayload = utf8.decode(base64Url.decode(payloadPart));
final Map<String, dynamic> payload = jsonDecode(decodedPayload);
if (payload['token_type'] == 'registration') {
Log.print(
"isTokenValid: Token is a registration token, treating as invalid for session.");
return false;
}
if (!payload.containsKey('exp')) {
Log.print("isTokenValid: No 'exp' claim in token.");
return false;
}
final int exp = payload['exp'];
final int now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
Log.print("isTokenValid: Now=$now, Exp=$exp, Diff=${exp - now}s");
// اعتبار التوكن منتهي قبل دقيقة للأمان
final bool valid = exp > (now + 60);
Log.print("isTokenValid: Result=$valid");
return valid;
} catch (e) {
Log.print("isTokenValid Error: $e");
return false;
}
}
// ─────────────────────────────────────────────────────────────
// الفرق عن getJWT:
// • يستخدم endpoint مختلف (loginWallet)
// • يرجع hmac مع الـ jwt ويخزنه في GetStorage
// • الـ JWT لا يُشفَّر ثلاثياً (يُستخدم مباشرة في الـ header)
// ─────────────────────────────────────────────────────────────
Future<String?> getJwtWallet() async {
dev = Platform.isAndroid ? 'android' : 'ios';
final String fp = box.read(BoxName.deviceFpEncrypted) ?? '';
var payload = {
'id': box.read(BoxName.passengerID),
'password': AK.passnpassenger,
'aud': '${AK.allowedWallet}$dev',
'fingerPrint': fp,
};
var response = await http.post(
Uri.parse(AppLink.loginJwtWalletRider),
body: payload,
);
Log.print('AppLink.loginJwtWalletRider: ${AppLink.loginJwtWalletRider}');
Log.print('response wallet: ${response.body}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
// ← الإصلاح: نقرأ من message أو data أو root
final inner = decoded['data'] ?? decoded['message'] ?? decoded;
final String? jwt = inner['jwt'];
final String? hmac = inner['hmac'];
Log.print('jwt extracted: $jwt');
Log.print('hmac extracted: $hmac');
if (hmac != null) {
box.write(BoxName.hmac, hmac);
}
return jwt;
}
return null;
}
Future<void> loginUsingCredentials(String passengerID, String email) async {
isloading = true;
update();
// await getJWT();
Log.print("LoginController.loginUsingCredentials: ");
try {
// 1) استعلام تسجيل الدخول
final res = await CRUD().get(
link: AppLink.loginFromGooglePassenger,
payload: {
'platform': Platform.isAndroid ? 'android' : 'ios',
'appName': AppInformation.appName,
'passengerID': passengerID,
},
);
if (res == 'token_expired' || res == 'failure' || res == 'error') {
Log.print(
'loginUsingCredentials: $res. Redirecting to PhoneNumberScreen to re-verify.');
box.erase();
storage.deleteAll();
Get.offAll(() => PhoneNumberScreen());
return;
}
// 2) فك JSON مرة واحدة والتحقق من النجاح
final decoded = res is String ? jsonDecode(res) : res;
if (decoded is! Map || decoded.isEmpty) return;
if (decoded['status'] == 'failure' || decoded['status'] == 'Failure') {
mySnackbarError("User does not exist.".tr);
return;
}
final status = (decoded['status'] ?? '').toString();
final data = (decoded['data'] as List?)?.firstOrNull as Map? ?? {};
if (status != 'success' || data['verified'].toString() != '1') {
// غير مُفعل -> أذهب للتسجيل بالـ SMS
Get.offAll(() => PhoneNumberScreen());
return;
}
// 3) كتابة القيم (خفيفة)
box.write(BoxName.isVerified, '1');
box.write(BoxName.email, data['email']);
box.write(BoxName.phone, data['phone']);
box.write(BoxName.name, data['first_name']);
box.write(BoxName.isTest, '1');
box.write(BoxName.package, data['package']);
box.write(BoxName.promo, data['promo']);
box.write(BoxName.discount, data['discount']);
box.write(BoxName.validity, data['validity']);
box.write(BoxName.isInstall, data['isInstall'] ?? 'none');
box.write(BoxName.isGiftToken, data['isGiftToken'] ?? 'none');
box.write(BoxName.isClaim, data['isClaim'] ?? '0');
if (data['inviteCode'] != null) {
box.write(BoxName.inviteCode, data['inviteCode'].toString());
}
// مهم: تأكد من passengerID في الـ box
box.write(BoxName.passengerID, passengerID);
// 4) فحص ما إذا كان التوكن موجوداً في رد الـ Login المدمج (V3 Optimization)
String? serverFCM;
String? serverFP;
var userData = decoded['data'] is List
? (decoded['data'] as List).firstOrNull
: decoded['data'];
if (userData is Map && userData['fcm_token'] != null) {
serverFCM = userData['fcm_token'].toString();
serverFP = userData['fcm_fingerprint']?.toString() ?? '';
Log.print(
"✅ FCM Token found in Login response. Skipping separate getTokens call.");
}
// إذا لم يكن موجوداً (توافقية قديمة)، نقوم بطلبه
dynamic tokenResp;
if (serverFCM == null) {
tokenResp = await CRUD().get(
link: AppLink.getTokens, payload: {'passengerID': passengerID});
}
final localFP = (await DeviceHelper.getDeviceFingerprint()).toString();
await storage.write(key: BoxName.fingerPrint, value: localFP);
await box.write(BoxName.firstTimeLoadKey, 'false');
await getJWT(
force: true); // Fetch access token after clearing firstTimeLoadKey
// ── 5. المقارنة: FCM token + fingerprint ──────────────────────
if (email != '962798583052@intaleqapp.com') {
if (serverFCM == null &&
tokenResp != null &&
tokenResp != 'failure' &&
tokenResp != 'error' &&
tokenResp != 'token_expired') {
final tokenJson =
tokenResp is String ? jsonDecode(tokenResp) : tokenResp;
final serverData = tokenJson['data'] ?? tokenJson['message'];
if (serverData is Map) {
serverFCM = serverData['token']?.toString() ?? '';
serverFP = serverData['fingerPrint']?.toString() ?? '';
}
}
if (serverFCM != null && serverFCM.isNotEmpty) {
final localFCM = (box.read(BoxName.tokenFCM) ?? '').toString();
// ── اختلاف أي منهما = جهاز مختلف أو تثبيت جديد ─────────
final fcmChanged = serverFCM != localFCM;
final fpChanged =
serverFP != null && serverFP.isNotEmpty && serverFP != localFP;
if (fcmChanged || fpChanged) {
mySnackbarInfo('Device Change Detected'.tr);
await Get.to(() => OtpVerificationPage(
phone: data['phone'].toString(),
deviceToken: localFP,
token: tokenResp ?? '',
ptoken: serverFCM ?? '', // نمرر FCM القديم للـ OTP controller
));
return;
}
}
}
// 6) منطق الدعوة (إن وُجد) ثم الانتقال
final invite = box.read(BoxName.inviteCode)?.toString() ?? 'none';
final isInstall = box.read(BoxName.isInstall)?.toString() ?? '0';
if (invite != 'none' && isInstall != '1') {
try {
await CRUD().post(link: AppLink.updatePassengersInvitation, payload: {
"inviteCode": invite,
"passengerID": passengerID,
});
// سجل الدعوة أيضاً في النظام الموحد الجديد
await CRUD().post(link: AppLink.addUnifiedInvite, payload: {
"inviter_code": invite,
});
// تحديث الحالة محلياً لضمان عدم إرسال الطلب مرة أخرى
box.write(BoxName.isInstall, '1');
await Get.defaultDialog(
title: 'Invitation Used'.tr,
middleText: "Your invite code was successfully applied!".tr,
textConfirm: "OK".tr,
confirmTextColor: Colors.white,
onConfirm: () async {
try {
await CRUD().post(link: AppLink.addPassengersPromo, payload: {
"promoCode":
'I-${(box.read(BoxName.name)).toString().split(' ').first}',
"amount": '25',
"passengerID": passengerID,
"description": 'promo first'
});
} catch (e) {
addError(
e.toString(), 'promo on invitation in login_controller');
} finally {
Get.offAll(() => const MapPagePassenger());
}
},
);
return;
} catch (_) {
// حتى لو فشل، كمل للصفحة الرئيسية
}
}
sendPassengerLocation();
Get.offAll(() => const MapPagePassenger());
} catch (e) {
addError('$e', 'loginUsingCredentials');
mySnackbarError(e.toString());
} finally {
isloading = false;
update();
}
}
// Future<bool?> _confirmDeviceChangeDialog() {
// return Get.defaultDialog<bool>(
// barrierDismissible: false,
// title: 'Device Change Detected'.tr,
// middleText: 'Please verify your identity'.tr,
// textConfirm: 'Verify'.tr,
// confirmTextColor: Colors.white,
// onConfirm: () => Get.back(result: true),
// textCancel: 'Cancel'.tr,
// onCancel: () => Get.back(result: false),
// );
// }
void loginTester() async {
isloading = true;
update();
try {
var fingerPrint = await DeviceHelper.getDeviceFingerprint();
var payload = {
'email': emailController.text.trim(),
'password': passwordController.text.trim(),
'fingerPrint': fingerPrint,
'aud': '${AK.allowed}${Platform.isAndroid ? 'android' : 'ios'}',
};
Log.print(
"Tester Login Payload: email=${payload['email']}, password=${payload['password']}, aud=${payload['aud']}");
var response = await http.post(
Uri.parse(AppLink.loginUsingCredentialsWithoutGooglePassenger),
body: payload,
);
if (response.statusCode == 200 || response.statusCode == 201) {
var jsonDecoeded = jsonDecode(response.body);
Log.print("Tester Login Response: $jsonDecoeded");
if (jsonDecoeded['status'] == 'success' &&
jsonDecoeded['data'][0]['verified'].toString() == '1') {
var d = jsonDecoeded['data'][0];
var jwt = jsonDecoeded['jwt'];
// حفظ التوكن
if (jwt != null) {
box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: c(jwt));
}
box.write(BoxName.isVerified, '1');
box.write(BoxName.email, d['email']);
box.write(BoxName.name, d['first_name']);
box.write(BoxName.phone, d['phone']);
box.write(BoxName.passengerID, d['id']);
box.write(BoxName.isTest, '1');
await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
// تحديث التوكن والبصمة في الباك إند ليتمكن التستر من استقبال الإشعارات
await CRUD().post(
link: "${AppLink.server}/ride/firebase/addToken.php",
payload: {
'token': box.read(BoxName.tokenFCM).toString(),
'fingerPrint': fingerPrint.toString(),
'passengerID': box.read(BoxName.passengerID).toString(),
});
sendPassengerLocation();
Get.offAll(() => const MapPagePassenger());
} else {
Log.print(
"Tester Login Failed due to condition mismatch: status=${jsonDecoeded['status']}, verified=${jsonDecoeded['data']?[0]?['verified']}");
Get.offAll(() => LoginPage());
isloading = false;
update();
}
} else {
Log.print(
"Tester Login Failed with status code: ${response.statusCode}, body: ${response.body}");
isloading = false;
update();
}
} catch (e) {
Log.print("Tester Login Error: $e");
isloading = false;
update();
}
}
void goToMapPage() {
if (box.read(BoxName.email) != null) {
Get.offAll(() => const MapPagePassenger());
}
}
final location = loc.Location();
// late PermissionStatus permissionGranted = PermissionStatus.denied;
Future<void> getLocationPermission() async {
bool serviceEnabled;
loc.PermissionStatus permissionGranted;
// Check if location services are enabled
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
if (Platform.isAndroid) {
serviceEnabled = await location.requestService();
}
if (!serviceEnabled) {
// Location services are still not enabled, show a message
mySnackbarError(
'Please enable location services in your device settings to continue.'
.tr);
return;
}
}
// Check if the app has permission to access location
permissionGranted = await location.hasPermission();
if (permissionGranted == loc.PermissionStatus.deniedForever) {
mySnackbarError(
'Please go to app settings and grant location permission.'.tr);
return;
}
if (permissionGranted == loc.PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != loc.PermissionStatus.granted &&
permissionGranted != loc.PermissionStatus.grantedLimited) {
// Location permission is still not granted, handle the error
mySnackbarError(
'Location permission is required to use the app. Please grant permission.'
.tr);
return;
}
}
// Initialize geofencing now that we have permission
if (Platform.isAndroid || Platform.isIOS) {
await SiroGeofencingService.initAndStart();
}
if (permissionGranted.toString() == 'PermissionStatus.granted' ||
permissionGranted == loc.PermissionStatus.granted ||
permissionGranted == loc.PermissionStatus.grantedLimited) {
box.write(BoxName.locationPermission, 'true');
}
update();
}
// ─────────────────────────────────────────────────────────────
// sendPassengerLocation: تسجيل موقع فتح التطبيق للراكب
// ─────────────────────────────────────────────────────────────
Future<void> sendPassengerLocation() async {
try {
final String? passengerID = box.read(BoxName.passengerID)?.toString();
if (passengerID == null || passengerID.isEmpty) {
Log.print("sendPassengerLocation: No passenger ID found, skipping.");
return;
}
// ── [THROTTLING] تحديث الموقع مرة كل 12 ساعة فقط لتقليل الضغط ──
final String cacheKey = '${BoxName.lastLocationPush}_$passengerID';
final String? lastUploadStr = box.read(cacheKey);
if (lastUploadStr != null) {
final DateTime lastUpload = DateTime.parse(lastUploadStr);
final Duration diff = DateTime.now().difference(lastUpload);
if (diff.inHours < 12) {
Log.print(
"sendPassengerLocation: Location uploaded recently (${diff.inHours}h ago). Skipping.");
return;
}
}
// تحقق من تفعيل خدمات الموقع الجغرافي
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
Log.print("sendPassengerLocation: Location service disabled.");
return;
}
// التحقق من الصلاحيات والسكوت الممنهج لتفادي النوافذ المزعجة
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
Log.print(
"sendPassengerLocation: Permission is denied. Skipping silently.");
return;
}
if (permission == LocationPermission.deniedForever) {
Log.print(
"sendPassengerLocation: Permission permanently denied. Skipping silently.");
return;
}
// جلب الموقع الحالي بمهلة 5 ثوانٍ
final Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.medium,
timeLimit: const Duration(seconds: 5),
);
Log.print(
"sendPassengerLocation: Got location: ${position.latitude}, ${position.longitude}");
// إرسال البيانات إلى السيرفر
final response = await CRUD().post(
link: AppLink.savePassengerLocation,
payload: {
'latitude': position.latitude.toString(),
'longitude': position.longitude.toString(),
},
);
Log.print("sendPassengerLocation response: $response");
if (response != 'failure' && response != null) {
final decoded =
response is Map ? response : jsonDecode(response.toString());
if (decoded['status'] == 'success') {
box.write(cacheKey, DateTime.now().toIso8601String());
Log.print(
"sendPassengerLocation: Location logged and cached successfully.");
}
}
} catch (e) {
Log.print("sendPassengerLocation error: $e");
}
}
}
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/views/auth/login_page.dart';
import '../../models/model/onboarding_model.dart';
abstract class OnBoardingController extends GetxController {
next();
onPageChanged(int index);
}
class OnBoardingControllerImp extends OnBoardingController {
late PageController pageController;
int currentPage = 0;
@override
next() {
currentPage++;
if (currentPage > onBoardingList.length - 1) {
box.write(BoxName.onBoarding, 'yes');
Get.offAll(() => LoginPage());
} else {
pageController.animateToPage(currentPage,
duration: const Duration(milliseconds: 900), curve: Curves.easeInOut);
}
}
@override
onPageChanged(int index) {
currentPage = index;
update();
}
@override
void onInit() {
pageController = PageController();
super.onInit();
}
}
@@ -0,0 +1,178 @@
import 'package:intaleq_rider/constant/links.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../views/auth/otp_page.dart';
import '../../views/widgets/error_snakbar.dart';
import '../functions/crud.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/package_info.dart';
import '../functions/country_logic.dart';
import 'login_controller.dart';
// --- Helper Class for Phone Authentication ---
class PhoneAuthHelper {
// Define your server URLs
static final String _sendOtpUrl = '${AppLink.server}/auth/otp/request.php';
static final String _verifyOtpUrl = '${AppLink.server}/auth/otp/verify.php';
static final String _registerUrl =
'${AppLink.server}/auth/passenger/register.php';
// removed formatSyrianPhone
/// Sends an OTP to the provided phone number.
static Future<bool> sendOtp(String phoneNumber) async {
try {
final detectedCountry = AppLink.detectCountryFromPhone(phoneNumber);
if (detectedCountry.isNotEmpty) {
await box.write(BoxName.countryCode, detectedCountry);
}
// إصلاح الرقم قبل الإرسال
final fixedPhone = CountryLogic.formatCurrentCountryPhone(phoneNumber);
final response = await CRUD().post(
link: _sendOtpUrl,
payload: {
'receiver': fixedPhone,
'context': 'login',
'user_type': 'passenger'
},
);
if (response != 'failure') {
final data = response;
if (data['status'] == 'success') {
mySnackbarSuccess('An OTP has been sent to your number.'.tr);
return true;
} else {
mySnackbarError(data['message'] ?? 'Failed to send OTP.');
return false;
}
} else {
mySnackbarError('Server error. Please try again.'.tr);
return false;
}
} catch (e) {
return false;
}
}
/// Verifies the OTP and logs the user in.
static Future<void> verifyOtp(String phoneNumber, String otpCode) async {
try {
final fixedPhone = CountryLogic.formatCurrentCountryPhone(phoneNumber);
final String fingerPrint = await DeviceHelper.getDeviceFingerprint();
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {
'phone_number': fixedPhone,
'token_code': otpCode,
'context': 'login',
'user_type': 'passenger',
'fingerPrint': fingerPrint,
},
);
if (response != 'failure') {
final data = (response);
Log.print('data: ${data}');
if (data['status'] == 'success') {
final isRegistered = data['message']['isRegistered'] ?? false;
Log.print('isRegistered: ${isRegistered}');
if (isRegistered) {
final jwtToken = data['message']?['jwt'] ??
data['message']?['token'] ??
data['message']?['access_token'];
if (jwtToken != null && jwtToken.toString().isNotEmpty) {
box.write(BoxName.jwt, c(jwtToken.toString()));
await storage.write(
key: BoxName.jwt, value: c(jwtToken.toString()));
}
// ✅ المستخدم موجود مسبقاً -> تسجيل دخول مباشر
await _handleSuccessfulLogin(data['message']['passenger']);
} else {
// ✅ مستخدم جديد -> الذهاب لصفحة التسجيل
mySnackbarSuccess(
'Phone verified. Please complete registration.'.tr);
Get.to(() => RegistrationScreen(phoneNumber: phoneNumber));
}
} else {
mySnackbarError(data['message']);
}
} else {
mySnackbarError('Server error. Please try again.'.tr);
}
} catch (e) {
mySnackbarError('An error occurred: $e');
}
}
static Future<void> _addTokens() async {
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
var res = await CRUD()
// this for register and login //
.post(link: "${AppLink.server}/ride/firebase/addToken.php", payload: {
'token': (box.read(BoxName.tokenFCM)),
'passengerID': box.read(BoxName.passengerID).toString(),
"fingerPrint": fingerPrint
});
Log.print('res token: ${res}');
}
static Future<void> registerUser({
required String phoneNumber,
required String firstName,
required String lastName,
String? email,
}) async {
try {
final fixedPhone = CountryLogic.formatCurrentCountryPhone(phoneNumber);
final response = await CRUD().post(
link: _registerUrl,
payload: {
'phone_number': fixedPhone,
'first_name': firstName,
'last_name': lastName,
'email': email ?? '', // Send empty string if null
},
);
final data = (response);
if (data != 'failure') {
// Registration successful, log user in
await _handleSuccessfulLogin(data['message']['data']);
} else {
mySnackbarError(
"User with this phone number or email already exists.".tr);
}
} catch (e) {
Log.print('e: ${e}');
mySnackbarError('An error occurred: $e');
}
}
static Future<void> _handleSuccessfulLogin(
Map<String, dynamic> userData) async {
mySnackbarSuccess('Welcome, ${userData['first_name']}!');
// Save user data to local storage (Hive box) using new keys
box.write(BoxName.passengerID, userData['id']);
box.write(BoxName.name, userData['first_name']);
box.write(BoxName.lastName, userData['last_name']);
box.write(BoxName.email, userData['email']);
box.write(BoxName.phone, userData['phone']);
box.write(BoxName.isVerified, '1');
await _addTokens();
await Get.put(LoginController()).loginUsingCredentials(
box.read(BoxName.passengerID).toString(),
box.read(BoxName.email).toString(),
); // Navigate to home
}
}
@@ -0,0 +1,128 @@
import 'dart:async';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/main.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
import '../../print.dart';
import '../../views/home/map_page_passenger.dart';
import '../firebase/firbase_messge.dart';
import '../firebase/notification_service.dart';
import '../functions/package_info.dart';
class OtpVerificationController extends GetxController {
final String phone;
final String deviceToken;
final String token;
final otpCode = ''.obs;
final isLoading = false.obs;
final isVerifying = false.obs;
var canResend = false.obs;
var countdown = 120.obs;
Timer? _timer;
OtpVerificationController({
required this.phone,
required this.deviceToken,
required this.token,
});
@override
void onInit() {
super.onInit();
sendOtp(); // ترسل تلقائيًا عند فتح الصفحة
startCountdown();
}
void startCountdown() {
canResend.value = false;
countdown.value = 120;
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (countdown.value > 0) {
countdown.value--;
} else {
canResend.value = true;
timer.cancel();
}
});
}
Future<void> sendOtp() async {
if (isLoading.value) return;
isLoading.value = true;
try {
final response = await CRUD().post(
link: '${AppLink.server}/auth/otp/request.php',
payload: {
'receiver': phone,
'context': 'token_change',
'user_type': 'passenger',
// 'device_token': deviceToken,
},
);
if (response != 'failure') {
isLoading.value = true;
// بإمكانك عرض رسالة نجاح هنا
} else {
mySnackbarError('Failed to send OTP'.tr);
}
} catch (e) {
mySnackbarError(e.toString());
} finally {
// isLoading.value = false;
}
}
Future<void> verifyOtp(String ptoken) async {
isVerifying.value = true;
try {
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
final response = await CRUD().post(
link: '${AppLink.server}/auth/otp/verify.php',
payload: {
'phone_number': phone,
'token_code': otpCode.value,
'context': 'token_change',
'user_type': 'passenger',
'token': box.read(BoxName.tokenFCM).toString(),
'fingerPrint': fingerPrint.toString(),
},
);
if (response != 'failure' && response['status'] == 'success') {
// تحديث التوكن والبصمة الجديدة في الباك إند
await CRUD().post(
link: "${AppLink.server}/ride/firebase/addToken.php",
payload: {
'token': box.read(BoxName.tokenFCM).toString(),
'fingerPrint': fingerPrint.toString(),
'passengerID': box.read(BoxName.passengerID).toString(),
});
await NotificationService.sendNotification(
category: 'token change',
target: ptoken.toString(),
title: 'token change'.tr,
body: 'change device'.tr,
isTopic: false, // Important: this is a token
tone: 'cancel',
driverList: [],
);
Get.offAll(() => const MapPagePassenger());
} else {
mySnackbarError('OTP is incorrect or expired');
}
} catch (e) {
mySnackbarError(e.toString());
} finally {
isVerifying.value = false;
}
}
}
@@ -0,0 +1,39 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../functions/encrypt_decrypt.dart';
class TokenController extends GetxController {
bool isloading = false;
Future addToken() async {
String? basicAuthCredentials =
await storage.read(key: BoxName.basicAuthCredentials);
isloading = true;
update();
var res = await http.post(
Uri.parse(AppLink.addTokens),
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode(basicAuthCredentials.toString()))}',
},
body: {
'token': (box.read(BoxName.tokenFCM.toString())),
'passengerID': box.read(BoxName.passengerID).toString()
},
);
isloading = false;
update();
var jsonToken = jsonDecode(res.body);
if (jsonToken['status'] == 'The token has been updated successfully.') {
mySnackbarInfo('token updated'.tr);
}
}
}
@@ -0,0 +1,50 @@
// import 'dart:convert';
// import 'package:googleapis_auth/auth_io.dart';
// class AccessTokenManager {
// static final AccessTokenManager _instance = AccessTokenManager._internal();
// late final String serviceAccountJsonKey;
// AccessToken? _accessToken;
// DateTime? _expiryDate;
// AccessTokenManager._internal();
// factory AccessTokenManager(String jsonKey) {
// if (_instance._isServiceAccountKeyInitialized()) {
// // Prevent re-initialization
// return _instance;
// }
// _instance.serviceAccountJsonKey = jsonKey;
// return _instance;
// }
// bool _isServiceAccountKeyInitialized() {
// try {
// serviceAccountJsonKey; // Access to check if initialized
// return true;
// } catch (e) {
// return false;
// }
// }
// Future<String> getAccessToken() async {
// if (_accessToken != null && DateTime.now().isBefore(_expiryDate!)) {
// return _accessToken!.data;
// }
// try {
// final serviceAccountCredentials = ServiceAccountCredentials.fromJson(
// json.decode(serviceAccountJsonKey));
// final client = await clientViaServiceAccount(
// serviceAccountCredentials,
// ['https://www.googleapis.com/auth/firebase.messaging'],
// );
// _accessToken = client.credentials.accessToken;
// _expiryDate = client.credentials.accessToken.expiry;
// client.close();
// return _accessToken!.data;
// } catch (e) {
// throw Exception('Failed to obtain access token');
// }
// }
// }
@@ -0,0 +1,784 @@
import 'dart:convert';
import 'dart:io';
import 'package:intaleq_rider/views/home/HomePage/trip_monitor/trip_link_monitor.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/controller/functions/toast.dart';
import 'package:intaleq_rider/views/widgets/elevated_btn.dart';
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../../print.dart';
import '../../views/Rate/rate_captain.dart';
import '../../views/home/map_page_passenger.dart';
import '../../views/home/profile/promos_passenger_page.dart';
import 'package:intaleq_rider/controller/voice_call_controller.dart';
import '../home/map/ride_lifecycle_controller.dart';
import '../home/map/ride_state.dart';
import 'local_notification.dart';
import '../../views/widgets/mydialoug.dart';
class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance;
List<String> tokens = [];
List dataTokens = [];
late String driverID;
late String driverToken;
NotificationSettings? notificationSettings;
Future<void> getNotificationSettings() async {
// Get the current notification settings
NotificationSettings? notificationSettings =
await FirebaseMessaging.instance.getNotificationSettings();
'Notification authorization status: ${notificationSettings.authorizationStatus}';
// Call the update function if needed
update();
}
Future<void> requestFirebaseMessagingPermission() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Check if the platform is Android
if (Platform.isAndroid) {
// Request permission for Android
await messaging.requestPermission();
} else if (Platform.isIOS) {
// Request permission for iOS
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: true,
criticalAlert: true,
provisional: false,
sound: true,
);
messaging.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
}
}
NotificationController notificationController =
Get.isRegistered<NotificationController>()
? Get.find<NotificationController>()
: Get.put(NotificationController());
Future getToken() async {
fcmToken.getToken().then((token) {
// Log.print('fcmToken: ${token}');
box.write(BoxName.tokenFCM, (token.toString()));
});
// 🔹 الاشتراك في topic العام
await fcmToken.subscribeToTopic("passengers");
Log.print("Subscribed to 'passengers' topic ✅");
// اشتراك شخصي بـ passenger_{id} لاستقبال إشعارات العضوية في مواصلاتي
final passengerId = box.read(BoxName.passengerID);
if (passengerId != null && passengerId.toString().isNotEmpty) {
await fcmToken.subscribeToTopic('passenger_${passengerId}');
Log.print("Subscribed to 'passenger_${passengerId}' topic ✅");
}
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// If the app is in the background or terminated, show a system tray message
RemoteNotification? notification = message.notification;
AndroidNotification? android = notification?.android;
// if (notification != null && android != null) {
if (message.data.isNotEmpty) {
fireBaseTitles(message);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
}
Future<void> fireBaseTitles(RemoteMessage message) async {
// [!! تعديل !!]
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان
String category = message.data['category'] ?? '';
final mapCtrl = Get.isRegistered<RideLifecycleController>()
? Get.find<RideLifecycleController>()
: null;
// اقرأ العنوان (للعرض)
String title = message.data['title'] ?? message.notification?.title ?? '';
String body = message.data['body'] ?? message.notification?.body ?? '';
if (category == 'ORDER') {
// <-- مثال: كان 'Order'.tr
Log.print('message: ${message}');
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'Order');
}
}
// ... داخل معالج الإشعارات في تطبيق الراكب ...
else if (category == 'Accepted Ride') {
if (Platform.isAndroid) {
String notifTitle = 'Ride Accepted ✅'.tr;
String notifBody = 'The captain is on their way to you now.'.tr;
notificationController.showNotification(notifTitle, notifBody, 'ding');
}
if (mapCtrl != null) {
Map<String, dynamic>? driverInfoMap;
// 2. معالجة driver_info (تأتي كـ String JSON من PHP)
if (message.data['driver_info'] != null) {
try {
String rawJson = message.data['driver_info'];
// 🔥 فك التشفير: تحويل الـ String إلى Map
driverInfoMap = jsonDecode(rawJson);
} catch (e) {
Log.print("❌ Error decoding FCM driver_info: $e");
}
}
// 3. تمرير البيانات الجاهزة للكنترولر
await mapCtrl.processRideAcceptance(
driverData: driverInfoMap,
source: "FCM",
);
}
} else if (category == 'Promo') {
// <-- كان 'Promo'.tr
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'promo');
}
Get.to(const PromosPassengerPage());
} else if (category == 'Trip Monitoring') {
// <-- كان 'Trip Monitoring'.tr
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'iphone_ringtone');
}
var myListString = message.data['passengerList'];
var myList = jsonDecode(myListString) as List<dynamic>;
Get.to(() => TripMonitor(), arguments: {
'rideId': myList[0].toString(),
'driverId': myList[1].toString(),
});
} else if (category == 'token change') {
// <-- كان 'token change'.tr
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'cancel');
}
box.remove(BoxName.jwt);
} else if (category == 'Driver Is Going To Passenger') {
// <-- كان 'Driver Is Going To Passenger'
Get.find<RideLifecycleController>().isDriverInPassengerWay = true;
Get.find<RideLifecycleController>().update();
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone1');
}
} else if (category == 'MSG_FROM_PASSENGER') {
// <-- كان 'message From passenger'
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding');
}
MyDialog().getChatDialog(
title.isNotEmpty ? title : 'message From passenger'.tr,
body,
() {},
);
update();
} else if (category == 'message From Driver') {
// <-- كان 'message From Driver'
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding');
}
MyDialog().getChatDialog(
title.isNotEmpty ? title : 'message From Driver'.tr,
body,
() {},
);
update();
} else if (category == 'Trip is Begin') {
// <-- كان 'Trip is Begin'
if (Platform.isAndroid) {
String notifTitle = 'Trip Begun 🚀'.tr;
String notifBody = 'Have a safe and comfortable trip.'.tr;
notificationController.showNotification(notifTitle, notifBody, 'ding');
}
Log.print('[FCM] استقبل إشعار "TRIP_BEGUN".');
// استدعاء الحارس
mapCtrl!.processRideBegin(source: "FCM");
} else if (category == 'Hi ,I will go now') {
// <-- كان 'Hi ,I will go now'.tr
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding');
}
update();
} else if (category == "Arrive Ride") {
if (Platform.isAndroid) {
String notifTitle = 'Driver is outside 🚖'.tr;
String notifBody = 'The captain has arrived at your location.'.tr;
notificationController.showNotification(notifTitle, notifBody, 'ding');
}
// استدعاء الحارس
mapCtrl!.processDriverArrival("FCM");
} else if (category == 'Driver Finish Trip') {
if (Platform.isAndroid) {
String notifTitle = 'Trip Finished 🏁'.tr;
String notifBody = 'Please pay and rate the driver.'.tr;
notificationController.showNotification(notifTitle, notifBody, 'ding');
}
List<dynamic> driverList = [];
// ✅ معالجة آمنة للبيانات
var rawData = message.data['DriverList'];
if (rawData != null && rawData.isNotEmpty) {
try {
driverList = jsonDecode(rawData) as List<dynamic>;
} catch (e) {
Log.print("❌ Error decoding DriverList: $e");
}
}
if (driverList.isNotEmpty) {
Get.find<RideLifecycleController>()
.processRideFinished(driverList, source: "FCM");
}
} else if (category == 'Finish Monitor') {
// <-- كان "Finish Monitor".tr
Get.defaultDialog(
titleStyle: AppStyle.title,
title: 'Trip finished '.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () {
Get.offAll(() => const MapPagePassenger());
}));
} else if (category == 'Cancel Trip from driver') {
Log.print("🔔 FCM: Ride Cancelled by Driver received.");
// لا داعي لكتابة منطق التنظيف هنا، الكنترولر يتكفل بكل شيء
if (Get.isRegistered<RideLifecycleController>()) {
// استدعاء الحارس (سيتجاهل الأمر إذا كان السوكيت قد سبقه)
Get.find<RideLifecycleController>()
.processRideCancelledByDriver(message.data, source: "FCM");
}
// إشعار محلي (اختياري، لأن الديالوج سيظهر)
if (Platform.isAndroid) {
notificationController.showNotification(
'Trip Cancelled'.tr, 'The driver cancelled the trip.'.tr, 'cancel');
}
}
// ... (باقي الحالات مثل Call Income, Call End, إلخ) ...
// ... بنفس الطريقة ...
else if (category == 'incoming_call') {
final sessionId = message.data['session_id'];
final callerName = message.data['caller_name'];
final rideId = message.data['ride_id'];
if (sessionId != null && callerName != null && rideId != null) {
Get.find<VoiceCallController>().receiveCall(
sessionIdVal: sessionId.toString(),
remoteNameVal: callerName.toString(),
rideIdVal: rideId.toString(),
);
}
} else if (category == 'Order Applied') {
if (Platform.isAndroid) {
notificationController.showNotification(
'The order Accepted by another Driver'.tr,
'We regret to inform you that another driver has accepted this order.'
.tr,
'order');
}
}
}
// Future<void> fireBaseTitles(RemoteMessage message) async {
// if (message.notification!.title! == 'Order'.tr) {
// Log.print('message: ${message}');
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Order'.tr, message.notification!.body!, 'Order');
// }
// } else // ... داخل معالج الإشعارات في تطبيق الراكب ...
// if (message.notification!.title! == 'Accepted Ride') {
// // ...
// // انظر هنا: قمنا بتغيير "passengerList" إلى "driverList"
// var driverListJson = message.data['driverList'];
// // تأكد من أن البيانات ليست null قبل المتابعة
// if (driverListJson != null) {
// var myList = jsonDecode(driverListJson) as List<dynamic>;
// Log.print('myList: ${myList}');
// final controller = Get.find<RideLifecycleController>();
// // استدعاء الدالة الموحدة الجديدة التي أنشأناها
// await controller.processRideAcceptance(
// driverIdFromFCM: myList[0].toString(),
// rideIdFromFCM: myList[3].toString());
// } else {
// Log.print(
// '❌ خطأ فادح: إشعار "Accepted Ride" وصل بدون بيانات (driverList is null)');
// }
// } else if (message.notification!.title! == 'Promo'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Promo', 'Show latest promo'.tr, 'promo');
// }
// Get.to(const PromosPassengerPage());
// } else if (message.notification!.title! == 'Trip Monitoring'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Trip Monitoring'.tr, '', 'iphone_ringtone');
// }
// var myListString = message.data['DriverList'];
// var myList = jsonDecode(myListString) as List<dynamic>;
// Get.toNamed('/tripmonitor', arguments: {
// 'rideId': myList[0].toString(),
// 'driverId': myList[1].toString(),
// });
// } else if (message.notification!.title! == 'token change'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'token change'.tr, 'token change'.tr, 'cancel');
// }
// // GoogleSignInHelper.signOut();
// } else if (message.notification!.title! == 'Driver Is Going To Passenger') {
// Get.find<RideLifecycleController>().isDriverInPassengerWay = true;
// Get.find<RideLifecycleController>().update();
// if (Platform.isAndroid) {
// notificationController.showNotification('Driver is Going To You'.tr,
// 'Please stay on the picked point.'.tr, 'tone1');
// }
// // Get.snackbar('Driver is Going To Passenger', '',
// // backgroundColor: AppColor.greenColor);
// } else if (message.notification!.title! == 'message From passenger') {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'message From passenger'.tr, ''.tr, 'ding');
// }
// passengerDialog(message.notification!.body!);
// update();
// } else if (message.notification!.title! == 'message From Driver') {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'message From Driver'.tr, ''.tr, 'ding');
// }
// passengerDialog(message.notification!.body!);
// update();
// } else // (هذا الكود في معالج الإشعارات لديك)
// if (message.notification!.title! == 'Trip is Begin') {
// Log.print('[FCM] استقبل إشعار "Trip is Begin".');
// // (تم حذف الإشعار المحلي من هنا، نُقل إلى الدالة الموحدة)
// final controller = Get.find<RideLifecycleController>();
// // استدعاء حارس البوابة الجديد والآمن
// controller.processRideBegin();
// // (تم حذف كل الأوامر التالية من هنا)
// // Get.find<RideLifecycleController>().getBeginRideFromDriver();
// // box.write(BoxName.passengerWalletTotal, '0');
// // update();
// } else if (message.notification!.title! == 'Hi ,I will go now'.tr) {
// // Get.snackbar('Hi ,I will go now', '',
// // backgroundColor: AppColor.greenColor);
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'ding');
// }
// update();
// } // ... داخل معالج الإشعارات (FCM Handler) ...
// if (message.notification!.title! == 'Hi ,I Arrive your site'.tr) {
// final controller = Get.find<RideLifecycleController>();
// // 1. التأكد أننا في الحالة الصحيحة (السائق كان في الطريق)
// if (controller.currentRideState.value == RideState.driverApplied) {
// Log.print('[FCM] السائق وصل. تغيير الحالة إلى driverArrived');
// // 2. تغيير الحالة فقط!
// controller.currentRideState.value = RideState.driverArrived;
// }
// } else if (message.notification!.title! == "Cancel Trip from driver") {
// Get.back();
// if (Platform.isAndroid) {
// notificationController.showNotification("Cancel Trip from driver".tr,
// "We will look for a new driver.\nPlease wait.".tr, '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<RideLifecycleController>()
// .reSearchAfterCanceledFromDriver();
// },
// ),
// cancel: MyElevatedButton(
// title: 'Cancel'.tr,
// kolor: AppColor.redColor,
// onPressed: () {
// Get.offAll(() => const MapPagePassenger());
// },
// )
// // Get.find<RideLifecycleController>()
// // .searchNewDriverAfterRejectingFromDriver();
// );
// } else if (message.notification!.title! == 'Driver Finish Trip'.tr) {
// // الخطوة 1: استقبل البيانات وتحقق من وجودها
// final rawData = message.data['DriverList'];
// List<dynamic> driverList = []; // ابدأ بقائمة فارغة كإجراء وقائي
// // الخطوة 2: قم بفك تشفير البيانات بأمان
// if (rawData != null && rawData is String) {
// try {
// driverList = jsonDecode(rawData);
// Log.print('Successfully decoded DriverList: $driverList');
// } catch (e) {
// Log.print('Error decoding DriverList JSON: $e');
// // اترك القائمة فارغة في حالة حدوث خطأ
// }
// } else {
// Log.print('Error: DriverList data is null or not a String.');
// }
// // الخطوة 3: استخدم البيانات فقط إذا كانت القائمة تحتوي على العناصر المطلوبة
// // هذا يمنع خطأ "RangeError" إذا كانت القائمة أقصر من المتوقع
// if (driverList.length >= 4) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// "Driver Finish Trip".tr,
// '${'you will pay to Driver'.tr} ${driverList[3].toString()} \$', // تم تحسين طريقة عرض النص
// 'tone1');
// }
// Get.find<AudioRecorderController>().stopRecording();
// if ((double.tryParse(
// box.read(BoxName.passengerWalletTotal).toString()) ??
// 0) <
// 0) {
// box.write(BoxName.passengerWalletTotal, 0);
// }
// Get.find<RideLifecycleController>().tripFinishedFromDriver();
// NotificationController().showNotification(
// 'Don’t forget your personal belongings.'.tr,
// 'Please make sure you have all your personal belongings and that any remaining fare, if applicable, has been added to your wallet before leaving. Thank you for choosing the Siro app'
// .tr,
// 'ding');
// Get.to(() => RateDriverFromPassenger(), arguments: {
// 'driverId': driverList[0].toString(),
// 'rideId': driverList[1].toString(),
// 'price': driverList[3].toString()
// });
// } else {
// Log.print(
// 'Error: Decoded driverList does not have enough elements. Received: $driverList');
// // هنا يمكنك عرض رسالة خطأ للمستخدم إذا لزم الأمر
// }
// } else if (message.notification!.title! == "Finish Monitor".tr) {
// Get.defaultDialog(
// titleStyle: AppStyle.title,
// title: 'Trip finished '.tr,
// middleText: '',
// confirm: MyElevatedButton(
// title: 'Ok'.tr,
// onPressed: () {
// Get.offAll(() => const MapPagePassenger());
// }));
// }
// // else if (message.notification!.title! == "Trip Monitoring".tr) {
// // Get.to(() => const TripMonitor());
// // }
// else if (message.notification!.title! == 'Call Income') {
// try {
// var myListString = message.data['DriverList'];
// var driverList = jsonDecode(myListString) as List<dynamic>;
// // if (Platform.isAndroid) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Call Income'.tr,
// message.notification!.body!,
// 'iphone_ringtone',
// );
// }
// // }
// // Assuming GetMaterialApp is initialized and context is valid for navigation
// // Get.to(() => PassengerCallPage(
// // channelName: driverList[1].toString(),
// // token: driverList[0].toString(),
// // remoteID: driverList[2].toString(),
// // ));
// } catch (e) { Log.print("Error occurred: $e"); }
// } else if (message.notification!.title! == 'Call Income from Driver'.tr) {
// try {
// var myListString = message.data['DriverList'];
// var driverList = jsonDecode(myListString) as List<dynamic>;
// // if (Platform.isAndroid) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Call Income'.tr,
// message.notification!.body!,
// 'iphone_ringtone',
// );
// }
// // Assuming GetMaterialApp is initialized and context is valid for navigation
// // Get.to(() => PassengerCallPage(
// // channelName: driverList[1].toString(),
// // token: driverList[0].toString(),
// // remoteID: driverList[2].toString(),
// // ));
// } catch (e) { Log.print("Error occurred: $e"); }
// } else if (message.notification!.title! == 'Call End'.tr) {
// try {
// var myListString = message.data['DriverList'];
// var driverList = jsonDecode(myListString) as List<dynamic>;
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Call End'.tr,
// message.notification!.body!,
// 'ding',
// );
// }
// // Assuming GetMaterialApp is initialized and context is valid for navigation
// // Get.off(const CallPage());
// } catch (e) { Log.print("Error occurred: $e"); }
// } else if (message.notification!.title! == 'Driver Cancelled Your Trip') {
// // Get.snackbar(
// // 'You will be pay the cost to driver or we will get it from you on next trip'
// // .tr,
// // 'message',
// // backgroundColor: AppColor.redColor);
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'Driver Cancelled Your Trip'.tr,
// 'you will pay to Driver you will be pay the cost of driver time look to your Siro Wallet'
// .tr,
// 'cancel');
// }
// box.write(BoxName.parentTripSelected, false);
// box.remove(BoxName.tokenParent);
// Get.find<RideLifecycleController>().restCounter();
// Get.offAll(() => const MapPagePassenger());
// }
// // else if (message.notification!.title! == 'Order Applied') {
// // Get.snackbar(
// // "The order has been accepted by another driver."
// // .tr, // Corrected grammar
// // "Be more mindful next time to avoid dropping orders."
// // .tr, // Improved sentence structure
// // backgroundColor: AppColor.yellowColor,
// // snackPosition: SnackPosition.BOTTOM,
// // );
// // }
// else if (message.notification!.title! == 'Order Applied'.tr) {
// if (Platform.isAndroid) {
// notificationController.showNotification(
// 'The order Accepted by another Driver'.tr,
// 'We regret to inform you that another driver has accepted this order.'
// .tr,
// 'order');
// }
// }
// }
SnackbarController? driverAppliedTripSnakBar() {
mySnackbarInfo('Driver Applied the Ride for You'.tr);
return null;
}
Future<dynamic> passengerDialog(String message) {
return Get.defaultDialog(
barrierDismissible: false,
title: message.tr,
titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title,
middleText: message.tr,
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () {
// Get.find<FirebaseMessagesController>().sendNotificationToPassengerToken(
// 'Hi ,I will go now'.tr,
// 'I will go now'.tr,
// Get.find<RideLifecycleController>().driverToken, []);
// Get.find<RideLifecycleController>()
// .startTimerDriverWaitPassenger5Minute();
Get.back();
}));
}
Future<dynamic> driverFinishTripDialoge(List<dynamic> driverList) {
return Get.defaultDialog(
title: 'Driver Finish Trip'.tr,
content: const DriverTipWidget(),
confirm: MyElevatedButton(
title: 'Yes'.tr,
onPressed: () async {
Get.to(() => RateDriverFromPassenger(), arguments: {
'driverId': driverList[0].toString(),
'rideId': driverList[1].toString(),
'price': driverList[3].toString()
});
},
kolor: AppColor.greenColor,
),
cancel: MyElevatedButton(
title: 'No,I want'.tr,
onPressed: () {
Get.to(() => RateDriverFromPassenger(), arguments: {
'driverId': driverList[0].toString(),
'rideId': driverList[1].toString(),
'price': driverList[3].toString()
});
},
kolor: AppColor.redColor,
));
}
}
class DriverTipWidget extends StatelessWidget {
const DriverTipWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return GetBuilder<RideLifecycleController>(builder: (controller) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Text(
// '${'Your fee is '.tr}${Get.find<RideLifecycleController>().totalPassenger.toStringAsFixed(2)}'),
Text(
'Do you want to pay Tips for this Driver'.tr,
textAlign: TextAlign.center,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
box.write(BoxName.tipPercentage, '0.05');
Toast.show(
context,
'${'Tip is '.tr}${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
child: Container(
decoration: BoxDecoration(border: Border.all()),
child: const Padding(
padding: EdgeInsets.all(4),
child: Center(
child: Text('5%'),
),
),
),
),
InkWell(
onTap: () {
box.write(BoxName.tipPercentage, '0.10');
Toast.show(
context,
'${'Tip is'.tr} ${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
child: Container(
decoration: BoxDecoration(border: Border.all()),
child: const Center(
child: Padding(
padding: EdgeInsets.all(5),
child: Text('10%'),
),
),
),
),
InkWell(
onTap: () {
box.write(BoxName.tipPercentage, '0.15');
Toast.show(
context,
'${'Tip is'.tr} ${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
child: Container(
decoration: BoxDecoration(border: Border.all()),
child: const Center(
child: Padding(
padding: EdgeInsets.all(5),
child: Text('15%'),
),
),
),
),
InkWell(
onTap: () {
box.write(BoxName.tipPercentage, '0.20');
Toast.show(
context,
'${'Tip is'.tr} ${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
child: Container(
decoration: BoxDecoration(border: Border.all()),
child: const Center(
child: Padding(
padding: EdgeInsets.all(5),
child: Text('20%'),
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
kolor: AppColor.redColor,
title: 'No i want'.tr,
onPressed: () {
box.write(BoxName.tipPercentage, '0');
controller.update();
}),
Container(
decoration: AppStyle.boxDecoration1,
child: Padding(
padding: const EdgeInsets.all(6),
child: Text(
'${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))} ${box.read(BoxName.countryCode) == 'Egypt' ? 'LE'.tr : 'JOD'.tr}',
style: AppStyle.title,
),
),
),
],
)
],
);
});
}
}
@@ -0,0 +1,60 @@
import 'package:intaleq_rider/print.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class LiveActivityScreen extends StatefulWidget {
@override
_LiveActivityScreenState createState() => _LiveActivityScreenState();
}
class _LiveActivityScreenState extends State<LiveActivityScreen> {
static const platform = MethodChannel('live_activity_channel');
Future<void> _startLiveActivity() async {
try {
await platform.invokeMethod('startLiveActivity');
} on PlatformException catch (e) {
Log.print("Failed to start Live Activity: '${e.message}'.");
}
}
Future<void> _updateLiveActivity(double progress) async {
try {
await platform.invokeMethod('updateLiveActivity', {"progress": progress});
} on PlatformException catch (e) {
Log.print("Failed to update Live Activity: '${e.message}'.");
}
}
Future<void> _endLiveActivity() async {
try {
await platform.invokeMethod('endLiveActivity');
} on PlatformException catch (e) {
Log.print("Failed to end Live Activity: '${e.message}'.");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Live Activity Test")),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _startLiveActivity,
child: Text("Start Live Activity"),
),
ElevatedButton(
onPressed: () => _updateLiveActivity(0.5),
child: Text("Update Progress to 50%"),
),
ElevatedButton(
onPressed: _endLiveActivity,
child: Text("End Live Activity"),
),
],
),
);
}
}
@@ -0,0 +1,357 @@
import 'package:intaleq_rider/print.dart';
import 'dart:async';
import 'dart:io';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import '../../main.dart';
class NotificationController extends GetxController {
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
FlutterLocalNotificationsPlugin get plugin =>
_flutterLocalNotificationsPlugin;
@override
void onInit() {
super.onInit();
initNotifications();
}
// Initializes the local notifications plugin
Future<void> initNotifications() async {
const AndroidInitializationSettings android =
AndroidInitializationSettings('@mipmap/launcher_icon');
DarwinInitializationSettings ios = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
// onDidReceiveLocalNotification:
// (int id, String? title, String? body, String? payload) async {},
);
InitializationSettings initializationSettings =
InitializationSettings(android: android, iOS: ios);
await _flutterLocalNotificationsPlugin.initialize(
settings: initializationSettings);
tz.initializeTimeZones();
Log.print('Notifications initialized');
}
// Displays a notification with the given title and message
void showNotification(String title, String message, String tone) async {
final AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
showWhen: false,
sound: RawResourceAndroidNotificationSound(tone),
);
const DarwinNotificationDetails ios = DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
await _flutterLocalNotificationsPlugin.show(
id: 0, title: title, body: message, notificationDetails: details);
Log.print('Notification shown: $title - $message');
}
// 🆕 Static method for background isolates where GetX might not be initialized
static Future<void> showBackgroundNotification(String title, String message, String tone) async {
final FlutterLocalNotificationsPlugin plugin = FlutterLocalNotificationsPlugin();
const AndroidInitializationSettings android = AndroidInitializationSettings('@mipmap/launcher_icon');
const DarwinInitializationSettings ios = DarwinInitializationSettings();
await plugin.initialize(settings: const InitializationSettings(android: android, iOS: ios));
final AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
showWhen: false,
sound: RawResourceAndroidNotificationSound(tone),
);
const DarwinNotificationDetails iosDetails = DarwinNotificationDetails(sound: 'default');
await plugin.show(id: 0, title: title, body: message, notificationDetails: NotificationDetails(android: androidDetails, iOS: iosDetails));
Log.print('Background notification shown: $title - $message');
}
// /Users/hamzaaleghwairyeen/development/App/ride 2/lib/controller/firebase/local_notification.dart
// Assume _flutterLocalNotificationsPlugin is initialized somewhere in your code
// void scheduleNotificationsForSevenDays(
// String title, String message, String tone) async {
// final AndroidNotificationDetails android = AndroidNotificationDetails(
// 'high_importance_channel',
// 'High Importance Notifications',
// importance: Importance.max,
// priority: Priority.high,
// sound: RawResourceAndroidNotificationSound(tone),
// );
// const DarwinNotificationDetails ios = DarwinNotificationDetails(
// sound: 'default',
// presentAlert: true,
// presentBadge: true,
// presentSound: true,
// );
// final NotificationDetails details =
// NotificationDetails(android: android, iOS: ios);
// // Check for the exact alarm permission on Android 12 and above
// if (Platform.isAndroid) {
// if (await Permission.scheduleExactAlarm.isDenied) {
// if (await Permission.scheduleExactAlarm.request().isGranted) {
// Log.print('SCHEDULE_EXACT_ALARM permission granted');
// } else {
// Log.print('SCHEDULE_EXACT_ALARM permission denied');
// return;
// }
// }
// }
// // Schedule notifications for the next 7 days
// for (int day = 0; day < 7; day++) {
// // Schedule for 8:00 AM
// await _scheduleNotificationForTime(
// day, 8, 0, title, message, details, day * 1000 + 1);
// // Schedule for 3:00 PM
// await _scheduleNotificationForTime(
// day, 15, 0, title, message, details, day * 1000 + 2); // Unique ID
// // Schedule for 8:00 PM
// await _scheduleNotificationForTime(
// day, 20, 0, title, message, details, day * 1000 + 3); // Unique ID
// }
// Log.print('Notifications scheduled successfully for the next 7 days');
// }
void scheduleNotificationsForSevenDays(
String title, String message, String tone) async {
final AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound(tone),
);
const DarwinNotificationDetails ios = DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
// Check for the exact alarm permission on Android 12 and above
if (Platform.isAndroid) {
if (await Permission.scheduleExactAlarm.isDenied) {
if (await Permission.scheduleExactAlarm.request().isGranted) {
Log.print('SCHEDULE_EXACT_ALARM permission granted');
} else {
Log.print('SCHEDULE_EXACT_ALARM permission denied');
return;
}
}
}
// Schedule notifications for the next 7 days
for (int day = 0; day < 7; day++) {
// List of notification times
final notificationTimes = [
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1}, // 8:00 AM
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2}, // 3:00 PM
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3}, // 8:00 PM
];
for (var time in notificationTimes) {
final notificationId = time['id'] as int;
// Check if this notification ID is already stored
bool isScheduled = box.read('notification_$notificationId') ?? false;
if (!isScheduled) {
// Schedule the notification if not already scheduled
await _scheduleNotificationForTime(
day,
time['hour'] as int,
time['minute'] as int,
title,
message,
details,
notificationId,
);
// Mark this notification ID as scheduled in GetStorage
box.write('notification_$notificationId', true);
} else {
Log.print('Notification with ID $notificationId is already scheduled.');
}
}
}
Log.print('Notifications scheduled successfully for the next 7 days');
}
void scheduleNotificationsForTimeSelected(
String title, String message, String tone, DateTime timeSelected) async {
final AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound(tone),
);
const DarwinNotificationDetails ios = DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
// Check for the exact alarm permission on Android 12 and above
if (Platform.isAndroid) {
if (await Permission.scheduleExactAlarm.isDenied) {
if (await Permission.scheduleExactAlarm.request().isGranted) {
Log.print('SCHEDULE_EXACT_ALARM permission granted');
} else {
Log.print('SCHEDULE_EXACT_ALARM permission denied');
return;
}
}
}
// Schedule notifications for 10 and 30 minutes before the timeSelected
await _scheduleNotificationForTimeVIP(
timeSelected.subtract(const Duration(minutes: 10)), // 10 minutes before
title,
message,
details,
1, // Unique ID for 10-minute before notification
);
await _scheduleNotificationForTimeVIP(
timeSelected.subtract(const Duration(minutes: 30)), // 30 minutes before
title,
message,
details,
2, // Unique ID for 30-minute before notification
);
Log.print('Notifications scheduled successfully for the time selected');
}
Future<void> _scheduleNotificationForTimeVIP(
DateTime scheduledDate,
String title,
String message,
NotificationDetails details,
int notificationId,
) async {
// Initialize and set Cairo timezone
tz.initializeTimeZones();
var cairoLocation = tz.getLocation('Africa/Cairo');
final now = tz.TZDateTime.now(cairoLocation);
// Convert to Cairo time
tz.TZDateTime scheduledTZDateTime =
tz.TZDateTime.from(scheduledDate, cairoLocation);
// Check if 10 minutes before the scheduled time is in the past
if (scheduledTZDateTime
.subtract(const Duration(minutes: 10))
.isBefore(now)) {
// If the 10 minutes before the scheduled time is in the past, don't schedule
Log.print(
'Scheduled time minus 10 minutes is in the past. Skipping notification.');
return; // Skip this notification
}
Log.print('Current time (Cairo): $now');
Log.print('Scheduling notification for: $scheduledTZDateTime');
await _flutterLocalNotificationsPlugin.zonedSchedule(
id: notificationId, // Unique ID for each notification
title: title,
body: message,
scheduledDate: scheduledTZDateTime,
notificationDetails: details,
androidScheduleMode: AndroidScheduleMode.exact,
// uiLocalNotificationDateInterpretation:
// UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents:
null, // Don't repeat automatically; we handle manually
);
Log.print('Notification scheduled successfully for: $scheduledTZDateTime');
}
Future<void> _scheduleNotificationForTime(
int dayOffset,
int hour,
int minute,
String title,
String message,
NotificationDetails details,
int notificationId,
) async {
// Initialize and set Cairo timezone
tz.initializeTimeZones();
var cairoLocation = tz.getLocation('Africa/Cairo');
final now = tz.TZDateTime.now(cairoLocation);
tz.TZDateTime scheduledDate = tz.TZDateTime(
cairoLocation,
now.year,
now.month,
now.day + dayOffset, // Add offset to schedule for the next days
hour,
minute,
);
// If the scheduled time is in the past, move it to the next day
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
Log.print('Current time (Cairo): $now');
Log.print('Scheduling notification for: $scheduledDate');
await _flutterLocalNotificationsPlugin.zonedSchedule(
id: notificationId, // Unique ID for each notification
title: title,
body: message,
scheduledDate: scheduledDate,
notificationDetails: details,
androidScheduleMode: AndroidScheduleMode.exact,
// uiLocalNotificationDateInterpretation:
// UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents:
null, // Don't repeat automatically; we handle 7 days manually
);
Log.print('Notification scheduled successfully for: $scheduledDate');
}
}
@@ -0,0 +1,66 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../print.dart'; // للترجمة .tr
class NotificationService {
static String get _serverUrl => '${AppLink.server}/ride/firebase/send_fcm.php';
static Future<void> sendNotification({
required String target,
required String title,
required String body,
required String category, // إلزامي للتصنيف
String? tone,
List<String>? driverList,
bool isTopic = false,
}) async {
try {
// 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,
'title': title,
'body': body,
'isTopic': isTopic,
'data':
customData, // 🔥🔥 التغيير الجوهري: وضعنا البيانات داخل "data" 🔥🔥
};
if (tone != null) {
requestPayload['tone'] = tone;
}
final response = await http.post(
Uri.parse(_serverUrl),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(requestPayload),
);
if (response.statusCode == 200) {
Log.print('✅ Notification sent successfully.');
// Log.print('Response: ${response.body}');
} else {
Log.print(
'❌ Failed to send notification. Code: ${response.statusCode}');
Log.print('Error Body: ${response.body}');
}
} catch (e) {
Log.print('❌ Error sending notification: $e');
}
}
}
@@ -0,0 +1,19 @@
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import 'crud.dart';
addError(String error, where) async {
CRUD().post(link: AppLink.addError, payload: {
'error': error.toString(), // Example error description
'userId': box.read(BoxName.driverID) ??
box.read(BoxName.passengerID), // Example user ID
'userType': box.read(BoxName.driverID) != null
? 'Driver'
: 'passenger', // Example user type
'phone': box.read(BoxName.phone) ??
box.read(BoxName.phoneDriver), // Example phone number
'device': where
});
}
@@ -0,0 +1,169 @@
import 'package:intaleq_rider/print.dart';
import 'dart:io';
// import 'package:flutter_sound/flutter_sound.dart';
import 'package:path_provider/path_provider.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import 'package:record/record.dart';
class AudioRecorderController extends GetxController {
AudioPlayer audioPlayer = AudioPlayer();
AudioRecorder recorder = AudioRecorder();
bool isRecording = false;
bool isPlaying = false;
bool isPaused = false;
String filePath = '';
String? selectedFilePath;
double currentPosition = 0;
double totalDuration = 0;
Future<void> playSoundFromAssets(String sound) async {
try {
await audioPlayer.setAsset(sound);
audioPlayer.play();
} catch (e) {
Log.print("Error playing sound: $e");
}
}
// Start recording
Future<void> startRecording({String? rideId}) async {
final bool isPermissionGranted = await recorder.hasPermission();
if (!isPermissionGranted) {
// RecordingPermissionException('l');
return;
}
final directory = await getApplicationDocumentsDirectory();
final String dateStr =
'${DateTime.now().year}-${DateTime.now().month.toString().padLeft(2, '0')}-${DateTime.now().day.toString().padLeft(2, '0')}';
// Generate a unique file name using the current timestamp
String fileName = (rideId != null && rideId.isNotEmpty && rideId != 'yet' && rideId != 'null')
? '${dateStr}_$rideId.m4a'
: '$dateStr.m4a';
filePath = '${directory.path}/$fileName';
// Define the configuration for the recording
const config = RecordConfig(
// Specify the format, encoder, sample rate, etc., as needed
encoder: AudioEncoder.aacLc, // For example, using AAC codec
sampleRate: 44100, // Sample rate
bitRate: 128000, // Bit rate
);
// Start recording to file with the specified configuration
await recorder.start(config, path: filePath);
isRecording = true;
update();
}
// Pause recording
Future<void> pauseRecording() async {
if (isRecording && !isPaused) {
await recorder.pause();
isPaused = true;
update();
}
}
// Resume recording
Future<void> resumeRecording() async {
if (isRecording && isPaused) {
await recorder.resume();
isPaused = false;
update();
}
}
// Stop recording
Future<void> stopRecording() async {
recorder.stop();
isRecording = false;
isPaused = false;
update();
}
// Play the selected recorded file
Future<void> playRecordedFile(String filePath) async {
await audioPlayer.setFilePath(filePath);
totalDuration = audioPlayer.duration?.inSeconds.toDouble() ?? 0;
audioPlayer.play();
isPlaying = true;
isPaused = false;
audioPlayer.positionStream.listen((position) {
currentPosition = position.inSeconds.toDouble();
update();
});
selectedFilePath = filePath;
update();
}
// Pause playback
Future<void> pausePlayback() async {
if (isPlaying && !isPaused) {
await audioPlayer.pause();
isPaused = true;
update();
}
}
// Resume playback
Future<void> resumePlayback() async {
if (isPlaying && isPaused) {
await audioPlayer.play();
isPaused = false;
update();
}
}
// Stop playback
Future<void> stopPlayback() async {
await audioPlayer.stop();
isPlaying = false;
isPaused = false;
currentPosition = 0;
update();
}
// Get a list of recorded files
Future<List<String>> getRecordedFiles() async {
final directory = await getApplicationDocumentsDirectory();
final files = await directory.list().toList();
return files
.map((file) => file.path)
.where((path) => path.endsWith('.m4a'))
.toList();
}
// Delete a specific recorded file
Future<void> deleteRecordedFile(String filePath) async {
final file = File(filePath);
if (await file.exists()) {
await file.delete();
update();
}
}
// Delete all recorded files
Future<void> deleteAllRecordedFiles() async {
final directory = await getApplicationDocumentsDirectory();
final files = await directory.list().toList();
for (final file in files) {
if (file.path.endsWith('.m4a')) {
await deleteRecordedFile(file.path);
}
}
}
@override
void onClose() {
audioPlayer.dispose();
recorder.dispose();
super.onClose();
}
}
@@ -0,0 +1,134 @@
// import 'package:SEFER/constant/api_key.dart';
// import 'package:SEFER/controller/functions/crud.dart';
// import 'package:SEFER/controller/home/map_passenger_controller.dart';
// import 'package:agora_rtc_engine/agora_rtc_engine.dart';
// import 'package:get/get.dart';
// import 'package:permission_handler/permission_handler.dart';
// import '../../constant/box_name.dart';
// import '../firebase/firbase_messge.dart';
// import '../../main.dart';
// class CallController extends GetxController {
// String channelName = ''; // Get.find<MapDriverController>().rideId;
// String token = '';
// // int uid = int.parse(box.read(BoxName.phoneDriver)); // uid of the local user
// int uid = 0;
// int? remoteUid; // uid of the remote user
// bool _isJoined = false; // Indicates if the local user has joined the channel
// String status = '';
// late RtcEngine agoraEngine; // Agora engine instance
// @override
// void onInit() {
// super.onInit();
// // if (box.read(BoxName.passengerID) != null) {
// channelName = Get.find<MapPassengerController>().rideId; // 'sefer300'; //
// remoteUid = int.parse(Get.find<MapPassengerController>().driverPhone);
// uid = int.parse(box.read(BoxName.phone));
// // } else {
// // channelName = Get.find<MapDriverController>().rideId; // 'sefer300'; //
// // remoteUid = int.parse(Get.find<MapDriverController>().passengerPhone);
// // uid = int.parse(box.read(BoxName.phoneDriver));
// // }
// initAgoraFull();
// }
// initAgoraFull() async {
// await fetchToken();
// // Set up an instance of Agora engine
// setupVoiceSDKEngine();
// // join();
// FirebaseMessagesController().sendNotificationToPassengerToken(
// 'Call Income from Passenger',
// '${'You have call from Passenger'.tr} ${box.read(BoxName.name)}',
// Get.find<MapPassengerController>().driverToken,
// [
// token,
// channelName,
// uid.toString(),
// remoteUid.toString(),
// ],
// 'iphone_ringtone.wav',
// );
// join();
// }
// @override
// void onClose() {
// agoraEngine.leaveChannel();
// super.onClose();
// }
// Future<void> setupVoiceSDKEngine() async {
// // retrieve or request microphone permission
// await [Permission.microphone].request();
// //create an instance of the Agora engine
// agoraEngine = createAgoraRtcEngine();
// await agoraEngine.initialize(RtcEngineContext(appId: AK.agoraAppId));
// // Register the event handler
// agoraEngine.registerEventHandler(
// RtcEngineEventHandler(
// onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
// // Get.snackbar(
// // "Local user uid:${connection.localUid} joined the channel", '');
// status = 'joined'.tr;
// _isJoined = true;
// update();
// },
// onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
// // Get.snackbar("Remote user uid:$remoteUid joined the channel", '');
// status = Get.find<MapPassengerController>().driverName.toString();
// ' joined'.tr;
// remoteUid = remoteUid;
// update();
// },
// onUserOffline: (RtcConnection connection, int? remoteUid,
// UserOfflineReasonType reason) {
// // Get.snackbar("Remote user uid:$remoteUid left the channel", '');
// status = 'Call left'.tr;
// remoteUid = null;
// update();
// },
// ),
// );
// }
// void join() async {
// // Set channel options including the client role and channel profile
// ChannelMediaOptions options = const ChannelMediaOptions(
// clientRoleType: ClientRoleType.clientRoleBroadcaster,
// channelProfile: ChannelProfileType.channelProfileCommunication,
// );
// await agoraEngine.joinChannel(
// token: token,
// channelId: channelName,
// options: options,
// uid: uid,
// );
// }
// void leave() {
// _isJoined = false;
// remoteUid = null;
// update();
// agoraEngine.leaveChannel();
// }
// // Clean up the resources when you leave
// @override
// void dispose() async {
// await agoraEngine.leaveChannel();
// super.dispose();
// }
// fetchToken() async {
// var res = await CRUD()
// .getAgoraToken(channelName: channelName, uid: uid.toString());
// token = res;
// update();
// }
// }
@@ -0,0 +1,204 @@
import 'package:geolocator/geolocator.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/country_polygons.dart';
import 'package:intaleq_rider/main.dart';
import '../../print.dart';
class CountryLogic {
/// Formats the phone number based on the country's dialing rules.
static String formatPhone(String phone, String country) {
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
if (country == 'Egypt') {
// Rule: 010, 011, 012, 015 -> 2010, 2011, 2012, 2015
if (phone.startsWith('0020')) phone = phone.replaceFirst('0020', '20');
if (phone.startsWith('01') && phone.length >= 10) {
return '20${phone.substring(1)}';
}
if (phone.startsWith('1') &&
phone.length >= 9 &&
!phone.startsWith('20')) {
return '20$phone';
}
if (!phone.startsWith('20') && phone.length > 8) return '20$phone';
} else if (country == 'Jordan') {
// Rule: 07x -> 9627x
if (phone.startsWith('00962')) phone = phone.replaceFirst('00962', '962');
if (phone.startsWith('07') && phone.length >= 9) {
return '962${phone.substring(1)}';
}
if (phone.startsWith('7') &&
phone.length >= 8 &&
!phone.startsWith('962')) {
return '962$phone';
}
if (!phone.startsWith('962') && phone.length > 7) return '962$phone';
} else {
// Default to Syria
if (phone.startsWith('00963')) phone = phone.replaceFirst('00963', '963');
if (phone.startsWith('0963')) phone = phone.replaceFirst('0963', '963');
if (phone.startsWith('096309')) {
phone = phone.replaceFirst('096309', '963');
}
if (phone.startsWith('96309')) phone = '9639${phone.substring(5)}';
if (phone.startsWith('9630')) phone = '9639${phone.substring(4)}';
if (phone.startsWith('9639') && phone.length == 12) return phone;
if (phone.startsWith('963') &&
phone.length > 3 &&
!phone.startsWith('9639')) {
phone = '9639${phone.substring(3)}';
}
if (phone.startsWith('09')) return '963${phone.substring(1)}';
if (phone.startsWith('9') && phone.length == 9) return '963$phone';
if (phone.startsWith('0') && phone.length == 10) {
return '963${phone.substring(1)}';
}
}
return phone;
}
/// Returns the default country prefix (EG, JO, SY) for UI initial selection.
static String getCountryPrefix(String country) {
if (country == 'Egypt') return 'EG';
if (country == 'Jordan') return 'JO';
return 'SY';
}
/// Returns the default emergency number for the country.
static String getEmergencyNumber(String country) {
if (country == 'Egypt') return '122';
if (country == 'Jordan') return '911';
return '112'; // Syria
}
/// Returns the hint text for phone inputs based on the country.
static String getPhoneHint(String country) {
if (country == 'Egypt') return 'e.g. 01012345678 (Default +20)';
if (country == 'Jordan') return 'e.g. 0791234567 (Default +962)';
return 'e.g. 0912345678 (Default +963)'; // Syria
}
/// Helper to format phone using the current country in box.
static String formatCurrentCountryPhone(String phone) {
String cleanPhone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
// 1. Explicit International Code Detection
if (cleanPhone.startsWith('00963')) {
cleanPhone = cleanPhone.replaceFirst('00963', '963');
}
if (cleanPhone.startsWith('00962')) {
cleanPhone = cleanPhone.replaceFirst('00962', '962');
}
if (cleanPhone.startsWith('0020')) {
cleanPhone = cleanPhone.replaceFirst('0020', '20');
}
if (cleanPhone.startsWith('963')) {
return formatPhone(cleanPhone, 'Syria');
}
if (cleanPhone.startsWith('962')) {
return formatPhone(cleanPhone, 'Jordan');
}
if (cleanPhone.startsWith('20')) {
return formatPhone(cleanPhone, 'Egypt');
}
// 2. Local/National Format Detection by Country-Specific Mobile Prefixes
// Jordan: 07x / 7x (9 national digits)
if (cleanPhone.startsWith('07') && cleanPhone.length == 10) {
return formatPhone(cleanPhone, 'Jordan');
}
if (cleanPhone.startsWith('7') && cleanPhone.length == 9) {
return formatPhone(cleanPhone, 'Jordan');
}
// Syria: 09x / 9x (9 national digits)
if (cleanPhone.startsWith('09') && cleanPhone.length == 10) {
return formatPhone(cleanPhone, 'Syria');
}
if (cleanPhone.startsWith('9') && cleanPhone.length == 9) {
return formatPhone(cleanPhone, 'Syria');
}
// Egypt: 01x (10 national digits) / 1x (9 national digits)
if (cleanPhone.startsWith('01') && cleanPhone.length == 11) {
return formatPhone(cleanPhone, 'Egypt');
}
if (cleanPhone.startsWith('1') && cleanPhone.length == 10) {
return formatPhone(cleanPhone, 'Egypt');
}
// 3. Fallback: Default to current user's country code saved in box
final country = box.read(BoxName.countryCode) ?? 'Jordan';
return formatPhone(cleanPhone, country);
}
/// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm)
static bool _isPointInPolygon(LatLng point, List<LatLng> polygon) {
int intersections = 0;
for (int i = 0; i < polygon.length; i++) {
final v1 = polygon[i];
final v2 = polygon[(i + 1) % polygon.length];
if ((v1.latitude <= point.latitude && v2.latitude > point.latitude) ||
(v2.latitude <= point.latitude && v1.latitude > point.latitude)) {
final t = (point.latitude - v1.latitude) / (v2.latitude - v1.latitude);
if (point.longitude <
v1.longitude + t * (v2.longitude - v1.longitude)) {
intersections++;
}
}
}
return intersections % 2 != 0;
}
/// كشف الدولة تلقائياً عبر الموقع الجغرافي باستخدام مضلعات CountryPolygons
static Future<String?> detectByLocation() async {
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) return null;
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
return null;
}
final pos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.low,
timeLimit: const Duration(seconds: 8),
);
final point = LatLng(pos.latitude, pos.longitude);
if (_isPointInPolygon(point, CountryPolygons.jordanBoundary)) {
return 'Jordan';
}
if (_isPointInPolygon(point, CountryPolygons.syriaBoundary)) {
return 'Syria';
}
if (_isPointInPolygon(point, CountryPolygons.egyptBoundary)) {
return 'Egypt';
}
} catch (e) {
Log.print('[CountryLogic] detectByLocation error: $e');
}
return null;
}
/// تهيئة الدولة عند بدء التطبيق
static Future<void> initializeCountry() async {
final existing = box.read(BoxName.countryCode);
if (existing != null && existing.toString().isNotEmpty) {
Log.print('[CountryLogic] الدولة موجودة مسبقاً: $existing');
return;
}
Log.print('[CountryLogic] كشف الدولة عبر الموقع...');
final country = await detectByLocation();
if (country != null) {
box.write(BoxName.countryCode, country);
Log.print('[CountryLogic] تم الكشف: $country');
} else {
box.write(BoxName.countryCode, 'Jordan');
Log.print('[CountryLogic] افتراضي: Jordan');
}
}
}
@@ -0,0 +1,625 @@
import 'dart:async';
import 'dart:convert';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/auth/login_controller.dart';
import 'package:intaleq_rider/main.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:intaleq_rider/env/env.dart';
import '../../constant/api_key.dart';
import '../../print.dart';
import '../../views/widgets/elevated_btn.dart';
import '../../views/widgets/error_snakbar.dart';
import 'encrypt_decrypt.dart';
import 'upload_image.dart';
import 'dart:io';
import 'network/net_guard.dart';
import 'ssl_pinning.dart';
class CRUD {
final NetGuard _netGuard = NetGuard();
final _client = SslPinning.createPinnedClient();
static bool _isRefreshingJWT = false;
static String _lastErrorSignature = '';
static DateTime _lastErrorTimestamp = DateTime(2000);
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
/// JWT validity check without external libraries.
static bool _isJwtValid(String? token) {
if (token == null || token.isEmpty) return false;
try {
final parts = token.split('.');
if (parts.length != 3) return false;
String payload = parts[1];
switch (payload.length % 4) {
case 2:
payload += '==';
break;
case 3:
payload += '=';
break;
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
final exp = decoded['exp'];
if (exp == null) return false;
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
} catch (_) {
return false;
}
}
static Future<void> addError(
String error, String details, String where) async {
try {
final currentErrorSignature = '$where-$error';
final now = DateTime.now();
if (currentErrorSignature == _lastErrorSignature &&
now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) {
return;
}
_lastErrorSignature = currentErrorSignature;
_lastErrorTimestamp = now;
final userId =
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
final userType =
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
Log.print(
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
CRUD().post(
link: AppLink.addError,
payload: {
'error': error.toString(),
'userId': userId.toString(),
'userType': userType,
'phone': phone.toString(),
'device': where,
'details': details,
},
);
} catch (e) {
Log.print("Error occurred: $e");
}
}
String _getFpHeader() {
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
}
Future<String> _getJwt() async {
try {
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
if (encryptedJwt == null || encryptedJwt.isEmpty) {
final String? fallback = box.read(BoxName.jwt);
if (fallback != null) {
return r(fallback).toString().split(Env.addd)[0];
}
return '';
}
return r(encryptedJwt).toString().split(Env.addd)[0];
} catch (e) {
Log.print('Error reading JWT from SecureStorage: $e');
final String? fallback = box.read(BoxName.jwt);
if (fallback != null) {
return r(fallback).toString().split(Env.addd)[0];
}
return '';
}
}
/// Centralized request handler with retry for weak networks.
/// For Syria (3G): 60s total timeout, 3 retries, exponential backoff.
Future<dynamic> _makeRequest({
required String link,
Map<String, dynamic>? payload,
required Map<String, String> headers,
}) async {
const totalTimeout = Duration(seconds: 60);
Future<http.Response> doPost() {
final url = Uri.parse(link);
return _client
.post(url, body: payload, headers: headers)
.timeout(totalTimeout);
}
http.Response? response;
int attempts = 0;
while (attempts < 3) {
try {
attempts++;
response = await doPost();
break;
} on SocketException catch (_) {
Log.print('⚠️ SocketException attempt $attempts — $link');
if (attempts >= 3) {
_netGuard.notifyOnce((title, msg) => mySnackbarError(msg));
return 'no_internet';
}
await Future.delayed(Duration(seconds: attempts));
} on TimeoutException catch (_) {
Log.print('⚠️ TimeoutException attempt $attempts — $link');
if (attempts >= 3) return 'failure';
} catch (e) {
if (e.toString().contains('errno = 9') && attempts < 3) {
await Future.delayed(const Duration(milliseconds: 500));
continue;
}
addError(
'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
return 'failure';
}
}
if (response == null) return 'failure';
final sc = response.statusCode;
final body = response.body;
Log.print('request: ${response.request}');
Log.print('body: $body');
Log.print('payload: $payload');
if (sc >= 200 && sc < 300) {
try {
return jsonDecode(body);
} catch (e, st) {
addError('JSON Decode Error', 'Body: $body\n$st',
'CRUD._makeRequest $link');
return 'failure';
}
}
if (sc == 401) {
final isNonCritical = link.contains('errorApp.php');
if (!_isRefreshingJWT && !isNonCritical) {
_isRefreshingJWT = true;
try {
await Get.put(LoginController()).getJWT();
} finally {
_isRefreshingJWT = false;
}
}
return 'token_expired';
}
if (sc >= 500) {
addError(
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
return 'failure';
}
return 'failure';
}
Future<dynamic> post({
required String link,
Map<String, dynamic>? payload,
}) async {
String token = await _getJwt();
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(),
};
return await _makeRequest(link: link, payload: payload, headers: headers);
}
Future<dynamic> get({
required String link,
Map<String, dynamic>? payload,
}) async {
String token = await _getJwt();
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(),
};
return await _makeRequest(link: link, payload: payload, headers: headers);
}
// ═══════════════════════════════════════════════════════════════
// postWallet — طلب POST لسيرفر المدفوعات
// ───────────────────────────────────────────────────────────────
// التغيير: إضافة X-Device-FP header
// 3 headers معاً: JWT + HMAC + FP
// ═══════════════════════════════════════════════════════════════
Future<dynamic> postWallet({
required String link,
Map<String, dynamic>? payload,
}) async {
var jwt = await LoginController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $jwt',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(),
};
Log.print('headers: $headers');
Log.print('payload: $payload');
Log.print('link: $link');
return await _makeRequest(link: link, payload: payload, headers: headers);
}
Future<dynamic> getWallet({
required String link,
Map<String, dynamic>? payload,
}) async {
var s = await LoginController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(),
};
return await _makeRequest(link: link, payload: payload, headers: headers);
}
// =======================================================================
// All other specialized methods remain below unchanged.
// They interact with external third-party APIs and have unique
// authentication or body structures that don't need the FP header.
// =======================================================================
Future<dynamic> postWalletMtn(
{required String link, Map<String, dynamic>? payload}) async {
final s = await LoginController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(),
};
final result = await _makeRequest(link: link, payload: payload, headers: headers);
if (result is Map || result is List) return result;
if (result == 'no_internet') {
return {'status': 'failure', 'message': 'no_internet', 'code': -1};
}
return result;
}
Future sendWhatsAppAuth(String to, String token) async {
var res = await CRUD()
.get(link: AppLink.getApiKey, payload: {'keyName': 'whatsapp_key'});
if (res is! Map) return;
var accesstoken = res['message']['whatsapp_key'];
var headers = {
'Authorization': 'Bearer $accesstoken',
'Content-Type': 'application/json'
};
var url = 'https://graph.facebook.com/v20.0/${Env.whatappID}/messages';
var request = http.Request('POST', Uri.parse(url));
var body = json.encode({
"messaging_product": "whatsapp",
"to": to,
"type": "template",
"template": {
"name": "sefer1",
"language": {"code": "en"},
"components": [
{
"type": "body",
"parameters": [
{"type": "text", "text": token}
]
}
]
}
});
request.body = body;
request.headers.addAll(headers);
try {
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
String responseBody = await response.stream.bytesToString();
Get.defaultDialog(
title: 'You will receive a code in WhatsApp Messenger'.tr,
middleText: 'wait 1 minute to recive message'.tr,
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () => Get.back(),
),
);
} else {
String errorBody = await response.stream.bytesToString();
}
} catch (e) {
Log.print("Error occurred: $e");
}
}
Future<dynamic> getAgoraToken({
required String channelName,
required String uid,
}) async {
var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
var res = await _client.get(
Uri.parse(
'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'),
headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'},
);
if (res.statusCode == 200) {
var response = jsonDecode(res.body);
return response['token'];
}
}
Future<dynamic> getLlama({
required String link,
required String payload,
required String prompt,
}) async {
var url = Uri.parse(link);
var headers = {
'Content-Type': 'application/json',
'Authorization':
'Bearer LL-X5lJ0Px9CzKK0HTuVZ3u2u4v3tGWkImLTG7okGRk4t25zrsLqJ0qNoUzZ2x4ciPy'
};
var data = json.encode({
"model": "Llama-3-70b-Inst-FW",
"messages": [
{
"role": "user",
"content":
"Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload"
}
],
"temperature": 0.9
});
var response = await _client.post(url, body: data, headers: headers);
if (response.statusCode == 200) return response.body;
return response.statusCode;
}
Future allMethodForAI(String prompt, linkPHP, imagePath) async {
await ImageController().choosImage(linkPHP, imagePath);
Future.delayed(const Duration(seconds: 2));
String extracted =
await arabicTextExtractByVisionAndAI(imagePath: imagePath);
}
Future<dynamic> arabicTextExtractByVisionAndAI({
required String imagePath,
}) async {
var headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': '21010e54b50f41a4904708c526e102df'
};
var url = Uri.parse(
'https://ocrhamza.cognitiveservices.azure.com/vision/v2.1/ocr?language=ar');
String imagePathFull =
'${AppLink.server}card_image/$imagePath-${box.read(BoxName.driverID) ?? box.read(BoxName.passengerID)}.jpg';
var requestBody = {"url": imagePathFull};
var response =
await _client.post(url, body: jsonEncode(requestBody), headers: headers);
if (response.statusCode == 200) {
var responseBody = jsonDecode(response.body);
return responseBody.toString();
}
return response.statusCode;
}
Future<dynamic> getChatGPT({
required String link,
required String payload,
}) async {
var url = Uri.parse(link);
var headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${Env.chatGPTkeySeferNew}'
};
var data = json.encode({
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content":
"Extract the desired information from the following passage as json decoded like vin,make,made,year,expiration_date,color,owner,registration_date just in this:\n\n$payload"
}
],
"temperature": 0.9
});
var response = await _client.post(url, body: data, headers: headers);
if (response.statusCode == 200) return response.body;
return response.statusCode;
}
Future<dynamic> postPayMob({
required String link,
Map<String, dynamic>? payload,
}) async {
var url = Uri.parse(link);
var response = await _client.post(url,
body: payload, headers: {'Content-Type': 'application/json'});
var jsonData = jsonDecode(response.body);
if (response.statusCode == 200) {
if (jsonData['status'] == 'success') return response.body;
return jsonData['status'];
} else {
return response.statusCode;
}
}
sendEmail(String link, Map<String, String>? payload) async {
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
};
var request = http.Request('POST', Uri.parse(link));
request.bodyFields = payload!;
request.headers.addAll(headers);
await request.send();
}
Future<dynamic> postFromDialogue({
required String link,
Map<String, dynamic>? payload,
}) async {
var url = Uri.parse(link);
var response = await _client.post(
url,
body: payload,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
},
);
if (response.body.isNotEmpty) {
var jsonData = jsonDecode(response.body);
if (response.statusCode == 200) {
if (jsonData['status'] == 'success') {
Get.back();
return response.body;
}
}
return jsonData['status'];
}
}
Future<void> sendVerificationRequest(String phoneNumber) async {
final accountSid = AK.accountSIDTwillo;
final authToken = AK.authTokenTwillo;
final verifySid = AK.twilloRecoveryCode;
final Uri verificationUri = Uri.parse(
'https://verify.twilio.com/v2/Services/$verifySid/Verifications');
await _client.post(
verificationUri,
headers: {
'Authorization':
'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')),
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {'To': phoneNumber, 'Channel': 'sms'},
);
final otpCode = "123456";
final checkUri = Uri.parse(
'https://verify.twilio.com/v2/Services/$verifySid/VerificationCheck');
final checkResponse = await _client.post(
checkUri,
headers: {
'Authorization':
'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')),
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {'To': phoneNumber, 'Code': otpCode},
);
}
Future<dynamic> getGoogleApi({
required String link,
Map<String, dynamic>? payload,
}) async {
var url = Uri.parse(link);
var response = await _client.post(url, body: payload);
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'OK') return jsonData;
return jsonData['status'];
}
Future<dynamic> getHereMap({required String link}) async {
var url = Uri.parse(link);
try {
var response = await _client.get(url);
if (response.statusCode == 200) {
var decodedBody = utf8.decode(response.bodyBytes);
return jsonDecode(decodedBody);
}
return null;
} catch (e) {
return null;
}
}
Future<dynamic> getMapSaas({
required String link,
}) async {
var url = Uri.parse(link);
try {
var response = await _client.get(
url,
headers: {
'Content-Type': 'application/json',
'x-api-key': Env.mapSaasKey,
},
);
Log.print('link -MapSaas: $link');
Log.print('response -MapSaas: ${response.body}');
if (response.statusCode == 200) {
return jsonDecode(response.body);
}
Log.print('MapSaas Error: ${response.statusCode} - ${response.body}');
return null;
} catch (e) {
Log.print('MapSaas Exception: $e');
return null;
}
}
Future<dynamic> postMapSaas({
required String link,
required Map<String, dynamic> payload,
}) async {
var url = Uri.parse(link);
try {
var response = await _client.post(
url,
body: jsonEncode(payload),
headers: {
'Content-Type': 'application/json',
'x-api-key': Env.mapSaasKey,
},
);
Log.print('post -MapSaas link: $link');
Log.print('post -MapSaas payload: $payload');
Log.print('post -MapSaas response: ${response.body}');
if (response.statusCode == 200 || response.statusCode == 201) {
return jsonDecode(response.body);
}
Log.print(
'MapSaas Post Error: ${response.statusCode} - ${response.body}');
return null;
} catch (e) {
Log.print('MapSaas Post Exception: $e');
return null;
}
}
}
@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
class LineChartPainter extends CustomPainter {
final List<double> data;
LineChartPainter(this.data);
@override
void paint(Canvas canvas, Size size) {
// Calculate the scale factor.
final scaleFactor = size.height / 240;
// Draw the line chart.
for (var i = 0; i < data.length - 1; i++) {
final x1 = i * size.width / data.length;
final y1 = data[i] * scaleFactor;
final x2 = (i + 1) * size.width / data.length;
final y2 = data[i + 1] * scaleFactor;
canvas.drawLine(Offset(x1, y1), Offset(x2, y2), Paint());
}
}
@override
bool shouldRepaint(LineChartPainter oldDelegate) => false;
}
@@ -0,0 +1,92 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import '../../print.dart';
class DeviceInfoPlus {
static List<Map<String, dynamic>> deviceDataList = [];
static Future<List<Map<String, dynamic>>> getDeviceInfo() async {
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
try {
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
Map<String, dynamic> deviceData = {
'platform': 'Android',
'brand': androidInfo.brand,
'model': androidInfo.model,
'androidId': androidInfo.device,
'versionRelease': androidInfo.version.release,
'sdkVersion': androidInfo.version.sdkInt,
'manufacturer': androidInfo.manufacturer,
'isPhysicalDevice': androidInfo.isPhysicalDevice,
'serialNumber': androidInfo.fingerprint,
'fingerprint': androidInfo.fingerprint,
'type': androidInfo.type,
'data': androidInfo.data,
'version': androidInfo.version,
'tags': androidInfo.tags,
'display': androidInfo.display,
};
// Log.print('deviceData: ${deviceData}');
deviceDataList.add(deviceData);
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
Map<String, dynamic> deviceData = {
'brand': 'Apple',
'model': iosInfo.model,
'systemName': iosInfo.systemName,
'systemVersion': iosInfo.systemVersion,
'utsname': iosInfo.utsname,
'isPhysicalDevice': iosInfo.isPhysicalDevice,
'identifierForVendor': iosInfo.identifierForVendor,
'name': iosInfo.name,
'localizedModel': iosInfo.localizedModel,
};
deviceDataList.add(deviceData);
} else if (Platform.isMacOS) {
MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo;
Map<String, dynamic> deviceData = {
'platform': 'macOS',
'model': macInfo.model,
'version': macInfo.systemGUID,
};
deviceDataList.add(deviceData);
} else if (Platform.isWindows) {
WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo;
Map<String, dynamic> deviceData = {
'platform': 'Windows',
'manufacturer': windowsInfo.computerName,
'version': windowsInfo.majorVersion,
'deviceId': windowsInfo.deviceId,
'userName': windowsInfo.userName,
'productName': windowsInfo.productName,
'installDate': windowsInfo.installDate,
'productId': windowsInfo.productId,
'numberOfCores': windowsInfo.numberOfCores,
'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
};
deviceDataList.add(deviceData);
} else if (Platform.isLinux) {
LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo;
Map<String, dynamic> deviceData = {
'platform': 'Linux',
'manufacturer': linuxInfo.name,
'version': linuxInfo.version,
};
deviceDataList.add(deviceData);
}
} catch (e) { Log.print("Error occurred: $e"); }
return deviceDataList;
}
// Method to print all device data
static void printDeviceInfo() {
for (Map<String, dynamic> deviceData in deviceDataList) {
'Version: ${deviceData['version'] ?? deviceData['versionRelease'] ?? 'N/A'}';
}
}
}
@@ -0,0 +1,42 @@
// import 'package:flutter/services.dart';
// class DigitObscuringFormatter extends TextInputFormatter {
// @override
// TextEditingValue formatEditUpdate(
// TextEditingValue oldValue, TextEditingValue newValue) {
// final maskedText = maskDigits(newValue.text);
// return newValue.copyWith(
// text: maskedText,
// selection: updateCursorPosition(maskedText, newValue.selection));
// }
// String maskDigits(String text) {
// final totalDigits = text.length;
// final visibleDigits = 4;
// final hiddenDigits = totalDigits - visibleDigits * 2;
// final firstVisibleDigits = text.substring(0, visibleDigits);
// final lastVisibleDigits = text.substring(totalDigits - visibleDigits);
// final maskedDigits = List.filled(hiddenDigits, '*').join();
// return '$firstVisibleDigits$maskedDigits$lastVisibleDigits';
// }
// TextSelection updateCursorPosition(
// String maskedText, TextSelection currentSelection) {
// final cursorPosition = currentSelection.baseOffset;
// final cursorOffset =
// currentSelection.extentOffset - currentSelection.baseOffset;
// final totalDigits = maskedText.length;
// const visibleDigits = 4;
// final hiddenDigits = totalDigits - visibleDigits * 2;
// final updatedPosition = cursorPosition <= visibleDigits
// ? cursorPosition
// : hiddenDigits + visibleDigits + (cursorPosition - visibleDigits);
// return TextSelection.collapsed(
// offset: updatedPosition, affinity: currentSelection.affinity);
// }
// }
@@ -0,0 +1,123 @@
import 'dart:convert';
import 'dart:math';
import 'package:intaleq_rider/env/env.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:flutter/foundation.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import '../../constant/char_map.dart';
class EncryptionHelper {
static EncryptionHelper? _instance;
late final encrypt.Key key;
late final encrypt.IV iv;
static const String _gcmPrefix = 'GCM:';
EncryptionHelper._(this.key, this.iv);
static EncryptionHelper get instance {
if (_instance == null) {
throw Exception(
"EncryptionHelper is not initialized. Call `await EncryptionHelper.initialize()` in main.");
}
return _instance!;
}
/// Initializes and stores the instance globally
static Future<void> initialize() async {
if (_instance != null) {
debugPrint("EncryptionHelper is already initialized.");
return; // Prevent re-initialization
}
debugPrint("Initializing EncryptionHelper...");
var keyOfApp = r(Env.keyOfApp).toString().split(Env.addd)[0];
var initializationVector =
r(Env.initializationVector).toString().split(Env.addd)[0];
// Set the global instance
_instance = EncryptionHelper._(
encrypt.Key.fromUtf8(keyOfApp!),
encrypt.IV.fromUtf8(initializationVector!),
);
debugPrint("EncryptionHelper initialized successfully.");
}
/// ✅ FIX H-04: Encrypts a string using AES-256-GCM (new) with random IV
String encryptData(String plainText) {
try {
// Try GCM first (secure mode with random IV)
final randomIv = _generateRandomIv(12);
// GCM mode in encrypt package handles nonce/IV length automatically (12 bytes recommended)
final gcmEncrypter =
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm));
final encrypted = gcmEncrypter.encrypt(plainText, iv: randomIv);
// Prefix with GCM: to distinguish from legacy CBC
return '$_gcmPrefix${randomIv.base64}:${encrypted.base64}';
} catch (e) {
debugPrint('GCM Encryption failed, falling back to CBC: $e');
// Fallback to CBC with a warning
try {
final cbcEncrypter =
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
final encrypted = cbcEncrypter.encrypt(plainText, iv: iv);
return encrypted.base64;
} catch (e2) {
debugPrint('CBC Encryption Error: $e2');
return '';
}
}
}
/// ✅ FIX H-04: Decrypts a string (supports both GCM and legacy CBC)
String decryptData(String encryptedText) {
try {
// Check if it's GCM format
if (encryptedText.startsWith(_gcmPrefix)) {
final parts = encryptedText.substring(_gcmPrefix.length).split(':');
if (parts.length != 2) {
debugPrint('Invalid GCM format, falling back to CBC');
return _decryptLegacyCbc(encryptedText);
}
final iv = encrypt.IV.fromBase64(parts[0]);
final encrypted = parts[1];
final gcmEncrypter =
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm));
return gcmEncrypter.decrypt(encrypt.Encrypted.fromBase64(encrypted),
iv: iv);
}
// Legacy CBC format
return _decryptLegacyCbc(encryptedText);
} catch (e) {
debugPrint('Decryption Error: $e');
// Try CBC as last resort
try {
return _decryptLegacyCbc(encryptedText);
} catch (_) {
return '';
}
}
}
/// Legacy CBC decryption (backward compatibility)
String _decryptLegacyCbc(String encryptedText) {
final cbcEncrypter =
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
final encrypted = encrypt.Encrypted.fromBase64(encryptedText);
return cbcEncrypter.decrypt(encrypted, iv: iv);
}
/// Generate cryptographically secure random IV
encrypt.IV _generateRandomIv(int length) {
final random = Random.secure();
final bytes = List<int>.generate(length, (_) => random.nextInt(256));
return encrypt.IV(Uint8List.fromList(bytes));
}
}
r(String string) {
return X.r(X.r(X.r(string, cn), cC), cs).toString();
}
c(String string) {
return X.c(X.c(X.c(string, cn), cC), cs).toString();
}
@@ -0,0 +1,63 @@
import 'package:geolocator/geolocator.dart';
import 'package:intaleq_rider/services/geofencing_service.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
class GeoLocation {
Future<Position> getCurrentLocation() async {
bool serviceEnabled;
LocationPermission permission;
// Check if location services are enabled.
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Location services are not enabled, so we request the user to enable it.
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
// Prominent Disclosure for Background Location
bool? userAgreed = await Get.defaultDialog<bool>(
title: 'صلاحية الموقع'.tr,
middleText: 'يجمع تطبيق سيرو بيانات الموقع لتفعيل ميزة التتبع الجغرافي (Geofence) ولتمكين السائق من الوصول إليك بدقة وتتبع رحلتك حتى عندما يكون التطبيق في الخلفية أو مغلقاً.'.tr,
titleStyle: const TextStyle(fontWeight: FontWeight.bold),
textConfirm: 'موافق'.tr,
textCancel: 'إلغاء'.tr,
confirmTextColor: Colors.white,
onConfirm: () => Get.back(result: true),
onCancel: () => Get.back(result: false),
barrierDismissible: false,
);
if (userAgreed != true) {
return Future.error('Location permissions are denied');
}
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Permissions are denied, we cannot fetch the location.
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Permissions are denied forever, we cannot request permissions.
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
// When we reach here, permissions are granted and we can fetch the location.
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
// Update Geofences based on the fresh location
try {
SiroGeofencingService.syncZonesWithServer(position.latitude, position.longitude);
} catch (e) {
// Ignore errors so it doesn't break the main location flow
}
return position;
}
}
@@ -0,0 +1,112 @@
import 'package:intaleq_rider/print.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
import 'package:intaleq_rider/controller/functions/country_logic.dart';
void showInBrowser(String url) async {
if (await canLaunchUrl(Uri.parse(url))) {
launchUrl(Uri.parse(url));
} else {}
}
String cleanAndFormatPhoneNumber(String phoneNumber) {
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
if (formattedNumber.length > 6) {
formattedNumber = CountryLogic.formatCurrentCountryPhone(formattedNumber);
if (!formattedNumber.startsWith('+')) {
formattedNumber = '+$formattedNumber';
}
}
return formattedNumber;
}
Future<void> makePhoneCall(String phoneNumber) async {
String formattedNumber = cleanAndFormatPhoneNumber(phoneNumber);
// ملاحظة: الأرقام القصيرة (مثل 112) ستتجاوز الشرط أعلاه وتبقى "112" وهو الصحيح
// 3. التنفيذ (Launch)
final Uri launchUri = Uri(
scheme: 'tel',
path: formattedNumber,
);
try {
// استخدام 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) {
// طباعة الخطأ في حال الفشل التام
Log.print("Error launching call: $e");
}
}
void launchCommunication(
String method, String contactInfo, String message) async {
String formattedContact = cleanAndFormatPhoneNumber(contactInfo);
// WhatsApp prefers the phone number without the '+' prefix
String whatsappContact = formattedContact.replaceAll('+', '');
String url;
if (Platform.isIOS) {
switch (method) {
case 'phone':
url = 'tel:$formattedContact';
break;
case 'sms':
url = 'sms:$formattedContact?body=${Uri.encodeComponent(message)}';
break;
case 'whatsapp':
url =
'https://api.whatsapp.com/send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
break;
case 'email':
url =
'mailto:$formattedContact?subject=Subject&body=${Uri.encodeComponent(message)}';
break;
default:
return;
}
} else if (Platform.isAndroid) {
switch (method) {
case 'phone':
url = 'tel:$formattedContact';
break;
case 'sms':
url = 'sms:$formattedContact?body=${Uri.encodeComponent(message)}';
break;
case 'whatsapp':
// Check if WhatsApp is installed
final bool whatsappInstalled =
await canLaunchUrl(Uri.parse('whatsapp://'));
if (whatsappInstalled) {
url =
'whatsapp://send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
} else {
// Provide an alternative action, such as opening the WhatsApp Web API
url =
'https://api.whatsapp.com/send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
}
break;
case 'email':
url =
'mailto:$formattedContact?subject=Subject&body=${Uri.encodeComponent(message)}';
break;
default:
return;
}
} else {
return;
}
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {}
}
@@ -0,0 +1,147 @@
// import 'dart:async';
// import 'package:get/get.dart';
// import 'package:location/location.dart';
// import 'package:intaleq_rider/constant/box_name.dart';
// import 'package:intaleq_rider/constant/links.dart';
// import 'package:intaleq_rider/controller/functions/crud.dart';
// import 'package:intaleq_rider/controller/home/payment/captain_wallet_controller.dart';
// import 'package:intaleq_rider/main.dart';
// // LocationController.dart
// class LocationController extends GetxController {
// LocationData? _currentLocation;
// late Location location;
// bool isLoading = false;
// late double heading = 0;
// late double accuracy = 0;
// late double previousTime = 0;
// late double latitude;
// late double totalDistance = 0;
// late double longitude;
// late DateTime time;
// late double speed = 0;
// late double speedAccuracy = 0;
// late double headingAccuracy = 0;
// bool isActive = false;
// late LatLng myLocation;
// String totalPoints = '0';
// LocationData? get currentLocation => _currentLocation;
// Timer? _locationTimer;
// @override
// void onInit() async {
// super.onInit();
// location = Location();
// getLocation();
// // startLocationUpdates();
// totalPoints = Get.put(CaptainWalletController()).totalPoints;
// }
// Future<void> startLocationUpdates() async {
// if (box.read(BoxName.driverID) != null) {
// _locationTimer =
// Timer.periodic(const Duration(seconds: 5), (timer) async {
// try {
// totalPoints = Get.find<CaptainWalletController>().totalPoints;
// // if (isActive) {
// if (double.parse(totalPoints) > -300) {
// await getLocation();
// // if (box.read(BoxName.driverID) != null) {
// await CRUD()
// .post(link: AppLink.addCarsLocationByPassenger, payload: {
// 'driver_id': box.read(BoxName.driverID).toString(),
// 'latitude': myLocation.latitude.toString(),
// 'longitude': myLocation.longitude.toString(),
// 'heading': heading.toString(),
// 'speed': (speed * 3.6).toStringAsFixed(1),
// 'distance': totalDistance == 0
// ? '0'
// : totalDistance < 1
// ? totalDistance.toStringAsFixed(3)
// : totalDistance.toStringAsFixed(1),
// 'status': box.read(BoxName.statusDriverLocation).toString()
// });
// }
// // }
// } catch (e) {
// // Handle the error gracefully
// }
// });
// }
// }
// void stopLocationUpdates() {
// _locationTimer?.cancel();
// }
// Future<void> getLocation() async {
// // isLoading = true;
// // update();
// bool serviceEnabled;
// PermissionStatus permissionGranted;
// // Check if location services are enabled
// serviceEnabled = await location.serviceEnabled();
// if (!serviceEnabled) {
// serviceEnabled = await location.requestService();
// if (!serviceEnabled) {
// // Location services are still not enabled, handle the error
// return;
// }
// }
// // Check if the app has permission to access location
// permissionGranted = await location.hasPermission();
// if (permissionGranted == PermissionStatus.denied) {
// permissionGranted = await location.requestPermission();
// if (permissionGranted != PermissionStatus.granted) {
// // Location permission is still not granted, handle the error
// return;
// }
// }
// // Configure location accuracy
// // LocationAccuracy desiredAccuracy = LocationAccuracy.high;
// // Get the current location
// LocationData _locationData = await location.getLocation();
// myLocation =
// (_locationData.latitude != null && _locationData.longitude != null
// ? LatLng(_locationData.latitude!, _locationData.longitude!)
// : null)!;
// speed = _locationData.speed!;
// heading = _locationData.heading!;
// // Calculate the distance between the current location and the previous location
// // if (Get.find<HomeCaptainController>().rideId == 'rideId') {
// // if (previousTime > 0) {
// // double distance = calculateDistanceInKmPerHour(
// // previousTime, _locationData.time, speed);
// // totalDistance += distance;
// // }
// // previousTime = _locationData.time!;
// // }
// // Print location details
// // isLoading = false;
// update();
// }
// double calculateDistanceInKmPerHour(
// double? startTime, double? endTime, double speedInMetersPerSecond) {
// // Calculate the time difference in hours
// double timeDifferenceInHours = (endTime! - startTime!) / 1000 / 3600;
// // Convert speed to kilometers per hour
// double speedInKmPerHour = speedInMetersPerSecond * 3.6;
// // Calculate the distance in kilometers
// double distanceInKilometers = speedInKmPerHour * timeDifferenceInHours;
// return distanceInKilometers;
// }
// }
@@ -0,0 +1,16 @@
import 'package:location/location.dart';
import 'package:get/get.dart';
class LocationPermissions {
late Location location;
Future locationPermissions() async {
location = Location();
var permissionStatus = await location.requestPermission();
if (permissionStatus == PermissionStatus.denied) {
// The user denied the location permission.
Get.defaultDialog(title: 'GPS Required Allow !.'.tr, middleText: '');
return null;
}
}
}
@@ -0,0 +1,205 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/colors.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/onbording_page.dart';
import 'package:intaleq_rider/views/widgets/elevated_btn.dart';
import 'package:intaleq_rider/views/widgets/my_textField.dart';
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
import '../../constant/style.dart';
import 'package:intaleq_rider/controller/home/map/map_socket_controller.dart';
import 'package:intaleq_rider/controller/home/map/map_engine_controller.dart';
import 'package:intaleq_rider/controller/home/map/location_search_controller.dart';
import 'package:intaleq_rider/controller/home/map/nearby_drivers_controller.dart';
import 'package:intaleq_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:intaleq_rider/controller/home/map/ui_interactions_controller.dart';
import 'package:intaleq_rider/controller/home/menu_controller.dart';
import 'package:intaleq_rider/controller/home/points_for_rider_controller.dart';
import 'package:intaleq_rider/controller/functions/secure_storage.dart';
class LogOutController extends GetxController {
TextEditingController checkTxtController = TextEditingController();
final formKey = GlobalKey<FormState>();
final formKey1 = GlobalKey<FormState>();
final emailTextController = TextEditingController();
Future deleteMyAccountDriver(String id) async {
await CRUD().post(link: AppLink.removeUser, payload: {'id': id}).then(
(value) => mySnackbarSuccess('Your Account is Deleted'.tr));
}
checkBeforeDelete() async {
var res = await CRUD().post(
link: AppLink.deletecaptainAccounr,
payload: {'id': box.read(BoxName.driverID)}).then((value) => exit(0));
}
deletecaptainAccount() {
Get.defaultDialog(
backgroundColor: AppColor.yellowColor,
title: 'Are you sure to delete your account?'.tr,
middleText:
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month ',
titleStyle: AppStyle.title,
content: Column(
children: [
Container(
width: Get.width,
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month'
.tr,
style: AppStyle.title.copyWith(color: AppColor.redColor),
),
),
),
const SizedBox(
height: 20,
),
Form(
key: formKey,
child: SizedBox(
width: Get.width,
child: MyTextForm(
controller: checkTxtController,
label: 'Enter Your First Name'.tr,
hint: 'Enter Your First Name'.tr,
type: TextInputType.name,
),
))
],
),
confirm: MyElevatedButton(
title: 'Delete'.tr,
onPressed: () {
if (checkTxtController.text == box.read(BoxName.nameDriver)) {
deletecaptainAccount();
}
}));
}
Future logOutPassenger() async {
Get.defaultDialog(
title: 'Are you Sure to LogOut?'.tr,
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
),
onPressed: () {
// box.remove(BoxName.agreeTerms);
box.remove(BoxName.passengerPhotoUrl);
box.remove(BoxName.driverID);
box.remove(BoxName.email);
box.remove(BoxName.lang);
box.remove(BoxName.name);
box.remove(BoxName.passengerID);
box.remove(BoxName.phone);
box.remove(BoxName.tokenFCM);
box.remove(BoxName.tokens);
box.remove(BoxName.addHome);
box.remove(BoxName.addWork);
box.remove(BoxName.agreeTerms);
box.remove(BoxName.apiKeyRun);
box.remove(BoxName.countryCode);
SecureStorage().deleteData(BoxName.cardNumber);
SecureStorage().deleteData(BoxName.cvvCode);
SecureStorage().deleteData(BoxName.cardHolderName);
SecureStorage().deleteData(BoxName.expiryDate);
box.remove(BoxName.passengerWalletTotal);
box.remove(BoxName.isVerified);
Get.delete<MapSocketController>(force: true);
Get.delete<MapEngineController>(force: true);
Get.delete<LocationSearchController>(force: true);
Get.delete<NearbyDriversController>(force: true);
Get.delete<RideLifecycleController>(force: true);
Get.delete<UiInteractionsController>(force: true);
Get.delete<MyMenuController>(force: true);
Get.delete<CRUD>(force: true);
Get.delete<WayPointController>(force: true);
Get.offAll(OnBoardingPage());
},
child: Text(
'Sign Out'.tr,
style:
AppStyle.title.copyWith(color: AppColor.secondaryColor),
))
],
));
}
Future logOutCaptain() async {
Get.defaultDialog(
title: 'Are you Sure to LogOut?'.tr,
titleStyle: AppStyle.title,
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
),
onPressed: () {
// box.remove(BoxName.agreeTerms);
box.remove(BoxName.driverID);
box.remove(BoxName.sexDriver);
box.remove(BoxName.dobDriver);
box.remove(BoxName.nameDriver);
box.remove(BoxName.emailDriver);
box.remove(BoxName.phoneDriver);
box.remove(BoxName.statusDriverLocation);
box.remove(BoxName.cvvCodeDriver);
box.remove(BoxName.lastNameDriver);
box.remove(BoxName.passwordDriver);
box.remove(BoxName.cardNumberDriver);
box.remove(BoxName.expiryDateDriver);
box.remove(BoxName.cardHolderNameDriver);
box.remove(BoxName.vin);
box.remove(BoxName.make);
box.remove(BoxName.year);
box.remove(BoxName.owner);
box.remove(BoxName.onBoarding);
box.remove(BoxName.agreeTerms);
Get.offAll(OnBoardingPage());
},
child: Text(
'Sign Out'.tr,
style:
AppStyle.title.copyWith(color: AppColor.secondaryColor),
))
],
));
}
deletePassengerAccount() async {
if (formKey1.currentState!.validate()) {
if (box.read(BoxName.email).toString() == emailTextController.text) {
await CRUD().post(link: AppLink.passengerRemovedAccountEmail, payload: {
'email': box.read(BoxName.email),
});
} else {
mySnackbarError('Email you inserted is Wrong.'.tr);
}
}
}
}
@@ -0,0 +1,48 @@
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'net_guard.dart';
typedef BodyEncoder = Future<http.Response> Function();
class HttpRetry {
/// ريتراي لـ network/transient errors فقط.
static Future<http.Response> sendWithRetry(
BodyEncoder send, {
int maxRetries = 3,
Duration baseDelay = const Duration(milliseconds: 400),
Duration timeout = const Duration(seconds: 12),
}) async {
// ✅ Pre-flight check for internet connection
if (!await NetGuard().hasInternet()) {
// Immediately throw a specific exception if there's no internet.
// This avoids pointless retries.
throw const SocketException("No internet connection");
}
int attempt = 0;
while (true) {
attempt++;
try {
final res = await send().timeout(timeout);
return res;
} on TimeoutException catch (_) {
if (attempt >= maxRetries) rethrow;
} on SocketException catch (_) {
if (attempt >= maxRetries) rethrow;
} on HandshakeException catch (_) {
if (attempt >= maxRetries) rethrow;
} on http.ClientException catch (e) {
// مثال: Connection reset by peer
final msg = e.message.toLowerCase();
final transient = msg.contains('connection reset') ||
msg.contains('broken pipe') ||
msg.contains('timed out');
if (!transient || attempt >= maxRetries) rethrow;
}
// backoff: 0.4s, 0.8s, 1.6s
final delay = baseDelay * (1 << (attempt - 1));
await Future.delayed(delay);
}
}
}
@@ -0,0 +1,48 @@
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
class NetGuard {
static final NetGuard _i = NetGuard._();
NetGuard._();
factory NetGuard() => _i;
bool _notified = false;
/// فحص: (أ) فيه شبكة؟ (ب) فيه انترنت؟ (ج) السيرفر نفسه reachable؟
Future<bool> hasInternet({Uri? mustReach}) async {
final connectivity = await Connectivity().checkConnectivity();
if (connectivity == ConnectivityResult.none) return false;
final hasNet =
await InternetConnectionChecker.createInstance().hasConnection;
if (!hasNet) return false;
if (mustReach != null) {
try {
final host = mustReach.host;
final result = await InternetAddress.lookup(host);
if (result.isEmpty || result.first.rawAddress.isEmpty) return false;
// اختباري خفيف عبر TCP (80/443) — 400ms timeout
final port = mustReach.scheme == 'http' ? 80 : 443;
final socket = await Socket.connect(host, port,
timeout: const Duration(milliseconds: 400));
socket.destroy();
} catch (_) {
return false;
}
}
return true;
}
/// إظهار إشعار مرة واحدة ثم إسكات التكرارات
void notifyOnce(void Function(String title, String msg) show) {
if (_notified) return;
_notified = true;
show('لا يوجد اتصال بالإنترنت', 'تحقق من الشبكة ثم حاول مجددًا.');
// إعادة السماح بعد 15 ثانية
Future.delayed(const Duration(seconds: 15), () => _notified = false);
}
}
@@ -0,0 +1,242 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:ui';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../main.dart';
import '../../print.dart';
import 'encrypt_decrypt.dart';
Future<void> checkForUpdate(BuildContext context) async {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.buildNumber;
final version = packageInfo.version;
Log.print('currentVersion is : $currentVersion');
// Fetch the latest version from your server
String latestVersion = box.read(BoxName.package)?.toString() ?? '';
box.write(BoxName.packagInfo, version);
if (latestVersion.isNotEmpty && latestVersion != currentVersion) {
showUpdateDialog(context);
}
}
checkForBounusInvitation() {
if (box.read(BoxName.inviteCode) != null) {}
}
// Future<String> getPackageInfo() async {
// final response = await CRUD().get(link: AppLink.packageInfo, payload: {
// "platform": Platform.isAndroid ? 'android' : 'ios',
// "appName": AppInformation.appName,
// });
// if (response != 'failure') {
// return jsonDecode(response)['message'][0]['version'];
// }
// return '';
// }
// getDeviceFingerprint() async {
// final deviceInfo = await DeviceInfoPlugin().deviceInfo;
// var deviceData;
// if (Platform.isAndroid) {
// deviceData = deviceInfo.data;
// Log.print('deviceData: ${jsonEncode(deviceData)}');
// } else if (Platform.isIOS) {
// deviceData = deviceInfo.data;
// }
// final String deviceId =
// deviceData['device'] ?? deviceData['identifierForVendor'];
// final String deviceModel = deviceData['model'];
// final String osVersion = deviceData['systemVersion'];
// Log.print('fingr: ${'${deviceId}_${deviceModel}_$osVersion'}');
// Log.print('deviceModel: ${deviceModel}');
// Log.print('osVersion: ${osVersion}');
// return EncryptionHelper.instance
// .encryptData('${deviceId}_${deviceModel}_$osVersion');
// }
void showUpdateDialog(BuildContext context) {
final String storeUrl = Platform.isAndroid
? 'https://play.google.com/store/apps/details?id=com.Siro.siro'
: 'https://apps.apple.com/jo/app/siro-rider/id6748075179';
showGeneralDialog(
context: context,
barrierDismissible: false,
barrierColor: Colors.black.withOpacity(0.5),
pageBuilder: (_, __, ___) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: Center(
child: AlertDialog(
// Using AlertDialog for a more Material Design look
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16)), // More rounded corners
elevation: 4, // Add a bit more elevation
contentPadding: EdgeInsets.zero, // Remove default content padding
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Image.asset(
'assets/images/logo.png',
height: 72, // Slightly larger logo
width: 72,
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Text(
'Update Available'.tr,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
// Use theme's title style
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
'A new version of the app is available. Please update to the latest version.'
.tr, // More encouraging message
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
// Use theme's body style
color: Colors.black87,
),
),
),
const Divider(height: 0),
Row(
children: [
Expanded(
child: TextButton(
// Using TextButton for "Cancel"
onPressed: () => Navigator.pop(context),
style: TextButton.styleFrom(
foregroundColor: Colors.grey,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16),
),
),
),
child: Text('Cancel'.tr),
),
),
const SizedBox(
height: 48,
child: VerticalDivider(width: 0), // Using VerticalDivider
),
Expanded(
child: ElevatedButton(
// Using ElevatedButton for "Update"
onPressed: () async {
if (await canLaunchUrl(Uri.parse(storeUrl))) {
await launchUrl(Uri.parse(storeUrl));
}
if (context.mounted) Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColor
.primaryColor, // Use theme's primary color
foregroundColor: Theme.of(context)
.colorScheme
.onPrimary, // Use theme's onPrimary color
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(16),
),
),
),
child: Text('Update'.tr),
),
),
],
),
],
),
),
),
);
},
transitionBuilder: (_, animation, __, child) {
return ScaleTransition(
scale: CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic, // More natural curve
),
child: child,
);
},
);
}
class DeviceHelper {
static Future<String> getDeviceFingerprint() async {
// ── التحقق من وجود بصمة مخزّنة مسبقاً ──────────────────────
// AES-GCM يستخدم IV عشوائي كل مرة، فالتشفير ينتج نتيجة مختلفة
// حتى لو النص الأصلي نفسه. لذلك نخزّن البصمة المشفرة أول مرة
// ونرجعها من التخزين في كل مرة بعدها لضمان الثبات.
final String? cachedFp = box.read(BoxName.deviceFpEncrypted);
if (cachedFp != null && cachedFp.isNotEmpty) {
return cachedFp;
}
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
var deviceData;
try {
if (Platform.isAndroid) {
// Fetch Android-specific device information
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
deviceData = androidInfo.toMap(); // Convert to a map for easier access
// Log.print('deviceData: ${jsonEncode(deviceData)}');
} else if (Platform.isIOS) {
// Fetch iOS-specific device information
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
deviceData = iosInfo.toMap(); // Convert to a map for easier access
} else {
throw UnsupportedError('Unsupported platform');
}
// Extract relevant device information
final String deviceId = Platform.isAndroid
? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
: deviceData['identifierForVendor'] ?? 'unknown';
final String deviceModel = deviceData['model'] ?? 'unknown';
// final String osVersion = Platform.isAndroid
// ? deviceData['version']['release'] ?? 'unknown'
// : deviceData['systemVersion'] ?? 'unknown';
// Log the extracted information
// Generate and return the encrypted fingerprint
final String fingerprint = '${deviceId}_$deviceModel';
final String encryptedFp =
EncryptionHelper.instance.encryptData(fingerprint);
box.write(BoxName.deviceFpEncrypted, encryptedFp);
//Log.print(EncryptionHelper.instance.encryptData(fingerprint));
return encryptedFp;
} catch (e) {
throw Exception('Failed to generate device fingerprint');
}
}
}
@@ -0,0 +1,8 @@
// import 'package:ride/controller/functions/crud.dart';
// class RemoveAccount {
// void removeAccount()async{
// var res=await CRUD().post(link: link)
// }
// }
@@ -0,0 +1,25 @@
// import 'package:credit_card_scanner/credit_card_scanner.dart';
// import 'package:get/get.dart';
//
// class ScanIdCard extends GetxController {
// CardDetails? _cardDetails;
// CardScanOptions scanOptions = const CardScanOptions(
// scanCardHolderName: true,
// enableDebugLogs: true,
// validCardsToScanBeforeFinishingScan: 5,
// possibleCardHolderNamePositions: [
// CardHolderNameScanPosition.aboveCardNumber,
// ],
// );
//
// Future<void> scanCard() async {
// final CardDetails? cardDetails =
// await CardScanner.scanCard(scanOptions: scanOptions);
// if (cardDetails == null) {
// return;
// }
//
// _cardDetails = cardDetails;
// update();
// }
// }
@@ -0,0 +1,87 @@
import 'dart:convert';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/info.dart';
import 'package:intaleq_rider/controller/auth/login_controller.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import '../../constant/links.dart';
import '../../main.dart';
import 'crud.dart';
import 'encrypt_decrypt.dart';
class SecureStorage {
void saveData(String key, value) async {
await storage.write(key: key, value: value);
}
Future<String?> readData(String boxName) async {
final String? value = await storage.read(key: boxName);
return value.toString();
}
Future<void> deleteData(String key) async {
await storage.delete(key: key);
}
}
class AppInitializer {
List<Map<String, dynamic>> links = [];
/// تقرأ JWT من FlutterSecureStorage أولاً، مع fallback إلى GetStorage
Future<String?> _readJwt() async {
try {
final secureJwt = await storage.read(key: 'jwt');
if (secureJwt != null && secureJwt.isNotEmpty) return secureJwt;
} catch (_) {}
return box.read(BoxName.jwt);
}
Future<void> initializeApp() async {
final jwt = await _readJwt();
if (jwt == null) {
await LoginController().getJWT();
} else {
bool isTokenExpired =
JwtDecoder.isExpired(r(jwt).toString().split(AppInformation.addd)[0]);
if (isTokenExpired) {
await LoginController().getJWT();
}
}
// await getKey();
}
getAIKey(String key1) async {
if (box.read(BoxName.firstTimeLoadKey) == null) {
var res =
await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key1});
if (res != 'failure') {
var d = res['message'];
storage.write(key: key1, value: d[key1].toString());
} else {}
}
}
Future<void> getKey() async {
try {
var res =
await CRUD().get(link: AppLink.getLocationAreaLinks, payload: {});
if (res != 'failure') {
links = List<Map<String, dynamic>>.from(res['message']);
await box.remove(BoxName.locationName);
await box.remove(BoxName.basicLink);
await box.remove(links[4]['name']);
await box.remove(links[1]['name']);
await box.remove(links[2]['name']);
await box.write(BoxName.locationName, links);
await box.write(BoxName.basicLink, (links[0]['server_link']));
await box.write(links[2]['name'], (links[2]['server_link']));
await box.write(links[1]['name'], (links[3]['server_link']));
await box.write(links[3]['name'], (links[1]['server_link']));
await box.write(BoxName.paymentLink, (links[4]['server_link']));
}
} catch (e) {}
}
}
@@ -0,0 +1,56 @@
import 'package:intaleq_rider/print.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
class SecurityChecks {
static const platform = MethodChannel(
'com.Siro.siro/security'); // Choose a unique channel name
static Future<bool> isDeviceCompromised() async {
try {
final bool result = await platform
.invokeMethod('isNativeRooted'); // Invoke the native method
return result;
} on PlatformException catch (e) {
Log.print("Failed to check security status: ${e.message}");
return false; // Platform not supported → treat as secure (not compromised)
} catch (e) {
Log.print("Security check error: $e");
return false;
}
}
static isDeviceRootedFromNative(BuildContext context) async {
bool compromised = await isDeviceCompromised();
if (compromised) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text("Security Warning".tr),
content: Text(
"Your device appears to be compromised. The app will now close."
.tr),
actions: [
TextButton(
onPressed: () {
SystemNavigator.pop(); // Close the app
},
child: Text("OK"),
),
],
),
);
} else {
// Continue with normal app flow
box.write(BoxName.security_check, 'passed');
Log.print("Device is secure.");
}
}
}
@@ -0,0 +1,167 @@
import 'dart:convert';
import 'package:intaleq_rider/constant/api_key.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/info.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/auth/login_controller.dart';
import 'package:intaleq_rider/env/env.dart';
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/views/widgets/elevated_btn.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../print.dart';
import 'crud.dart';
class SmsEgyptController extends GetxController {
var headers = {'Content-Type': 'application/json'};
Future<String> getSender() async {
var res = await CRUD().get(link: AppLink.getSender, payload: {});
if (res != 'failure') {
var d = res['message'][0]['senderId'].toString();
return d;
} else {
return "Sefer Egy";
}
}
Future<dynamic> sendSmsEgypt(String phone, otp) async {
// String sender = await getSender();
// var body = jsonEncode({
// "username": 'Sefer',
// "password": AK.smsPasswordEgypt,
// "message": "${AppInformation.appName} app code is $otp\ncopy it to app",
// "language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
// "sender": sender, //"Sefer Egy",
// "receiver": phone
// });
var res = await CRUD().post(link: AppLink.sendSmsFromPHP, payload: {
"language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
"receiver": phone,
});
if (res != 'failure') {
// var res = await http.post(
// Uri.parse(AppLink.sendSms),
// body: body,
// headers: headers,
// );
// else if (jsonDecode(res)['message'].toString() ==
// "Invalid Sender with Connection") {
//
// }
// else {
Get.defaultDialog(
title: 'You will receive a code in SMS message'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
}));
} else {
await CRUD().post(link: AppLink.updatePhoneInvalidSMSPassenger, payload: {
"phone_number":
'+2${Get.find<LoginController>().phoneController.text}'
});
box.write(BoxName.phoneDriver,
'+2${Get.find<LoginController>().phoneController.text}');
box.write(BoxName.isVerified, '1');
await Get.put(LoginController()).loginUsingCredentials(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(),
);
}
}
Future checkCredit(String phone, otp) async {
var res = await http.post(
Uri.parse(AppLink.checkCredit),
body: {
"username": AppInformation.appName,
"password": AK.smsPasswordEgypt,
},
headers: headers,
);
}
Future sendSmsWithValidaty(String phone, otp) async {
var res = await http.post(
Uri.parse(AppLink.checkCredit),
body: {
"username": AppInformation.appName,
"password": AK.smsPasswordEgypt,
"message": "This is an example SMS message.",
"language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
"sender": "Kazumi", // todo add sefer sender name
"receiver": "2$phone",
"validity": "10",
"StartTime": DateTime.now().toString() // "1/1/2024 10:00:00"
},
headers: headers,
);
}
Future sendSmsStatus(String smsid) async {
var res = await http.post(
Uri.parse(AppLink.checkCredit),
body: {
"username": AppInformation.appName,
"password": AK.smsPasswordEgypt,
"smsid": smsid //"00b77dfc-5b8f-474d-9def-9f0158b70f98"
},
headers: headers,
);
}
Future sendWhatsAppAuth(String to, String token) async {
var headers = {
'Authorization': 'Bearer ${Env.whatsapp}',
'Content-Type': 'application/json'
};
var request = http.Request(
'POST',
Uri.parse(
'https://graph.facebook.com/v20.0/${Env.whatappID}/messages'));
request.body = json.encode({
"messaging_product": "whatsapp",
"to": to, //"962798583052",
"type": "template",
"template": {
"name": "sefer1",
"language": {"code": "en"},
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": token,
}
]
}
]
}
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
Get.defaultDialog(
title: 'You will receive a code in WhatsApp Messenger'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
}));
} else {
print(response.reasonPhrase);
}
}
}
@@ -0,0 +1,42 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http_io;
class SslPinning {
SslPinning._();
static final Map<String, List<String>> _pins = {
'intaleq.xyz': [
'/tNRUeeLxUhQU5gbgdpVWC6QBGAqc/ujg8Kcf0wQiAM=',
'Hlx/0EWNDH5Xkt2KzvqxUzbw0vvEsyZSlibialSyGqI=',
],
'siromove.com': [
'C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=',
'diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=',
],
};
static final List<String> _globalPins = [
'Ex/Od4QBaJmloAIDqe/IDxjrvXVYBxftwVU1gJMINuw=',
'lrzsBiZJdvN0YHeazyjFp8/oo8Cq4RqP/O4FwL3fCMY=',
'aXKbjhWobvwXelevtxcd/GSt0owvyozxUH40RTzLFHA=',
];
static http.Client createPinnedClient() {
final httpClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) {
final derHash = base64.encode(sha256.convert(cert.der).bytes);
for (final entry in _pins.entries) {
if (host == entry.key || host.endsWith('.${entry.key}')) {
if (entry.value.contains(derHash)) return true;
}
}
if (_globalPins.contains(derHash)) return true;
return false;
};
return http_io.IOClient(httpClient);
}
}
@@ -0,0 +1,18 @@
import 'package:secure_string_operations/secure_string_operations.dart';
import '../../constant/char_map.dart';
import '../../main.dart';
class Sss {
static read(String boxname) async {
return box.read(X.r(X.r(X.r(boxname, cn), cC), cs));
}
static write(String boxname, value) async {
return box.write(boxname, X.c(X.c(X.c(value, cn), cC), cs));
}
static delete(String boxname) async {
return box.remove(boxname);
}
}
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/colors.dart';
import 'package:intaleq_rider/constant/style.dart';
class Toast {
static void show(BuildContext context, String message, Color color) {
final snackBar = SnackBar(
clipBehavior: Clip.antiAliasWithSaveLayer,
backgroundColor: color,
elevation: 3,
content: Text(
message,
style: AppStyle.title.copyWith(color: AppColor.secondaryColor),
),
behavior: SnackBarBehavior.floating,
animation: const AlwaysStoppedAnimation(1.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0), // Custom border radius
),
width: Get.width * .8,
// shape: const StadiumBorder(
// side: BorderSide(
// color: AppColor.secondaryColor,
// width: 1.0,
// style: BorderStyle.solid,
// )),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context).showSnackBar(
snackBar,
);
}
}
@@ -0,0 +1,30 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
class TranslateHelper {
static Future<String> translateText(String text, String targetLang) async {
if (text.isEmpty) return text;
try {
final url = Uri.parse(
'https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=$targetLang&dt=t&q=${Uri.encodeComponent(text)}'
);
final response = await http.get(url);
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
if (decoded != null && decoded is List && decoded.isNotEmpty && decoded[0] is List) {
final List parts = decoded[0];
String translated = '';
for (var part in parts) {
if (part is List && part.isNotEmpty) {
translated += part[0].toString();
}
}
return translated;
}
}
} catch (e) {
// Fallback to original text on any exception
}
return text;
}
}
@@ -0,0 +1,75 @@
import 'dart:io';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:get/get.dart';
import '../../main.dart';
class TextToSpeechController extends GetxController {
final flutterTts = FlutterTts();
@override
void onInit() {
super.onInit();
initTts();
}
@override
void onClose() {
flutterTts.stop(); // Stop any ongoing TTS
super.onClose();
}
// Initialize TTS engine with language check
Future<void> initTts() async {
try {
String langCode = box.read(BoxName.lang) ?? 'ar-SA';
if (langCode == 'ar') langCode = 'ar-SA';
if (langCode == 'en') langCode = 'en-US';
bool isAvailable = await flutterTts.isLanguageAvailable(langCode);
if (!isAvailable) {
if (langCode.startsWith('ar')) {
// Try to find ANY available Arabic language
List<dynamic> languages = await flutterTts.getLanguages;
String? fallbackAr = languages
.cast<String>()
.firstWhere((l) => l.startsWith('ar'), orElse: () => '');
if (fallbackAr != null && fallbackAr.isNotEmpty) {
langCode = fallbackAr;
isAvailable = true;
}
}
if (!isAvailable) langCode = 'en-US';
}
await flutterTts.setLanguage(langCode);
await flutterTts.setSpeechRate(0.5);
await flutterTts.setVolume(1.0);
if (Platform.isIOS) {
await flutterTts.setIosAudioCategory(
IosTextToSpeechAudioCategory.playback,
[
IosTextToSpeechAudioCategoryOptions.mixWithOthers,
IosTextToSpeechAudioCategoryOptions.duckOthers,
],
);
}
} catch (error) {
print('TTS Init Error: $error');
}
}
// Function to speak the given text (stops current speech first)
Future<void> speakText(String text) async {
if (text.isEmpty) return;
try {
await flutterTts.stop();
await flutterTts.speak(text);
} catch (error) {
print('Failed to speak text: $error');
}
}
}
@@ -0,0 +1,22 @@
// import 'package:ride/constant/credential.dart';
// import 'package:twilio_flutter/twilio_flutter.dart';
//
// class TwilioSMS {
// TwilioFlutter twilioFlutter = TwilioFlutter(
// accountSid: AppCredintials.accountSIDTwillo,
// authToken: AppCredintials.authTokenTwillo,
// twilioNumber: '+962 7 9858 3052');
//
// Future<void> sendSMS({
// required String recipientPhoneNumber,
// required String message,
// }) async {
// try {
// await twilioFlutter.sendSMS(
// toNumber: recipientPhoneNumber,
// messageBody: message,
// );
// } catch (e) {
// }
// }
// }
@@ -0,0 +1,108 @@
import 'dart:convert';
import 'dart:io';
import 'package:intaleq_rider/constant/api_key.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
import '../../constant/box_name.dart';
import '../../constant/char_map.dart';
import '../../constant/colors.dart';
import '../../constant/info.dart';
import '../../main.dart';
import 'encrypt_decrypt.dart';
class ImageController extends GetxController {
File? myImage;
bool isloading = false;
CroppedFile? croppedFile;
final picker = ImagePicker();
var image;
choosImage(String link, String imageType) async {
final pickedImage = await picker.pickImage(source: ImageSource.gallery);
image = File(pickedImage!.path);
croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
IOSUiSettings(
title: 'Cropper'.tr,
),
],
);
myImage = File(pickedImage.path);
isloading = true;
update();
// Save the cropped image
File savedCroppedImage = File(croppedFile!.path);
try {
await uploadImage(
savedCroppedImage,
{
'driverID':
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
'imageType': imageType
},
link,
);
} catch (e) {
mySnackbarError(e.toString());
} finally {
isloading = false;
update();
}
}
uploadImage(File file, Map data, String link) async {
var request = http.MultipartRequest(
'POST',
Uri.parse(link), //'https://ride.mobile-app.store/uploadImage1.php'
);
var length = await file.length();
var stream = http.ByteStream(file.openRead());
var multipartFile = http.MultipartFile(
'image',
stream,
length,
filename: basename(file.path),
);
final String fingerPrint =
box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
request.headers.addAll({
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0]}',
'X-Device-FP': fingerPrint,
});
// Set the file name to the driverID
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
if (res.statusCode == 200) {
return jsonDecode(res.body);
} else {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
}
@@ -0,0 +1,91 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/elevated_btn.dart';
import '../functions/crud.dart';
class BlinkingController extends GetxController {
final promoFormKey = GlobalKey<FormState>();
final promo = TextEditingController();
bool promoTaken = false;
void applyPromoCodeToPassenger() async {
//TAWJIHI
if (promoFormKey.currentState!.validate()) {
CRUD().get(link: AppLink.getPassengersPromo, payload: {
'promo_code': promo.text,
}).then((value) {
if (value == 'failure') {
Get.defaultDialog(
title: 'Promo End !'.tr,
confirm: MyElevatedButton(
title: 'Back',
onPressed: () {
Get.back();
Get.back();
},
));
}
var decode = value;
// if (decode["status"] == "success") {
// var firstElement = decode["message"][0];
// if (double.parse(box.read(BoxName.passengerWalletTotal)) < 0) {
// totalPassenger = totalCostPassenger -
// (totalCostPassenger * int.parse(firstElement['amount']) / 100);
// update();
// } else {
// totalPassenger = totalCostPassenger -
// (totalCostPassenger * int.parse(firstElement['amount']) / 100);
// update();
// }
// totalDriver = totalDriver -
// (totalDriver * int.parse(firstElement['amount']) / 100);
// promoTaken = true;
// update();
// Get.back();
// }
});
}
}
// Reactive variable for blinking (on/off)
var isLightOn = false.obs;
// To animate the border color
var borderColor = Colors.black.obs;
Timer? _blinkingTimer;
// Method to start blinking for 5 seconds
void startBlinking() {
int count = 0;
_blinkingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// Toggle light on/off
isLightOn.value = !isLightOn.value;
borderColor.value = isLightOn.value
? Colors.yellow
: Colors.black; // Animate border color
count++;
// Stop blinking after 5 seconds
if (count >= 35) {
timer.cancel();
isLightOn.value = false; // Ensure light turns off
borderColor.value = Colors.black; // Reset the border color
}
});
}
@override
void onClose() {
_blinkingTimer?.cancel();
super.onClose();
}
}
@@ -0,0 +1,89 @@
#!/bin/bash
ORIG_FILE="lib/controller/home/map_passenger_controller.dart"
ALL_FILES="lib/controller/home/map/location_search_controller.dart lib/controller/home/map/map_engine_controller.dart lib/controller/home/map/map_screen_binding.dart lib/controller/home/map/map_socket_controller.dart lib/controller/home/map/nearby_drivers_controller.dart lib/controller/home/map/ride_lifecycle_controller.dart lib/controller/home/map/ui_interactions_controller.dart"
echo "Extracting methods from original controller..."
# Methods typically start with spaces and have patterns like:
# returnType methodName( or methodName(
# Let's extract words that precede ( on lines that don't start with keywords (if, for, while, switch, catch, etc.)
# We will use awk to parse.
METHODS=$(cat "$ORIG_FILE" | awk '
# Skip single-line comments
/\/\// { next }
# Skip imports and class declarations
/import/ || /class/ { next }
# Find lines with "("
/\(/ {
# Replace anything inside parentheses and curly braces to simplify
gsub(/\(.*\)/, "()")
# Find word before "()"
for (i = 1; i <= NF; i++) {
if ($i ~ /[a-zA-Z0-9_]+\(\)/) {
name = $i
sub(/\(\)/, "", name)
# Remove any leading modifiers like async, Future, static, etc.
# Only keep valid identifiers that are not control keywords
if (name !~ /^(if|for|while|switch|catch|super|await|print|assert|dynamic|void|return|with|override|get|set|else|try|final|const|var|late|static|factory|new|abstract|covariant|external|operator|part|required|typedef|yield)$/ && name ~ /^[a-zA-Z_][a-zA-Z0-9_]*$/) {
print name
}
}
}
}' | sort -u)
echo "Extracting fields/variables from original controller..."
# Fields are usually declared inside the class at the beginning of lines or indented.
# e.g., RxBool isSearching = false.obs; or String? rideId;
VARS=$(cat "$ORIG_FILE" | awk '
/\/\// { next }
/import/ || /class/ { next }
# Lines ending with ";" or containing "=" followed by ";"
/;/ {
# Extract words that look like declarations.
# We look for typical type names or var/final followed by variable name
for (i = 1; i < NF; i++) {
if ($i ~ /^(var|final|const|late|RxBool|RxInt|RxDouble|RxString|RxList|RxMap|RxSet|Rx|String|int|double|bool|List|Map|Set|Timer|LatLng|Position|SiroMapController)$/) {
# The next field might be the variable name, or it might have a type like String?
name = $(i+1)
# Remove trailing ?, ;, =
sub(/\?/, "", name)
sub(/;/, "", name)
sub(/=/, "", name)
if (name ~ /^[a-zA-Z_][a-zA-Z0-9_]*$/) {
print name
}
}
}
}' | sort -u)
echo "Checking split files for methods..."
echo "--- MISSING METHODS ---"
MISSING_METHODS_COUNT=0
# Create a temporary file with all split contents to search efficiently
cat $ALL_FILES > lib/controller/home/temp_split_combined.txt
for method in $METHODS; do
# Search for this method name as a whole word in split controllers
FOUND=$(grep -w "$method" lib/controller/home/temp_split_combined.txt 2>/dev/null)
if [ -z "$FOUND" ]; then
echo " - $method"
MISSING_METHODS_COUNT=$((MISSING_METHODS_COUNT+1))
fi
done
echo "Total missing methods: $MISSING_METHODS_COUNT"
echo ""
echo "Checking split files for variables/fields..."
echo "--- MISSING VARIABLES ---"
MISSING_VARS_COUNT=0
for var in $VARS; do
FOUND=$(grep -w "$var" lib/controller/home/temp_split_combined.txt 2>/dev/null)
if [ -z "$FOUND" ]; then
echo " - $var"
MISSING_VARS_COUNT=$((MISSING_VARS_COUNT+1))
fi
done
echo "Total missing variables: $MISSING_VARS_COUNT"
# Clean up temp file
rm lib/controller/home/temp_split_combined.txt
@@ -0,0 +1,104 @@
import sys
import re
def parse_stream(stream_text):
# Splits the stream by our custom file delimiters
files = {}
parts = re.split(r'=== FILE: (.*?) ===\n', stream_text)
# The first part is the original monolithic file
if parts:
files['original'] = parts[0]
for i in range(1, len(parts), 2):
filename = parts[i]
content = parts[i+1] if i+1 < len(parts) else ""
files[filename] = content
return files
def strip_comments(text):
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
text = re.sub(r'//.*', '', text)
return text
def extract_declarations(text):
clean = strip_comments(text)
# Matches method/function declarations inside a class in Dart
# e.g., void myMethod(..., Future<void> myMethod(..., myMethod(..., get myProp, set myProp
# We look for word followed by ( or get/set followed by word.
method_decl_pattern = re.compile(
r'(?:[a-zA-Z0-9_<>\?\[\]]+(?:\s+[a-zA-Z0-9_<>\?\[\]]+)*\s+)?([a-zA-Z0-9_]+)\s*\([^\)]*\)\s*(?:async)?\s*(?:=>|\{)'
)
methods = set()
for match in method_decl_pattern.finditer(clean):
method_name = match.group(1)
if method_name not in keywords and not method_name.isdigit():
methods.add(method_name)
# Also extract getters and setters
getset_pattern = re.compile(r'\b(?:get|set)\s+([a-zA-Z0-9_]+)\b')
for match in getset_pattern.finditer(clean):
name = match.group(1)
if name not in keywords:
methods.add(name)
# Extract variables/fields declarations
var_decl_pattern = re.compile(
r'\b(?:var|final|const|late|RxBool|RxInt|RxDouble|RxString|RxList|RxMap|RxSet|Rx|String|int|double|bool|List|Map|Set|Timer|LatLng|Position|SiroMapController)\??\s+([a-zA-Z0-9_]+)\b'
)
variables = set()
for match in var_decl_pattern.finditer(clean):
var_name = match.group(1)
if var_name not in keywords and not var_name.isdigit():
variables.add(var_name)
return methods, variables
keywords = {
'if', 'for', 'while', 'switch', 'catch', 'super', 'await', 'print',
'assert', 'dynamic', 'void', 'return', 'with', 'override', 'get', 'set',
'class', 'import', 'extends', 'implements', 'mixin', 'this', 'else', 'try',
'final', 'const', 'var', 'late', 'static', 'factory', 'new', 'abstract',
'covariant', 'external', 'operator', 'part', 'required', 'typedef', 'yield'
}
def main():
stream_text = sys.stdin.read()
files = parse_stream(stream_text)
orig_content = files.get('original', '')
split_contents = {k: v for k, v in files.items() if k != 'original'}
orig_methods, orig_vars = extract_declarations(orig_content)
# Combined declarations in split files
split_methods = set()
split_vars = set()
for filename, content in split_contents.items():
m, v = extract_declarations(content)
split_methods.update(m)
split_vars.update(v)
missing_methods = sorted(orig_methods - split_methods)
missing_vars = sorted(orig_vars - split_vars)
print("--- PRECISE MISSING METHODS ---")
print(f"Total original methods/getters/setters: {len(orig_methods)}")
print(f"Total defined in split controllers: {len(split_methods)}")
print(f"Total missing: {len(missing_methods)}")
for m in missing_methods:
print(f" - {m}")
print("\n--- PRECISE MISSING VARIABLES/FIELDS ---")
print(f"Total original variables: {len(orig_vars)}")
print(f"Total defined in split controllers: {len(split_vars)}")
print(f"Total missing: {len(missing_vars)}")
for v in missing_vars:
print(f" - {v}")
if __name__ == '__main__':
main()
@@ -0,0 +1,103 @@
Extracting methods from original controller...
Extracting fields/variables from original controller...
Checking split files for methods...
--- MISSING METHODS ---
- _applyLowEndModeIfNeeded
- _buildOsrmWaypointCoords
- _calculateDistance
- _checkAndRecalculateIfDeviated
- _fillDriverDataLocally
- _haversineKm
- _initMinimalIcons
- _initializePolygons
- _isActiveRideState
- _kmToLatDelta
- _kmToLngDelta
- _onDriverArrivedWithSocket
- _onRideCancelledWithSocket
- _onRideStartedWithSocket
- _relevanceScore
- _restorePolyline
- _stageNiceToHave
- _stagePricingAndState
- _startMasterTimer
- _startMasterTimerWithInterval
- _startPollingFallback
- _stopDriverLocationPolling
- _updateDriverMarker
- cancelRide
- detectPerfMode
- getAIKey
- getMapPointsForAllMethods
- getPassengerLocationUniversity
- handleActiveRideOnStartup
- isDriversDataValid
- onChangedPassengerCount
- onChangedPassengersChoose
- showDrawingBottomSheet
- showNoDriversDialog
- startSearchingTimer
Total missing methods: 35
Checking split files for variables/fields...
--- MISSING VARIABLES ---
- _isStateProcessing
- _isUsingFallback
- _maxReconnectAttempts
- apiDistanceMeters
- c
- carInfo
- carsOrder
- coordDestination
- currentCarType
- currentDriverLocation
- currentLocationOfDrivers
- currentRideId
- currentTimeSearchingCaptainWindow
- dInfo
- dLat
- datadriverCarsLocationToPassengerAfterApplied
- distanceOfTrip
- driverCarPlate
- driverLocationToPassenger
- driverOrderStatus
- durationByPassenger
- endLocation
- fName
- finalReason
- headingList
- increaseFeeFormKey
- isDriversTokensSend
- isFirstWaypoint
- isInUniversity
- isSaaSRequest
- kmInDegree
- lName
- latDest
- latestPosition
- lngDest
- lowPerf
- messagesFormKey
- originCoords
- pLower
- passengerLocationStringUnvirsity
- previousLocationOfDrivers
- progressTimerRideBeginVip
- qLower
- rLat1
- rLat2
- ram
- rideData
- sdk
- selectedPassengerCount
- startLng
- startLocation
- stringElapsedTimeRideBegin
- tax
- totalPassengerBalashDiscount
- totalPassengerComfortDiscount
- totalPassengerElectricDiscount
- totalPassengerLadyDiscount
- totalPassengerRaihGaiDiscount
- totalPassengerSpeedDiscount
Total missing variables: 59
@@ -0,0 +1,121 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../functions/launch.dart';
class ContactUsController extends GetxController {
/// WORKING HOURS (10:00 → 16:00)
final TimeOfDay workStartTime = const TimeOfDay(hour: 10, minute: 0);
final TimeOfDay workEndTime = const TimeOfDay(hour: 16, minute: 0);
bool get isWorkTime {
final now = TimeOfDay.now();
return (now.hour > workStartTime.hour ||
(now.hour == workStartTime.hour &&
now.minute >= workStartTime.minute)) &&
(now.hour < workEndTime.hour ||
(now.hour == workEndTime.hour && now.minute <= workEndTime.minute));
}
/// Helper to format working hours for UI
String get workHoursString =>
'${workStartTime.hour.toString().padLeft(2, '0')}:${workStartTime.minute.toString().padLeft(2, '0')} - '
'${workEndTime.hour.toString().padLeft(2, '0')}:${workEndTime.minute.toString().padLeft(2, '0')}';
/// PHONE LIST (USED FOR CALLS + WHATSAPP)
final List<String> phoneNumbers = [
'+963952475734',
'+963952475740',
'+963952475742'
];
/// RANDOM PHONE SELECTOR
String getRandomPhone() {
final random = Random();
return phoneNumbers[random.nextInt(phoneNumbers.length)];
}
/// DIRECT ACTIONS
void makeCall() {
if (isWorkTime) {
makePhoneCall(getRandomPhone());
}
}
void sendWhatsApp() {
launchCommunication('whatsapp', getRandomPhone(), 'Hello'.tr);
}
void sendEmail() {
launchCommunication('email', 'support@intaleqapp.com', 'Hello'.tr);
}
/// SHOW DIALOG (Optional legacy support)
void showContactDialog(BuildContext context) {
bool withinHours = isWorkTime;
showCupertinoModalPopup(
context: context,
builder: (context) => CupertinoActionSheet(
title: Text('Contact Us'.tr),
message: Text('Choose a contact option'.tr),
actions: <Widget>[
if (withinHours)
CupertinoActionSheetAction(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(CupertinoIcons.phone),
Text('Call Support'.tr),
],
),
onPressed: () {
Navigator.pop(context);
makeCall();
},
),
if (!withinHours)
CupertinoActionSheetAction(
child: Text(
'Work time is from 10:00 AM to 16:00 PM.\nYou can send a WhatsApp message or email.'
.tr,
textAlign: TextAlign.center,
),
onPressed: () => Navigator.pop(context),
),
CupertinoActionSheetAction(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
Text('Send WhatsApp Message'.tr),
],
),
onPressed: () {
Navigator.pop(context);
sendWhatsApp();
},
),
CupertinoActionSheetAction(
child: Text('Send Email'.tr),
onPressed: () {
Navigator.pop(context);
sendEmail();
},
),
],
cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr),
onPressed: () => Navigator.pop(context),
),
),
);
}
}
@@ -0,0 +1,62 @@
import 'package:intaleq_maps/intaleq_maps.dart';
List<LatLng> decodePolylineIsolate(String encoded) {
List<LatLng> points = [];
int index = 0, len = encoded.length;
int lat = 0, lng = 0;
while (index < len) {
int b, shift = 0, result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
points.add(LatLng(lat / 1E5, lng / 1E5));
}
return points;
}
// Helper method for decoding polyline (if not already defined)
// List<LatLng> decodePolyline(String encoded) {
// List<LatLng> points = [];
// int index = 0, len = encoded.length;
// int lat = 0, lng = 0;
// while (index < len) {
// int b, shift = 0, result = 0;
// do {
// b = encoded.codeUnitAt(index++) - 63;
// result |= (b & 0x1f) << shift;
// shift += 5;
// } while (b >= 0x20);
// int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
// lat += dlat;
// shift = 0;
// result = 0;
// do {
// b = encoded.codeUnitAt(index++) - 63;
// result |= (b & 0x1f) << shift;
// shift += 5;
// } while (b >= 0x20);
// int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
// lng += dlng;
// points.add(LatLng(lat / 1E5, lng / 1E5));
// }
// return points;
// }
@@ -0,0 +1,68 @@
import 'package:intaleq_rider/print.dart';
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:get/get.dart';
import '../../main.dart';
import 'profile/invites_rewards_controller.dart';
class DeepLinkController extends GetxController {
final _appLinks = AppLinks();
StreamSubscription<Uri>? _linkSubscription;
// تخزين الرابط الخام (URL) ليتم معالجته لاحقاً في MapPassengerController
final Rx<String?> rawDeepLink = Rx<String?>(null);
@override
void onInit() {
super.onInit();
initDeepLinks();
}
Future<void> initDeepLinks() async {
// الاستماع للروابط والتطبيق يعمل في الخلفية
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
Log.print('🔗 Received deep link (Stream): $uri');
rawDeepLink.value = uri.toString();
_processInviteCode(uri);
});
// الاستماع للروابط إذا كان التطبيق مغلقاً تماماً (Cold Start)
try {
final initialUri = await _appLinks.getInitialLink();
if (initialUri != null) {
Log.print('🔗 Received initial deep link (Cold Start): $initialUri');
rawDeepLink.value = initialUri.toString();
_processInviteCode(initialUri);
}
} catch (e) {
Log.print('Error getting initial link: $e');
}
}
void _processInviteCode(Uri uri) {
String? inviteCode = uri.queryParameters['code'] ??
uri.queryParameters['inviteCode'] ??
uri.queryParameters['ref'];
if (inviteCode != null && inviteCode.isNotEmpty) {
// دائماً نخزن الكود حتى لو لم يكن مسجلاً، ليتم استخدامه بعد التسجيل
box.write('inviteCode', inviteCode);
// إذا كان المستخدم مسجل دخول (يمتلك JWT)، نربط الدعوة فوراً
if (box.read('jwt') != null) {
try {
var rewardsController = Get.find<InvitesRewardsController>();
rewardsController.linkInviteCode(inviteCode);
} catch (e) {
var rewardsController = Get.put(InvitesRewardsController());
rewardsController.linkInviteCode(inviteCode);
}
}
}
}
@override
void onClose() {
_linkSubscription?.cancel();
super.onClose();
}
}
@@ -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;
}
}
@@ -0,0 +1,86 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import '../../main.dart';
// مفاتيح التخزين (بسيطة)
const _kDeviceTierKey = 'deviceTier'; // 'low' | 'mid' | 'high'
const _kDeviceTierCheckedAtKey = 'deviceTierTime'; // millisSinceEpoch
Future<String> detectAndCacheDeviceTier({bool force = false}) async {
// لا تعيد الفحص إذا عملناه خلال آخر 24 ساعة
if (!force) {
final last = box.read(_kDeviceTierCheckedAtKey);
if (last is int) {
final dt = DateTime.fromMillisecondsSinceEpoch(last);
if (DateTime.now().difference(dt) < const Duration(hours: 24)) {
final cached = box.read(_kDeviceTierKey);
if (cached is String && cached.isNotEmpty)
return cached; // low/mid/high
}
}
}
final info = DeviceInfoPlugin();
int score = 0;
if (Platform.isAndroid) {
final a = await info.androidInfo;
final int sdk = a.version.sdkInt ?? 0;
final int cores = Platform.numberOfProcessors;
final int abisCount = a.supportedAbis.length;
final bool isEmu = !(a.isPhysicalDevice ?? true);
// SDK (أقدم = أضعف)
if (sdk <= 26)
score += 3; // 8.0 وأقدم
else if (sdk <= 29)
score += 2; // 9-10
else if (sdk <= 30) score += 1; // 11
// الأنوية
if (cores <= 4)
score += 3;
else if (cores <= 6)
score += 2;
else if (cores <= 8) score += 1;
// ABI count (القليل غالباً أضعف)
if (abisCount <= 1)
score += 2;
else if (abisCount == 2) score += 1;
// محاكي
if (isEmu) score += 2;
} else {
// iOS/منصات أخرى: تقدير سريع بالأنوية فقط
final int cores = Platform.numberOfProcessors;
if (cores <= 4)
score += 3;
else if (cores <= 6)
score += 2;
else if (cores <= 8) score += 1;
}
// تحويل السكور إلى تصنيف
final String tier = (score >= 6)
? 'low'
: (score >= 3)
? 'mid'
: 'high';
box.write(_kDeviceTierKey, tier);
box.write(_kDeviceTierCheckedAtKey, DateTime.now().millisecondsSinceEpoch);
return tier;
}
// للقراءة السريعة وقت ما تحتاج:
String getCachedDeviceTier() {
final t = box.read(_kDeviceTierKey);
if (t is String && t.isNotEmpty) return t;
return 'mid';
}
bool isLowEnd() => getCachedDeviceTier() == 'low';
bool isMidEnd() => getCachedDeviceTier() == 'mid';
bool isHighEnd() => getCachedDeviceTier() == 'high';
@@ -0,0 +1,15 @@
import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
class HomePageController extends GetxController {
late bool isVibrate = box.read(BoxName.isvibrate) ?? true;
void changeVibrateOption(bool value) {
isVibrate = box.read(BoxName.isvibrate) ?? true;
isVibrate = value;
box.write(BoxName.isvibrate, value);
update();
}
}
@@ -0,0 +1,74 @@
import 'package:intaleq_rider/print.dart';
import 'dart:io';
import 'dart:convert';
import 'package:live_activities/live_activities.dart';
class IosLiveActivityService {
static final _liveActivitiesPlugin = LiveActivities();
static String? _activityId;
static void init() {
if (Platform.isIOS) {
_liveActivitiesPlugin.init(
appGroupId: "group.com.Siro.siro",
);
}
}
static Future<void> startRideActivity({
required String rideId,
required String driverName,
required String carDetails,
required String etaText,
required double progress,
}) async {
if (!Platform.isIOS) return;
try {
await _liveActivitiesPlugin.endAllActivities();
// استخدام dynamic يسمح للحزمة بحفظ النصوص والأرقام في الذاكرة المشتركة
final Map<String, dynamic> activityModel = {
'status': 'waiting',
'driverName': driverName,
'carDetails': carDetails,
'etaText': etaText,
'progress': progress,
};
// الدالة هنا تأخذ المعاملات مباشرة
_activityId = await _liveActivitiesPlugin.createActivity(
rideId,
activityModel,
);
} catch (e) {
Log.print("❌ Live Activity Start Error: $e");
}
}
static Future<void> updateRideActivity({
required String status,
required String driverName,
required String carDetails,
required String etaText,
required double progress,
}) async {
if (!Platform.isIOS || _activityId == null) return;
final Map<String, dynamic> updatedModel = {
'status': status,
'driverName': driverName,
'carDetails': carDetails,
'etaText': etaText,
'progress': progress,
};
await _liveActivitiesPlugin.updateActivity(_activityId!, updatedModel);
}
static Future<void> endRideActivity() async {
if (!Platform.isIOS || _activityId == null) return;
await _liveActivitiesPlugin.endActivity(_activityId!);
_activityId = null;
}
}
@@ -0,0 +1,15 @@
class CarLocation {
final String id;
final double latitude;
final double longitude;
final double distance;
final double duration;
CarLocation({
required this.id,
required this.latitude,
required this.longitude,
this.distance = 10000,
this.duration = 10000,
});
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,873 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'dart:math' show cos, max, min, pi, pow, sqrt;
import 'dart:typed_data';
import 'package:intaleq_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:image/image.dart' as img;
import '../../../constant/colors.dart';
// contains global 'box'
import '../../../print.dart';
import '../../../views/home/map_widget.dart/cancel_raide_page.dart';
import 'location_search_controller.dart';
import 'nearby_drivers_controller.dart';
import 'ride_lifecycle_controller.dart';
import '../points_for_rider_controller.dart';
import '../../../constant/univeries_polygon.dart';
class MapEngineController extends GetxController {
IntaleqMapController? mapController;
bool isStyleLoaded = false;
bool isIconsLoaded = false;
Set<Marker> markers = {};
Set<Polyline> polyLines = {};
int markerRevision = 0;
List<LatLng> polylineCoordinates = [];
Set<Polygon> polygons = {};
Set<Circle> circles = {};
LatLngBounds? lastComputedBounds;
bool mapType = false;
bool mapTrafficON = false;
bool isMarkersShown = false;
String markerIcon = "marker_icon";
String tripIcon = "trip_icon";
String startIcon = "start_icon";
String endIcon = "end_icon";
String carIcon = "car_icon";
String motoIcon = "moto_icon";
String ladyIcon = "lady_icon";
int _routeRenderRevision = 0;
double height = 150;
double heightMenu = 0;
double widthMenu = 0;
double heightPickerContainer = 90;
double heightPointsPageForRider = 0;
double mainBottomMenuMapHeight = Get.height * .2;
double wayPointSheetHeight = 0;
bool heightMenuBool = false;
bool _isPickerShown = false;
bool get isPickerShown => _isPickerShown;
set isPickerShown(bool value) {
_isPickerShown = value;
update();
}
bool isPointsPageForRider = false;
bool isBottomSheetShown = false;
bool reloadStartApp = false;
bool isCancelRidePageShown = false;
bool isCashConfirmPageShown = false;
bool isPaymentMethodPageShown = false;
bool isRideFinished = false;
bool rideConfirm = false;
bool isMainBottomMenuMap = true;
bool isWayPointSheet = false;
bool isWayPointStopsSheet = false;
bool isWayPointStopsSheetUtilGetMap = false;
double heightBottomSheetShown = 0;
double cashConfirmPageShown = 250;
double widthMapTypeAndTraffic = 50;
double paymentPageShown = Get.height * .6;
bool isAnotherOreder = false;
bool isWhatsAppOrder = false;
Map<String, Timer> _animationTimers = {};
final int updateIntervalMs = 100;
final double minMovementThreshold = 1.0;
void onMapCreated(IntaleqMapController controller) {
mapController = controller;
update();
}
Future<void> onStyleLoaded() async {
Log.print('🗺️ Siro Map Style Loaded. Initializing...');
isStyleLoaded = true;
await _loadMapIcons();
final locationSearch = Get.find<LocationSearchController>();
Get.find<RideLifecycleController>().reinit();
if (mapController != null) {
if (markers.isNotEmpty && lastComputedBounds != null) {
await _safeAnimateCameraBounds(lastComputedBounds);
} else {
mapController!.animateCamera(
CameraUpdate.newLatLng(locationSearch.passengerLocation),
);
}
}
update();
}
Future<void> _safeAnimateCameraBounds(LatLngBounds? bounds,
{double left = 60,
double top = 60,
double right = 60,
double bottom = 60}) async {
if (bounds == null || mapController == null) return;
try {
if (bounds.northeast.latitude == bounds.southwest.latitude &&
bounds.northeast.longitude == bounds.southwest.longitude) {
Log.print(
'⚠️ _safeAnimateCameraBounds: Bounds are a single point, zooming to point instead.');
await mapController
?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 15));
return;
}
await Future.delayed(const Duration(milliseconds: 200));
await mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
bounds,
left: left,
top: top,
right: right,
bottom: bottom,
),
);
} catch (e) {
Log.print('❌ _safeAnimateCameraBounds CRASH PREVENTED: $e');
try {
await mapController
?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 14));
} catch (_) {}
}
}
Future<void> _loadMapIcons() async {
isIconsLoaded = false;
for (int i = 0; i < 15; i++) {
if (mapController != null && isStyleLoaded) break;
await Future.delayed(const Duration(milliseconds: 200));
}
if (mapController == null || !isStyleLoaded) {
Log.print(
'⚠️ _loadMapIcons: mapController or style not ready. Icons may not load.');
}
await _addMapImage(startIcon, 'assets/images/A.png');
await _addMapImage(endIcon, 'assets/images/b.png');
await _addMapImage(carIcon, 'assets/images/car.png');
await _addMapImage(motoIcon, 'assets/images/moto.png');
await _addMapImage(ladyIcon, 'assets/images/lady.png');
await _addMapImage('picker_icon', 'assets/images/picker.png');
await _addMapImage('orange_marker', 'assets/images/moto1.png');
await _addMapImage('violet_marker', 'assets/images/lady1.png');
try {
final walkBytes = await _createWalkMarkerBytes();
await mapController?.addImage('walk_icon', walkBytes);
Log.print('delimited: successfully added dynamic walk_icon');
} catch (e) {
Log.print('❌ Error loading dynamic walk icon: $e');
}
isIconsLoaded = true;
markers = markers.map((m) => m.copyWith()).toSet();
update();
if (Get.isRegistered<NearbyDriversController>()) {
Get.find<NearbyDriversController>()
.getCarsLocationByPassengerAndReloadMarker();
}
}
Future<void> _addMapImage(String id, String path) async {
try {
final ByteData bytes = await rootBundle.load(path);
final size = _getImageSize(id);
if (size != null && (id == carIcon || id == motoIcon || id == ladyIcon)) {
final resized = await _resizeImage(bytes.buffer.asUint8List(), size);
await mapController?.addImage(id, resized);
Log.print(
'delimited: successfully added resized map image: $id (${size}x${size})');
} else {
await mapController?.addImage(id, bytes.buffer.asUint8List());
Log.print('delimited: successfully added map image: $id');
}
} catch (e) {
Log.print('❌ Error loading map icon $id: $e');
}
}
int? _getImageSize(String id) {
if (id == carIcon || id == motoIcon || id == ladyIcon) {
// Scale based on pixel ratio (48dp for standard sleek car markers)
double ratio = ui.window.devicePixelRatio;
if (ratio == 0) ratio = 3.0; // fallback just in case
return (48 * ratio).toInt();
}
return null;
}
Future<Uint8List> _resizeImage(Uint8List bytes, int size) async {
return await compute((Uint8List data) {
final image = img.decodeImage(data);
if (image == null) return data;
final resized = img.copyResize(image, width: size, height: size);
return Uint8List.fromList(img.encodePng(resized));
}, bytes);
}
void clearPolyline() {
polyLines = <Polyline>{};
update();
}
LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) {
const double earthRadius = 6378137.0;
double latDelta = (radiusInMeters / earthRadius) * (180 / pi);
double lngDelta =
(radiusInMeters / (earthRadius * cos(pi * lat / 180))) * (180 / pi);
double minLat = lat - latDelta;
double maxLat = lat + latDelta;
double minLng = lng - lngDelta;
double maxLng = lng + lngDelta;
minLat = max(-90.0, minLat);
maxLat = min(90.0, maxLat);
minLng = (minLng + 180) % 360 - 180;
maxLng = (maxLng + 180) % 360 - 180;
if (minLng > maxLng) {
double temp = minLng;
minLng = maxLng;
maxLng = temp;
}
return LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
);
}
Future<void> playRouteAnimation(
List<LatLng> coords, LatLngBounds? bounds) async {
_routeRenderRevision++;
const List<Color> segmentColors = [
Color(0xFF109642), // Green
Color(0xFFF59E0B), // Amber
Color(0xFF7C3AED), // Purple
Color(0xFFEF4444), // Red
];
Set<Polyline> newPolylines = {};
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.activeMenuWaypointCount > 0) {
List<int> splitIndices = [];
for (int w = 0; w < locationSearch.activeMenuWaypointCount; w++) {
final wp = locationSearch.menuWaypoints[w];
if (wp == null) continue;
int bestIdx = 0;
double bestDist = double.infinity;
for (int j = 0; j < coords.length; j++) {
final dx = coords[j].latitude - wp.latitude;
final dy = coords[j].longitude - wp.longitude;
final d = dx * dx + dy * dy;
if (d < bestDist) {
bestDist = d;
bestIdx = j;
}
}
splitIndices.add(bestIdx);
}
splitIndices.sort();
List<int> boundaries = [0, ...splitIndices, coords.length - 1];
for (int s = 0; s < boundaries.length - 1; s++) {
int from = boundaries[s];
int to = boundaries[s + 1] + 1;
if (to > coords.length) to = coords.length;
if (from >= to - 1) continue;
final segCoords = coords.sublist(from, to);
if (segCoords.length < 2) continue;
final color = segmentColors[s % segmentColors.length];
newPolylines.add(Polyline(
polylineId: PolylineId('route_primary_${_routeRenderRevision}_$s'),
points: segCoords,
color: color,
width: 6,
));
}
} else {
newPolylines.add(Polyline(
polylineId: PolylineId('route_primary_${_routeRenderRevision}_0'),
points: coords,
color: AppColor.primaryColor,
width: 6,
));
}
polyLines = newPolylines;
update();
Log.print(
'🗺️ Drawing ${markers.length} markers + ${polyLines.length} polylines on map');
if (bounds != null) {
await _safeAnimateCameraBounds(bounds);
}
}
void _fitCameraToPoints(LatLng p1, LatLng p2) async {
if (mapController == null) return;
if (p1.latitude == p2.latitude && p1.longitude == p2.longitude) {
try {
mapController?.animateCamera(CameraUpdate.newLatLngZoom(p1, 17));
} catch (e) {
Log.print("Error animating to single point: $e");
}
return;
}
double minLat = min(p1.latitude, p2.latitude);
double maxLat = max(p1.latitude, p2.latitude);
double minLng = min(p1.longitude, p2.longitude);
double maxLng = max(p1.longitude, p2.longitude);
if ((maxLat - minLat).abs() < 0.002 && (maxLng - minLng).abs() < 0.002) {
try {
mapController?.animateCamera(CameraUpdate.newLatLngZoom(p1, 16));
} catch (e) {
Log.print("Error animating to single point: $e");
}
return;
}
double padding = 50.0;
try {
await mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
),
left: padding,
top: padding,
right: padding,
bottom: padding,
),
);
} catch (e) {
Log.print("Error animating bounds: $e");
try {
LatLng center = LatLng((minLat + maxLat) / 2, (minLng + maxLng) / 2);
mapController?.animateCamera(CameraUpdate.newLatLngZoom(center, 14));
} catch (_) {}
}
}
void fitCameraToPoints(LatLng p1, LatLng p2) {
_fitCameraToPoints(p1, p2);
}
void clearMarkersExceptStartEndAndDriver() {
const String currentDriverMarkerId = 'assigned_driver_marker';
markers.removeWhere((marker) {
String id = marker.markerId.value;
if (id == 'start') return false;
if (id == 'end') return false;
if (id == currentDriverMarkerId) return false;
return true;
});
update();
}
void clearMarkersExceptStartEnd() {
markers.removeWhere((marker) {
String id = marker.markerId.value;
return id != 'start' && id != 'end';
});
update();
}
void _updateMarkerPosition(
LatLng newPosition, double newHeading, String icon) {
const String markerId = 'driverToPassengers';
final mId = MarkerId(markerId);
final existingMarker = markers.cast<Marker?>().firstWhere(
(m) => m?.markerId == mId,
orElse: () => null,
);
if (existingMarker != null) {
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
} else {
markers = {
...markers,
Marker(
markerId: mId,
position: newPosition,
rotation: newHeading,
icon: InlqBitmap.fromAsset(icon),
anchor: const Offset(0.5, 0.5),
),
};
update();
}
mapController?.animateCamera(CameraUpdate.newLatLng(newPosition));
}
void updateMarkerPosition(
LatLng newPosition, double newHeading, String icon) {
_updateMarkerPosition(newPosition, newHeading, icon);
}
void _smoothlyUpdateMarker(
Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
double distance = Geolocator.distanceBetween(
oldMarker.position.latitude,
oldMarker.position.longitude,
newPosition.latitude,
newPosition.longitude);
if (distance < 2.0) return;
final MarkerId markerIdKey = oldMarker.markerId;
_animationTimers[markerIdKey.value]?.cancel();
int ticks = 0;
const int totalSteps = 20;
const int stepDuration = 50;
double latStep =
(newPosition.latitude - oldMarker.position.latitude) / totalSteps;
double lngStep =
(newPosition.longitude - oldMarker.position.longitude) / totalSteps;
double headingStep = (newHeading - oldMarker.rotation) / totalSteps;
LatLng currentPos = oldMarker.position;
double currentHeading = oldMarker.rotation;
_animationTimers[markerIdKey.value] =
Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
ticks++;
currentPos =
LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
currentHeading += headingStep;
final updatedMarker = oldMarker.copyWith(
position: currentPos,
rotation: currentHeading,
icon: InlqBitmap.fromAsset(icon),
);
markers = {
...markers.where((m) => m.markerId != markerIdKey),
updatedMarker,
};
if (mapController != null) {
mapController!.animateCamera(CameraUpdate.newLatLng(currentPos));
}
update();
if (ticks >= totalSteps) {
timer.cancel();
_animationTimers.remove(markerIdKey.value);
}
});
}
// تحديث موقع العلامة (Marker) واتجاهها بسلاسة على الخريطة.
// تحسب الدالة المسافة بين الموقع الحالي والجديد؛ وإذا كانت أكبر من مترين،
// تقوم بتقسيم الحركة والدوران إلى 20 خطوة متباعدة بـ 50 مللي ثانية (إجمالي ثانية واحدة).
// يتم تحديث موضع العلامة وتحريك الكاميرا تدريجياً لتبدو حركة السيارة انسيابية.
void smoothlyUpdateMarker(
Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
_smoothlyUpdateMarker(oldMarker, newPosition, newHeading, icon);
}
void changeBottomSheetShown({bool? forceValue}) {
if (forceValue != null) {
isBottomSheetShown = forceValue;
} else {
isBottomSheetShown = !isBottomSheetShown;
}
heightBottomSheetShown = isBottomSheetShown == true ? 250 : 0;
update();
}
void changeCashConfirmPageShown() {
isCashConfirmPageShown = !isCashConfirmPageShown;
final rideLife = Get.find<RideLifecycleController>();
rideLife.isCashSelectedBeforeConfirmRide = true;
cashConfirmPageShown = isCashConfirmPageShown == true ? 250 : 0;
update();
rideLife.update();
}
void changePaymentMethodPageShown() {
isPaymentMethodPageShown = !isPaymentMethodPageShown;
paymentPageShown = isPaymentMethodPageShown == true ? Get.height * .6 : 0;
update();
}
void changeMapType() {
mapType = !mapType;
update();
}
void changeMapTraffic() {
mapTrafficON = !mapTrafficON;
update();
}
void changeisAnotherOreder(bool val) {
isAnotherOreder = val;
update();
}
void changeIsWhatsAppOrder(bool val) {
isWhatsAppOrder = val;
update();
}
void changeCancelRidePageShow() {
showCancelRideBottomSheet();
isCancelRidePageShown = !isCancelRidePageShown;
update();
if (Get.isRegistered<RideLifecycleController>()) {
Get.find<RideLifecycleController>().update();
}
}
void getDrawerMenu() {
heightMenuBool = !heightMenuBool;
widthMapTypeAndTraffic = heightMenuBool == true ? 0 : 50;
heightMenu = heightMenuBool == true ? 80 : 0;
widthMenu = heightMenuBool == true ? 110 : 0;
update();
}
void changeMainBottomMenuMap() {
if (isWayPointStopsSheetUtilGetMap == true) {
changeWayPointSheet();
} else {
isMainBottomMenuMap = !isMainBottomMenuMap;
mainBottomMenuMapHeight =
isMainBottomMenuMap == true ? Get.height * .22 : Get.height * .6;
isWayPointSheet = false;
if (heightMenuBool == true) {
getDrawerMenu();
}
Get.find<RideLifecycleController>().initilizeGetStorage();
update();
}
}
void downPoints() {
if (Get.find<WayPointController>().wayPoints.length < 2) {
isWayPointStopsSheetUtilGetMap = false;
isWayPointSheet = false;
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
update();
}
update();
}
void changeWayPointSheet() {
isWayPointSheet = !isWayPointSheet;
wayPointSheetHeight = isWayPointSheet == false ? 0 : Get.height * .45;
update();
}
void changeWayPointStopsSheet() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPointIndex > -1) {
isWayPointStopsSheet = true;
isWayPointStopsSheetUtilGetMap = true;
}
isWayPointStopsSheet = !isWayPointStopsSheet;
wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
update();
}
void changeHeightPlaces() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.placesDestination.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightStartPlaces() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.placesStart.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlacesAll(int index) {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.placeListResponseAll[index].isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces1() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint1.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces2() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint2.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces3() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint3.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void changeHeightPlaces4() {
final locationSearch = Get.find<LocationSearchController>();
if (locationSearch.wayPoint4.isEmpty) {
height = 0;
update();
} else {
height = 150;
update();
}
}
void hidePlaces() {
height = 0;
update();
}
void changePickerShown() {
_isPickerShown = !_isPickerShown;
heightPickerContainer = _isPickerShown == true ? 150 : 90;
update();
}
void _initializePolygons() {
List<List<LatLng>> universityPolygons =
UniversitiesPolygons.universityPolygons;
for (int i = 0; i < universityPolygons.length; i++) {
Polygon polygon = Polygon(
polygonId: PolygonId('univ_$i'),
points: universityPolygons[i],
fillColor: Colors.blueAccent.withOpacity(0.2),
strokeColor: Colors.blueAccent,
strokeWidth: 2,
);
polygons.add(polygon);
}
update();
}
void _applyLowEndModeIfNeeded() {
// Placeholder comment from original
}
Future<void> _initMinimalIcons() async {
// Icons are loaded dynamically
}
Future<void> _playRouteAnimation(
List<LatLng> coords, LatLngBounds? bounds) async {
_routeRenderRevision++;
const List<Color> segmentColors = [
Color(0xFF109642), // Green
Color(0xFFF59E0B), // Amber
Color(0xFF7C3AED), // Purple
Color(0xFFEF4444), // Red
];
Set<Polyline> newPolylines = {};
final locSearch = Get.find<LocationSearchController>();
if (locSearch.activeMenuWaypointCount > 0) {
List<int> splitIndices = [];
for (int w = 0; w < locSearch.activeMenuWaypointCount; w++) {
final wp = locSearch.menuWaypoints[w];
if (wp == null) continue;
int bestIdx = 0;
double bestDist = double.infinity;
for (int j = 0; j < coords.length; j++) {
final dx = coords[j].latitude - wp.latitude;
final dy = coords[j].longitude - wp.longitude;
final d = dx * dx + dy * dy;
if (d < bestDist) {
bestDist = d;
bestIdx = j;
}
}
splitIndices.add(bestIdx);
}
splitIndices.sort();
List<int> boundaries = [0, ...splitIndices, coords.length - 1];
for (int s = 0; s < boundaries.length - 1; s++) {
int from = boundaries[s];
int to = boundaries[s + 1] + 1;
if (to > coords.length) to = coords.length;
if (from >= to - 1) continue;
final segCoords = coords.sublist(from, to);
if (segCoords.length < 2) continue;
final color = segmentColors[s % segmentColors.length];
newPolylines.add(Polyline(
polylineId: PolylineId('route_primary_${_routeRenderRevision}_$s'),
points: segCoords,
color: color,
width: 6,
));
}
} else {
newPolylines.add(Polyline(
polylineId: PolylineId('route_primary_${_routeRenderRevision}_0'),
points: coords,
color: AppColor.primaryColor,
width: 6,
));
}
polyLines = newPolylines;
update();
Log.print(
'🗺️ Drawing ${markers.length} markers + ${polyLines.length} polylines on map');
update();
if (bounds != null) {
await _safeAnimateCameraBounds(bounds);
}
}
void reset() {
isPickerShown = false;
isPointsPageForRider = false;
isBottomSheetShown = false;
isCancelRidePageShown = false;
isCashConfirmPageShown = false;
isPaymentMethodPageShown = false;
isRideFinished = false;
rideConfirm = false;
isMainBottomMenuMap = true;
isWayPointSheet = false;
isWayPointStopsSheet = false;
isWayPointStopsSheetUtilGetMap = false;
heightBottomSheetShown = 0;
mainBottomMenuMapHeight = Get.height * 0.22;
wayPointSheetHeight = 0;
markers = <Marker>{};
polyLines = <Polyline>{};
polylineCoordinates.clear();
_animationTimers.forEach((key, timer) => timer.cancel());
_animationTimers.clear();
update();
}
Future<Uint8List> _createWalkMarkerBytes() async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
const double size = 60.0;
final Paint paint = Paint()..color = const Color(0xFF0288D1);
final Paint borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 3.0;
canvas.drawCircle(const Offset(size / 2, size / 2), size / 2.5, paint);
canvas.drawCircle(
const Offset(size / 2, size / 2), size / 2.5, borderPaint);
TextPainter iconPainter = TextPainter(textDirection: TextDirection.ltr);
iconPainter.text = TextSpan(
text: String.fromCharCode(Icons.directions_walk.codePoint),
style: TextStyle(
fontSize: 30.0,
fontFamily: Icons.directions_walk.fontFamily,
color: Colors.white,
),
);
iconPainter.layout();
iconPainter.paint(
canvas,
Offset((size - iconPainter.width) / 2, (size - iconPainter.height) / 2),
);
final ui.Image image = await pictureRecorder
.endRecording()
.toImage(size.toInt(), size.toInt());
final ByteData? data =
await image.toByteData(format: ui.ImageByteFormat.png);
return data!.buffer.asUint8List();
}
@override
void onClose() {
_animationTimers.forEach((key, timer) => timer.cancel());
_animationTimers.clear();
mapController = null;
super.onClose();
}
}
@@ -0,0 +1,29 @@
import 'package:get/get.dart';
import 'map_socket_controller.dart';
import 'map_engine_controller.dart';
import 'location_search_controller.dart';
import 'nearby_drivers_controller.dart';
import 'ride_lifecycle_controller.dart';
import 'ui_interactions_controller.dart';
import 'ride_simulation.dart';
class MapScreenBinding extends Bindings {
@override
void dependencies() {
// 1. WebSocket Controller: Permanent and immediate
Get.put(MapSocketController());
// 2. Core Controllers (initialized when the screen opens or on demand)
Get.lazyPut(() => MapEngineController());
Get.lazyPut(() => LocationSearchController());
Get.lazyPut(() => NearbyDriversController());
// 3. Lifecycle and UI Interaction Controllers
Get.lazyPut(() => RideLifecycleController());
Get.lazyPut(() => UiInteractionsController(), fenix: true);
// 4. Simulation Controller (for development/testing)
Get.lazyPut(() => RideSimulationController(), fenix: true);
}
}
@@ -0,0 +1,490 @@
import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:socket_io_client/socket_io_client.dart' as io_client;
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../env/env.dart';
import '../../../main.dart'; // contains global 'box' + 'storage'
import '../../../print.dart';
import '../../functions/encrypt_decrypt.dart'; // r()
import 'ride_lifecycle_controller.dart';
import 'nearby_drivers_controller.dart';
import 'map_engine_controller.dart';
class MapSocketController extends GetxController {
late io_client.Socket socket;
bool isSocketConnected = false;
bool _isSocketInitialized = false;
// يغطّي نافذة الـ await على قراءة الـ JWT: بلا هذا يقدر نداءان متزامنان
// (initializeDataAfterLogin + startSearchingForDriver) يفتحان سوكيتين.
bool _isConnecting = false;
/// جاهز لاستقبال الأحداث — أي منضمّ فعلياً لغرفة passenger_{id} على السيرفر.
bool get isReadyForEvents => isSocketConnected && _isSocketInitialized;
Timer? _heartbeatTimer;
DateTime? _lastSocketLocationTime;
int _socketLocationUpdatesCount = 0;
Timer? _watchdogTimer;
// 🚌 مواصلاتي — الخط الحالي المشترَك فيه + مستمع تحديث موقع الباص
int? _subscribedTransitRouteId;
void Function(Map<String, dynamic> data)? onBusLocationUpdate;
void Function(String errorCode)? onTransitError;
DateTime? get lastDriverLocationTime => _lastSocketLocationTime;
int get socketLocationUpdatesCount => _socketLocationUpdatesCount;
/// يقرأ الـ JWT بنفس آلية CRUD._getJwt (SecureStorage أولاً ثم GetStorage).
/// passenger_socket.php يرفض أي اتصال بلا jwt، فبدونه لا تصل أي أحداث للراكب.
Future<String> _getJwtForSocket() async {
try {
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
if (encryptedJwt != null && encryptedJwt.isNotEmpty) {
return r(encryptedJwt).toString().split(Env.addd)[0];
}
} catch (e) {
Log.print('Error reading JWT from SecureStorage for socket: $e');
}
final String? fallback = box.read(BoxName.jwt);
if (fallback != null && fallback.toString().isNotEmpty) {
try {
return r(fallback).toString().split(Env.addd)[0];
} catch (e) {
Log.print('Error decrypting fallback JWT for socket: $e');
}
}
return '';
}
/// يجب أن يُنادى **قبل** إنشاء الرحلة: غرف Socket.IO لا تخزّن الأحداث، فإن
/// قبل كابتن قبل أن ينضم الراكب لغرفته فإن ride_status_change يُرمى نهائياً
/// ولا يبقى إلا الـ FCM/polling. النداء آمن ومتكرر (idempotent).
Future<void> initConnectionWithSocket() async {
if (isSocketConnected || _isSocketInitialized || _isConnecting) return;
_isConnecting = true;
String passengerId = box.read(BoxName.passengerID).toString();
final String jwt = await _getJwtForSocket();
if (jwt.isEmpty) {
_isConnecting = false;
Log.print(
"⚠️ No JWT available — passenger socket would be rejected by the server. Aborting connect.");
return;
}
if (passengerId.isEmpty || passengerId == 'null') {
_isConnecting = false;
Log.print("⚠️ No passengerID yet — deferring socket connect.");
return;
}
Log.print("🔌 Initializing Socket for Passenger: $passengerId");
socket = io_client.io(
AppLink.serverSocket,
io_client.OptionBuilder()
.setTransports(['websocket'])
.disableAutoConnect()
.setQuery({'id': passengerId, 'jwt': jwt})
// محاولات لا نهائية، توحيداً مع تطبيق السائق (background_service.dart).
// كانت 20: بعدها يستسلم السوكيت **نهائياً** فيصمت للأبد بعد بضع دقائق
// من شبكة سيئة، ويبقى الراكب على الـ polling بلا أن يدري. صار الاتصال
// يعيش من فتح الخريطة لا من إنشاء الرحلة، فالسقف الثابت خطر أكبر الآن.
.setReconnectionAttempts(double.infinity)
.setReconnectionDelay(2000)
.setReconnectionDelayMax(10000)
.enableReconnection()
.setTimeout(20000)
.setExtraHeaders({'Connection': 'Upgrade'})
.build(),
);
_isSocketInitialized = true;
_isConnecting = false;
socket.connect();
socket.onConnect((_) {
Log.print("✅ Socket Connected Successfully");
isSocketConnected = true;
_startHeartbeat();
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.rideId != 'yet' && rideLifecycle.driverId.isNotEmpty) {
socket.emit('subscribe_driver_location', {
'ride_id': rideLifecycle.rideId,
'driver_id': rideLifecycle.driverId,
});
Log.print("📡 Re-subscribed to driver location after connect");
}
if (_subscribedTransitRouteId != null) {
socket.emit('subscribe_transit_route', {'route_id': _subscribedTransitRouteId});
Log.print("🚌 Re-subscribed to transit route after connect");
}
update();
});
socket.onDisconnect((_) {
Log.print("⚠️ Socket Disconnected — Auto-Reconnect will handle it");
isSocketConnected = false;
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.isActiveRideState()) {
Log.print("🔄 Enabling Fast Polling Fallback (4s) until reconnect...");
rideLifecycle.startMasterTimerWithInterval(4);
}
update();
});
socket.onReconnect((_) {
Log.print("🔁 Socket Reconnected Successfully!");
isSocketConnected = true;
_startHeartbeat();
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.rideId != 'yet' && rideLifecycle.driverId.isNotEmpty) {
socket.emit('subscribe_driver_location', {
'ride_id': rideLifecycle.rideId,
'driver_id': rideLifecycle.driverId,
});
Log.print("📡 Re-subscribed to driver location after reconnect");
}
if (_subscribedTransitRouteId != null) {
socket.emit('subscribe_transit_route', {'route_id': _subscribedTransitRouteId});
Log.print("🚌 Re-subscribed to transit route after reconnect");
}
if (rideLifecycle.isActiveRideState()) {
Log.print("✅ Socket back online — stopping Fast Polling Fallback");
rideLifecycle.cancelMasterTimer();
}
update();
});
socket.onReconnectAttempt((attemptNumber) {
Log.print("🔄 Socket Reconnect Attempt #$attemptNumber...");
});
socket.onError((error) {
Log.print("❌ Socket Error: $error");
isSocketConnected = false;
});
socket.on('connect_error', (error) {
Log.print("❌ Socket Connect Error: $error");
isSocketConnected = false;
// في الإصدار 1.0.2 أحياناً auto-reconnect لا يعمل بعد connect_error
// نتأكد يدوياً من إعادة الاتصال
Future.delayed(const Duration(seconds: 3), () {
if (!isSocketConnected && _isSocketInitialized) {
Log.print("🔄 Manual reconnect after connect_error...");
try {
socket.connect();
} catch (e) {
Log.print("Manual reconnect error: $e");
}
}
});
});
socket.on('ride_status_change', (data) {
Log.print("📩 Socket Event: ride_status_change -> $data");
_handleRideStatusChangeWithSocket(data);
});
socket.on('driver_location_update', (data) {
handleDriverLocationUpdate(data);
});
// 🚌 مواصلاتي — بثّ موقع الباص الحي لخط مشترَك فيه
socket.on('bus_location_update', (data) {
if (data == null) return;
try {
final map = Map<String, dynamic>.from(data as Map);
onBusLocationUpdate?.call(map);
} catch (e) {
Log.print('Error parsing bus_location_update: $e');
}
});
socket.on('transit_error', (data) {
if (data == null) return;
try {
final map = Map<String, dynamic>.from(data as Map);
final code = map['code']?.toString() ?? '';
Log.print('⚠️ transit_error: $code');
onTransitError?.call(code);
} catch (e) {
Log.print('Error parsing transit_error: $e');
}
});
}
/// يوصل السوكيت وينتظر انضمامه فعلياً لغرفة الراكب، بسقف زمني.
/// يُنادى قبل إنشاء الرحلة حتى لا يقبل كابتن والراكب ما زال خارج غرفته
/// (الحدث يُفقد نهائياً في تلك الحالة — لا طابور في غرف Socket.IO).
/// لا يُفشل إنشاء الرحلة أبداً: عند انتهاء المهلة نكمل ونتّكل على
/// الـ FCM/polling كشبكة احتياطية.
/// آخر مرة فشل فيها الاتصال — حتى لا نُبطئ كل طلب رحلة بانتظارٍ عقيم إذا كان
/// السوكيت غير قابل للوصول أصلاً (منفذ مغلق/TLS غير مُنهى/سيرفر واقف).
static DateTime? _lastConnectFailure;
static const Duration _failureCooldown = Duration(minutes: 2);
Future<bool> ensureConnectedBeforeRide({
Duration timeout = const Duration(seconds: 2),
}) async {
if (isReadyForEvents) return true;
final lastFail = _lastConnectFailure;
if (lastFail != null &&
DateTime.now().difference(lastFail) < _failureCooldown) {
// فشل قريباً — نبدأ محاولة الاتصال في الخلفية ولا نُعطّل الطلب إطلاقاً
initConnectionWithSocket();
Log.print(
"⏭️ Skipping socket wait (recent failure). Ride proceeds on FCM/polling.");
return false;
}
await initConnectionWithSocket();
final deadline = DateTime.now().add(timeout);
while (!isSocketConnected && DateTime.now().isBefore(deadline)) {
await Future.delayed(const Duration(milliseconds: 100));
}
if (isSocketConnected) {
_lastConnectFailure = null;
Log.print("✅ Socket ready before ride creation.");
return true;
}
_lastConnectFailure = DateTime.now();
Log.print(
"⚠️ Socket not ready within ${timeout.inMilliseconds}ms — continuing; FCM/polling will cover.");
return false;
}
// ── مواصلاتي: اشتراك/إلغاء اشتراك ببثّ موقع خط ─────────────
// يُستدعى عند فتح/إغلاق شاشة تتبع الباص الحي. يضمن السوكيت متصلاً أولاً.
Future<void> subscribeToTransitRoute(int routeId) async {
_subscribedTransitRouteId = routeId;
if (!isSocketConnected) {
await initConnectionWithSocket();
// سيُعاد الاشتراك تلقائياً من onConnect إن أضفنا ذلك، لكن نحاول فوراً أيضاً
}
// socket هو late — لو انسحب initConnectionWithSocket (بلا JWT مثلاً) يبقى
// غير مُهيّأ، فقراءته مباشرة ترمي LateInitializationError.
if (_isSocketInitialized && socket.connected) {
socket.emit('subscribe_transit_route', {'route_id': routeId});
Log.print('🚌 Subscribed to transit route #$routeId');
}
}
void unsubscribeFromTransitRoute(int routeId) {
if (_subscribedTransitRouteId == routeId) _subscribedTransitRouteId = null;
if (_isSocketInitialized && isSocketConnected && socket.connected) {
socket.emit('unsubscribe_transit_route', {'route_id': routeId});
Log.print('🚌 Unsubscribed from transit route #$routeId');
}
}
/// ⚠️ زائدة عن الحاجة عملياً: Socket.IO يعمل ping/pong على مستوى البروتوكول،
/// ومستمع 'heartbeat' في passenger_socket.php فارغ ولا يفعل شيئاً. أبقيناها
/// بفاصل 30ث بدل 15ث لتنصيف حركتها — الاتصال صار يعيش من فتح الخريطة لا من
/// إنشاء الرحلة، فعدد الاتصالات الخاملة أعلى بكثير. حذفها كاملاً ممكن لاحقاً
/// بعد التأكد أن لا شيء على السيرفر يعتمد عليها لقياس الحضور.
void _startHeartbeat() {
_heartbeatTimer?.cancel();
_heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
if (isSocketConnected && socket.connected) {
socket.emit('heartbeat',
{'passenger_id': box.read(BoxName.passengerID).toString()});
}
});
}
bool isSocketHealthy() {
if (!isSocketConnected) return false;
if (_lastSocketLocationTime == null) return false;
final diff = DateTime.now().difference(_lastSocketLocationTime!).inSeconds;
return diff < 20;
}
void _handleRideStatusChangeWithSocket(dynamic data) {
if (data == null || data['status'] == null) return;
String newStatus = data['status'].toString().toLowerCase();
Log.print("🔔 Socket Status Update: $newStatus");
final rideLifecycle = Get.find<RideLifecycleController>();
Map<String, dynamic>? driverInfo;
if (data['driver_info'] != null && data['driver_info'] is Map) {
driverInfo = Map<String, dynamic>.from(data['driver_info']);
}
switch (newStatus) {
case 'accepted':
case 'apply':
case 'applied':
rideLifecycle.processRideAcceptance(
driverData: driverInfo, source: "Socket");
break;
case 'arrived':
rideLifecycle.processDriverArrival("Socket");
break;
case 'started':
case 'begin':
rideLifecycle.processRideBegin(source: "Socket");
break;
case 'finished':
case 'ended':
_onRideFinishedWithSocket(data);
break;
// cancel_ride_by_driver.php يبعث 'cancelled_by_driver' حرفياً،
// و cancel_ride_by_passenger.php يبعث 'cancelled_by_passenger'.
// كانت الحالتان تسقطان من الـ switch فيبقى الراكب معلّقاً على الشاشة.
case 'cancelled':
case 'cancelled_by_driver':
case 'canceled_by_driver':
rideLifecycle.processRideCancelledByDriver(data, source: "Socket");
break;
case 'no_drivers_found':
rideLifecycle.showNoDriverDialog();
break;
}
}
void _onRideFinishedWithSocket(dynamic data) {
Log.print("🏁 Ride Finished (Socket)");
final rideLifecycle = Get.find<RideLifecycleController>();
var rawList = data['DriverList'];
List<dynamic> listToSend = [];
if (rawList != null) {
if (rawList is List) {
listToSend = rawList;
} else if (rawList is String) {
try {
listToSend = jsonDecode(rawList);
} catch (e) {
Log.print("Error decoding DriverList: $e");
}
}
}
if (listToSend.isEmpty && data['price'] != null) {
listToSend = [
rideLifecycle.driverId,
rideLifecycle.rideId,
rideLifecycle.driverToken,
data['price'].toString()
];
}
rideLifecycle.processRideFinished(listToSend, source: "Socket");
}
void handleDriverLocationUpdate(dynamic data) {
if (!isSocketConnected || data == null) return;
_lastSocketLocationTime = DateTime.now();
_socketLocationUpdatesCount++;
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.driverId.isEmpty &&
(data['driver_id'] ?? data['driverId']) != null) {
rideLifecycle.driverId =
(data['driver_id'] ?? data['driverId']).toString();
}
if (_socketLocationUpdatesCount >= 3 &&
rideLifecycle.locationPollingTimer != null) {
Log.print("✅ Socket delivering locations reliably. Stopping polling.");
rideLifecycle.stopDriverLocationPolling();
}
try {
double lat = double.tryParse(
(data['latitude'] ?? data['lat'])?.toString() ?? '0') ??
0;
double lng = double.tryParse(
(data['longitude'] ?? data['lng'])?.toString() ?? '0') ??
0;
double heading = double.tryParse(data['heading']?.toString() ?? '0') ?? 0;
if (lat == 0 || lng == 0) return;
LatLng newPos = LatLng(lat, lng);
final nearbyDrivers = Get.find<NearbyDriversController>();
if (nearbyDrivers.driverCarsLocationToPassengerAfterApplied.isEmpty) {
nearbyDrivers.driverCarsLocationToPassengerAfterApplied.add(newPos);
} else {
nearbyDrivers.driverCarsLocationToPassengerAfterApplied[0] = newPos;
}
double speed = double.tryParse(data['speed']?.toString() ?? '0') ?? 0;
rideLifecycle.checkAndRecalculateIfDeviated(
newPos,
heading: heading,
speed: speed,
);
final mapEngine = Get.find<MapEngineController>();
if (mapEngine.mapController != null) {
double zoom = 16.5;
if (speed > 0) {
zoom = 17.0 - ((speed - 10) / 70) * 2.5;
zoom = zoom.clamp(14.5, 17.0);
}
mapEngine.mapController!
.animateCamera(CameraUpdate.newLatLngZoom(newPos, zoom));
}
final dynamic distanceValue =
data['distance_m'] ?? data['distance_meters'];
final double? distanceMeters =
double.tryParse(distanceValue?.toString() ?? '');
final int? etaSeconds = data['eta_seconds'] == null
? null
: int.tryParse(data['eta_seconds'].toString());
final bool hasServerMetrics = (etaSeconds != null && etaSeconds > 0) ||
(distanceMeters != null && distanceMeters > 0);
if (hasServerMetrics) {
rideLifecycle.updateDriverRouteMetrics(
etaSeconds: etaSeconds != null && etaSeconds > 0 ? etaSeconds : null,
distanceMeters: distanceMeters,
);
}
rideLifecycle.updateDriverMarker(newPos, heading);
rideLifecycle.updateRemainingRoute(newPos, updateEta: !hasServerMetrics);
rideLifecycle.update();
} catch (e) {
Log.print('Error in handleDriverLocationUpdate: $e');
}
}
void disposeRideSocket() {
_heartbeatTimer?.cancel();
_watchdogTimer?.cancel();
if (_isSocketInitialized) {
socket.disconnect();
socket.dispose();
isSocketConnected = false;
_isSocketInitialized = false;
Log.print("🔌 Socket Disposed");
}
}
@override
void onClose() {
disposeRideSocket();
super.onClose();
}
}
@@ -0,0 +1,473 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math' show Random, atan2, cos, pi, pow, sin, sqrt;
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/links.dart';
import '../../../constant/api_key.dart';
import '../../../print.dart';
import '../../functions/crud.dart';
import 'map_engine_controller.dart';
import 'location_search_controller.dart';
import 'ride_lifecycle_controller.dart';
import '../../../models/model/locations.dart';
import 'car_location.dart';
import 'package:device_info_plus/device_info_plus.dart';
class NearbyDriversController extends GetxController {
List carsLocationByPassenger = [];
List<LatLng> driverCarsLocationToPassengerAfterApplied = [];
List<CarLocationModel> carLocationsModels = [];
String? currentDriverMarkerId;
bool lowPerf = false;
dynamic dataCarsLocationByPassenger;
bool noCarString = false;
final double minMovementThreshold = 2.0;
final Map<String, Timer> _animationTimers = {};
final List<Map<String, dynamic>> fakeCarData = [];
Future<bool> getCarsLocationByPassengerAndReloadMarker() async {
carsLocationByPassenger = [];
final locSearch = Get.find<LocationSearchController>();
if (locSearch.passengerLocation.latitude == 0 && locSearch.passengerLocation.longitude == 0) {
return false;
}
var res = await CRUD().get(
link: AppLink.getCarsLocationByPassenger,
payload: {
'lat': locSearch.passengerLocation.latitude.toString(),
'lng': locSearch.passengerLocation.longitude.toString(),
'radius': '5',
'limit': '50',
},
);
if (res == 'failure' || res is! Map) {
noCarString = true;
dataCarsLocationByPassenger = 'failure';
update();
return false;
}
noCarString = false;
var responseData = res;
dataCarsLocationByPassenger = responseData;
List driversList = [];
if (responseData['status'] == true && responseData['data'] != null) {
driversList = responseData['data'];
} else if (responseData['message'] != null) {
driversList = responseData['message'];
}
final mapEngine = Get.find<MapEngineController>();
if (driversList.isEmpty) {
carsLocationByPassenger.clear();
mapEngine.update();
return false;
}
carsLocationByPassenger.clear();
for (var i = 0; i < driversList.length; i++) {
var carData = driversList[i];
double lat = double.tryParse(carData['latitude'].toString()) ?? 0.0;
double lng = double.tryParse(carData['longitude'].toString()) ?? 0.0;
double heading = double.tryParse(carData['heading'].toString()) ?? 0.0;
if (lat == 0.0 || lng == 0.0) continue;
String driverId = (carData['driver_id'] ?? carData['id'] ?? '').toString();
if (driverId.isEmpty || driverId == 'null') continue;
_updateOrCreateMarker(
driverId,
LatLng(lat, lng),
heading,
_getIconForCar(carData),
);
}
mapEngine.update();
return true;
}
void _addFakeCarMarkers(LatLng center, int count) {
if (fakeCarData.isEmpty) {
Random random = Random();
double radiusInKm = 2.5;
for (int i = 0; i < count; i++) {
double angle = random.nextDouble() * 2 * pi;
double distance = sqrt(random.nextDouble()) * radiusInKm;
double latOffset = (distance / 111.32);
double lonOffset =
(distance / (111.32 * cos(center.latitude * pi / 180.0)));
double lat = center.latitude + (latOffset * cos(angle));
double lon = center.longitude + (lonOffset * sin(angle));
double heading = random.nextDouble() * 360;
fakeCarData.add({
'id': 'fake_$i',
'latitude': lat,
'longitude': lon,
'heading': heading,
'gender': 'Male',
});
}
}
for (var carData in fakeCarData) {
_updateOrCreateMarker(
carData['id'].toString(),
LatLng(carData['latitude'], carData['longitude']),
carData['heading'],
_getIconForCar(carData),
);
}
}
void addFakeCarMarkers(LatLng center, int count) {
_addFakeCarMarkers(center, count);
}
Future<CarLocation?> getNearestDriverByPassengerLocation() async {
final rideLife = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
if (!rideLife.rideConfirm) {
if (dataCarsLocationByPassenger != 'failure' &&
dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.containsKey('message') &&
dataCarsLocationByPassenger['message'] != null &&
dataCarsLocationByPassenger['message'].length > 0) {
double nearestDistance = double.infinity;
CarLocation? nearestCar;
for (var i = 0;
i < dataCarsLocationByPassenger['message'].length;
i++) {
var carLocation = dataCarsLocationByPassenger['message'][i];
try {
final distance = Geolocator.distanceBetween(
locSearch.passengerLocation.latitude,
locSearch.passengerLocation.longitude,
double.parse(carLocation['latitude']),
double.parse(carLocation['longitude']),
);
int durationToPassenger = (distance / 1000 / 25 * 3600).round();
update();
if (distance < nearestDistance) {
nearestDistance = distance;
nearestCar = CarLocation(
distance: distance,
duration: durationToPassenger.toDouble(),
id: carLocation['driver_id'],
latitude: double.parse(carLocation['latitude']),
longitude: double.parse(carLocation['longitude']),
);
update();
}
} catch (e) {
Log.print('Error calculating distance/duration: $e');
}
}
return nearestCar;
}
}
return null;
}
Future<CarLocation?> getNearestDriverByPassengerLocationAPIGOOGLE() async {
final rideLife = Get.find<RideLifecycleController>();
final mapEngine = Get.find<MapEngineController>();
final locSearch = Get.find<LocationSearchController>();
if (mapEngine.polyLines.isEmpty || rideLife.totalCostPassenger == 0) {
return null;
}
if (!rideLife.rideConfirm) {
double nearestDistance = double.infinity;
if (dataCarsLocationByPassenger != 'failure' &&
dataCarsLocationByPassenger != null &&
dataCarsLocationByPassenger.containsKey('message') &&
dataCarsLocationByPassenger['message'] != null) {
if (dataCarsLocationByPassenger['message'].length > 0) {
CarLocation? nearestCar;
for (var i = 0;
i < dataCarsLocationByPassenger['message'].length;
i++) {
var carLocation = dataCarsLocationByPassenger['message'][i];
update();
String apiUrl =
'${AppLink.googleMapsLink}distancematrix/json?destinations=${carLocation['latitude']},${carLocation['longitude']}&origins=${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}&units=metric&key=${AK.mapAPIKEY}';
var response = await CRUD().getGoogleApi(link: apiUrl, payload: {});
if (response != null && response['status'] == "OK") {
var data = response;
int distance1 =
data['rows'][0]['elements'][0]['distance']['value'];
rideLife.distanceByPassenger =
data['rows'][0]['elements'][0]['distance']['text'];
rideLife.durationToPassenger =
data['rows'][0]['elements'][0]['duration']['value'];
Duration durationFromDriverToPassenger =
Duration(seconds: rideLife.durationToPassenger.toInt());
rideLife.stringRemainingTimeToPassenger =
data['rows'][0]['elements'][0]['duration']['text'];
update();
if (distance1 < nearestDistance) {
nearestDistance = distance1.toDouble();
nearestCar = CarLocation(
distance: distance1.toDouble(),
duration: rideLife.durationToPassenger.toDouble(),
id: carLocation['driver_id'],
latitude: double.parse(carLocation['latitude']),
longitude: double.parse(carLocation['longitude']),
);
update();
}
} else {
Log.print('${response?['status']}: error Google distance matrix');
}
}
return nearestCar;
}
}
}
return null;
}
Future getCarForFirstConfirm(String carType) async {
bool foundCars = false;
int attempt = 0;
Timer.periodic(const Duration(seconds: 4), (Timer t) async {
foundCars = await getCarsLocationByPassengerAndReloadMarker();
Log.print('foundCars: $foundCars');
if (foundCars) {
t.cancel();
} else if (attempt >= 4) {
t.cancel();
if (!foundCars) {
noCarString = true;
dataCarsLocationByPassenger = 'failure';
}
update();
}
attempt++;
});
}
void startCarLocationSearch(String carType) {
int searchInterval = 5;
Log.print('searchInterval: $searchInterval');
int boundIncreaseStep = 2500;
Log.print('boundIncreaseStep: $boundIncreaseStep');
int maxAttempts = 3;
int maxBoundIncreaseStep = 6000;
int attempt = 0;
Log.print('initial attempt: $attempt');
Timer.periodic(Duration(seconds: searchInterval), (Timer timer) async {
Log.print('Current attempt: $attempt');
bool foundCars = false;
final mapEngine = Get.find<MapEngineController>();
if (attempt >= maxAttempts) {
timer.cancel();
if (foundCars == false) {
noCarString = true;
update();
}
} else if (mapEngine.reloadStartApp == true) {
Log.print('reloadStartApp: ${mapEngine.reloadStartApp}');
foundCars = await getCarsLocationByPassengerAndReloadMarker();
Log.print('foundCars: $foundCars');
if (foundCars) {
timer.cancel();
} else {
attempt++;
Log.print('Incrementing attempt to: $attempt');
if (boundIncreaseStep < maxBoundIncreaseStep) {
boundIncreaseStep += 1500;
if (boundIncreaseStep > maxBoundIncreaseStep) {
boundIncreaseStep = maxBoundIncreaseStep;
}
Log.print('New boundIncreaseStep: $boundIncreaseStep');
}
}
}
});
}
String _getIconForCar(Map<String, dynamic> carData) {
if (carData['model'].toString().contains('دراجة')) {
return 'moto_icon';
} else if (carData['gender'] == 'Female') {
return 'lady_icon';
} else {
return 'car_icon';
}
}
void _updateOrCreateMarker(
String markerId, LatLng newPosition, double newHeading, String icon) {
final mapEngine = Get.find<MapEngineController>();
if (!mapEngine.isIconsLoaded) {
Log.print("⚠️ Skipping drawing marker $markerId because map icons are not fully loaded yet.");
return;
}
final mId = MarkerId(markerId);
final existingMarker = mapEngine.markers.cast<Marker?>().firstWhere(
(m) => m?.markerId == mId,
orElse: () => null,
);
if (existingMarker == null) {
mapEngine.markers = {
...mapEngine.markers,
Marker(
markerId: mId,
position: newPosition,
rotation: newHeading,
icon: InlqBitmap.fromStyleImage(icon),
anchor: const Offset(0.5, 0.5),
),
};
mapEngine.update();
} else {
double distance = Geolocator.distanceBetween(
existingMarker.position.latitude,
existingMarker.position.longitude,
newPosition.latitude,
newPosition.longitude);
if (distance >= minMovementThreshold) {
_smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
}
}
}
void _smoothlyUpdateMarker(
Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
final mapEngine = Get.find<MapEngineController>();
final MarkerId markerIdKey = oldMarker.markerId;
_animationTimers[markerIdKey.value]?.cancel();
int ticks = 0;
const int totalSteps = 20;
const int stepDuration = 50;
double latStep =
(newPosition.latitude - oldMarker.position.latitude) / totalSteps;
double lngStep =
(newPosition.longitude - oldMarker.position.longitude) / totalSteps;
double headingStep = (newHeading - oldMarker.rotation) / totalSteps;
LatLng currentPos = oldMarker.position;
double currentHeading = oldMarker.rotation;
_animationTimers[markerIdKey.value] =
Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
ticks++;
currentPos =
LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
currentHeading += headingStep;
final updatedMarker = oldMarker.copyWith(
position: currentPos,
rotation: currentHeading,
icon: InlqBitmap.fromStyleImage(icon),
);
mapEngine.markers = {
...mapEngine.markers.where((m) => m.markerId != markerIdKey),
updatedMarker,
};
if (mapEngine.mapController != null) {
mapEngine.mapController!.animateCamera(CameraUpdate.newLatLng(currentPos));
}
mapEngine.update();
if (ticks >= totalSteps) {
timer.cancel();
_animationTimers.remove(markerIdKey.value);
}
});
}
double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
double deltaLon = lon2 - lon1;
double y = sin(deltaLon) * cos(lat2);
double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(deltaLon);
double bearing = atan2(y, x);
return (bearing * 180 / pi + 360) % 360;
}
void analyzeBehavior(Position currentPosition, List<LatLng> routePoints) {
double actualBearing = currentPosition.heading;
double expectedBearing = calculateBearing(
routePoints[0].latitude,
routePoints[0].longitude,
routePoints[1].latitude,
routePoints[1].longitude,
);
double bearingDifference = (expectedBearing - actualBearing).abs();
if (bearingDifference > 30) {
Log.print("⚠️ السائق انحرف عن المسار!");
}
}
void detectStops(Position currentPosition) {
if (currentPosition.speed < 0.5) {
Log.print("🚦 السائق توقف في موقع غير متوقع!");
}
}
Future<void> detectPerfMode() async {
try {
if (GetPlatform.isAndroid) {
final info = await DeviceInfoPlugin().androidInfo;
final sdk = info.version.sdkInt;
final ram = info.availableRamSize;
lowPerf = (sdk < 28) || (ram > 0 && ram < 3 * 1024 * 1024 * 1024);
} else {
lowPerf = false;
}
} catch (_) {
lowPerf = false;
}
update();
}
@override
void onClose() {
_animationTimers.forEach((key, timer) => timer.cancel());
_animationTimers.clear();
super.onClose();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,438 @@
import 'dart:async';
import 'dart:math' show cos, pi, sin;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../print.dart';
import 'map_engine_controller.dart';
import 'map_socket_controller.dart';
import 'nearby_drivers_controller.dart';
import 'ride_lifecycle_controller.dart';
import 'ride_state.dart';
import 'location_search_controller.dart';
class RideSimulationController extends GetxController {
Timer? _simulationTimer;
int _step = 0;
bool _isSimulating = false;
List<LatLng> _simulatedRoute = [];
int _driverRouteIndex = 0;
String get simulationStatus => _isSimulating
? 'Simulating... Step $_step'
: 'Idle';
// آمود نقاط المسار (Jordan area: Amman center ~ 31.95, 35.91)
static const double _centerLat = 31.95;
static const double _centerLng = 35.91;
static const double _routeDelta = 0.03;
static const int _routePointCount = 100;
void _generateRoutePoints() {
_simulatedRoute = [];
for (int i = 0; i < _routePointCount; i++) {
double fraction = i / (_routePointCount - 1);
double lat = _centerLat + fraction * _routeDelta;
double lng = _centerLng + fraction * _routeDelta +
0.005 * sin(fraction * pi * 2);
_simulatedRoute.add(LatLng(lat, lng));
}
}
void startFullSimulation() {
if (_isSimulating) return;
_isSimulating = true;
_step = 0;
_driverRouteIndex = 0;
_generateRoutePoints();
Log.print('=' * 70);
Log.print('🚀 RIDE SIMULATION STARTED — Full lifecycle test');
Log.print('=' * 70);
_simulationTimer = Timer.periodic(const Duration(seconds: 2), (timer) {
switch (_step) {
case 0:
_simulateSearchingState();
break;
case 1:
_simulateDriverAppliedState();
break;
case 2:
_simulateDriverEnRoute(isFirst: true);
break;
case 3:
_simulateDriverEnRoute(isFirst: false);
break;
case 4:
_simulateDriverArrivedState();
break;
case 5:
_simulateRideBeginState();
break;
case 6:
_simulateRideInProgress();
break;
case 7:
_simulateRideFinishedState();
timer.cancel();
_isSimulating = false;
Log.print('=' * 70);
Log.print('✅ RIDE SIMULATION COMPLETED');
Log.print('=' * 70);
break;
}
_step++;
update();
});
}
void stopSimulation() {
_simulationTimer?.cancel();
_simulationTimer = null;
_isSimulating = false;
_step = 0;
_driverRouteIndex = 0;
Log.print('🛑 Simulation stopped');
update();
}
void _logPolylineState(String stage) {
final mapEngine = Get.find<MapEngineController>();
Log.print('');
Log.print('╔══ Polyline State [$stage] ══╗');
if (mapEngine.polyLines.isEmpty) {
Log.print(' ║ (no polylines)');
}
for (final p in mapEngine.polyLines) {
String colorStr;
if (p.color == Colors.amber) {
colorStr = 'AMBER';
} else if (p.color.value == 0xFF2196F3) {
colorStr = 'BLUE';
} else if (p.color == Colors.blueGrey) {
colorStr = 'BLUE_GREY';
} else {
colorStr = '#${p.color.value.toRadixString(16).padLeft(8, '0')}';
}
Log.print(' ║ • ${p.polylineId.value}: ${p.points.length} pts, '
'color: $colorStr, w: ${p.width}');
}
Log.print('╠══ Markers [$stage] ══╣');
if (mapEngine.markers.isEmpty) {
Log.print(' ║ (no markers)');
}
for (final m in mapEngine.markers) {
Log.print(' ║ • ${m.markerId.value}: '
'(${m.position.latitude.toStringAsFixed(5)}, '
'${m.position.longitude.toStringAsFixed(5)}), '
'heading: ${m.rotation.toStringAsFixed(1)}');
}
Log.print('╚══════════════════════╝');
Log.print('');
}
void _verifyPolylineCondition(bool condition, String description) {
if (!condition) {
Log.print(' ❌ VERIFY FAILED: $description');
} else {
Log.print(' ✅ VERIFY PASSED: $description');
}
}
LatLng _interpolateRoutePoint(double fraction) {
if (_simulatedRoute.isEmpty) return LatLng(_centerLat, _centerLng);
int total = _simulatedRoute.length;
double exactIdx = fraction * (total - 1);
int idx = exactIdx.floor();
if (idx >= total - 1) return _simulatedRoute.last;
double t = exactIdx - idx;
return LatLng(
_simulatedRoute[idx].latitude +
t * (_simulatedRoute[idx + 1].latitude - _simulatedRoute[idx].latitude),
_simulatedRoute[idx].longitude +
t * (_simulatedRoute[idx + 1].longitude - _simulatedRoute[idx].longitude),
);
}
void _simulateSearchingState() {
Log.print('🟡 [Step $_step] STATE: SEARCHING');
final ride = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
ride.currentRideState.value = RideState.searching;
ride.rideId = 'sim_ride_001';
ride.isSearchingWindow = true;
// آمود موقع الراكب
LatLng passengerLoc = _simulatedRoute.isNotEmpty
? _simulatedRoute.first
: LatLng(_centerLat, _centerLng);
locSearch.passengerLocation = passengerLoc;
ride.passengerLocation = passengerLoc;
ride.update();
_logPolylineState('searching');
_verifyPolylineCondition(
Get.find<MapEngineController>().polyLines.isEmpty,
'No route polylines during searching');
Log.print('');
}
void _simulateDriverAppliedState() {
Log.print('🟢 [Step $_step] STATE: DRIVER APPLIED');
final ride = Get.find<RideLifecycleController>();
final nearbyDrivers = Get.find<NearbyDriversController>();
final locSearch = Get.find<LocationSearchController>();
ride.currentRideState.value = RideState.driverApplied;
ride.statusRide = 'Apply';
ride.isSearchingWindow = false;
// آمود معلومات السائق
ride.driverId = 'sim_driver_001';
ride.driverName = 'Simulated Captain';
ride.make = 'Toyota';
ride.model = 'Corolla';
ride.carColor = 'White';
ride.licensePlate = 'SIM-123';
ride.driverRate = '4.8';
ride.driverPhone = '+9627XXXXXXXX';
ride.driverToken = 'sim_token_001';
// آمود مواقع البداية والنهاية
ride.passengerLocation = _simulatedRoute.first;
ride.myDestination = _simulatedRoute.last;
locSearch.passengerLocation = _simulatedRoute.first;
locSearch.myDestination = _simulatedRoute.last;
// محاكاة موقع السائق (45% على طول المسار)
LatLng driverPos = _interpolateRoutePoint(0.45);
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [driverPos];
// رسم مسار السائق إلى الراكب
ride.calculateDriverToPassengerRoute(driverPos, ride.passengerLocation);
// وضع ماركر السائق على الخريطة
ride.updateDriverMarker(driverPos, 45.0);
ride.update();
_logPolylineState('driver_applied');
// التحقق من وجود الخطوط المطلوبة
final polylines = Get.find<MapEngineController>().polyLines;
_verifyPolylineCondition(
polylines.any((p) => p.polylineId.value == 'driver_route_solid'),
'driver_route_solid (amber solid line) exists');
_verifyPolylineCondition(
polylines.any((p) => p.polylineId.value.startsWith('passenger_walk_line')),
'passenger_walk_line (dashed walk line) exists');
_verifyPolylineCondition(
polylines.any((p) => p.color == Colors.amber),
'Amber-colored polyline exists (driver route)');
_verifyPolylineCondition(
polylines.any((p) => p.color == Colors.blueGrey),
'BlueGrey-colored polyline exists (walk dashes)');
Log.print('');
}
void _simulateDriverEnRoute({required bool isFirst}) {
String label = isFirst ? '1' : '2';
Log.print('🔵 [Step $_step] STATE: DRIVER APPROACHING (update #$label)');
final ride = Get.find<RideLifecycleController>();
final nearbyDrivers = Get.find<NearbyDriversController>();
_driverRouteIndex += 18;
if (_driverRouteIndex > _routePointCount - 1) {
_driverRouteIndex = _routePointCount - 1;
}
LatLng newPos = _simulatedRoute[_driverRouteIndex];
// تحديث موقع السائق
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [newPos];
ride.updateDriverMarker(newPos, 50.0);
ride.updateRemainingRoute(newPos);
ride.update();
_logPolylineState('driver_en_route_$label');
// التحقق: driver_route_solid يبقى موجود ويتقلص
final polylines = Get.find<MapEngineController>().polyLines;
_verifyPolylineCondition(
polylines.any((p) => p.polylineId.value == 'driver_route_solid'),
'driver_route_solid still exists (shrinking)');
_verifyPolylineCondition(
polylines.any((p) => p.polylineId.value.startsWith('passenger_walk_line')),
'passenger_walk_line still exists');
Log.print('');
}
void _simulateDriverArrivedState() {
Log.print('🟣 [Step $_step] STATE: DRIVER ARRIVED');
final ride = Get.find<RideLifecycleController>();
ride.currentRideState.value = RideState.driverArrived;
ride.statusRide = 'Arrived';
// محاكاة وصول السائق: تحديد الموقع كآخر نقطة طريق
LatLng driverPos = _simulatedRoute.first;
// تطبيق arrived logic
ride.polyLines = ride.polyLines
.where((p) =>
p.polylineId.value != 'main_route' &&
p.polylineId.value != 'route_direct' &&
!p.polylineId.value.startsWith('driver_route'))
.toSet();
// رسم المسار الجديد (من السائق إلى الوجهة النهائية)
ride.calculateDriverToPassengerRoute(driverPos, ride.myDestination,
isBeginPhase: true);
ride.update();
_logPolylineState('driver_arrived');
final polylines = Get.find<MapEngineController>().polyLines;
// في حالة الوصول: يجب أن يظهر main_route (أزرق) بدلاً من driver_route_solid
_verifyPolylineCondition(
!polylines.any((p) => p.polylineId.value == 'driver_route_solid'),
'driver_route_solid (amber) has been cleared');
_verifyPolylineCondition(
polylines.any((p) => p.polylineId.value == 'main_route'),
'main_route (blue) has been drawn to destination');
Log.print('');
}
void _simulateRideBeginState() {
Log.print('🔴 [Step $_step] STATE: RIDE IN PROGRESS (Begin)');
final ride = Get.find<RideLifecycleController>();
ride.currentRideState.value = RideState.inProgress;
ride.statusRide = 'Begin';
LatLng driverPos = _simulatedRoute.first;
// محاكاة processRideBegin: مسح الخطوط القديمة ورسم الخط الجديد
ride.polyLines = ride.polyLines
.where((p) =>
p.polylineId.value != 'main_route' &&
p.polylineId.value != 'route_direct' &&
!p.polylineId.value.startsWith('driver_route'))
.toSet();
ride.calculateDriverToPassengerRoute(driverPos, ride.myDestination,
isBeginPhase: true);
ride.rideIsBeginPassengerTimer();
ride.update();
_logPolylineState('ride_begin');
final polylines = Get.find<MapEngineController>().polyLines;
_verifyPolylineCondition(
polylines.any((p) => p.polylineId.value == 'main_route'),
'main_route (blue) exists for trip');
_verifyPolylineCondition(
!polylines.any((p) => p.polylineId.value.startsWith('driver_route')),
'No driver_route* polylines remain');
_verifyPolylineCondition(
polylines.any((p) => p.color.value == 0xFF2196F3),
'Blue polyline (main route) is present');
Log.print('');
}
void _simulateRideInProgress() {
Log.print('📱 [Step $_step] STATE: CAR MOVING (remaining route update)');
final ride = Get.find<RideLifecycleController>();
final nearbyDrivers = Get.find<NearbyDriversController>();
_driverRouteIndex += 30;
if (_driverRouteIndex > _routePointCount - 1) {
_driverRouteIndex = _routePointCount - 1;
}
LatLng newPos = _simulatedRoute[_driverRouteIndex];
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [newPos];
ride.updateDriverMarker(newPos, 70.0);
ride.updateRemainingRoute(newPos, updateEta: true);
ride.update();
_logPolylineState('ride_in_progress');
final polylines = Get.find<MapEngineController>().polyLines;
_verifyPolylineCondition(
polylines.any((p) => p.polylineId.value == 'main_route'),
'main_route (blue) exists with remaining points');
_verifyPolylineCondition(
!polylines.any((p) => p.polylineId.value.startsWith('driver_route')),
'No driver_route* polylines remain');
Log.print('');
}
void _simulateRideFinishedState() {
Log.print('🏁 [Step $_step] STATE: RIDE FINISHED');
final ride = Get.find<RideLifecycleController>();
final mapEngine = Get.find<MapEngineController>();
ride.currentRideState.value = RideState.finished;
ride.statusRide = 'Finished';
ride.isSearchingWindow = false;
ride.stopAllTimers();
mapEngine.clearPolyline();
mapEngine.markers = {};
ride.update();
_logPolylineState('ride_finished');
_verifyPolylineCondition(
Get.find<MapEngineController>().polyLines.isEmpty,
'All polylines cleared');
_verifyPolylineCondition(
Get.find<MapEngineController>().markers.isEmpty,
'All markers removed');
Log.print('');
}
// Simulate a single WebSocket location update event
void simulateSocketLocationUpdate(LatLng position, double heading) {
final ride = Get.find<RideLifecycleController>();
final nearbyDrivers = Get.find<NearbyDriversController>();
final mapSocket = Get.find<MapSocketController>();
Log.print('📡 Simulating socket location update: '
'(${position.latitude.toStringAsFixed(5)}, ${position.longitude.toStringAsFixed(5)})');
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [position];
ride.driverCarsLocationToPassengerAfterApplied = [position];
ride.checkAndRecalculateIfDeviated(
position,
heading: heading,
speed: 30.0,
);
final mapEngine = Get.find<MapEngineController>();
if (mapEngine.mapController != null) {
mapEngine.mapController!
.animateCamera(CameraUpdate.newLatLngZoom(position, 16.5));
}
ride.updateDriverMarker(position, heading);
ride.updateRemainingRoute(position);
ride.update();
}
@override
void onClose() {
_simulationTimer?.cancel();
super.onClose();
}
}
@@ -0,0 +1,10 @@
enum RideState {
noRide, // لا يوجد رحلة جارية، عرض واجهة البحث
cancelled, // تم إلغاء الرحلة
preCheckReview, // يوجد رحلة منتهية، تحقق من التقييم
searching, // جاري البحث عن كابتن
driverApplied, // تم قبول الطلب
driverArrived, // وصل السائق
inProgress, // الرحلة بدأت بالفعل
finished, // انتهت الرحلة (سيتم تحويلها إلى preCheckReview)
}
@@ -0,0 +1,406 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../constant/info.dart';
import '../../../main.dart'; // contains global 'box'
import '../../../print.dart';
import '../../../services/emergency_signal_service.dart';
import '../../../views/widgets/elevated_btn.dart';
import '../../../views/widgets/my_textField.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../functions/launch.dart';
import '../../firebase/notification_service.dart';
import '../../functions/crud.dart';
import '../../functions/tts.dart';
import 'ride_lifecycle_controller.dart';
import 'location_search_controller.dart';
import '../../functions/country_logic.dart';
class UiInteractionsController extends GetxController {
TextEditingController sosPhonePassengerProfile = TextEditingController();
TextEditingController whatsAppLocationText = TextEditingController();
final sosFormKey = GlobalKey<FormState>();
@override
void onInit() {
super.onInit();
EmergencySignalService.instance.startListening(() {
final rideLifecycle = Get.find<RideLifecycleController>();
if (rideLifecycle.statusRide == 'Begin' ||
rideLifecycle.statusRide == 'start') {
Log.print("🚨 Emergency shake verified! Prompting SOS...");
sosPassenger();
}
});
}
Future<void> _ensureSosNumber(Function onSuccess) async {
String? storedPhone = box.read(BoxName.sosPhonePassenger);
if (storedPhone != null && storedPhone.isNotEmpty) {
onSuccess();
return;
}
sosPhonePassengerProfile.clear();
Get.defaultDialog(
title: 'Add SOS Phone'.tr,
titleStyle: AppStyle.title,
content: Form(
key: sosFormKey,
child: Column(
children: [
MyTextForm(
controller: sosPhonePassengerProfile,
label: 'insert sos phone'.tr,
hint: CountryLogic.getPhoneHint(
box.read(BoxName.countryCode) ?? 'Syria').tr,
type: TextInputType.phone,
),
const SizedBox(height: 10),
Text(
"Note: If no country code is entered, it will be saved as Syrian (+963)."
.tr,
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
confirm: MyElevatedButton(
title: 'Save'.tr,
onPressed: () async {
if (sosFormKey.currentState!.validate()) {
Get.back();
var numberPhone =
CountryLogic.formatCurrentCountryPhone(sosPhonePassengerProfile.text);
await CRUD().post(
link: AppLink.updateprofile,
payload: {
'id': box.read(BoxName.passengerID),
'sosPhone': numberPhone,
},
);
box.write(BoxName.sosPhonePassenger, numberPhone);
onSuccess();
}
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
kolor: AppColor.redColor,
));
}
void sosPassenger() {
_ensureSosNumber(() {
Get.defaultDialog(
barrierDismissible: false,
title: "Emergency SOS".tr,
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
content: Column(
children: [
Icon(Icons.warning_amber_rounded,
size: 50, color: AppColor.redColor),
const SizedBox(height: 10),
Text(
"Do you want to send an emergency message to your SOS contact?"
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
],
),
confirm: MyElevatedButton(
title: "Send SOS".tr,
kolor: AppColor.redColor,
onPressed: () {
Get.back();
_shareTripDetailsSOS();
},
),
cancel: MyElevatedButton(
title: "I'm Safe".tr,
kolor: AppColor.greenColor,
onPressed: () {
Get.back();
},
),
);
});
}
void _shareTripDetailsSOS() {
final rideLifecycle = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
String message = "**Emergency SOS from Passenger:**\n";
String origin = locSearch.startNameAddress;
String destination = locSearch.endNameAddress;
message += "* ${'Origin'.tr}: $origin\n";
message += "* ${'Destination'.tr}: $destination\n";
message += "* ${'Driver Name'.tr}: ${rideLifecycle.driverName}\n";
message +=
"* ${'Car'.tr}: ${rideLifecycle.make} - ${rideLifecycle.model} - ${rideLifecycle.licensePlate}\n";
message += "* ${'Phone'.tr}: ${rideLifecycle.driverPhone}\n\n";
message +=
"${'Location'.tr}: https://www.google.com/maps/search/?api=1&query=${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}\n";
message += "Please help! Contact me as soon as possible.".tr;
launchCommunication(
'whatsapp', box.read(BoxName.sosPhonePassenger), message);
}
// removed formatSyrianPhone and formatSyrianPhoneNumber
void sendSMS(String to) async {
final rideLifecycle = Get.find<RideLifecycleController>();
String formattedDriverPhone =
rideLifecycle.driverPhone.replaceAll(' ', '').replaceAll('+', '');
String message =
'Hi! This is ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n I am using ${box.read(AppInformation.appName)} to ride with ${rideLifecycle.passengerName} as the driver. ${rideLifecycle.passengerName} \nis driving a ${rideLifecycle.model}\n with license plate ${rideLifecycle.licensePlate}.\n I am currently located at ${Get.find<LocationSearchController>().passengerLocation}.\n If you need to reach me, please contact the driver directly at\n\n $formattedDriverPhone.';
launchCommunication('sms', to, message);
}
void sendWhatsapp(String to) async {
final rideLifecycle = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
String formattedPhone = CountryLogic.formatCurrentCountryPhone(to);
String message =
'${'${'Hi! This is'.tr} ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n${' I am using'.tr}'} ${AppInformation.appName}${' to ride with'.tr} ${rideLifecycle.passengerName}${' as the driver.'.tr} ${rideLifecycle.passengerName} \n${'is driving a '.tr}${rideLifecycle.model}\n${' with license plate '.tr}${rideLifecycle.licensePlate}.\n${' I am currently located at '.tr} https://www.google.com/maps/place/${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}.\n${' If you need to reach me, please contact the driver directly at'.tr}\n\n ${rideLifecycle.driverPhone}.';
launchCommunication('whatsapp', formattedPhone, message);
}
Future<dynamic> driverArrivePassengerDialoge() {
final rideLifecycle = Get.find<RideLifecycleController>();
return Get.defaultDialog(
barrierDismissible: false,
title: 'Hi ,I Arrive your location'.tr,
titleStyle: AppStyle.title,
middleText: 'Please go to Car Driver'.tr,
middleTextStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'Ok I will go now.'.tr,
onPressed: () {
NotificationService.sendNotification(
target: rideLifecycle.driverToken.toString(),
title: 'Hi ,I will go now'.tr,
body: 'I will go now'.tr,
isTopic: false,
tone: 'ding',
driverList: [],
category: 'Hi ,I will go now',
);
Get.back();
rideLifecycle.remainingTime = 0;
rideLifecycle.update();
},
),
);
}
void getDialog(String title, String? midTitle, VoidCallback onPressed) {
final textToSpeechController = Get.find<TextToSpeechController>();
Get.defaultDialog(
title: title,
titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title,
content: Column(
children: [
IconButton(
onPressed: () async {
await textToSpeechController.speakText(title ?? midTitle!);
},
icon: const Icon(Icons.headphones),
),
Text(
midTitle!,
style: AppStyle.title,
)
],
),
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: onPressed,
kolor: AppColor.greenColor,
),
cancel: MyElevatedButton(
title: 'Cancel',
kolor: AppColor.redColor,
onPressed: () {
Get.back();
},
),
);
}
Future shareTripWithFamily() async {
_ensureSosNumber(() {
final rideLifecycle = Get.find<RideLifecycleController>();
String storedPhone = box.read(BoxName.sosPhonePassenger)!;
if (rideLifecycle.rideId == 'yet' || rideLifecycle.driverId.isEmpty) {
mySnackbarWarning("Wait for the trip to start first".tr);
return;
}
var numberPhone = CountryLogic.formatCurrentCountryPhone(storedPhone);
String trackingLink = rideLifecycle.generateTrackingLink(
rideLifecycle.rideId, rideLifecycle.driverId);
String message = """
مرحباً، تابع رحلتي مباشرة على تطبيق سيرو 🚗
يمكنك تتبع مسار الرحلة من هنا:
$trackingLink
السائق: ${rideLifecycle.passengerName}
السيارة: ${rideLifecycle.model} - ${rideLifecycle.licensePlate}
شكراً لاستخدامك سيرو!
"""
.tr;
String messageEn = """Hello, follow my trip live on Siro 🚗
Track my ride here:
$trackingLink
Driver: ${rideLifecycle.passengerName}
Car: ${rideLifecycle.model} - ${rideLifecycle.licensePlate}
Thank you for using Siro!
""";
String userLanguage = box.read(BoxName.lang) ?? 'ar';
message = (userLanguage == 'ar') ? message : messageEn;
Log.print("Sending WhatsApp to: $numberPhone");
launchCommunication('whatsapp', numberPhone, message);
box.write(BoxName.parentTripSelected, true);
update();
});
}
Future getTokenForParent() async {
_ensureSosNumber(() async {
String storedPhone = box.read(BoxName.sosPhonePassenger)!;
var numberPhone = CountryLogic.formatCurrentCountryPhone(storedPhone);
Log.print("Searching for Parent Token with Phone: $numberPhone");
var res = await CRUD()
.post(link: AppLink.getTokenParent, payload: {'phone': numberPhone});
if (res is Map<String, dynamic>) {
handleResponse(res);
} else {
try {
var decoded = jsonDecode(res);
handleResponse(decoded);
} catch (e) {
Log.print("Error parsing parent response: $res");
}
}
});
}
void handleResponse(Map<String, dynamic> res) {
final rideLifecycle = Get.find<RideLifecycleController>();
if (res['status'] == 'failure') {
if (Get.isDialogOpen ?? false) Get.back();
Get.defaultDialog(
title: "No user found".tr,
titleStyle: AppStyle.title,
content: Column(
children: [
Text(
"No passenger found for the given phone number".tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Text(
"Send Siro app to him".tr,
style: AppStyle.title
.copyWith(color: AppColor.greenColor, fontSize: 14),
textAlign: TextAlign.center,
)
],
),
confirm: MyElevatedButton(
title: 'Send Invite'.tr,
onPressed: () {
Get.back();
var rawPhone = box.read(BoxName.sosPhonePassenger);
if (rawPhone == null) return;
var phone = CountryLogic.formatCurrentCountryPhone(rawPhone);
var message = '''Dear Friend,
🚀 I have just started an exciting trip on Siro!
Download the app to track my ride:
👉 Android: https://play.google.com/store/apps/details?id=com.Siro.siro&hl=en-US
👉 iOS: https://apps.apple.com/st/app/siro-rider/id6748075179
See you there!
Siro Team''';
launchCommunication('whatsapp', phone, message);
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () {
Get.back();
},
),
);
} else if (res['status'] == 'success') {
if (Get.isDialogOpen ?? false) Get.back();
mySnackbarSuccess("The invitation was sent successfully".tr);
List tokensData = res['data'];
for (var device in tokensData) {
String tokenParent = device['token'];
NotificationService.sendNotification(
category: "Trip Monitoring",
target: tokenParent,
title: "Trip Monitoring".tr,
body: "Click to track the trip".tr,
isTopic: false,
tone: 'tone1',
driverList: [rideLifecycle.rideId, rideLifecycle.driverId],
);
box.write(BoxName.tokenParent, tokenParent);
}
box.write(BoxName.parentTripSelected, true);
}
}
@override
void onClose() {
EmergencySignalService.instance.stopListening();
super.onClose();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
import 'package:get/get.dart';
class MyMenuController extends GetxController {
bool isDrawerOpen = true;
void getDrawerMenu() {
if (isDrawerOpen == true) {
isDrawerOpen = false;
} else {
isDrawerOpen = true;
}
update();
}
}
@@ -0,0 +1,111 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/main.dart';
class CaptainWalletController extends GetxController {
bool isLoading = false;
Map walletDate = {};
Map walletDateVisa = {};
Map walletDriverPointsDate = {};
final formKey = GlobalKey<FormState>();
String totalAmount = '0';
String totalAmountVisa = '0';
String totalPoints = '0';
final amountFromBudgetController = TextEditingController();
payFromBudget() async {
if (formKey.currentState!.validate()) {
var pointFromBudget = box.read(BoxName.countryCode) == 'Jordan'
? int.parse((amountFromBudgetController.text)) * 100
: int.parse((amountFromBudgetController.text));
await addDriverPayment('fromBudgetToPoints',
int.parse((amountFromBudgetController.text)) * -1);
Future.delayed(const Duration(seconds: 2));
await addDriverWallet('fromBudget', pointFromBudget.toString());
update();
Get.back();
// getCaptainWalletFromRide();
// getCaptainWalletFromBuyPoints();
// checkAccountCaptainBank();
}
}
// Future getCaptainWalletFromRide() async {
// isLoading = true;
// update();
// var res = await CRUD().get(
// link: AppLink.getAllPaymentFromRide,
// payload: {'driverID': box.read(BoxName.driverID)},
// );
// walletDate = jsonDecode(res);
// totalAmount = walletDate['message'][0]['total_amount'].toString() == null
// ? '0'
// : walletDate['message'][0]['total_amount'];
// var res1 = await CRUD().get(
// link: AppLink.getAllPaymentVisa,
// payload: {'driverID': box.read(BoxName.driverID)});
// walletDateVisa = jsonDecode(res1);
// totalAmountVisa = walletDateVisa['message'][0]['diff'].toString() == null
// ? '0'
// : walletDateVisa['message'][0]['diff'];
// isLoading = false;
// update();
// }
// Future getCaptainWalletFromBuyPoints() async {
// isLoading = true;
// update();
// var res = await CRUD().get(
// link: AppLink.getDriverPaymentPoints,
// payload: {'driverID': box.read(BoxName.driverID)},
// );
// walletDriverPointsDate = jsonDecode(res);
// if (walletDriverPointsDate['message'][0]['driverID'].toString() ==
// box.read(BoxName.driverID)) {
// double totalPointsDouble = double.parse(
// walletDriverPointsDate['message'][0]['total_amount'].toString());
// totalPoints = totalPointsDouble.toStringAsFixed(0);
// } else {
// totalPoints = '0';
// }
// isLoading = false;
// update();
// }
late String paymentID;
Future addDriverPayment(String paymentMethod, amount) async {
var res =
await CRUD().postWallet(link: AppLink.addDriverPaymentPoints, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'amount': amount.toString(),
'payment_method': paymentMethod.toString(),
});
var d = res;
paymentID = d['message'].toString();
}
Future addDriverWallet(String paymentMethod, point) async {
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'paymentID': paymentID.toString(),
'amount': point,
'paymentMethod': paymentMethod.toString(),
});
}
@override
void onInit() {
// getCaptainWalletFromRide();
// getCaptainWalletFromBuyPoints();
// checkAccountCaptainBank();
super.onInit();
}
}
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../functions/digit_obsecur_formate.dart';
import '../../functions/secure_storage.dart';
class CreditCardController extends GetxController {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController cardNumberController = TextEditingController();
final TextEditingController cardHolderNameController =
TextEditingController();
final TextEditingController expiryDateController = TextEditingController();
final TextEditingController cvvCodeController = TextEditingController();
openPayment() async {
String? cardNumber = await SecureStorage().readData(BoxName.cardNumber);
String? cardHolderName =
await SecureStorage().readData(BoxName.cardHolderName);
String? expiryDate = await SecureStorage().readData(BoxName.expiryDate);
}
@override
void onInit() async {
super.onInit();
openPayment();
// String? cardNumber = await SecureStorage().readData(BoxName.cardNumber);
// String? cardHolderName =
// await SecureStorage().readData(BoxName.cardHolderName);
// String? expiryDate = await SecureStorage().readData(BoxName.expiryDate);
// String? cvvCode = await SecureStorage().readData(BoxName.cvvCode);
// if (cvvCode != null && cvvCode.isNotEmpty) {
// final maskedCardNumber = DigitObscuringFormatter()
// .formatEditUpdate(
// TextEditingValue.empty,
// TextEditingValue(text: cardNumber ?? ''),
// )
// .text;
// cardNumberController.text = maskedCardNumber;
// cardHolderNameController.text = cardHolderName ?? '';
// expiryDateController.text = expiryDate ?? '';
// cvvCodeController.text = cvvCode;
// }
}
}
class CreditCardModel {
String cardNumber;
String cardHolderName;
String expiryDate;
CreditCardModel({
required this.cardNumber,
required this.cardHolderName,
required this.expiryDate,
});
}
@@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:intaleq_rider/controller/home/map/ride_lifecycle_controller.dart';
import '../../constant/api_key.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../functions/crud.dart';
import '../functions/location_controller.dart';
class PointsForRiderController extends GetxController {
List<String> locations = [];
String hintTextDestinationPoint = 'Search for your destination'.tr;
TextEditingController placeStartController = TextEditingController();
void addLocation(String location) {
locations.add(location);
update();
}
void getTextFromList(String location) {
locations.add(location);
update();
Get.back();
}
void removeLocation(int index) {
locations.removeAt(index);
update();
}
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
update();
}
final item = locations.removeAt(oldIndex);
locations.insert(newIndex, item);
update();
}
}
class LocationModel {
String name;
double lat, lon;
LocationModel({required this.name, required this.lat, required this.lon});
}
class WayPointController extends GetxController {
// A list of text editing controllers for each text field
// final textFields = [TextEditingController()].obs;
List<String> wayPoints = [];
List<List<dynamic>> placeListResponse = [];
double wayPointHeight = 400;
String hintTextDestinationPoint = 'Search for your destination'.tr;
TextEditingController textSearchCotroller = TextEditingController();
// A list of places corresponding to each text field
final places = <String>[];
final hintTextPointList = <String>[];
late LatLng myLocation;
void addWayPoints() {
String wayPoint = 'Add a Stop'.tr;
if (wayPoints.length < 5) {
wayPoints.add(wayPoint);
update();
} else {
Get.defaultDialog(
title: 'This is most WayPoints',
titleStyle: AppStyle.title,
middleText: '');
}
update();
}
void removeTextField(int index) {
wayPoints.removeAt(index);
update();
}
// A method to reorder the text fields and the places
void reorderTextFields(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final wayPoint = wayPoints.removeAt(oldIndex);
wayPoints.insert(newIndex, wayPoint);
update();
}
void updatePlace(int index, String input) async {
var url =
'${AppLink.googleMapsLink}place/nearbysearch/json?keyword=$input&location=${myLocation.latitude},${myLocation.longitude}&radius=50000&language=en&key=${AK.mapAPIKEY.toString()}';
var response = await CRUD().getGoogleApi(link: url, payload: {});
// final place = input;
// if (index == 0) {
List<dynamic> newList = [];
placeListResponse.add(newList);
newList = response['results'];
placeListResponse[index].add(newList);
update();
// }
update();
}
@override
void onInit() {
// Get.put(LocationController());
addWayPoints();
myLocation = Get.find<RideLifecycleController>().passengerLocation;
super.onInit();
}
void reset() {
wayPoints.clear();
addWayPoints();
placeListResponse.clear();
update();
}
}
class PlaceList extends StatelessWidget {
// Get the controller instance
final controller = Get.find<WayPointController>();
@override
Widget build(BuildContext context) {
// Use the Obx widget to rebuild the widget when the controller changes
return Obx(() {
// Use the ListView widget to display the list of places
return ListView(
// The children of the list are the places
children: [
// Loop through the places in the controller
for (final place in controller.places)
// Create a text widget for each place
Text(
// Use the place as the text
place,
// Add some style and padding
style: const TextStyle(fontSize: 18.0),
),
],
);
});
}
}
@@ -0,0 +1,159 @@
--- PRECISE MISSING METHODS ---
Total original methods/getters/setters: 270
Total defined in split controllers: 270
Total missing: 53
- Column
- CupertinoDialogAction
- Future
- _applyLowEndModeIfNeeded
- _buildOsrmWaypointCoords
- _calculateDistance
- _checkAndRecalculateIfDeviated
- _fillDriverDataLocally
- _haversineKm
- _initMinimalIcons
- _initializePolygons
- _isActiveRideState
- _kmToLatDelta
- _kmToLngDelta
- _onDriverAcceptedWithSocket
- _onDriverArrivedWithSocket
- _onRideCancelledWithSocket
- _onRideStartedWithSocket
- _playRouteAnimation
- _relevanceScore
- _restorePolyline
- _retryProcess
- _stageNiceToHave
- _stagePricingAndState
- _startMasterTimer
- _startMasterTimerWithInterval
- _startPollingFallback
- _stopDriverLocationPolling
- _updateDriverMarker
- addPostFrameCallback
- cancelRide
- checkPassengerLocation
- currentDriverMarkerId
- detectPerfMode
- directions
- getAIKey
- getDirectionMap
- getDistanceFromDriverAfterAcceptedRide
- getMapPointsForAllMethods
- getPassengerLocationUniversity
- getRideStatus
- handleActiveRideOnStartup
- handleNoDriverFound
- isDriversDataValid
- lastWhere
- onChangedPassengerCount
- onChangedPassengersChoose
- processRideAcceptance
- retrySearchForDrivers
- showDrawingBottomSheet
- showNoDriversDialog
- startSearchingTimer
- wait
--- PRECISE MISSING VARIABLES/FIELDS ---
Total original variables: 626
Total defined in split controllers: 558
Total missing: 97
- EdgeInsets
- Error
- InfoWindow
- LatLngBounds
- LocationData
- R
- _buildOsrmWaypointCoords
- _calculateDistance
- _haversineKm
- _isActiveRideState
- _isStateProcessing
- _isUsingFallback
- _kmToLatDelta
- _kmToLngDelta
- _reconnectTimer
- _relevanceScore
- a
- aerialDistance
- apiDistanceMeters
- apiKey
- attemptCount
- c
- carInfo
- cardNumber
- carsOrder
- checkPassengerLocation
- commissionPct
- context
- coordDestination
- currentAttempt
- currentCarType
- currentLocationOfDrivers
- currentRideId
- currentTimeSearchingCaptainWindow
- dInfo
- dLat
- dataCarsLocationByPassenger
- datadriverCarsLocationToPassengerAfterApplied
- dest
- deviation
- distanceOfTrip
- driverCarPlate
- driverLocationToPassenger
- driverName
- driverOrderStatus
- driverPhone
- durationByPassenger
- dynamicApiUrl
- etaText
- fName
- finalReason
- firebaseMessagesController
- increaseFeeFormKey
- info
- isBeginRideFromDriverRunning
- isDrawingRoute
- isDriversDataValid
- isDriversTokensSend
- isInUniversity
- isRequestValid
- kDurationScalar
- key
- km
- kmInDegree
- lName
- latDest
- latestWaypoint
- lngDest
- lowPerf
- mapAPIKEY
- messagesFormKey
- might
- minBillableKm
- minFareSYP
- newValue
- northeast
- originCoords
- pLower
- passengerLocation
- passengerLocationStringUnvirsity
- placeName
- polylineString
- previousLocationOfDrivers
- progressTimerRideBeginVip
- promoFormKey
- qLower
- query
- rLat1
- rLat2
- ram
- rideData
- sdk
- selectedPassengerCount
- southwest
- startLng
- status
- stringElapsedTimeRideBegin
@@ -0,0 +1,201 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/colors.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
import '../../../env/env.dart';
import '../../../print.dart';
import '../../../views/widgets/mydialoug.dart';
import '../../functions/encrypt_decrypt.dart';
class ComplaintController extends GetxController {
bool isLoading = false;
final formKey = GlobalKey<FormState>();
final complaintController = TextEditingController();
List feedBack = [];
Map<String, dynamic>? passengerReport;
Map<String, dynamic>? driverReport;
var isUploading = false.obs;
var uploadSuccess = false.obs;
String audioLink = ''; // سيتم تخزين رابط الصوت هنا بعد الرفع
@override
void onInit() {
super.onInit();
getLatestRidesForPassengers();
}
// --- دالة مخصصة لعرض إشعارات Snackbar بشكل جميل ---
void _showCustomSnackbar(String title, String message,
{bool isError = false}) {
if (isError) {
mySnackbarError(message.tr);
} else {
mySnackbarSuccess(message.tr);
}
}
// --- هذه الدالة تبقى كما هي لجلب بيانات الرحلة ---
getLatestRidesForPassengers() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getFeedBack, payload: {
'passengerId': box.read(BoxName.passengerID).toString(),
});
if (res != 'failure') {
var d = res['message'];
feedBack = d;
}
isLoading = false;
update();
}
// --- تم تحديث الهيدر في هذه الدالة ---
Future<void> uploadAudioFile(File audioFile) async {
try {
isUploading.value = true;
update();
var uri = Uri.parse('${AppLink.server}/upload_audio.php');
var request = http.MultipartRequest('POST', uri);
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
final String fingerPrint =
box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
var mimeType = lookupMimeType(audioFile.path);
// ** التعديل: تم استخدام نفس هيدر التوثيق **
request.headers.addAll({
'Authorization': 'Bearer $token',
'X-Device-FP': fingerPrint,
});
request.files.add(
await http.MultipartFile.fromPath(
'audio',
audioFile.path,
contentType: mimeType != null ? MediaType.parse(mimeType) : null,
),
);
var response = await request.send();
var responseBody = await http.Response.fromStream(response);
if (response.statusCode == 200) {
var jsonResponse = jsonDecode(responseBody.body);
if (jsonResponse['status'] == 'Audio file uploaded successfully.') {
uploadSuccess.value = true;
audioLink = jsonResponse['link']; // تخزين الرابط في المتغير
Get.back();
// استخدام الـ Snackbar الجديد
_showCustomSnackbar('Success', 'Audio uploaded successfully.');
} else {
uploadSuccess.value = false;
_showCustomSnackbar('Error', 'Failed to upload audio file.',
isError: true);
}
} else {
uploadSuccess.value = false;
_showCustomSnackbar('Error', 'Server error: ${response.statusCode}',
isError: true);
}
} catch (e) {
uploadSuccess.value = false;
_showCustomSnackbar(
'Error', 'An application error occurred during upload.',
isError: true);
} finally {
isUploading.value = false;
update();
}
}
// --- الدالة الجديدة التي تتصل بالسكريبت الجديد ---
Future<void> submitComplaintToServer() async {
// 1. التحقق من صحة الفورم
if (!formKey.currentState!.validate() || complaintController.text.isEmpty) {
// استخدام الـ Snackbar الجديد
_showCustomSnackbar(
'Error', 'Please describe your issue before submitting.',
isError: true);
return;
}
// 2. التحقق من وجود بيانات الرحلة
if (feedBack.isEmpty) {
// استخدام الـ Snackbar الجديد
_showCustomSnackbar(
'Error', 'Ride information not found. Please refresh the page.',
isError: true);
return;
}
isLoading = true;
update();
try {
// 3. استخراج البيانات المطلوبة
final rideId = feedBack[0]['id'].toString(); // ! تأكد أن اسم حقل ID صحيح
final complaint = complaintController.text;
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
// 4. استدعاء سكربت PHP الجديد باستخدام http.post
final response = await http.post(
Uri.parse(AppLink.add_solve_all),
headers: {'Authorization': 'Bearer $token'},
body: {
'ride_id': rideId,
'complaint_text': complaint,
'audio_link': audioLink,
},
);
Log.print('Server Response: ${response.body}');
if (response.statusCode != 200) {
_showCustomSnackbar(
'Error', 'Failed to connect to the server. Please try again.',
isError: true);
return;
}
// 5. التعامل مع محتوى الرد من الخادم
final responseData = jsonDecode(response.body);
if (responseData['status'] == 'success') {
passengerReport = responseData['data']['passenger_response'];
driverReport = responseData['data']['driver_response'];
update();
MyDialogContent().getDialog(
'Success'.tr, Text('Your complaint has been submitted.'.tr), () {
Get.back();
complaintController.clear();
audioLink = '';
formKey.currentState?.reset();
});
} else {
String errorMessage =
responseData['message'] ?? 'An unknown server error occurred'.tr;
_showCustomSnackbar('Submission Failed', errorMessage, isError: true);
}
} catch (e) {
Log.print("Submit Complaint Error: $e");
_showCustomSnackbar('Error', 'An application error occurred.'.tr,
isError: true);
} finally {
isLoading = false;
update();
}
}
}
@@ -0,0 +1,330 @@
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/colors.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/country_logic.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../../views/widgets/mydialoug.dart';
import '../../functions/launch.dart';
import '../../notification/notification_captain_controller.dart';
import '../../payment/payment_controller.dart';
class InviteController extends GetxController {
final TextEditingController invitePhoneController = TextEditingController();
List driverInvitationData = [];
List driverInvitationDataToPassengers = [];
String? couponCode;
String? driverCouponCode;
int selectedTab = 0;
PassengerStats passengerStats = PassengerStats();
List<Contact> contacts = <Contact>[];
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
@override
void onInit() {
super.onInit();
// It's good practice to fetch initial data in onInit or onReady
// fetchDriverStats();
// fetchDriverStatsPassengers();
}
void updateSelectedTab(int index) {
selectedTab = index;
update();
}
// --- Sharing Methods ---
Future<void> shareDriverCode() async {
if (driverCouponCode != null) {
final String shareText = '''
${'Join Siro as a driver using my referral code!'.tr}
${'Use code:'.tr} $driverCouponCode
${'Download the Siro Driver app now and earn rewards:'.tr}
${AppLink.inviteRedirectUrl}?code=$driverCouponCode&app=driver
''';
await Share.share(shareText);
}
}
Future<void> sharePassengerCode() async {
if (couponCode != null) {
final String shareText = '''
${'Get a discount on your first Siro ride!'.tr}
${'Use my referral code:'.tr} $couponCode
${'Download the Siro app now and enjoy your ride:'.tr}
${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider
''';
await Share.share(shareText);
}
}
// --- Data Fetching ---
void fetchDriverStats() async {
try {
var response = await CRUD().get(link: AppLink.getInviteDriver, payload: {
"driverId": box.read(BoxName.driverID),
});
if (response != 'failure' && response is Map) {
driverInvitationData = response['message'];
update();
}
} catch (e) {
Log.print("Error fetching driver stats: $e");
}
}
void fetchDriverStatsPassengers() async {
try {
var response = await CRUD()
.get(link: AppLink.getDriverInvitationToPassengers, payload: {
"driverId": box.read(BoxName.passengerID),
});
if (response != 'failure' && response is Map) {
driverInvitationDataToPassengers = response['message'];
update();
}
} catch (e) {
Log.print("Error fetching passenger stats: $e");
}
}
// --- Contact Handling ---
/// **IMPROVEMENT**: This function now filters out contacts without any phone numbers.
/// This is the fix for the `RangeError` you were seeing, which happened when the UI
/// tried to access the first phone number of a contact that had none.
Future<void> pickContacts() async {
try {
// 1. Check current permission status using permission_handler for better control
PermissionStatus status = await Permission.contacts.status;
// 2. If status is permanently denied, direct user to settings
if (status.isPermanentlyDenied) {
Get.defaultDialog(
title: 'Permission Required'.tr,
middleText:
'Contact permission is permanently denied. Please enable it in settings to continue.'
.tr,
textConfirm: 'Settings'.tr,
textCancel: 'Cancel'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
openAppSettings();
Get.back();
},
);
return;
}
// 3. Request permission if not already granted
if (!status.isGranted) {
status = await Permission.contacts.request();
}
// 4. Proceed if granted
if (status.isGranted) {
// Also call flutter_contacts requestPermission to ensure it's synced (internal state)
await FlutterContacts.requestPermission(readonly: true);
final List<Contact> allContacts =
await FlutterContacts.getContacts(withProperties: true);
final int totalContactsOnDevice = allContacts.length;
// **FIX**: Filter contacts to only include those with at least one phone number.
contacts = allContacts.where((c) => c.phones.isNotEmpty).toList();
final int contactsWithPhones = contacts.length;
if (contactsWithPhones > 0) {
Log.print('Found $contactsWithPhones contacts with phone numbers.');
contactMaps.value = contacts.map((contact) {
return {
'name': contact.displayName,
'phones': contact.phones.map((p) => p.number).toList(),
'emails': contact.emails.map((e) => e.address).toList(),
};
}).toList();
update();
// **IMPROVEMENT**: Provide feedback if some contacts were filtered out.
if (contactsWithPhones < totalContactsOnDevice) {
// Get.snackbar('Contacts Loaded'.tr,
// '${'Showing'.tr} $contactsWithPhones ${'of'.tr} $totalContactsOnDevice ${'contacts. Others were hidden because they don\'t have a phone number.'.tr}',
// snackPosition: SnackPosition.BOTTOM);
}
} else {
mySnackbarWarning('No contacts with phone numbers were found on your device.'.tr);
}
} else {
mySnackbarWarning('Contact permission is required to pick contacts'.tr);
}
} catch (e) {
Log.print('Error picking contacts: $e');
mySnackbarError('An error occurred while picking contacts: $e'.tr);
}
}
void selectPhone(String phone) {
invitePhoneController.text = phone;
update();
Get.back();
}
/// Formats phone number based on the current country (Syria=963, Jordan=962, Egypt=20).
String _formatPhoneByCountry(String phone) {
return CountryLogic.formatCurrentCountryPhone(phone);
}
void sendInviteToPassenger() async {
if (invitePhoneController.text.isEmpty ||
invitePhoneController.text.length < 9) {
mySnackbarError('Please enter a correct phone'.tr);
return;
}
try {
String formattedPhoneNumber =
_formatPhoneByCountry(invitePhoneController.text);
var response =
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
"driverId": box.read(BoxName.passengerID),
"inviterPassengerPhone": formattedPhoneNumber,
});
if (response != 'failure' && response is Map) {
var d = response;
mySnackbarSuccess('Invite sent successfully'.tr);
var payload = d['data'] ?? d['message'];
if (payload is String) {
payload = d;
}
String expirationTime = (payload['expirationTime'] ?? '').toString();
String inviteCode = (payload['inviteCode'] ?? '').toString();
String message =
"👋 ${'Hello! I\'m inviting you to try Siro.'.tr}\n\n"
"🎁 ${'Use my invitation code to get a special gift on your first ride!'.tr}\n\n"
"${'Your personal invitation code is:'.tr}\n"
"*$inviteCode*\n\n"
"⏳ ${'Be sure to use it quickly! This code expires at'.tr} *$expirationTime*.\n\n"
"🔗 ${'Quick Invite Link:'.tr}\n"
"${AppLink.inviteRedirectUrl}?code=$inviteCode&app=rider\n\n"
"💡 ${'Note: If the link above is not clickable, please save this contact or reply to this message to activate all links.'.tr}\n\n"
"${'See you on the road!'.tr} 🚗";
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
update();
} else {
mySnackbarWarning("This phone number has already been invited.".tr);
}
} catch (e) {
Log.print("Error sending invite: $e");
mySnackbarError('An unexpected error occurred. Please try again.'.tr);
}
}
// This function is dependent on the `pickContacts` method filtering out contacts without phones.
savePhoneToServer() async {
for (var contactMap in contactMaps) {
// The `pickContacts` function ensures the 'phones' list is not empty here.
var phones = contactMap['phones'] as List<String>;
var res = await CRUD().post(link: AppLink.savePhones, payload: {
"name": contactMap['name'] ?? 'No Name',
"phones": phones.first, // Safely access the first phone number
"phones2": phones.join(', '),
});
if (res == 'failure') {
Log.print('Failed to save contact: ${contactMap['name']}');
}
}
}
void onSelectPassengerInvitation(int index) async {
try {
final invitation = driverInvitationDataToPassengers[index];
final tripCount =
int.tryParse(invitation['countOfInvitDriver'].toString()) ?? 0;
final passengerName = invitation['passengerName'].toString();
final isGiftTaken = invitation['isGiftToken'].toString() == '1';
if (tripCount >= 2) {
// Gift can be claimed
if (!isGiftTaken) {
MyDialog().getDialog(
'You deserve the gift'.tr,
'${'Claim your 20 LE gift for inviting'.tr} $passengerName!',
() async {
Get.back(); // Close dialog first
var response = await CRUD().post(
link: AppLink.claimInviteReward,
payload: {
'invite_id': invitation['id'].toString(),
'passenger_id': box.read(BoxName.passengerID).toString(),
'country_code': box.read(BoxName.countryCode).toString(),
},
);
if (response != 'failure' && response is Map) {
if (response['status'] == 'success') {
NotificationCaptainController().addNotificationCaptain(
invitation['passengerInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have earned 20".tr} ${'LE'}',
false,
);
fetchDriverStatsPassengers(); // Refresh list
} else {
mySnackbarError(response['message'] ?? 'Claim failed'.tr);
}
}
},
);
} else {
MyDialog().getDialog(
"Gift Already Claimed".tr,
"You have already received your gift for inviting $passengerName."
.tr,
() => Get.back(),
);
}
} else {
// Gift not yet earned
MyDialog().getDialog(
'${'Keep it up!'.tr}',
'$passengerName ${'has completed'.tr} $tripCount / 2 ${'trips'.tr}. ${"You can claim your gift once they complete 2 trips.".tr}',
() => Get.back(),
);
}
} catch (e) {
Log.print("Error in onSelectPassengerInvitation: $e");
}
}
}
class PassengerStats {
final int totalInvites;
final int activeUsers;
final double totalEarnings;
PassengerStats({
this.totalInvites = 0,
this.activeUsers = 0,
this.totalEarnings = 0.0,
});
}
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/views/widgets/error_snakbar.dart';
class InvitesRewardsController extends GetxController {
bool isLoading = false;
String? referralCode;
int totalInvitedDrivers = 0;
int totalInvitedPassengers = 0;
List<dynamic> referrals = [];
@override
void onInit() {
super.onInit();
fetchPassengerReferrals();
}
Future<void> fetchPassengerReferrals() async {
isLoading = true;
update();
try {
// Ensure a referral code exists first (creates one if not)
var codeResponse = await CRUD().post(
link: AppLink.getUnifiedCode,
payload: {},
);
if (codeResponse != 'failure' && codeResponse['status'] == 'success') {
referralCode = codeResponse['message']['referral_code'];
}
var response = await CRUD().post(
link: AppLink.getPassengerReferrals,
payload: {
'country_code': box.read(BoxName.countryCode) ?? 'Jordan',
},
);
if (response != 'failure') {
if (response['status'] == 'success') {
referralCode ??= response['message']['referral_code'];
totalInvitedDrivers = response['message']['total_invited_drivers'] ?? 0;
totalInvitedPassengers = response['message']['total_invited_passengers'] ?? 0;
referrals = response['message']['referrals'] ?? [];
} else {
referrals = [];
}
}
} catch (e) {
print("Error fetching passenger referrals: $e");
}
isLoading = false;
update();
}
Future<void> linkInviteCode(String inviteCode) async {
Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false);
try {
var response = await CRUD().post(
link: AppLink.addUnifiedInvite,
payload: {
"inviter_code": inviteCode,
}
);
Navigator.maybeOf(Get.context!)?.pop(); // close loading dialog only
if (response != 'failure') {
if (response['status'] == 'success') {
mySnackbarSuccess("You have been successfully referred!".tr);
} else {
mySnackbarWarning(response['message'] ?? "Could not add invite".tr);
}
} else {
mySnackbarError("Network error occurred".tr);
}
} catch (e) {
Navigator.maybeOf(Get.context!)?.pop(); // close loading dialog only
mySnackbarError("Network error occurred".tr);
}
}
}
@@ -0,0 +1,35 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:intaleq_rider/main.dart';
class OrderHistoryController extends GetxController {
List<dynamic> orderHistoryListPassenger = [];
bool isloading = true;
@override
void onInit() {
getOrderHistoryByPassenger();
super.onInit();
}
Future getOrderHistoryByPassenger() async {
var res = await CRUD().get(link: AppLink.getRides, payload: {
'passenger_id': box.read(BoxName.passengerID).toString(),
});
if (res.toString() == 'failure') {
// Get.snackbar('failure', 'message');
isloading = false;
update();
} else {
var jsonDecoded = res is String ? jsonDecode(res) : res;
var rawData = jsonDecoded['data'] ?? jsonDecoded['message'];
orderHistoryListPassenger = rawData is List ? rawData : [];
isloading = false;
update();
}
}
}
@@ -0,0 +1,41 @@
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:get/get.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import '../../../main.dart';
class PromosController extends GetxController {
List<dynamic> promoList = [];
bool isLoading = true;
late String promos;
@override
void onInit() {
getPromoByToday();
super.onInit();
}
Future getPromoByToday() async {
var res = await CRUD().get(link: AppLink.getPromoBytody, payload: {
'passengerID': box.read(BoxName.passengerID).toString(),
});
if (res.toString() == 'failure') {
// Get.defaultDialog(
// title: 'No Promo for today .'.tr,
// middleText: '',
// titleStyle: AppStyle.title,
// confirm: MyElevatedButton(
// title: 'Back'.tr,
// onPressed: () {
// Get.back();
// Get.back();
// }));
isLoading = false;
update();
} else {
promoList = res['message'];
isLoading = false;
update();
}
}
}
@@ -0,0 +1,233 @@
import 'package:intaleq_rider/print.dart';
import 'dart:async';
import 'dart:math';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/onbording_page.dart';
import 'package:intaleq_rider/views/auth/login_page.dart';
import 'package:intaleq_rider/controller/auth/login_controller.dart';
import 'package:intaleq_rider/controller/functions/secure_storage.dart';
import 'package:quick_actions/quick_actions.dart';
import '../../../constant/notification.dart';
import '../../../main.dart';
import '../firebase/firbase_messge.dart';
import '../firebase/local_notification.dart';
import '../functions/encrypt_decrypt.dart';
class SplashScreenController extends GetxController
with GetTickerProviderStateMixin {
// ─── انيميشن الـ splash الأصلي ───────────────────────────────────────────
late AnimationController _animationController;
late Animation<double> titleFadeAnimation,
titleScaleAnimation,
taglineFadeAnimation,
footerFadeAnimation;
late Animation<Offset> taglineSlideAnimation;
// ─── انيميشن الحلقات المدارية ────────────────────────────────────────────
late AnimationController _orbitController;
late Animation<double> orbitAnimation;
// ─── انيميشن التوهج المتنفّس ─────────────────────────────────────────────
late AnimationController _glowController;
late Animation<double> glowAnimation;
final progress = 0.0.obs;
Timer? _progressTimer;
@override
void onInit() {
super.onInit();
// ── الكنترولر الرئيسي للـ splash ─────────────────────────────────────
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 2000));
titleFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut)));
titleScaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.0, 0.5, curve: Curves.easeOut)));
taglineFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.3, 0.8, curve: Curves.easeOut)));
taglineSlideAnimation =
Tween<Offset>(begin: const Offset(0, 0.5), end: Offset.zero).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.3, 0.8, curve: Curves.easeOut)));
footerFadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: const Interval(0.5, 1.0, curve: Curves.easeOut)));
// ── كنترولر الدوران المداري — دورة كاملة كل 7 ثوانٍ ─────────────────
_orbitController =
AnimationController(vsync: this, duration: const Duration(seconds: 7))
..repeat();
orbitAnimation =
Tween<double>(begin: 0.0, end: 1.0).animate(_orbitController);
// ── كنترولر التوهج المتنفّس — نبضة كل ثانيتين ───────────────────────
_glowController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 2000))
..repeat(reverse: true);
glowAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _glowController, curve: Curves.easeInOut));
_animationController.forward();
_initializeApp();
}
Future<void> _initializeApp() async {
_startProgressAnimation();
// Run navigation logic and background services in parallel.
final logicFuture = _performNavigationLogic();
final backgroundServicesFuture = _initializeBackgroundServices();
// Ensure the splash screen is visible for a minimum duration.
final minTimeFuture = Future.delayed(const Duration(seconds: 3));
// Wait for all tasks to complete.
await Future.wait([logicFuture, backgroundServicesFuture, minTimeFuture]);
}
void _startProgressAnimation() {
// Visual timer for the progress bar.
const totalTime = 2800; // ms
const interval = 50; // ms
int elapsed = 0;
_progressTimer =
Timer.periodic(const Duration(milliseconds: interval), (timer) {
elapsed += interval;
progress.value = (elapsed / totalTime).clamp(0.0, 1.0);
if (elapsed >= totalTime) timer.cancel();
});
}
/// Initializes all heavy background services while the splash animation is running.
Future<void> _initializeBackgroundServices() async {
try {
await EncryptionHelper.initialize();
// --- [LAZY LOADING IN ACTION] ---
// This `Get.find()` call will create the NotificationController instance
// for the first time because it was defined with `lazyPut`.
final notificationController = Get.find<NotificationController>();
await notificationController.initNotifications();
// The same happens for FirebaseMessagesController.
final fcm = Get.find<FirebaseMessagesController>();
await fcm.requestFirebaseMessagingPermission();
// _scheduleDailyNotifications(notificationController);
_initializeQuickActions();
} catch (e, st) {
CRUD.addError('background_init_error: $e', st.toString(), 'SplashScreen');
}
}
/// Determines the next screen based on user's login state.
Future<void> _performNavigationLogic() async {
await _getPackageInfo();
SecureStorage().saveData('iss', 'mobile-app:');
// ── [OPTIMIZATION] جلب التوكن عند بداية تشغيل التطبيق ────────────────
Log.print("SplashScreen: Initializing JWT...");
await Get.find<LoginController>().getJWT();
if (box.read(BoxName.onBoarding) == null) {
Get.off(() => OnBoardingPage());
} else if (box.read(BoxName.email) != null &&
box.read(BoxName.phone) != null &&
box.read(BoxName.isVerified) == '1') {
// `Get.find()` creates the LoginController instance here.
final loginController = Get.find<LoginController>();
// The loginController itself will handle navigation via Get.offAll() upon success.
await loginController.loginUsingCredentials(
box.read(BoxName.passengerID).toString(),
box.read(BoxName.email).toString(),
);
} else {
Get.off(() => LoginPage());
}
}
Future<void> _getPackageInfo() async {
try {
final info = await PackageInfo.fromPlatform();
box.write(BoxName.packagInfo, info.version);
update();
} catch (e) {
Log.print("Could not get package info: $e");
}
}
void _scheduleDailyNotifications(NotificationController controller) {
try {
final List<String> msgs = passengerMessages ?? const [];
if (msgs.isEmpty) {
controller.scheduleNotificationsForSevenDays(
'Siro', 'مرحباً بك! تابع رحلاتك بأمان مع سيرو.', "tone1");
} else {
final rnd = Random();
final raw = msgs[rnd.nextInt(msgs.length)];
final parts = raw.split(':');
final title = parts.isNotEmpty ? parts.first.trim() : 'Siro';
final body = parts.length > 1
? parts.sublist(1).join(':').trim()
: 'مرحباً بك! تابع رحلاتك بأمان مع سيرو.';
controller.scheduleNotificationsForSevenDays(
title.isEmpty ? 'Siro' : title,
body.isEmpty ? 'مرحباً بك! تابع رحلاتك بأمان مع سيرو.' : body,
"tone1");
}
} catch (e, st) {
CRUD.addError('notif_init: $e', st.toString(), 'SplashScreen');
}
}
void _initializeQuickActions() {
final QuickActions quickActions = QuickActions();
quickActions.initialize((String shortcutType) {
Get.toNamed('/$shortcutType');
});
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'shareApp', localizedTitle: 'Share App'.tr, icon: 'icon_share'),
ShortcutItem(
type: 'wallet', localizedTitle: 'Wallet'.tr, icon: 'icon_wallet'),
ShortcutItem(
type: 'profile', localizedTitle: 'Profile'.tr, icon: 'icon_user'),
ShortcutItem(
type: 'contactSupport',
localizedTitle: 'Contact Support'.tr,
icon: 'icon_support'),
]);
}
@override
void onClose() {
_progressTimer?.cancel();
_animationController.dispose();
_orbitController.dispose();
_glowController.dispose();
super.onClose();
}
}
@@ -0,0 +1,107 @@
import 'dart:async';
import 'dart:convert';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/functions/crud.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
class TripMonitorController extends GetxController {
bool isLoading = false;
Map tripData = {};
late String rideId;
late String driverId;
IntaleqMapController? mapController;
List myListString = [];
late Timer timer;
late LatLng parentLocation;
String carIcon = 'car';
String motoIcon = 'moto';
String ladyIcon = 'lady';
double rotation = 0;
double speed = 0;
bool isStyleLoaded = false;
Set<Marker> markers = {};
getLocationParent() async {
var res = await CRUD().get(
link: AppLink.getLocationParents, payload: {"driver_id": driverId});
if (res != 'failure') {
tripData = res;
parentLocation = LatLng(
double.parse(tripData['message'][0]['latitude'].toString()),
double.parse(tripData['message'][0]['longitude'].toString()));
rotation = double.parse(tripData['message'][0]['heading'].toString());
speed = double.parse(tripData['message'][0]['speed'].toString());
_updateMarker();
update();
}
}
void onMapCreated(IntaleqMapController controller) async {
mapController = controller;
update();
}
void onStyleLoaded() async {
isStyleLoaded = true;
mapController?.animateCamera(
CameraUpdate.newLatLng(parentLocation),
);
_updateMarker();
// Set up a timer or interval to trigger the marker update every 10 seconds.
timer = Timer.periodic(const Duration(seconds: 10), (_) async {
await getLocationParent();
mapController?.animateCamera(CameraUpdate.newLatLng(parentLocation));
update();
});
}
void _updateMarker() {
String iconPath = 'assets/images/car.png';
if (tripData['message'] != null && tripData['message'].isNotEmpty) {
final model = tripData['message'][0]['model'].toString();
final gender = tripData['message'][0]['gender'].toString();
if (model.contains('دراجة')) {
iconPath = 'assets/images/moto1.png';
} else if (gender == 'Female') {
iconPath = 'assets/images/lady1.png';
}
}
markers = {
Marker(
markerId: const MarkerId('driver'),
position: parentLocation,
icon: InlqBitmap.fromAsset(iconPath),
rotation: rotation,
anchor: const Offset(0.5, 0.5),
),
};
update();
}
Future<void> init({String? rideId, String? driverId}) async {
this.driverId = driverId!;
this.rideId = rideId!;
await getLocationParent();
update();
}
@override
void onInit() {
super.onInit();
}
@override
void onClose() {
timer.cancel();
mapController = null;
super.onClose();
}
}
@@ -0,0 +1,323 @@
import 'dart:async';
import 'dart:convert';
import 'package:intaleq_rider/constant/box_name.dart';
import 'package:intaleq_rider/constant/colors.dart';
import 'package:intaleq_rider/constant/links.dart';
import 'package:intaleq_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:intaleq_rider/main.dart';
import 'package:intaleq_rider/views/widgets/elevated_btn.dart';
import 'package:intaleq_rider/views/widgets/my_scafold.dart';
import 'package:intaleq_rider/views/widgets/mycircular.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../constant/style.dart';
import '../functions/crud.dart';
import '../functions/encrypt_decrypt.dart';
class VipOrderController extends GetxController {
RxBool isLoading = false.obs;
final arguments = Get.arguments;
RxList<dynamic> tripData = <dynamic>[].obs;
RxBool isButtonVisible = false.obs;
RxInt countdown = 60.obs;
Timer? _countdownTimer;
@override
void onInit() {
super.onInit();
fetchOrder();
startCountdown();
}
@override
void onClose() {
_countdownTimer?.cancel();
super.onClose();
}
Future<void> fetchOrder() async {
try {
isLoading.value = true;
var mapPassengerController = Get.find<RideLifecycleController>();
var res = await CRUD().get(
link: AppLink.getMishwari,
payload: {
// 'driverId': mapPassengerController.driverIdVip.toString(),
'driverId': box.read(BoxName.passengerID).toString(),
},
);
if (res != 'failure') {
if (res['message'] is List) {
tripData.value = res['message'];
} else {
tripData.clear(); // Ensure empty list if no data
// mySnackbarError('No trip data found');
}
} else {
tripData.clear();
// mySnackbarError('Failed to fetch trip data');
}
} catch (e) {
tripData.clear();
// mySnackbarError('An error occurred: $e');
} finally {
isLoading.value = false;
}
}
void startCountdown() {
_countdownTimer?.cancel(); // Cancel any existing timer
countdown.value = 60;
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (countdown.value > 0) {
countdown.value--;
} else {
timer.cancel();
isButtonVisible.value = true;
}
});
}
void sendToDriverAgain() {
// Reset states
isButtonVisible.value = false;
fetchOrder(); // Refresh order
startCountdown(); // Restart countdown
}
}
class VipWaittingPage extends StatelessWidget {
VipWaittingPage({super.key});
final VipOrderController vipOrderController = Get.put(VipOrderController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: "Waiting VIP".tr,
body: [
Obx(() {
// Loading state
if (vipOrderController.isLoading.value) {
return const Center(child: MyCircularProgressIndicator());
}
// No data state
if (vipOrderController.tripData.isEmpty) {
return Center(
child: Text(
'No trip data available'.tr,
style: AppStyle.title,
),
);
}
// Data available
var data = vipOrderController.tripData[0];
// Function to get the localized status string
String getLocalizedStatus(String status) {
switch (status) {
case 'pending':
return 'pending'.tr;
case 'accepted':
return 'accepted'.tr;
case 'begin':
return 'begin'.tr;
case 'rejected':
return 'rejected'.tr;
case 'cancelled':
return 'cancelled'.tr;
default:
return 'unknown'.tr;
}
}
// Function to get the appropriate status color
Color getStatusColor(String status) {
switch (status) {
case 'pending':
return Colors.yellow;
case 'accepted':
return Colors.green;
case 'begin':
return Colors.green;
case 'rejected':
return Colors.red;
case 'cancelled':
return Colors.red;
default:
return Colors.grey;
}
}
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${'Driver Name:'.tr} ${data['name']}",
style: AppStyle.title,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${'Car Plate:'.tr} ${data['car_plate']}",
style: AppStyle.title,
),
Text(
"${'Car Make:'.tr} ${data['make']}",
style: AppStyle.title,
),
Text(
"${'Car Model:'.tr} ${data['model']}",
style: AppStyle.title,
),
Text(
"${"Car Color:".tr} ${data['color']}",
style: AppStyle.title,
),
],
),
SizedBox(
width: 100,
height: 100,
child: Icon(
Fontisto.car,
size: 80,
color: Color(
int.parse(
data['color_hex'].replaceFirst('#', '0xff'),
),
),
),
),
],
),
const SizedBox(height: 12),
const Divider(),
const SizedBox(height: 12),
Container(
color: getStatusColor(data['status']),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"${'Trip Status:'.tr} ${getLocalizedStatus(data['status'])}",
style: const TextStyle(fontSize: 16),
),
),
),
Text(
"${'Scheduled Time:'.tr} ${DateFormat('yyyy-MM-dd hh:mm a').format(DateTime.parse(data['timeSelected']))}",
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
data['status'].toString() != 'begin'
? MyElevatedButton(
title: "Cancel Trip".tr,
kolor: AppColor.redColor,
onPressed: () {
Get.find<RideLifecycleController>().cancelVip(
data['token'].toString(),
data['id'].toString(),
);
},
)
: const SizedBox(),
Obx(() {
// If countdown is still running, show countdown
if (!vipOrderController.isButtonVisible.value) {
return Column(
children: [
CircularProgressIndicator(
value: 1 -
(vipOrderController.countdown.value / 60),
strokeWidth: 6.0,
color: AppColor.greenColor,
backgroundColor: AppColor.accentColor,
),
const SizedBox(height: 10),
Text(
"${vipOrderController.countdown.value}s ${'remaining'.tr}",
style: const TextStyle(fontSize: 16),
),
],
);
}
// Once countdown is complete, show "Send to Driver Again" button
return MyElevatedButton(
title: "Send to Driver Again".tr,
kolor: AppColor.greenColor,
onPressed: () {
Get.find<RideLifecycleController>()
.sendToDriverAgain(data['token']);
vipOrderController.fetchOrder();
},
);
}),
],
),
const SizedBox(
height: 30,
),
data['status'].toString() == 'begin'
? MyElevatedButton(
title: "Click here to begin your trip\n\nGood luck, "
.tr +
(box.read(BoxName.name).toString().split(' ')[0])
.toString(),
kolor: AppColor.greenColor,
onPressed: () {
final mapPassengerController =
Get.find<RideLifecycleController>();
mapPassengerController.make = data['make'];
mapPassengerController.licensePlate =
data['car_plate'];
mapPassengerController.model = data['model'];
mapPassengerController.driverId = data['driverId'];
mapPassengerController.carColor = data['color'];
mapPassengerController.driverRate = data['rating'];
mapPassengerController.colorHex = data['color_hex'];
mapPassengerController.driverPhone = data['phone'];
mapPassengerController.driverToken = data['token'];
mapPassengerController.driverName =
data['name'].toString().split(' ')[0];
Get.back();
mapPassengerController.begiVIPTripFromPassenger();
},
)
: const SizedBox()
],
),
),
);
}),
],
isleading: true,
);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More