first commit
This commit is contained in:
194
siro_rider/lib/README.md
Normal file
194
siro_rider/lib/README.md
Normal file
@@ -0,0 +1,194 @@
|
||||
إليك التقرير الفني الشامل لمشروع **Intaleq Passenger App**، مكتوباً بصيغة توثيق تقني (Technical Documentation) موجهة لفريق التطوير، بناءً على تحليل الكود المصدري.
|
||||
|
||||
---
|
||||
|
||||
# 📘 Intaleq Passenger App - Technical Documentation Report
|
||||
|
||||
**Prepared by:** CTO Office
|
||||
**Target Audience:** Mobile Engineering Team
|
||||
**Version:** 1.0
|
||||
|
||||
---
|
||||
|
||||
## 1. 🚀 بداية التشغيل (Initialization & Startup)
|
||||
|
||||
تعتمد مرحلة الإقلاع على تهيئة الخدمات الأساسية قبل عرض واجهة المستخدم لضمان استقرار التطبيق.
|
||||
|
||||
### أ. نقطة الدخول (`main.dart`)
|
||||
|
||||
- **المسار:** `lib/main.dart`
|
||||
- **الوظيفة:**
|
||||
- يستخدم `runZonedGuarded` لالتقاط الأخطاء العامة (Global Error Handling) وإرسالها للسيرفر عبر `CRUD.addError`.
|
||||
- **تهيئة الخدمات:** يتم تهيئة `GetStorage` (للتخزين المحلي)، `WakelockPlus` (لمنع انطفاء الشاشة)، و `Firebase` (للإشعارات) قبل استدعاء `runApp`.
|
||||
- **إعدادات التوجيه:** يتم تحديد الاتجاه العمودي فقط (`portraitUp`) للجهاز.
|
||||
- **حقن التبعيات (DI):** يتم استدعاء `AppBindings` كـ `initialBinding` لتهيئة المتحكمات الأساسية.
|
||||
|
||||
### ب. إدارة التبعيات (`AppBindings`)
|
||||
|
||||
- **المسار:** `lib/app_bindings.dart`
|
||||
- **الآلية:**
|
||||
- يتم حقن `LocaleController` و `DeepLinkController` بشكل دائم (`permanent: true`) لضمان تواجدهم طوال دورة حياة التطبيق.
|
||||
- يتم استخدام `Get.lazyPut` مع `fenix: true` للمتحكمات الأخرى (مثل `LoginController`) ليتم إنشاؤها عند الحاجة وإعادة إنشائها إذا تم التخلص منها.
|
||||
|
||||
### ج. شاشة البداية (`Splash Screen`)
|
||||
|
||||
- **المسار:** `lib/splash_screen_page.dart` و `lib/controller/home/splash_screen_controlle.dart`
|
||||
- **المنطق:**
|
||||
- يتم عرض انيميشن باستخدام `AnimatedTextKit` و `FadeTransition`.
|
||||
- **العمليات الخلفية:** يقوم `SplashScreenController` بتنفيذ `_initializeBackgroundServices` لتهيئة خدمات التشفير (`EncryptionHelper`) والإشعارات.
|
||||
- **التوجيه الذكي:** تتحقق الدالة `_performNavigationLogic` من وجود بيانات المستخدم في `BoxName`. إذا كان المستخدم مسجلاً ومفعلاً (`isVerified == '1'`)، يتم توجيهه تلقائياً للصفحة الرئيسية، وإلا يتم توجيهه لصفحة الدخول أو الترحيب (`OnBoarding`).
|
||||
|
||||
---
|
||||
|
||||
## 2. 🔐 دورة المصادقة (Authentication Cycle)
|
||||
|
||||
يعتمد النظام على مصادقة هجينة (Token-based + Social Auth) مع تخزين آمن.
|
||||
|
||||
### أ. التسجيل والدخول (`Sign Up & Login`)
|
||||
|
||||
- **المسار:** `lib/controller/auth/login_controller.dart` و `register_controller.dart`
|
||||
- **الآلية:**
|
||||
- **Social Login:** يتم استخدام `GoogleSignInHelper` أو `AuthController` (Apple). عند النجاح، يتم إرسال التوكن للسيرفر للتحقق.
|
||||
- **Credentials:** في حالة الدخول التقليدي، يتم استدعاء `loginUsingCredentials` التي تتحقق من البيانات عبر API.
|
||||
- **التحقق (Verification):** إذا رد السيرفر بأن الحساب غير مفعل، يتم تحويل المستخدم لصفحة `PhoneNumberScreen` للتحقق عبر OTP.
|
||||
|
||||
### ب. التحقق عبر الهاتف (OTP)
|
||||
|
||||
- **المسار:** `lib/controller/auth/otp_controller.dart`
|
||||
- **المنطق:** يستخدم كلاس `PhoneAuthHelper` لإرسال OTP (غالباً عبر WhatsApp أو SMS حسب الدولة) ثم التحقق منه عبر الـ Endpoint `verifyOtp.php`.
|
||||
|
||||
### ج. إدارة الجلسة والتوكن
|
||||
|
||||
- **المخزن:** يتم تخزين الـ JWT في `GetStorage` تحت مفتاح `BoxName.jwt`.
|
||||
- **التشفير:** يتم استخدام `EncryptionHelper` لتشفير البيانات الحساسة محلياً.
|
||||
- **التجديد التلقائي:** في كلاس `CRUD`، إذا رد السيرفر بـ `401 Token expired`، يتم استدعاء `getJWT` تلقائياً لتجديد التوكن دون تسجيل خروج المستخدم.
|
||||
|
||||
---
|
||||
|
||||
## 3. 🗺️ الشاشة الرئيسية والخريطة (Home & Map Logic)
|
||||
|
||||
تعتبر `MapPagePassenger` هي الواجهة المركزية التي تديرها `MapPassengerController`.
|
||||
|
||||
### أ. تحميل الخريطة والموقع
|
||||
|
||||
- **المسار:** `lib/controller/home/map_passenger_controller.dart`
|
||||
- **المتحكم:** `MapPassengerController`
|
||||
- **المنطق:**
|
||||
- يتم استخدام `location.getLocation()` لجلب موقع الراكب الحالي عند البدء.
|
||||
- يتم تحديد المنطقة الجغرافية (سوريا، مصر، الأردن) عبر دالة `getLocationArea` التي تفحص وقوع الإحداثيات داخل مضلعات (Polygons) محددة مسبقاً.
|
||||
|
||||
### ب. السيارات القريبة (Real-time Updates)
|
||||
|
||||
- **الدالة:** `getCarsLocationByPassengerAndReloadMarker`.
|
||||
- **الآلية:** تقوم بطلب API (مثل `getSpeed.php`) بناءً على نوع السيارة المختار (Speed, Comfort, Lady). يتم تحديث الـ `markers` على الخريطة، ويتم استخدام دالة `_smoothlyUpdateMarker` لتحريك أيقونة السيارة بسلاسة بدلاً من القفز المفاجئ.
|
||||
|
||||
### ج. القائمة الجانبية (Drawer)
|
||||
|
||||
- **المسار:** `lib/views/home/map_widget.dart/map_menu_widget.dart`
|
||||
- **المتحكم:** `MyMenuController`
|
||||
- **الوظيفة:** تدير حالة القائمة (مفتوحة/مغلقة) وتوفر روابط لصفحات الملف الشخصي، المحفظة، والسجل.
|
||||
|
||||
---
|
||||
|
||||
## 4. 🚕 دورة طلب الرحلة (Ride Request Flow)
|
||||
|
||||
هذا هو الجزء الأكثر تعقيداً في التطبيق، حيث يدار عبر آلة حالة (State Machine).
|
||||
|
||||
### أ. اختيار الوجهة ورسم المسار
|
||||
|
||||
- **المسار:** `lib/controller/home/map_passenger_controller.dart`
|
||||
- **الوظيفة:** `getDirectionMap`.
|
||||
- **المنطق:**
|
||||
- يتم إرسال نقطة البداية والنهاية إلى خدمة التوجيه (OSRM/Google).
|
||||
- يتم استلام نقاط المسار (Polyline Points) وفك تشفيرها في `Isolate` منفصل (`decodePolylineIsolate`) لتحسين الأداء.
|
||||
- يتم رسم المسار على الخريطة وضبط الكاميرا لتشمل النقطتين.
|
||||
|
||||
### ب. اختيار نوع السيارة والسعر
|
||||
|
||||
- **المسار:** `lib/views/home/map_widget.dart/car_details_widget_to_go.dart`
|
||||
- **الآلية:** يتم عرض قائمة أنواع السيارات (Fixed Price, Comfort, etc.). عند الاختيار، يتم حساب السعر المتوقع بناءً على المسافة والوقت والتعرفة الخاصة بكل نوع والمخزنة في المتحكم (`totalPassengerSpeed`, `totalPassengerComfort`).
|
||||
|
||||
### ج. إرسال الطلب (البحث عن سائق)
|
||||
|
||||
- **الدالة:** `startSearchingForDriver`.
|
||||
- **العمليات:**
|
||||
1. تغيير الحالة إلى `RideState.searching`.
|
||||
2. استدعاء `postRideDetailsToServer` لإنشاء سجل الرحلة في قاعدة البيانات.
|
||||
3. تشغيل المؤقت `_startMasterTimer` الذي يدير دورة البحث.
|
||||
4. يتم توسيع نطاق البحث (Radius) تدريجياً (مراحل: 2400م -> 3000م -> 3100م) عبر `_findAndNotifyNearestDrivers`.
|
||||
|
||||
### د. انتظار السائق
|
||||
|
||||
- **الواجهة:** `SearchingCaptainWindow`.
|
||||
- **المنطق:** يظهر رادار بحث. يتم التحقق دورياً (`Polling`) من حالة الرحلة في السيرفر. إذا مر الوقت المحدد (90 ثانية) دون قبول، يظهر خيار "زيادة السعر" (`_showIncreaseFeeDialog`).
|
||||
|
||||
---
|
||||
|
||||
## 5. 🚘 أثناء الرحلة (Active Ride)
|
||||
|
||||
يتم إدارة هذه المرحلة عبر تحديثات الحالة في `_handleRideState`.
|
||||
|
||||
### أ. قبول السائق
|
||||
|
||||
- **الحدث:** وصول إشعار FCM أو تغيير الحالة في السيرفر إلى `Apply`.
|
||||
- **الإجراء:** يتم استدعاء `processRideAcceptance`.
|
||||
- يتم جلب بيانات السائق وموقعه.
|
||||
- يتم تفعيل تتبع السائق `startTimerFromDriverToPassengerAfterApplied`.
|
||||
- تتغير الواجهة لعرض معلومات السائق والوقت المقدر للوصول.
|
||||
|
||||
### ب. بدء الرحلة وميزات الأمان
|
||||
|
||||
- **بدء الرحلة:** عند وصول حالة `Begin`، يتم استدعاء `processRideBegin`.
|
||||
- **SOS:** زر الاستغاثة يستدعي `makePhoneCall` مع رقم الشرطة أو جهة اتصال الطوارئ المخزنة.
|
||||
- **مشاركة الرحلة:** الدالة `shareTripWithFamily` تولد رابط تتبع مشفر وترسله عبر واتساب.
|
||||
- **تسجيل الصوت:** يتم استخدام `AudioRecorderController` لتسجيل ما يدور في الرحلة لأغراض الأمان.
|
||||
|
||||
### ج. إلغاء الرحلة
|
||||
|
||||
- **الدالة:** `cancelRide`.
|
||||
- **المنطق:**
|
||||
- يتم إرسال طلب `cancel` للسيرفر لتحديث حالة الرحلة.
|
||||
- يتم إرسال إشعار للسائق بالإلغاء.
|
||||
- يتم تصفير الواجهة والعودة للخريطة.
|
||||
|
||||
---
|
||||
|
||||
## 6. 🏁 ما بعد الرحلة (Post-Ride)
|
||||
|
||||
### أ. شاشة الدفع
|
||||
|
||||
- **المسار:** `lib/controller/payment/payment_controller.dart`
|
||||
- **المنطق:**
|
||||
- يتم الخصم من المحفظة (`addPassengerWallet`) أو الدفع النقدي.
|
||||
- يتم التعامل مع بوابات الدفع الخارجية (مثل Paymob, Stripe) إذا اختار العميل الدفع الإلكتروني.
|
||||
|
||||
### ب. التقييم
|
||||
|
||||
- **المسار:** `lib/views/Rate/rate_captain.dart` و `RateController`.
|
||||
- **الآلية:** يقوم الراكب باختيار عدد النجوم وإضافة تعليق. يتم إرسال البيانات عبر `addRateToDriver`.
|
||||
|
||||
### ج. تقديم الشكاوى
|
||||
|
||||
- **المسار:** `lib/views/home/profile/complaint_page.dart`
|
||||
- **المتحكم:** `ComplaintController`.
|
||||
- **الميزة:** يمكن للراكب تسجيل رسالة صوتية وإرفاقها مع الشكوى، ثم يتم إرسالها للسيرفر عبر `submitComplaintToServer`.
|
||||
|
||||
---
|
||||
|
||||
## 7. ⚙️ الإعدادات والملف الشخصي
|
||||
|
||||
### أ. تعديل البيانات
|
||||
|
||||
- **المسار:** `lib/views/home/profile/passenger_profile_page.dart`.
|
||||
- **المتحكم:** `ProfileController`.
|
||||
- يسمح بتعديل الاسم، الجنس، ورقم الطوارئ.
|
||||
|
||||
### ب. المحفظة
|
||||
|
||||
- **المسار:** `lib/views/home/my_wallet/passenger_wallet.dart`.
|
||||
- يعرض الرصيد الحالي وسجل المعاملات (`PassengerWalletHistoryController`).
|
||||
|
||||
### ج. اللغة
|
||||
|
||||
- **المتحكم:** `LocaleController`.
|
||||
- يقوم بتغيير لغة التطبيق وتحديث `Get.updateLocale` وحفظ التفضيل في التخزين المحلي.
|
||||
68
siro_rider/lib/app_bindings.dart
Normal file
68
siro_rider/lib/app_bindings.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:siro_rider/controller/firebase/firbase_messge.dart';
|
||||
import 'package:siro_rider/controller/firebase/local_notification.dart';
|
||||
import 'package:siro_rider/controller/home/deep_link_controller.dart';
|
||||
import 'package:siro_rider/controller/local/local_controller.dart';
|
||||
import 'package:siro_rider/controller/functions/tts.dart';
|
||||
import 'package:siro_rider/controller/voice_call_controller.dart';
|
||||
|
||||
import 'package:siro_rider/controller/home/map/map_socket_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/map_engine_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/location_search_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/nearby_drivers_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/ui_interactions_controller.dart';
|
||||
import 'package:siro_rider/controller/home/menu_controller.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_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
siro_rider/lib/constant/api_key.dart
Normal file
76
siro_rider/lib/constant/api_key.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
// import 'package:siro_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);
|
||||
}
|
||||
111
siro_rider/lib/constant/box_name.dart
Normal file
111
siro_rider/lib/constant/box_name.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
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 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 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';
|
||||
}
|
||||
75
siro_rider/lib/constant/char_map.dart
Normal file
75
siro_rider/lib/constant/char_map.dart
Normal 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"
|
||||
};
|
||||
66
siro_rider/lib/constant/colors.dart
Normal file
66
siro_rider/lib/constant/colors.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
/// A class that holds the color palette for the 'Intaleq' 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 (Inspired by the Syrian Flag) ---
|
||||
|
||||
/// **Primary Color:** The brand's signature Twitter Blue representing trust and modern communication.
|
||||
/// Ideal for app bars, primary buttons, and major UI elements.
|
||||
static const Color primaryColor = Color(0xFF1DA1F2);
|
||||
|
||||
/// **Text/Write Color:** A very dark, near-black color for main text.
|
||||
/// It's softer on the eyes than pure black, improving readability.
|
||||
/// The variable name `writeColor` is kept as requested.
|
||||
static Color get writeColor => Get.isDarkMode ? Colors.white : const Color(0xFF1A1A1A);
|
||||
|
||||
/// **Secondary Color:** Pure white, used for backgrounds to create a clean
|
||||
/// and spacious look, ensuring content stands out.
|
||||
static Color get secondaryColor => Get.isDarkMode ? const Color(0xFF1E1E1E) : Colors.white;
|
||||
|
||||
/// **Accent Color:** A vibrant, energetic red from the Syrian flag.
|
||||
/// Perfect for calls-to-action, highlights, icons, and notifications.
|
||||
static const Color accentColor = Color.fromARGB(255, 148, 140, 141);
|
||||
|
||||
// --- Neutral & Status Colors ---
|
||||
|
||||
/// **Grey Color:** A neutral grey for secondary text, borders, dividers,
|
||||
/// and disabled states.
|
||||
static Color get grayColor => Get.isDarkMode ? Colors.grey[400]! : const Color(0xFF8E8E93);
|
||||
|
||||
/// **Red Color (Error):** A clear, attention-grabbing red for error messages and alerts.
|
||||
static const Color redColor = Color(0xFFD32F2F);
|
||||
|
||||
/// **Green Color (Success):** A positive green for success messages and confirmations.
|
||||
static const Color greenColor = Color(0xFF388E3C);
|
||||
|
||||
/// **Blue Color (Info):** A standard blue for informational text, links, or icons.
|
||||
static const Color blueColor = Color(0xFF108942);
|
||||
|
||||
/// **Yellow Color (Warning):** A warm yellow for warning messages or important highlights.
|
||||
static const Color yellowColor = Color(0xFFFFA000);
|
||||
|
||||
// --- Tier & Social Colors ---
|
||||
|
||||
/// **Gold Tier:** A bright gold for premium features, user ranks, or rewards.
|
||||
static const Color gold = Color(0xFFFFD700);
|
||||
|
||||
/// **Bronze Tiers:** Classic bronze colors for other user tiers or levels.
|
||||
static const Color bronze = Color(0xFFCD7F32);
|
||||
static const Color goldenBronze = Color(0xFFB87333); // Kept from original
|
||||
|
||||
/// **Twitter/X Color:** The official brand color for social login buttons.
|
||||
|
||||
/// **Twitter Blue:** The brand's signature blue color used for the drawer,
|
||||
/// menu icons, and secondary actions (formerly Cyan Blue).
|
||||
static Color get cyanBlue => const Color(0xFF1DA1F2);
|
||||
|
||||
/// **Blue Accent:** A softer, translucent version of the brand blue.
|
||||
static Color get cyanAccent => const Color(0xFF1DA1F2).withOpacity(0.12);
|
||||
|
||||
// --- Utility Colors ---
|
||||
|
||||
/// **Accent Tint:** A transparent version of the red accent color.
|
||||
static Color get deepPurpleAccent => const Color(0xFFCE1126).withOpacity(0.1);
|
||||
}
|
||||
137
siro_rider/lib/constant/country_polygons.dart
Normal file
137
siro_rider/lib/constant/country_polygons.dart
Normal file
@@ -0,0 +1,137 @@
|
||||
// في ملف: constant/country_polygons.dart
|
||||
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
|
||||
class CountryPolygons {
|
||||
// ==========================================================
|
||||
// 1. الأردن: تغطية الممر الحضري الرئيسي (من إربد شمالاً حتى العقبة جنوباً)
|
||||
// حوالي 12 نقطة
|
||||
// ==========================================================
|
||||
static final List<LatLng> jordanBoundary = [
|
||||
// شمال إربد (قرب الحدود)
|
||||
const LatLng(32.65, 35.80),
|
||||
// شمال شرق المفرق
|
||||
const LatLng(32.35, 37.00),
|
||||
// شرق الزرقاء / الأزرق
|
||||
const LatLng(31.85, 36.80),
|
||||
// جنوب شرق (نهاية الزحف السكاني)
|
||||
const LatLng(31.00, 36.50),
|
||||
// جنوب / معان
|
||||
const LatLng(30.30, 35.75),
|
||||
// العقبة
|
||||
const LatLng(29.50, 35.00),
|
||||
// البحر الأحمر / الحدود الغربية
|
||||
const LatLng(29.50, 34.85),
|
||||
// غرب وادي عربة
|
||||
const LatLng(30.80, 35.25),
|
||||
// منطقة البحر الميت / السلط
|
||||
const LatLng(32.00, 35.50),
|
||||
// العودة عبر وادي الأردن إلى الشمال
|
||||
const LatLng(32.45, 35.60),
|
||||
// العودة لنقطة إربد
|
||||
const LatLng(32.65, 35.80),
|
||||
];
|
||||
|
||||
// ==========================================================
|
||||
// 2. سوريا: تغطية الممر الغربي والساحلي (درعا، دمشق، حمص، حماة، حلب، الساحل)
|
||||
// حوالي 14 نقطة
|
||||
// ==========================================================
|
||||
static final List<LatLng> syriaBoundary = [
|
||||
// درعا / الجنوب
|
||||
const LatLng(32.65, 35.95),
|
||||
// شرق السويداء (حدود المنطقة المأهولة)
|
||||
const LatLng(32.85, 37.10),
|
||||
// أطراف دمشق الشرقية
|
||||
const LatLng(33.50, 36.65),
|
||||
// تدمر (أقصى امتداد شرقي للمضلع)
|
||||
const LatLng(34.50, 38.30),
|
||||
// الرقة (شمال شرق)
|
||||
const LatLng(35.95, 38.80),
|
||||
// حلب (الشمال)
|
||||
const LatLng(36.45, 37.15),
|
||||
// الحدود الشمالية الغربية (إدلب / تركيا)
|
||||
const LatLng(36.50, 36.50),
|
||||
// اللاذقية (الساحل)
|
||||
const LatLng(35.50, 35.75),
|
||||
// طرطوس (الساحل)
|
||||
const LatLng(34.80, 35.85),
|
||||
// حمص
|
||||
const LatLng(34.70, 36.70),
|
||||
// حماة
|
||||
const LatLng(35.10, 36.70),
|
||||
// العودة إلى منطقة دمشق
|
||||
const LatLng(33.40, 36.30),
|
||||
// العودة إلى درعا
|
||||
const LatLng(32.65, 35.95),
|
||||
];
|
||||
|
||||
// ==========================================================
|
||||
// 3. مصر: تغطية القاهرة الكبرى، الدلتا، والإسكندرية والإسماعيلية
|
||||
// حوالي 10 نقاط
|
||||
// ==========================================================
|
||||
static final List<LatLng> egyptBoundary = [
|
||||
// جنوب الفيوم (أقصى امتداد جنوبي غربي)
|
||||
const LatLng(29.20, 30.60),
|
||||
// جنوب القاهرة (العياط)
|
||||
const LatLng(29.80, 31.30),
|
||||
// شرق السويس
|
||||
const LatLng(29.95, 32.70),
|
||||
// الإسماعيلية / القناة
|
||||
const LatLng(30.60, 32.25),
|
||||
// بورسعيد / أطراف الدلتا الشمالية الشرقية
|
||||
const LatLng(31.30, 31.80),
|
||||
// دمياط / ساحل الدلتا
|
||||
const LatLng(31.50, 31.25),
|
||||
// الإسكندرية (أقصى الشمال الغربي)
|
||||
const LatLng(31.20, 29.80),
|
||||
// غرب الدلتا
|
||||
const LatLng(30.50, 30.20),
|
||||
// العودة لنقطة البداية
|
||||
const LatLng(29.20, 30.60),
|
||||
];
|
||||
|
||||
// دالة تُرجع رابط API بناءً على اسم الدولة
|
||||
// static String getRoutingApiUrl(String countryName) {
|
||||
// switch (countryName) {
|
||||
// case 'Jordan':
|
||||
// return 'https://routec.intaleq.xyz/route-jo';
|
||||
// case 'Syria':
|
||||
// return 'https://routec.intaleq.xyz/route';
|
||||
// case 'Egypt':
|
||||
// return 'https://routec.intaleq.xyz/route-eg';
|
||||
// default:
|
||||
// // الافتراضي في حالة لم يقع الموقع ضمن أي من المضلعات
|
||||
// return 'https://routec.intaleq.xyz/route';
|
||||
// }
|
||||
// }
|
||||
|
||||
/// دالة تحدد اسم الدولة (باللغة الإنجليزية للـ API) بناءً على الإحداثيات
|
||||
static String getCountryName(LatLng? point) {
|
||||
if (point == null) return "jordan";
|
||||
|
||||
if (_isPointInPolygon(point, jordanBoundary)) return "jordan";
|
||||
if (_isPointInPolygon(point, syriaBoundary)) return "syria";
|
||||
if (_isPointInPolygon(point, egyptBoundary)) return "egypt";
|
||||
|
||||
return "jordan"; // الافتراضي
|
||||
}
|
||||
|
||||
/// خوارزمية 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
siro_rider/lib/constant/credential.dart
Normal file
145
siro_rider/lib/constant/credential.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'package:siro_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 = jsonDecode(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;
|
||||
}
|
||||
}
|
||||
288
siro_rider/lib/constant/info.dart
Normal file
288
siro_rider/lib/constant/info.dart
Normal file
@@ -0,0 +1,288 @@
|
||||
class AppInformation {
|
||||
static const String companyName = 'Intaleq llc';
|
||||
static const String appName = 'Intaleq';
|
||||
static const String phoneNumber = '962798583052';
|
||||
static const String linkedInProfile =
|
||||
'https://www.linkedin.com/in/hamza-ayed/';
|
||||
static const String website = 'https://intaleqapp.com';
|
||||
static const String email = 'hamzaayed@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>Intaleq - 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 Intaleq 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>"Intaleq", "we", "us":</strong> Refers to the Intaleq 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". Intaleq 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>
|
||||
''';
|
||||
}
|
||||
372
siro_rider/lib/constant/links.dart
Normal file
372
siro_rider/lib/constant/links.dart
Normal file
@@ -0,0 +1,372 @@
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
|
||||
class AppLink {
|
||||
///https://walletintaleq.intaleq.xyz/v1/main
|
||||
static String paymentServer = 'https://walletintaleq.intaleq.xyz/v2/main';
|
||||
|
||||
///https://api.intaleq.xyz/intaleq/ride/location
|
||||
static String location = 'https://api.intaleq.xyz/intaleq_v3/ride/location';
|
||||
|
||||
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
|
||||
/// https://routesy.intaleq.xyz for syria
|
||||
/// for jordan https://routesjo.intaleq.xyz
|
||||
static String routesOsm = 'https://routesy.intaleq.xyz';
|
||||
static String mapSaasRoute = 'https://map-saas.intaleqapp.com/api/maps/route';
|
||||
static String reverseGeocoding =
|
||||
'https://map-saas.intaleqapp.com/api/geocoding/reverse';
|
||||
static String searchGeocoding =
|
||||
'https://map-saas.intaleqapp.com/api/geocoding/search';
|
||||
static String mapSaasPlaces =
|
||||
'https://map-saas.intaleqapp.com/api/geocoding/places';
|
||||
|
||||
///https://location.intaleq.xyz/intaleq/ride/location
|
||||
///locationServerSide هو السيرفر الجانبي الخاص بموقع السائقين، حيث يتم إرسال تحديثات الموقع من التطبيق إلى هذا السيرفر، ومن ثم يقوم هذا السيرفر بتوزيع هذه التحديثات إلى الركاب المتصلين الذين يتابعون السائق في الوقت الحقيقي.
|
||||
static String locationServerSide =
|
||||
'https://location.intaleq.xyz/intaleq/ride/location';
|
||||
|
||||
///https://api.intaleq.xyz/intaleq
|
||||
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v3';
|
||||
|
||||
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
|
||||
/// https://rides.intaleq.xyz/intaleq
|
||||
static final String rideServerSide = 'https://rides.intaleq.xyz/intaleq';
|
||||
|
||||
///https://api.intaleq.xyz/intaleq
|
||||
/// main api link for all api calls except rides and location
|
||||
static final String server = 'https://api.intaleq.xyz/intaleq_v3';
|
||||
|
||||
///https://rides.intaleq.xyz
|
||||
/// هذا الرابط خاص برحلات الركاب، ويستخدمه السيرفر الجانبي للرحلات (Ride Server Side) للتعامل مع عمليات إلغاء الرحلات وتحديث حالة الرحلات وغيرها من العمليات المتعلقة بالرحلات.
|
||||
static final String serverSocket = 'https://rides.intaleq.xyz';
|
||||
|
||||
///
|
||||
static String googleMapsLink = 'https://maps.googleapis.com/maps/api/';
|
||||
|
||||
/// here map link for searching for places
|
||||
static String searcMaps =
|
||||
'https://autosuggest.search.hereapi.com/v1/autosuggest';
|
||||
|
||||
static String test = "$server/test.php";
|
||||
//===============firebase==========================
|
||||
static String getTokens = "$server/ride/firebase/getTokensPassenger.php";
|
||||
static String getTokenParent = "$server/ride/firebase/getTokenParent.php";
|
||||
static String addTokens = "$server/ride/firebase/add.php";
|
||||
static String addFingerPrint = "$paymentServer/ride/firebase/add.php";
|
||||
static String addTokensDriver = "$server/ride/firebase/addDriver.php";
|
||||
static String packageInfo = "$server/auth/packageInfo.php";
|
||||
|
||||
//=======================Wallet===================
|
||||
static String wallet = '$paymentServer/ride/passengerWallet';
|
||||
static String walletDriver = '$paymentServer/ride/driverWallet';
|
||||
static String getAllPassengerTransaction =
|
||||
"$wallet/getAllPassengerTransaction.php";
|
||||
static String getWalletByPassenger = "$wallet/getWalletByPassenger.php";
|
||||
static String getPassengersWallet = "$wallet/get.php";
|
||||
static String payWithPayMobWalletPasenger =
|
||||
'$paymentServer/ride/payMob/wallet/payWithPayMob.php';
|
||||
static String payWithPayMobCardPassenger =
|
||||
'$paymentServer/ride/payMob/payWithPayMob.php';
|
||||
static String payWithEcash = "$paymentServer/ecash/payWithEcash.php";
|
||||
|
||||
static String paymetVerifyPassenger =
|
||||
"$paymentServer/ride/payMob/paymet_verfy.php";
|
||||
static String getPassengerWalletArchive =
|
||||
"$wallet/getPassengerWalletArchive.php";
|
||||
|
||||
static String addDrivePayment = "$paymentServer/ride/payment/add.php";
|
||||
static String addSeferWallet = "$paymentServer/ride/seferWallet/add.php";
|
||||
static String addPassengersWallet = "$wallet/add.php";
|
||||
static String deletePassengersWallet = "$wallet/delete.php";
|
||||
static String updatePassengersWallet = "$wallet/update.php";
|
||||
|
||||
static String getWalletByDriver = "$walletDriver/getWalletByDriver.php";
|
||||
static String getDriversWallet = "$walletDriver/get.php";
|
||||
static String addDriversWalletPoints = "$walletDriver/add.php";
|
||||
static String deleteDriversWallet = "$walletDriver/delete.php";
|
||||
static String updateDriversWallet = "$walletDriver/update.php";
|
||||
|
||||
//=======================promo===================ride.mobile-app.store/ride/promo/get.php
|
||||
static String promo = '$server/ride/promo';
|
||||
static String getPassengersPromo = "$promo/get.php";
|
||||
static String getPromoFirst = "$promo/getPromoFirst.php";
|
||||
static String getPromoBytody = "$promo/getPromoBytody.php";
|
||||
static String addPassengersPromo = "$promo/add.php";
|
||||
static String deletePassengersPromo = "$promo/delete.php";
|
||||
static String updatePassengersPromo = "$promo/update.php";
|
||||
|
||||
//===============contact==========================
|
||||
static String savePhones = "$server/ride/egyptPhones/add.php";
|
||||
static String getPhones = "$server/ride/egyptPhones/get.php";
|
||||
|
||||
////=======================cancelRide===================
|
||||
// static String ride = '$server/ride';
|
||||
static String addCancelRideFromPassenger =
|
||||
"$rideServerSide/cancelRide/add.php";
|
||||
static String cancelRide = "$rideServerSide/cancelRide/get.php";
|
||||
//-----------------ridessss------------------
|
||||
static String addRides = "$rideServerSide/ride/rides/add.php";
|
||||
static String getRides = "$rideServerSide/ride/rides/get.php";
|
||||
static String getRideOrderID =
|
||||
"$rideServerSide/ride/rides/getRideOrderID.php";
|
||||
static String getRideStatus = "$rideServerSide/ride/rides/getRideStatus.php";
|
||||
static String getRideStatusBegin =
|
||||
"$rideServerSide/ride/rides/getRideStatusBegin.php";
|
||||
static String getRideStatusFromStartApp =
|
||||
"$server/ride/rides/getRideStatusFromStartApp.php";
|
||||
static String updateRides = "$rideServerSide/ride/rides/update.php";
|
||||
static String updateStausFromSpeed =
|
||||
"$rideServerSide/ride/rides/updateStausFromSpeed.php";
|
||||
static String deleteRides = "$rideServerSide/ride/rides/delete.php";
|
||||
|
||||
//-----------------DriverPayment------------------
|
||||
static String adddriverScam = "$server/driver_scam/add.php";
|
||||
static String getdriverScam = "$server/ride/driver_scam/get.php";
|
||||
|
||||
/////////---getKazanPercent===////////////
|
||||
static String getKazanPercent = "$server/ride/kazan/get.php";
|
||||
static String addKazanPercent = "$server/ride/kazan/add.php";
|
||||
|
||||
////-----------------DriverPayment------------------
|
||||
static String addDriverpayment = "$paymentServer/ride/payment/add.php";
|
||||
static String addDriverPaymentPoints =
|
||||
"$paymentServer/ride/driverPayment/add.php";
|
||||
static String addPaymentTokenPassenger =
|
||||
"$paymentServer/ride/passengerWallet/addPaymentTokenPassenger.php";
|
||||
static String addPaymentTokenDriver =
|
||||
"$paymentServer/ride/driverWallet/addPaymentToken.php";
|
||||
static String getDriverPaymentPoints =
|
||||
"$paymentServer/ride/driverWallet/get.php";
|
||||
static String payWithEcashPassenger =
|
||||
"$paymentServer/ride/ecash/passenger/payWithEcash.php";
|
||||
static String payWithMTNConfirm =
|
||||
"$paymentServer/ride/mtn/passenger/mtn_confirm.php";
|
||||
static String payWithMTNStart =
|
||||
"$paymentServer/ride/mtn/passenger/mtn_start.php";
|
||||
static String payWithSyriatelConfirm =
|
||||
"$paymentServer/ride/syriatel/passenger/confirm_payment.php";
|
||||
static String payWithSyriatelStart =
|
||||
"$paymentServer/ride/syriatel/passenger/start_payment.php";
|
||||
static String getDriverpaymentToday = "$paymentServer/ride/payment/get.php";
|
||||
static String getCountRide = "$paymentServer/ride/payment/getCountRide.php";
|
||||
static String getAllPaymentFromRide =
|
||||
"$paymentServer/ride/payment/getAllPayment.php";
|
||||
static String getAllPaymentVisa =
|
||||
"$paymentServer/ride/payment/getAllPaymentVisa.php";
|
||||
|
||||
//-----------------Passenger NotificationCaptain------------------
|
||||
static String addNotificationPassenger =
|
||||
"$server/ride/notificationPassenger/add.php";
|
||||
static String getNotificationPassenger =
|
||||
"$server/ride/notificationPassenger/get.php";
|
||||
static String updateNotificationPassenger =
|
||||
"$server/ride/notificationPassenger/update.php";
|
||||
//-----------------Driver NotificationCaptain------------------
|
||||
static String addNotificationCaptain =
|
||||
"$server/ride/notificationCaptain/add.php";
|
||||
static String addWaitingRide =
|
||||
"$server/ride/notificationCaptain/addWaitingRide.php";
|
||||
static String updateWaitingTrip =
|
||||
"$server/ride/notificationCaptain/updateWaitingTrip.php";
|
||||
static String getRideWaiting =
|
||||
"$endPoint/ride/notificationCaptain/getRideWaiting.php";
|
||||
static String getNotificationCaptain =
|
||||
"$server/ride/notificationCaptain/get.php";
|
||||
static String updateNotificationCaptain =
|
||||
"$server/ride/notificationCaptain/update.php";
|
||||
static String deleteNotificationCaptain =
|
||||
"$server/ride/notificationCaptain/delete.php";
|
||||
//-----------------invitor------------------
|
||||
|
||||
static String addInviteDriver = "$server/ride/invitor/add.php";
|
||||
static String addInvitationPassenger =
|
||||
"$server/ride/invitor/addInvitationPassenger.php";
|
||||
static String getInviteDriver = "$server/ride/invitor/get.php";
|
||||
static String getDriverInvitationToPassengers =
|
||||
"$server/ride/invitor/getDriverInvitationToPassengers.php";
|
||||
static String updateInviteDriver = "$server/ride/invitor/update.php";
|
||||
static String updatePassengerGift =
|
||||
"$server/ride/invitor/updatePassengerGift.php";
|
||||
//-----------------Api Key------------------
|
||||
static String addApiKey = "$server/ride/apiKey/add.php";
|
||||
static String getApiKey = "$server/ride/apiKey/get.php";
|
||||
static String getCnMap = "$server/auth/cnMap.php";
|
||||
static String updateApiKey = "$server/ride/apiKey/update.php";
|
||||
static String deleteApiKey = "$server/ride/apiKey/delete.php";
|
||||
static String getPlacesSyria = "$server/ride/places_syria/get.php";
|
||||
|
||||
//-----------------Feed Back------------------
|
||||
static String addFeedBack = "$server/ride/feedBack/add.php";
|
||||
static String add_solve_all = "$server/ride/feedBack/add_solve_all.php";
|
||||
static String uploadAudio = "$server/ride/feedBack/upload_audio.php";
|
||||
static String getFeedBack = "$server/ride/feedBack/get.php";
|
||||
static String updateFeedBack = "$server/ride/feedBack/updateFeedBack.php";
|
||||
|
||||
//-----------------Tips------------------
|
||||
static String addTips = "$server/ride/tips/add.php";
|
||||
static String getTips = "$server/ride/tips/get.php";
|
||||
static String updateTips = "$server/ride/tips/update.php";
|
||||
|
||||
//-----------------Help Center------------------
|
||||
static String addhelpCenter = "$server/ride/helpCenter/add.php";
|
||||
static String gethelpCenter = "$server/ride/helpCenter/get.php";
|
||||
static String getByIdhelpCenter = "$server/ride/helpCenter/getById.php";
|
||||
static String updatehelpCenter = "$server/ride/helpCenter/update.php";
|
||||
static String deletehelpCenter = "$server/ride/helpCenter/delete.php";
|
||||
|
||||
//-----------------license------------------
|
||||
static String addLicense = "$server/ride/license/add.php";
|
||||
static String getLicense = "$server/ride/license/get.php";
|
||||
static String updateLicense = "$server/ride/license/updateFeedBack.php";
|
||||
//-----------------RegisrationCar------------------
|
||||
static String addRegisrationCar = "$server/ride/RegisrationCar/add.php";
|
||||
static String getRegisrationCar =
|
||||
"${box.read(BoxName.serverChosen)}/server/ride/RegisrationCar/get.php";
|
||||
static String selectDriverAndCarForMishwariTrip =
|
||||
"$server/ride/RegisrationCar/selectDriverAndCarForMishwariTrip.php";
|
||||
static String updateRegisrationCar = "$server/ride/RegisrationCar/update.php";
|
||||
|
||||
//-----------------mishwari------------------
|
||||
|
||||
static String addMishwari = "$server/ride/mishwari/add.php";
|
||||
static String cancelMishwari = "$server/ride/mishwari/cancel.php";
|
||||
static String getMishwari = "$server/ride/mishwari/get.php";
|
||||
static String sendChatMessage = "$server/ride/chat/send_message.php";
|
||||
|
||||
//-----------------DriverOrder------------------
|
||||
|
||||
static String addDriverOrder = "$server/ride/driver_order/add.php";
|
||||
static String getDriverOrder = "$server/ride/driver_order/get.php";
|
||||
static String getOrderCancelStatus =
|
||||
"$server/ride/driver_order/getOrderCancelStatus.php";
|
||||
static String updateDriverOrder = "$server/ride/driver_order/update.php";
|
||||
static String deleteDriverOrder = "$server/ride/driver_order/delete.php";
|
||||
|
||||
// =====================================
|
||||
static String addRateToPassenger = "$server/ride/rate/add.php";
|
||||
static String savePlacesServer = "$server/ride/places/add.php";
|
||||
static String getapiKey = "$server/ride/apiKey/get.php";
|
||||
static String addRateToDriver = "$server/ride/rate/addRateToDriver.php";
|
||||
static String getDriverRate = "$server/ride/rate/getDriverRate.php";
|
||||
static String getPassengerRate = "$server/ride/rate/getPassengerRate.php";
|
||||
|
||||
////////////////emails ============//
|
||||
static String sendEmailToPassengerForTripDetails =
|
||||
"$server/ride/rides/emailToPassengerTripDetail.php";
|
||||
|
||||
// ===========================================
|
||||
static String pathImage = "$server/upload/types/";
|
||||
static String uploadImage = "$server/uploadImage.php";
|
||||
static String uploadImage1 = "$server/uploadImage1.php";
|
||||
static String uploadImagePortrate = "$server/uploadImagePortrate.php";
|
||||
static String uploadImageType = "$server/uploadImageType.php";
|
||||
//=============egypt documents ==============
|
||||
static String uploadEgyptidFront =
|
||||
"$server/EgyptDocuments/uploadEgyptidFront.php";
|
||||
static String uploadEgypt = "$server/uploadEgypt.php";
|
||||
|
||||
//==================certifcate==========
|
||||
// static String location = '${box.read(BoxName.serverChosen)}/ride/location';
|
||||
static String getCarsLocationByPassenger = "$location/get.php";
|
||||
|
||||
static String getLocationAreaLinks =
|
||||
'$server/ride/location/get_location_area_links.php';
|
||||
static String addpassengerLocation =
|
||||
"$locationServerSide/addpassengerLocation.php";
|
||||
static String getCarsLocationByPassengerSpeed = "$location/getSpeed.php";
|
||||
static String getCarsLocationByPassengerComfort = "$location/getComfort.php";
|
||||
static String getCarsLocationByPassengerBalash = "$location/getBalash.php";
|
||||
static String getCarsLocationByPassengerElectric =
|
||||
"$location/getElectric.php";
|
||||
static String getCarsLocationByPassengerPinkBike =
|
||||
"$location/getPinkBike.php";
|
||||
static String getCarsLocationByPassengerVan =
|
||||
"$location/getCarsLocationByPassengerVan.php";
|
||||
static String getCarsLocationByPassengerDelivery =
|
||||
"$location/getDelivery.php";
|
||||
static String getLocationParents = "$location/getLocationParents.php";
|
||||
static String getFemalDriverLocationByPassenger =
|
||||
"$location/getFemalDriver.php";
|
||||
static String 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 profile = '$server/ride/profile';
|
||||
static String getprofile = "$profile/get.php";
|
||||
static String getCaptainProfile = "$profile/getCaptainProfile.php";
|
||||
static String addprofile = "$profile/add.php";
|
||||
static String deleteprofile = "$profile/delete.php";
|
||||
static String updateprofile = "$profile/update.php";
|
||||
|
||||
//===================Auth============
|
||||
|
||||
static String auth = '$server/auth';
|
||||
static String login = "$auth/login.php";
|
||||
static String loginJwtRider = "$server/login.php";
|
||||
static String loginJwtWalletRider = "$server/loginWallet.php";
|
||||
static String loginFirstTime = "$server/loginFirstTime.php";
|
||||
static String getTesterApp = "$auth/Tester/getTesterApp.php";
|
||||
static String updateTesterApp = "$auth/Tester/updateTesterApp.php";
|
||||
static String signUp = "$auth/signup.php";
|
||||
static String sendVerifyEmail = "$auth/sendVerifyEmail.php";
|
||||
static String loginFromGooglePassenger = "$auth/loginFromGooglePassenger.php";
|
||||
static String checkPhoneNumberISVerfiedPassenger =
|
||||
"$auth/checkPhoneNumberISVerfiedPassenger.php";
|
||||
|
||||
static String passengerRemovedAccountEmail =
|
||||
"$auth/passengerRemovedAccountEmail.php";
|
||||
static String verifyEmail = "$auth/verifyEmail.php";
|
||||
//===================Auth Captin============
|
||||
static String authCaptin = '$server/auth/captin';
|
||||
static String loginCaptin = "$authCaptin/login.php";
|
||||
static String loginFromGoogleCaptin = "$authCaptin/loginFromGoogle.php";
|
||||
static String signUpCaptin = "$authCaptin/register.php";
|
||||
static String sendVerifyEmailCaptin = "$authCaptin/sendVerifyEmail.php";
|
||||
static String sendVerifyOtpMessage = "$server/auth/otpmessage.php";
|
||||
static String verifyOtpMessage = "$server/auth/verifyOtpMessage.php";
|
||||
static String verifyEmailCaptin = "$authCaptin/verifyEmail.php";
|
||||
static String removeUser = "$authCaptin/removeAccount.php";
|
||||
static String deletecaptainAccounr = "$authCaptin/deletecaptainAccounr.php";
|
||||
static String updateAccountBank = "$authCaptin/updateAccountBank.php";
|
||||
static String getAccount = "$authCaptin/getAccount.php";
|
||||
static String updatePassengersInvitation =
|
||||
"$server/ride/invitor/updatePassengersInvitation.php";
|
||||
static String updateDriverInvitationDirectly =
|
||||
"$server/ride/invitor/updateDriverInvitationDirectly.php";
|
||||
//===================Admin Captin============
|
||||
|
||||
static String getPassengerDetailsByPassengerID =
|
||||
"$server/Admin/getPassengerDetailsByPassengerID.php";
|
||||
static String getPassengerDetails = "$server/Admin/getPassengerDetails.php";
|
||||
static String getPassengerbyEmail = "$server/Admin/getPassengerbyEmail.php";
|
||||
static String addAdminUser = "$server/Admin/adminUser/add.php";
|
||||
static String getAdminUser = "$server/Admin/adminUser/get.php";
|
||||
static String addError = "$server/Admin/errorApp.php";
|
||||
static String getCaptainDetailsByEmailOrIDOrPhone =
|
||||
"$server/Admin/AdminCaptain/getCaptainDetailsByEmailOrIDOrPhone.php";
|
||||
static String getCaptainDetails = "$server/Admin/AdminCaptain/get.php";
|
||||
static String getRidesPerMonth =
|
||||
"$server/Admin/AdminRide/getRidesPerMonth.php";
|
||||
static String getRidesDetails = "$server/Admin/AdminRide/get.php";
|
||||
|
||||
//////////Sms egypt///////////
|
||||
static String sendSms = "https://sms.kazumi.me/api/sms/send-sms";
|
||||
static String sendSmsFromPHP =
|
||||
'$server/auth/sms_new_backend/sendOtpPassenger.php';
|
||||
static String verifyOtpPassenger =
|
||||
'$server/auth/passengerOTP/verifyOtpPassenger.php';
|
||||
static String senddlr = "https://sms.kazumi.me/api/sms/send-dlr";
|
||||
static String sendvalidity = "https://sms.kazumi.me/api/sms/send-validity";
|
||||
static String sendmany = "https://sms.kazumi.me/api/sms/send-many";
|
||||
static String checkCredit = "https://sms.kazumi.me/api/sms/check-credit";
|
||||
static String getSender = "$server/auth/sms/getSender.php";
|
||||
static String checkStatus = "https://sms.kazumi.me/api/sms/check-status";
|
||||
static String updatePhoneInvalidSMSPassenger =
|
||||
"$server/auth/sms/updatePhoneInvalidSMSPassenger.php";
|
||||
}
|
||||
31
siro_rider/lib/constant/notification.dart
Normal file
31
siro_rider/lib/constant/notification.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
List<String> passengerMessages = [
|
||||
// --- رسائل العروض والتوفير ---
|
||||
"وفر على حالك: 🚗 أسعار انطلق نازلة كتير! شوف العروض الجديدة وفوت هلأ قبل ما تخلص. 🌟",
|
||||
"خصم اليوم: 🤔 لا تفوّت الفرصة! افتح تطبيق انطلق وشوف الأسعار يلي ما بتنعاد.",
|
||||
"عروض نارية: 🎁 اليوم خصم خاص إلك، احجز مشوارك الجاي بسعر ولا أروع!",
|
||||
"مشاوير اقتصادية: 💸 مع انطلق بتتحرك براحتك وبتدفع أقل، جرب وشوف الفرق!",
|
||||
|
||||
// --- رسائل السهولة والراحة ---
|
||||
"مشوار بكبسة زر: 📲 افتح تطبيق انطلق، وخلِّي السيارة توصلك لعندك بثواني.",
|
||||
"ارتاح من المواصلات: 🤔 خلّي انطلق يريحك من الانتظار والزحمة، وانطلق وين ما بدك.",
|
||||
"سيارتك جاهزة: 🚕 الكابتن ناطر طلبك، حدد وجهتك وخلّي الطريق علينا.",
|
||||
"رحلة مريحة: 🛣️ ارتاح بالكرسي، نحنا منهتم بكل التفاصيل من الباب للباب.",
|
||||
|
||||
// --- رسائل الأمان والثقة ---
|
||||
"رحلتك بأمان: 🙏 كل كباتنّا مدرّبين وملتزمين، وسلامتك أولويتنا.",
|
||||
"سافر وانت مطمّن: 🔒 مع انطلق كل شي موثوق ومسجّل لتكون مرتاح البال.",
|
||||
"شارك رحلتك: ❤️ فيك تبعت تفاصيل المشوار لأهلك أو رفقاتك بخطوة وحدة.",
|
||||
"انطلق بثقة: ✅ كل الرحلات مراقبة لتضمن تجربة آمنة ومريحة 100%.",
|
||||
|
||||
// --- رسائل تفاعلية ومناسبات ---
|
||||
"الويكند بلّش: 🥳 خلي مشاويرك مع الأصحاب علينا، وفر وقتك وفلوسك مع انطلق.",
|
||||
"رايح عالشغل: 💼 لا تتأخر، افتح التطبيق وخلي الكابتن يوصلك بلا تعب.",
|
||||
"الشمس مولّعة: ☀️ لا تمشي تحت الحر، خلي السيارة تجي لعندك.",
|
||||
"مستعجل: 🏃♂️ لا تقلق، انطلق أسرع طريق لتوصل عموعدك.",
|
||||
|
||||
// --- رسائل تشجيعية عامة ---
|
||||
"وين رايح اليوم؟ 🗺️ وين ما كانت وجهتك، انطلق بيخدمك بكل مكان وبأي وقت.",
|
||||
"جرب شي جديد: 🚘 شوف فئات السيارات الجديدة وخلي رحلتك أريح وأجمل.",
|
||||
"شكراً لاختيارك: ⭐ وجودك معنا بيفرحنا، ونتمنى دايماً تكون رحلتك مريحة وسعيدة.",
|
||||
"كل يوم جديد: ✨ فوت عالتطبيق وتابع آخر التحديثات والعروض يلي نازلة خصيصاً إلك."
|
||||
];
|
||||
198
siro_rider/lib/constant/sefer cairo server.php
Normal file
198
siro_rider/lib/constant/sefer cairo server.php
Normal file
@@ -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
siro_rider/lib/constant/style.dart
Normal file
74
siro_rider/lib/constant/style.dart
Normal 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: 36,
|
||||
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: 24,
|
||||
color: AppColor.writeColor,
|
||||
fontFamily: box.read(BoxName.lang) == 'ar'
|
||||
? GoogleFonts.markaziText().fontFamily
|
||||
: GoogleFonts.inter().fontFamily);
|
||||
static TextStyle get title => TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 16,
|
||||
color: AppColor.writeColor,
|
||||
fontFamily: box.read(BoxName.lang) == 'ar'
|
||||
? GoogleFonts.markaziText().fontFamily
|
||||
: GoogleFonts.inter().fontFamily);
|
||||
static TextStyle get subtitle => TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
19
siro_rider/lib/constant/table_names.dart
Normal file
19
siro_rider/lib/constant/table_names.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
// import 'package:siro_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_INTALEQ = 'FCM_PRIVATE_KEY_INTALEQ';
|
||||
}
|
||||
83
siro_rider/lib/constant/univeries_polygon.dart
Normal file
83
siro_rider/lib/constant/univeries_polygon.dart
Normal file
@@ -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...
|
||||
];
|
||||
}
|
||||
57
siro_rider/lib/controller/Widget/home_widget_provider.dart
Normal file
57
siro_rider/lib/controller/Widget/home_widget_provider.dart
Normal file
@@ -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});
|
||||
// }
|
||||
37
siro_rider/lib/controller/auth/apple_signin_controller.dart
Normal file
37
siro_rider/lib/controller/auth/apple_signin_controller.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
|
||||
|
||||
class AuthController extends GetxController {
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
||||
Future<User?> signInWithApple() async {
|
||||
try {
|
||||
final appleCredential = await SignInWithApple.getAppleIDCredential(
|
||||
scopes: [
|
||||
AppleIDAuthorizationScopes.email,
|
||||
AppleIDAuthorizationScopes.fullName,
|
||||
],
|
||||
);
|
||||
|
||||
final oAuthProvider = OAuthProvider('apple.com');
|
||||
final credential = oAuthProvider.credential(
|
||||
idToken: appleCredential.identityToken,
|
||||
accessToken: appleCredential.authorizationCode,
|
||||
);
|
||||
|
||||
UserCredential userCredential =
|
||||
await _auth.signInWithCredential(credential);
|
||||
|
||||
return userCredential.user;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> signOut() async {
|
||||
try {
|
||||
await _auth.signOut();
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
41
siro_rider/lib/controller/auth/certificate.dart
Normal file
41
siro_rider/lib/controller/auth/certificate.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:siro_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;
|
||||
}
|
||||
149
siro_rider/lib/controller/auth/google_sign.dart
Normal file
149
siro_rider/lib/controller/auth/google_sign.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.dart';
|
||||
import '../functions/crud.dart';
|
||||
import '../../onbording_page.dart';
|
||||
import 'login_controller.dart';
|
||||
|
||||
class GoogleSignInHelper {
|
||||
// ✅ GoogleSignIn singleton
|
||||
static final GoogleSignIn _signIn = GoogleSignIn.instance;
|
||||
|
||||
static GoogleSignInAccount? _lastUser;
|
||||
|
||||
/// 👇 استدعها في main() مرة واحدة
|
||||
static Future<void> init() async {
|
||||
await _signIn.initialize(
|
||||
serverClientId:
|
||||
'594687661098-2u640akrb3k7sak5t0nqki6f4v6hq1bq.apps.googleusercontent.com',
|
||||
);
|
||||
|
||||
// Events listener
|
||||
_signIn.authenticationEvents.listen((event) async {
|
||||
if (event is GoogleSignInAuthenticationEventSignIn) {
|
||||
_lastUser = event.user;
|
||||
await _handleSignUp(event.user);
|
||||
} else if (event is GoogleSignInAuthenticationEventSignOut) {
|
||||
_lastUser = null;
|
||||
}
|
||||
});
|
||||
|
||||
// silent login if possible
|
||||
try {
|
||||
await _signIn.attemptLightweightAuthentication();
|
||||
} catch (e) {
|
||||
Log.print("Error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// ✅ تسجيل دخول عادي
|
||||
static Future<GoogleSignInAccount?> signIn() async {
|
||||
try {
|
||||
final user =
|
||||
await _signIn.authenticate(scopeHint: const ['email', 'profile']);
|
||||
_lastUser = user;
|
||||
await _handleSignUp(user);
|
||||
|
||||
// اطبع القيم (للتأكد)
|
||||
Log.print("Google ID: ${user.id}");
|
||||
Log.print("Email: ${user.email}");
|
||||
Log.print("Name: ${user.displayName}");
|
||||
Log.print("Photo: ${user.photoUrl}");
|
||||
return user;
|
||||
} on PlatformException catch (e) {
|
||||
if (e.code == 'sign_in_required') {
|
||||
await showGooglePlayServicesError();
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
await addError("Google Sign-In error: $e", "signIn()");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// ✅ تسجيل دخول مخصص لشاشة Login (مع استدعاء الكنترولر)
|
||||
static Future<GoogleSignInAccount?> signInFromLogin() async {
|
||||
await init();
|
||||
final user = await signIn();
|
||||
if (user != null) {
|
||||
await Get.put(LoginController()).loginUsingCredentials(
|
||||
box.read(BoxName.passengerID).toString(),
|
||||
box.read(BoxName.email).toString(),
|
||||
);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/// ✅ طلب سكوبات إضافية (بديل withScopes القديم)
|
||||
static Future<void> requestExtraScopes(List<String> scopes) async {
|
||||
final user = _lastUser;
|
||||
if (user == null) return;
|
||||
await user.authorizationClient.authorizeScopes(scopes);
|
||||
}
|
||||
|
||||
/// ✅ تسجيل خروج
|
||||
static Future<void> signOut() async {
|
||||
await _signIn.signOut();
|
||||
await _handleSignOut();
|
||||
}
|
||||
|
||||
static GoogleSignInAccount? getCurrentUser() => _lastUser;
|
||||
|
||||
// ================= Helpers ==================
|
||||
|
||||
static Future<void> _handleSignUp(GoogleSignInAccount user) async {
|
||||
box.write(BoxName.passengerID, user.id);
|
||||
box.write(BoxName.email, user.email);
|
||||
box.write(BoxName.name, user.displayName ?? '');
|
||||
box.write(BoxName.passengerPhotoUrl, user.photoUrl ?? '');
|
||||
}
|
||||
|
||||
static Future<void> _handleSignOut() async {
|
||||
box.erase();
|
||||
|
||||
Get.offAll(OnBoardingPage());
|
||||
}
|
||||
|
||||
static Future<void> addError(String error, String where) async {
|
||||
await CRUD().post(link: AppLink.addError, payload: {
|
||||
'error': error,
|
||||
'userId': box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
|
||||
'userType': box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger',
|
||||
'phone': box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver),
|
||||
'device': where,
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> showGooglePlayServicesError() async {
|
||||
const playStoreUrl =
|
||||
'https://play.google.com/store/apps/details?id=com.google.android.gms&hl=en_US';
|
||||
final uri = Uri.parse(playStoreUrl);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Error'.tr),
|
||||
content: Text(
|
||||
'Could not open the Google Play Store. Please update Google Play Services manually.'
|
||||
.tr,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Close'.tr),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
526
siro_rider/lib/controller/auth/login_controller.dart
Normal file
526
siro_rider/lib/controller/auth/login_controller.dart
Normal file
@@ -0,0 +1,526 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:siro_rider/constant/api_key.dart';
|
||||
import 'package:siro_rider/controller/firebase/firbase_messge.dart';
|
||||
import 'package:siro_rider/views/auth/otp_page.dart';
|
||||
import 'package:siro_rider/views/widgets/error_snakbar.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:siro_rider/constant/info.dart';
|
||||
import 'package:siro_rider/controller/functions/add_error.dart';
|
||||
import 'package:siro_rider/views/auth/login_page.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_rider/views/home/map_page_passenger.dart';
|
||||
|
||||
import 'package:location/location.dart';
|
||||
|
||||
import '../../env/env.dart';
|
||||
import '../../print.dart';
|
||||
import '../../views/auth/otp_token_page.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)}');
|
||||
box.write(BoxName.countryCode, 'Syria');
|
||||
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() async {
|
||||
// إذا كان التوكن الحالي لا يزال صالحاً، لا داعي لطلب واحد جديد
|
||||
if (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: $response');
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
await EncryptionHelper.initialize();
|
||||
}
|
||||
} else {
|
||||
// ── مستخدم موجود: تجديد التوكن ────────────────────────
|
||||
await EncryptionHelper.initialize();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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.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: {
|
||||
'email': email,
|
||||
'id': passengerID, // استخدم المعامل مباشرة
|
||||
'platform': Platform.isAndroid ? 'android' : 'ios',
|
||||
'appName': AppInformation.appName,
|
||||
},
|
||||
);
|
||||
|
||||
// 2) فك JSON مرة واحدة والتحقق من النجاح
|
||||
final decoded = jsonDecode(res);
|
||||
if (decoded is! Map || decoded.isEmpty) return;
|
||||
|
||||
if (decoded['status'] == 'failure' || decoded['status'] == 'Failure') {
|
||||
Get.snackbar("User does not exist.".tr, '',
|
||||
backgroundColor: Colors.red);
|
||||
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');
|
||||
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.");
|
||||
}
|
||||
|
||||
// إذا لم يكن موجوداً (توافقية قديمة)، نقوم بطلبه
|
||||
String? 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');
|
||||
|
||||
// ── 5. المقارنة: FCM token + fingerprint ──────────────────────
|
||||
if (email != '962798583052@intaleqapp.com') {
|
||||
if (serverFCM == null &&
|
||||
tokenResp != null &&
|
||||
tokenResp != 'failure' &&
|
||||
tokenResp != 'error') {
|
||||
final tokenJson = jsonDecode(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 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 (_) {
|
||||
// حتى لو فشل، كمل للصفحة الرئيسية
|
||||
}
|
||||
}
|
||||
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
} catch (e) {
|
||||
addError('$e', 'loginUsingCredentials');
|
||||
Get.snackbar('Error', e.toString(), backgroundColor: Colors.redAccent);
|
||||
} 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 login() async {
|
||||
isloading = true;
|
||||
update();
|
||||
var res =
|
||||
await CRUD().get(link: AppLink.loginFromGooglePassenger, payload: {
|
||||
'email': (emailController.text),
|
||||
'id': passwordController.text,
|
||||
"platform": Platform.isAndroid ? 'android' : 'ios',
|
||||
"appName": AppInformation.appName,
|
||||
});
|
||||
|
||||
isloading = false;
|
||||
update();
|
||||
if (res == 'Failure') {
|
||||
//Failure
|
||||
Get.offAll(() => LoginPage());
|
||||
isloading = false;
|
||||
update();
|
||||
// Get.snackbar("User does not exist.".tr, '', backgroundColor: Colors.red);
|
||||
} else {
|
||||
var jsonDecoeded = jsonDecode(res);
|
||||
if (jsonDecoeded.isNotEmpty) {
|
||||
if (jsonDecoeded['status'] == 'success' &&
|
||||
jsonDecoeded['data'][0]['verified'].toString() == '1') {
|
||||
//
|
||||
box.write(BoxName.isVerified, '1');
|
||||
box.write(BoxName.email, jsonDecoeded['data'][0]['email']);
|
||||
box.write(BoxName.name, jsonDecoeded['data'][0]['first_name']);
|
||||
box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']);
|
||||
box.write(BoxName.passengerID, passwordController.text);
|
||||
// var token = await CRUD().get(link: AppLink.getTokens, payload: {
|
||||
// 'passengerID': box.read(BoxName.passengerID).toString()
|
||||
// });
|
||||
// await updateAppTester(AppInformation.appName);
|
||||
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
} else {
|
||||
// Get.offAll(() => SmsSignupEgypt());
|
||||
// Get.snackbar(jsonDecoeded['status'], jsonDecoeded['data'],
|
||||
// backgroundColor: Colors.redAccent);
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
} else {
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void goToMapPage() {
|
||||
if (box.read(BoxName.email) != null) {
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
}
|
||||
}
|
||||
|
||||
final location = Location();
|
||||
|
||||
// late PermissionStatus permissionGranted = PermissionStatus.denied;
|
||||
Future<void> getLocationPermission() async {
|
||||
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
|
||||
permissionGranted = await location.requestPermission();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (permissionGranted.toString() == 'PermissionStatus.granted') {
|
||||
box.write(BoxName.locationPermission, 'true');
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
43
siro_rider/lib/controller/auth/onboarding_controller.dart
Normal file
43
siro_rider/lib/controller/auth/onboarding_controller.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_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();
|
||||
}
|
||||
}
|
||||
210
siro_rider/lib/controller/auth/otp_controller.dart
Normal file
210
siro_rider/lib/controller/auth/otp_controller.dart
Normal file
@@ -0,0 +1,210 @@
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/views/home/map_page_passenger.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/package_info.dart';
|
||||
import 'login_controller.dart';
|
||||
|
||||
// --- Helper Class for Phone Authentication ---
|
||||
|
||||
class PhoneAuthHelper {
|
||||
// Define your server URLs
|
||||
static final String _baseUrl = '${AppLink.server}/auth/syria/';
|
||||
static final String _sendOtpUrl = '${_baseUrl}sendWhatsOpt.php';
|
||||
static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php';
|
||||
static final String _registerUrl = '${_baseUrl}register_passenger.php';
|
||||
|
||||
static String formatSyrianPhone(String phone) {
|
||||
// Remove spaces, symbols, +, -, ()
|
||||
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
|
||||
|
||||
// Normalize 00963 → 963
|
||||
if (phone.startsWith('00963')) {
|
||||
phone = phone.replaceFirst('00963', '963');
|
||||
}
|
||||
|
||||
// Normalize 0963 → 963
|
||||
if (phone.startsWith('0963')) {
|
||||
phone = phone.replaceFirst('0963', '963');
|
||||
}
|
||||
|
||||
// NEW: Fix 96309xxxx → 9639xxxx
|
||||
if (phone.startsWith('96309')) {
|
||||
phone = '9639' + phone.substring(5); // remove the "0" after 963
|
||||
}
|
||||
|
||||
// If starts with 9630 → correct to 9639
|
||||
if (phone.startsWith('9630')) {
|
||||
phone = '9639' + phone.substring(4);
|
||||
}
|
||||
|
||||
// If already in correct format: 9639xxxxxxxx
|
||||
if (phone.startsWith('9639') && phone.length == 12) {
|
||||
return phone;
|
||||
}
|
||||
|
||||
// If starts with 963 but missing the 9
|
||||
if (phone.startsWith('963') && phone.length > 3) {
|
||||
// Ensure it begins with 9639
|
||||
if (!phone.startsWith('9639')) {
|
||||
phone = '9639' + phone.substring(3);
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
|
||||
// If starts with 09xxxxxxxx → 9639xxxxxxxx
|
||||
if (phone.startsWith('09')) {
|
||||
return '963' + phone.substring(1);
|
||||
}
|
||||
|
||||
// If 9xxxxxxxx (9 digits)
|
||||
if (phone.startsWith('9') && phone.length == 9) {
|
||||
return '963' + phone;
|
||||
}
|
||||
|
||||
// If starts with incorrect 0xxxxxxx → assume Syrian and fix
|
||||
if (phone.startsWith('0') && phone.length == 10) {
|
||||
return '963' + phone.substring(1);
|
||||
}
|
||||
|
||||
return phone;
|
||||
}
|
||||
|
||||
/// Sends an OTP to the provided phone number.
|
||||
static Future<bool> sendOtp(String phoneNumber) async {
|
||||
try {
|
||||
// إصلاح الرقم قبل الإرسال
|
||||
final fixedPhone = formatSyrianPhone(phoneNumber);
|
||||
|
||||
final response = await CRUD().post(
|
||||
link: _sendOtpUrl,
|
||||
payload: {'receiver': fixedPhone}, // ← ← استخدام الرقم المُعدّل
|
||||
);
|
||||
|
||||
if (response != 'failure') {
|
||||
final data = response;
|
||||
|
||||
if (data['status'] == 'success') {
|
||||
mySnackbarSuccess('An OTP has been sent to your number.'.tr);
|
||||
return true;
|
||||
} else {
|
||||
mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError('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 = formatSyrianPhone(phoneNumber);
|
||||
final response = await CRUD().post(
|
||||
link: _verifyOtpUrl,
|
||||
payload: {
|
||||
'phone_number': fixedPhone,
|
||||
'otp': otpCode,
|
||||
},
|
||||
);
|
||||
|
||||
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) {
|
||||
// ✅ المستخدم موجود مسبقاً -> تسجيل دخول مباشر
|
||||
await _handleSuccessfulLogin(data['message']['passenger']);
|
||||
} else {
|
||||
// ✅ مستخدم جديد -> الذهاب لصفحة التسجيل
|
||||
mySnackbarSuccess(
|
||||
'Phone verified. Please complete registration.'.tr);
|
||||
Get.to(() => RegistrationScreen(phoneNumber: phoneNumber));
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError(data['message']);
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError('Server error. Please try again.'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('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.toString())),
|
||||
'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 response = await CRUD().post(
|
||||
link: _registerUrl,
|
||||
payload: {
|
||||
'phone_number': phoneNumber,
|
||||
'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 {
|
||||
mySnackeBarError(
|
||||
"User with this phone number or email already exists.".tr);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('e: ${e}');
|
||||
mySnackeBarError('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
|
||||
}
|
||||
}
|
||||
367
siro_rider/lib/controller/auth/register_controller.dart
Normal file
367
siro_rider/lib/controller/auth/register_controller.dart
Normal file
@@ -0,0 +1,367 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:siro_rider/controller/functions/add_error.dart';
|
||||
import 'package:siro_rider/controller/functions/encrypt_decrypt.dart';
|
||||
import 'package:siro_rider/views/home/map_page_passenger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/constant/style.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_rider/views/auth/login_page.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.dart';
|
||||
import '../../views/auth/verify_email_page.dart';
|
||||
import '../../views/widgets/mydialoug.dart';
|
||||
import '../functions/sms_controller.dart';
|
||||
|
||||
class RegisterController extends GetxController {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final formKey3 = GlobalKey<FormState>();
|
||||
|
||||
TextEditingController firstNameController = TextEditingController();
|
||||
TextEditingController lastNameController = TextEditingController();
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController phoneController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
TextEditingController siteController = TextEditingController();
|
||||
// TextEditingController verfyCode = TextEditingController();
|
||||
TextEditingController verifyCode = TextEditingController();
|
||||
int remainingTime = 300; // 5 minutes in seconds
|
||||
bool isSent = false;
|
||||
bool isLoading = false;
|
||||
Timer? _timer;
|
||||
String birthDate = 'Birth Date'.tr;
|
||||
String gender = 'Male'.tr;
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
void startTimer() {
|
||||
_timer?.cancel(); // Cancel any existing timer
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (remainingTime > 0) {
|
||||
remainingTime--;
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getBirthDate() {
|
||||
Get.defaultDialog(
|
||||
title: 'Select Date'.tr,
|
||||
titleStyle: AppStyle.title,
|
||||
content: SizedBox(
|
||||
width: 300,
|
||||
child: CalendarDatePicker(
|
||||
initialDate:
|
||||
DateTime.now().subtract(const Duration(days: 14 * 365)),
|
||||
firstDate: DateTime.parse('1940-06-01'),
|
||||
lastDate: DateTime.now().subtract(const Duration(days: 14 * 365)),
|
||||
onDateChanged: (date) {
|
||||
// Get the selected date and convert it to a DateTime object
|
||||
DateTime dateTime = date;
|
||||
// Call the getOrders() function from the controller
|
||||
birthDate = dateTime.toString().split(' ')[0];
|
||||
update();
|
||||
},
|
||||
|
||||
// onDateChanged: (DateTime value) {},
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(title: 'Ok'.tr, onPressed: () => Get.back()));
|
||||
}
|
||||
|
||||
void changeGender(String value) {
|
||||
gender = value;
|
||||
update();
|
||||
}
|
||||
|
||||
bool isValidEgyptianPhoneNumber(String phoneNumber) {
|
||||
// Remove any whitespace from the phone number
|
||||
phoneNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
|
||||
|
||||
// Check if the phone number has exactly 11 digits
|
||||
if (phoneNumber.length != 11) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the phone number starts with 010, 011, 012, or 015
|
||||
RegExp validPrefixes = RegExp(r'^01[0125]\d{8}$');
|
||||
|
||||
return validPrefixes.hasMatch(phoneNumber);
|
||||
}
|
||||
|
||||
bool isValidPhoneNumber(String phoneNumber) {
|
||||
// Remove any whitespace from the phone number
|
||||
phoneNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
|
||||
|
||||
// Check if the phone number has at least 10 digits
|
||||
if (phoneNumber.length < 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for valid prefixes (modify this to match your use case)
|
||||
RegExp validPrefixes = RegExp(r'^[0-9]+$');
|
||||
|
||||
// Check if the phone number contains only digits
|
||||
return validPrefixes.hasMatch(phoneNumber);
|
||||
}
|
||||
|
||||
sendOtpMessage() async {
|
||||
SmsEgyptController smsEgyptController;
|
||||
isLoading = true;
|
||||
update();
|
||||
try {
|
||||
// Initialize SmsEgyptController
|
||||
smsEgyptController = Get.put(SmsEgyptController());
|
||||
|
||||
isLoading = true;
|
||||
update();
|
||||
|
||||
// Get phone number from controller
|
||||
String phoneNumber = phoneController.text;
|
||||
|
||||
// Check if the phone number is from Egypt (Assuming Egyptian numbers start with +20)
|
||||
|
||||
if (phoneController.text.isNotEmpty) {
|
||||
bool isEgyptianNumber = phoneNumber.startsWith('+20');
|
||||
if (isEgyptianNumber && phoneNumber.length == 13) {
|
||||
// Check if the phone number is already verified
|
||||
var responseChecker = await CRUD().post(
|
||||
link: AppLink.checkPhoneNumberISVerfiedPassenger,
|
||||
payload: {
|
||||
'phone_number': (phoneNumber),
|
||||
'email': box.read(BoxName.email),
|
||||
},
|
||||
);
|
||||
|
||||
if (responseChecker != 'failure') {
|
||||
var data = jsonDecode(responseChecker);
|
||||
|
||||
// If the phone number is already verified
|
||||
if (data['message'][0]['verified'].toString() == '1') {
|
||||
Get.snackbar('Phone number is verified before'.tr, '',
|
||||
backgroundColor: AppColor.greenColor);
|
||||
box.write(BoxName.isVerified, '1');
|
||||
box.write(BoxName.phone, (phoneNumber));
|
||||
Get.offAll(const MapPagePassenger());
|
||||
} else {
|
||||
await sendOtp(phoneNumber, isEgyptianNumber, smsEgyptController);
|
||||
}
|
||||
} else {
|
||||
await sendOtp(phoneNumber, isEgyptianNumber, smsEgyptController);
|
||||
}
|
||||
} else if (phoneNumber.length > 9) {
|
||||
sendOtp(phoneNumber, isEgyptianNumber, smsEgyptController);
|
||||
}
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'Error'.tr, 'Phone number must be exactly 11 digits long'.tr, () {
|
||||
Get.back();
|
||||
});
|
||||
// sendOtp(
|
||||
// phoneNumber, randomNumber, isEgyptianNumber, smsEgyptController);
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to send OTP or WhatsApp message based on phone number location
|
||||
Future<void> sendOtp(String phoneNumber, bool isEgyptian,
|
||||
SmsEgyptController controller) async {
|
||||
// Trim any leading or trailing whitespace from the phone number
|
||||
phoneNumber = phoneNumber.trim();
|
||||
var dd = await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: {
|
||||
'phone_number': (phoneNumber),
|
||||
});
|
||||
Log.print('dd: ${dd}');
|
||||
|
||||
// Common Registration Logic (extracted for reuse)
|
||||
Future<void> registerUser() async {
|
||||
await CRUD().post(link: AppLink.updatePhoneInvalidSMSPassenger, payload: {
|
||||
"phone_number": (Get.find<RegisterController>().phoneController.text)
|
||||
});
|
||||
|
||||
box.write(BoxName.phone, (phoneController.text));
|
||||
|
||||
var nameParts = (box.read(BoxName.name)).toString().split(' ');
|
||||
var firstName = nameParts.isNotEmpty ? nameParts[0] : 'unknown';
|
||||
var lastName = nameParts.length > 1 ? nameParts[1] : 'unknown';
|
||||
|
||||
var payload = {
|
||||
'id': box.read(BoxName.passengerID),
|
||||
'phone': (phoneController.text),
|
||||
'email': box.read(BoxName.email),
|
||||
'password':
|
||||
('unknown'), //Consider if you *really* want to store 'unknown' passwords
|
||||
'gender': ('unknown'),
|
||||
'birthdate': ('2002-01-01'),
|
||||
'site': box.read(BoxName.passengerPhotoUrl) ?? 'unknown',
|
||||
'first_name': (firstName),
|
||||
'last_name': (lastName),
|
||||
};
|
||||
|
||||
var res1 = await CRUD().post(
|
||||
link: AppLink.signUp,
|
||||
payload: payload,
|
||||
);
|
||||
|
||||
if (res1 != 'failure') {
|
||||
//Multi-server signup (moved inside the successful registration check)
|
||||
// if (AppLink.IntaleqAlexandriaServer != AppLink.IntaleqSyriaServer) {
|
||||
// List<Future> signUp = [
|
||||
// CRUD().post(
|
||||
// link: '${AppLink.IntaleqAlexandriaServer}/auth/signup.php',
|
||||
// payload: payload,
|
||||
// ),
|
||||
// CRUD().post(
|
||||
// link: '${AppLink.IntaleqGizaServer}/auth/signup.php',
|
||||
// payload: payload,
|
||||
// )
|
||||
// ];
|
||||
// await Future.wait(signUp); // Wait for both sign-ups to complete.
|
||||
// }
|
||||
|
||||
box.write(BoxName.isVerified, '1');
|
||||
box.write(
|
||||
BoxName.isFirstTime, '0'); //Double-check the logic for isFirstTime
|
||||
box.write(BoxName.phone, (phoneController.text));
|
||||
|
||||
Get.put(LoginController()).loginUsingCredentials(
|
||||
box.read(BoxName.passengerID).toString(),
|
||||
box.read(BoxName.email).toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isEgyptian) {
|
||||
// verifySMSCode();
|
||||
// await registerUser(); // Use the common registration logic
|
||||
// await controller.sendSmsEgypt(phoneNumber, otp.toString()); // Optional: Send SMS if Egyptian
|
||||
} else if (phoneController.text.toString().length >= 10) {
|
||||
await registerUser(); // Use the common registration logic for non-Egyptian users as well.
|
||||
// this for whatsapp messsage // Optional: Send WhatsApp message
|
||||
// await CRUD().sendWhatsAppAuth(phoneNumber, otp.toString());
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
isSent = true;
|
||||
remainingTime = 300;
|
||||
update(); // Reset to 5 minutes
|
||||
// startTimer(); // Consider whether you need a timer here, or if it's handled elsewhere.
|
||||
}
|
||||
|
||||
verifySMSCode() async {
|
||||
try {
|
||||
if (formKey3.currentState!.validate()) {
|
||||
var res = await CRUD().post(link: AppLink.verifyOtpPassenger, payload: {
|
||||
'phone_number': phoneController.text,
|
||||
'token': verifyCode.text,
|
||||
});
|
||||
|
||||
if (res != 'failure') {
|
||||
box.write(BoxName.phone, (phoneController.text));
|
||||
var nameParts = (box.read(BoxName.name)).toString().split(' ');
|
||||
var firstName = nameParts.isNotEmpty ? nameParts[0] : 'unknown';
|
||||
var lastName = nameParts.length > 1 ? nameParts[1] : 'unknown';
|
||||
|
||||
var payload = {
|
||||
'id': box.read(BoxName.passengerID),
|
||||
'phone': (phoneController.text),
|
||||
'email': box.read(BoxName.email),
|
||||
'password': 'unknown',
|
||||
'gender': 'unknown',
|
||||
'birthdate': '2002-01-01',
|
||||
'site': box.read(BoxName.passengerPhotoUrl) ?? 'unknown',
|
||||
'first_name': firstName,
|
||||
'last_name': lastName,
|
||||
};
|
||||
|
||||
var res1 = await CRUD().post(
|
||||
link: AppLink.signUp,
|
||||
payload: payload,
|
||||
);
|
||||
|
||||
if (res1 != 'failure') {
|
||||
box.write(BoxName.isVerified, '1');
|
||||
box.write(BoxName.isFirstTime, '0');
|
||||
box.write(BoxName.phone, (phoneController.text));
|
||||
|
||||
Get.put(LoginController()).loginUsingCredentials(
|
||||
box.read(BoxName.passengerID).toString(),
|
||||
box.read(BoxName.email).toString(),
|
||||
);
|
||||
} else {
|
||||
Get.snackbar('Error'.tr,
|
||||
"The email or phone number is already registered.".tr,
|
||||
backgroundColor: Colors.redAccent);
|
||||
}
|
||||
} else {
|
||||
Get.snackbar('Error'.tr, "phone not verified".tr,
|
||||
backgroundColor: Colors.redAccent);
|
||||
}
|
||||
} else {
|
||||
Get.snackbar('Error'.tr, "you must insert token code".tr,
|
||||
backgroundColor: AppColor.redColor);
|
||||
}
|
||||
} catch (e) {
|
||||
addError(e.toString(), 'passenger sign up ');
|
||||
Get.snackbar('Error'.tr, "Something went wrong. Please try again.".tr,
|
||||
backgroundColor: Colors.redAccent);
|
||||
}
|
||||
}
|
||||
|
||||
sendVerifications() async {
|
||||
var res = await CRUD().post(link: AppLink.verifyEmail, payload: {
|
||||
'email': emailController.text,
|
||||
'token': verifyCode.text,
|
||||
});
|
||||
var dec = jsonDecode(res);
|
||||
if (dec['status'] == 'success') {
|
||||
Get.offAll(() => LoginPage());
|
||||
}
|
||||
}
|
||||
|
||||
void register() async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
var res = await CRUD().post(link: AppLink.signUp, payload: {
|
||||
'first_name': firstNameController.text.toString(),
|
||||
'last_name': lastNameController.text.toString(),
|
||||
'email': emailController.text.toString(),
|
||||
'phone': phoneController.text.toString(),
|
||||
'password': passwordController.text.toString(),
|
||||
'gender': 'yet',
|
||||
'site': siteController.text,
|
||||
'birthdate': birthDate,
|
||||
});
|
||||
if (jsonDecode(res)['status'] == 'success') {
|
||||
int randomNumber = Random().nextInt(100000) + 1;
|
||||
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
|
||||
'email': emailController.text,
|
||||
'token': randomNumber.toString(),
|
||||
});
|
||||
Get.to(() => const VerifyEmailPage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_timer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
114
siro_rider/lib/controller/auth/token_otp_change_controller.dart
Normal file
114
siro_rider/lib/controller/auth/token_otp_change_controller.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:get/get.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/token_passenger/send_otp.php',
|
||||
payload: {
|
||||
'receiver': phone,
|
||||
// 'device_token': deviceToken,
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure') {
|
||||
isLoading.value = true;
|
||||
// بإمكانك عرض رسالة نجاح هنا
|
||||
} else {
|
||||
Get.snackbar('Error'.tr, 'Failed to send OTP'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error', 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/token_passenger/verify_otp.php',
|
||||
payload: {
|
||||
'phone_number': phone,
|
||||
'otp': otpCode.value,
|
||||
'token': box.read(BoxName.tokenFCM).toString(),
|
||||
'fingerPrint': fingerPrint.toString(),
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure' && response['status'] == 'success') {
|
||||
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 {
|
||||
Get.snackbar('Verification Failed', 'OTP is incorrect or expired');
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error', e.toString());
|
||||
} finally {
|
||||
isVerifying.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
siro_rider/lib/controller/auth/tokens_controller.dart
Normal file
38
siro_rider/lib/controller/auth/tokens_controller.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
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.') {
|
||||
Get.snackbar('token updated'.tr, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
16
siro_rider/lib/controller/auth/verify_email_controller.dart
Normal file
16
siro_rider/lib/controller/auth/verify_email_controller.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
|
||||
class VerifyEmailController extends GetxController {
|
||||
TextEditingController verfyCode = TextEditingController();
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
sendverfications() async {
|
||||
await CRUD().post(link: AppLink.sendVerifyEmail);
|
||||
}
|
||||
}
|
||||
50
siro_rider/lib/controller/firebase/access_token.dart
Normal file
50
siro_rider/lib/controller/firebase/access_token.dart
Normal file
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
774
siro_rider/lib/controller/firebase/firbase_messge.dart
Normal file
774
siro_rider/lib/controller/firebase/firbase_messge.dart
Normal file
@@ -0,0 +1,774 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:siro_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:siro_rider/controller/functions/toast.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.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 '../auth/google_sign.dart';
|
||||
import 'package:siro_rider/controller/voice_call_controller.dart';
|
||||
import '../home/map/ride_lifecycle_controller.dart';
|
||||
import '../home/map/ride_state.dart';
|
||||
import 'local_notification.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"); // أو "users" حسب نوع المستخدم
|
||||
Log.print("Subscribed to 'passengers' 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.onBackgroundMessage((RemoteMessage message) async {
|
||||
// Handle background message
|
||||
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 (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');
|
||||
}
|
||||
GoogleSignInHelper.signOut();
|
||||
} 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');
|
||||
}
|
||||
passengerDialog(body);
|
||||
update();
|
||||
} else if (category == 'message From Driver') {
|
||||
// <-- كان 'message From Driver'
|
||||
if (Platform.isAndroid) {
|
||||
notificationController.showNotification(title, body, 'ding');
|
||||
}
|
||||
passengerDialog(body);
|
||||
update();
|
||||
} else if (category == 'Trip is Begin') {
|
||||
// <-- كان 'Trip is Begin'
|
||||
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") {
|
||||
// استدعاء الحارس
|
||||
mapCtrl!.processDriverArrival("FCM");
|
||||
} else if (category == 'Driver Finish Trip') {
|
||||
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 Intaleq 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 Intaleq 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() {
|
||||
return Get.snackbar(
|
||||
'Driver Applied the Ride for You'.tr,
|
||||
'',
|
||||
colorText: AppColor.greenColor,
|
||||
duration: const Duration(seconds: 3),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
titleText: Text(
|
||||
'Applied'.tr,
|
||||
style: TextStyle(color: AppColor.redColor),
|
||||
),
|
||||
messageText: Text(
|
||||
'Driver Applied the Ride for You'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
icon: Icon(Icons.approval, color: AppColor.primaryColor),
|
||||
shouldIconPulse: true,
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
);
|
||||
}
|
||||
|
||||
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}${(controller.totalPassenger) * (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} ${(controller.totalPassenger) * (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} ${(controller.totalPassenger) * (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} ${(controller.totalPassenger) * (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(
|
||||
'${(controller.totalPassenger) * (double.parse(box.read(BoxName.tipPercentage.toString())))} ${box.read(BoxName.countryCode) == 'Egypt' ? 'LE'.tr : 'JOD'.tr}',
|
||||
style: AppStyle.title,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
60
siro_rider/lib/controller/firebase/live_activity.dart
Normal file
60
siro_rider/lib/controller/firebase/live_activity.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'package:siro_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"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
336
siro_rider/lib/controller/firebase/local_notification.dart
Normal file
336
siro_rider/lib/controller/firebase/local_notification.dart
Normal file
@@ -0,0 +1,336 @@
|
||||
import 'package:siro_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');
|
||||
}
|
||||
// /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');
|
||||
}
|
||||
}
|
||||
66
siro_rider/lib/controller/firebase/notification_service.dart
Normal file
66
siro_rider/lib/controller/firebase/notification_service.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../print.dart'; // للترجمة .tr
|
||||
|
||||
class NotificationService {
|
||||
static const String _serverUrl =
|
||||
'https://api.intaleq.xyz/intaleq/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');
|
||||
}
|
||||
}
|
||||
}
|
||||
19
siro_rider/lib/controller/functions/add_error.dart
Normal file
19
siro_rider/lib/controller/functions/add_error.dart
Normal file
@@ -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
|
||||
});
|
||||
}
|
||||
169
siro_rider/lib/controller/functions/audio_record1.dart
Normal file
169
siro_rider/lib/controller/functions/audio_record1.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'package:siro_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();
|
||||
}
|
||||
}
|
||||
134
siro_rider/lib/controller/functions/call_controller.dart
Normal file
134
siro_rider/lib/controller/functions/call_controller.dart
Normal file
@@ -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();
|
||||
// }
|
||||
// }
|
||||
720
siro_rider/lib/controller/functions/crud.dart
Normal file
720
siro_rider/lib/controller/functions/crud.dart
Normal file
@@ -0,0 +1,720 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:siro_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';
|
||||
|
||||
class CRUD {
|
||||
final NetGuard _netGuard = NetGuard();
|
||||
final _client = http.Client();
|
||||
|
||||
/// Stores the signature of the last logged error to prevent duplicates.
|
||||
static String _lastErrorSignature = '';
|
||||
|
||||
/// Stores the timestamp of the last logged error.
|
||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||
|
||||
/// The minimum time that must pass before logging the same error again.
|
||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
||||
|
||||
/// Asynchronously logs an error to the server with debouncing to prevent log flooding.
|
||||
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");
|
||||
|
||||
// Fire-and-forget call to prevent infinite loops if the logger itself fails.
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// دالة مساعدة خاصة: يجيب البصمة المشفرة من GetStorage
|
||||
// هي نفس القيمة المرسلة في login وعُملها hash في JWT payload
|
||||
// السيرفر يعمل: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
String _getFpHeader() {
|
||||
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
||||
}
|
||||
|
||||
/// Centralized private method to handle all API requests.
|
||||
/// Includes retry logic, network checking, and standardized error handling.
|
||||
Future<dynamic> _makeRequest({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
const connectTimeout = Duration(seconds: 6);
|
||||
const receiveTimeout = Duration(seconds: 10);
|
||||
|
||||
Future<http.Response> doPost() {
|
||||
final url = Uri.parse(link);
|
||||
return _client
|
||||
.post(url, body: payload, headers: headers)
|
||||
.timeout(connectTimeout + receiveTimeout);
|
||||
}
|
||||
|
||||
http.Response response;
|
||||
try {
|
||||
// retry ذكي: محاولة واحدة إضافية فقط لأخطاء شبكة/5xx
|
||||
try {
|
||||
response = await doPost();
|
||||
} on SocketException catch (_) {
|
||||
response = await doPost();
|
||||
} on TimeoutException catch (_) {
|
||||
response = await doPost();
|
||||
}
|
||||
|
||||
final sc = response.statusCode;
|
||||
final body = response.body;
|
||||
Log.print('request: ${response.request}');
|
||||
Log.print('body: $body');
|
||||
// Log.print('link: $link');
|
||||
Log.print('headers: $headers');
|
||||
Log.print('payload: $payload');
|
||||
|
||||
// 2xx
|
||||
if (sc >= 200 && sc < 300) {
|
||||
try {
|
||||
final jsonData = jsonDecode(body);
|
||||
return jsonData;
|
||||
} catch (e, st) {
|
||||
addError('JSON Decode Error', 'Body: $body\n$st',
|
||||
'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
// 401 → تجديد التوكن تلقائياً
|
||||
if (sc == 401) {
|
||||
await Get.put(LoginController()).getJWT();
|
||||
return 'token_expired';
|
||||
}
|
||||
|
||||
// 5xx
|
||||
if (sc >= 500) {
|
||||
addError(
|
||||
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
// 4xx أخرى
|
||||
return 'failure';
|
||||
} on SocketException {
|
||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||
return 'no_internet';
|
||||
} on TimeoutException {
|
||||
return 'failure';
|
||||
} catch (e, st) {
|
||||
addError('HTTP Request Exception: $e', 'Stack: $st',
|
||||
'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// post — طلب POST عادي للراكب/السائق
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// التغيير: إضافة X-Device-FP header
|
||||
// القيمة: fp_encrypted من GetStorage
|
||||
// السيرفر يتحقق: sha256(fp_encrypted + FP_PEPPER) == JWT.fingerPrint
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Future<dynamic> post({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// get — طلب GET للراكب/السائق (يستخدم POST method)
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// التغيير: إضافة X-Device-FP header
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Future<dynamic> get({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
var response = await http.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization':
|
||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
},
|
||||
);
|
||||
|
||||
Log.print('request: ${response.request}');
|
||||
Log.print('body: ${response.body}');
|
||||
Log.print('payload: $payload');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return response.body;
|
||||
} else if (response.statusCode == 401) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['error'] == 'Token expired') {
|
||||
print("CRUD.get: Token expired, refreshing and retrying once...");
|
||||
await Get.put(LoginController()).getJWT();
|
||||
|
||||
// إعادة المحاولة مرة واحدة فقط بتوكن جديد
|
||||
var retryResponse = await http.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization':
|
||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
},
|
||||
);
|
||||
|
||||
if (retryResponse.statusCode == 200) {
|
||||
return retryResponse.body;
|
||||
}
|
||||
return jsonEncode(
|
||||
{'status': 'failure', 'message': 'token_expired_retry_failed'});
|
||||
} else {
|
||||
return jsonEncode({'status': 'failure', 'message': '401_unauthorized'});
|
||||
}
|
||||
} else {
|
||||
addError('Non-200 response code: ${response.statusCode}',
|
||||
'crud().get - Other', url.toString());
|
||||
return jsonEncode({
|
||||
'status': 'failure',
|
||||
'message': 'server_error_${response.statusCode}'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 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(), // ← إثبات الجهاز
|
||||
};
|
||||
// add print debug
|
||||
Log.print('headers: $headers');
|
||||
Log.print('payload: $payload');
|
||||
Log.print('link: $link');
|
||||
|
||||
return await _makeRequest(
|
||||
link: link,
|
||||
payload: payload,
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// getWallet — طلب GET لسيرفر المدفوعات (يستخدم POST method)
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// التغيير: إضافة X-Device-FP header
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Future<dynamic> getWallet({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var s = await LoginController().getJwtWallet();
|
||||
final hmac = box.read(BoxName.hmac);
|
||||
var url = Uri.parse(link);
|
||||
|
||||
var response = await http.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['status'] == 'success') {
|
||||
return response.body;
|
||||
}
|
||||
return jsonData['status'];
|
||||
} else if (response.statusCode == 401) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['error'] == 'Token expired') {
|
||||
await Get.put(LoginController()).getJwtWallet();
|
||||
return 'token_expired';
|
||||
} else {
|
||||
addError('Unauthorized: ${jsonData['error']}', 'crud().getWallet - 401',
|
||||
url.toString());
|
||||
return 'failure';
|
||||
}
|
||||
} else {
|
||||
addError('Non-200 response code: ${response.statusCode}',
|
||||
'crud().getWallet - Other', url.toString());
|
||||
return 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// 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 url = Uri.parse(link);
|
||||
|
||||
try {
|
||||
final response = await http.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||
},
|
||||
);
|
||||
|
||||
Map<String, dynamic> wrap(String status, {Object? message, int? code}) {
|
||||
return {
|
||||
'status': status,
|
||||
'message': message,
|
||||
'code': code ?? response.statusCode,
|
||||
};
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
try {
|
||||
return jsonDecode(response.body);
|
||||
} catch (e) {
|
||||
return wrap('failure',
|
||||
message: 'JSON decode error', code: response.statusCode);
|
||||
}
|
||||
} else if (response.statusCode == 401) {
|
||||
try {
|
||||
final jsonData = jsonDecode(response.body);
|
||||
if (jsonData is Map && jsonData['error'] == 'Token expired') {
|
||||
await Get.put(LoginController()).getJWT();
|
||||
return {
|
||||
'status': 'failure',
|
||||
'message': 'token_expired',
|
||||
'code': 401
|
||||
};
|
||||
}
|
||||
return wrap('failure', message: jsonData);
|
||||
} catch (_) {
|
||||
return wrap('failure', message: response.body);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
final jsonData = jsonDecode(response.body);
|
||||
return wrap('failure', message: jsonData);
|
||||
} catch (_) {
|
||||
return wrap('failure', message: response.body);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
'status': 'failure',
|
||||
'message': 'HTTP request error: $e',
|
||||
'code': -1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Future sendWhatsAppAuth(String to, String token) async {
|
||||
var res = await CRUD()
|
||||
.get(link: AppLink.getApiKey, payload: {'keyName': 'whatsapp_key'});
|
||||
var accesstoken = jsonDecode(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 http.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 http.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 http.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 http.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 http.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 http.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 http.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 http.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 http.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 http.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 http.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 http.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
siro_rider/lib/controller/functions/custom_pant.dart
Normal file
26
siro_rider/lib/controller/functions/custom_pant.dart
Normal file
@@ -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;
|
||||
}
|
||||
92
siro_rider/lib/controller/functions/device_info.dart
Normal file
92
siro_rider/lib/controller/functions/device_info.dart
Normal file
@@ -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);
|
||||
// }
|
||||
// }
|
||||
75
siro_rider/lib/controller/functions/encrypt_decrypt.dart
Normal file
75
siro_rider/lib/controller/functions/encrypt_decrypt.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:siro_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;
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
/// Encrypts a string
|
||||
String encryptData(String plainText) {
|
||||
try {
|
||||
final encrypter =
|
||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
|
||||
final encrypted = encrypter.encrypt(plainText, iv: iv);
|
||||
return encrypted.base64;
|
||||
} catch (e) {
|
||||
debugPrint('Encryption Error: $e');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypts a string
|
||||
String decryptData(String encryptedText) {
|
||||
try {
|
||||
final encrypter =
|
||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
|
||||
final encrypted = encrypt.Encrypted.fromBase64(encryptedText);
|
||||
return encrypter.decrypt(encrypted, iv: iv);
|
||||
} catch (e) {
|
||||
debugPrint('Decryption Error: $e');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
34
siro_rider/lib/controller/functions/geolocation.dart
Normal file
34
siro_rider/lib/controller/functions/geolocation.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:geolocator/geolocator.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) {
|
||||
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.
|
||||
return await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
}
|
||||
}
|
||||
108
siro_rider/lib/controller/functions/launch.dart
Normal file
108
siro_rider/lib/controller/functions/launch.dart
Normal file
@@ -0,0 +1,108 @@
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'dart:io';
|
||||
|
||||
void showInBrowser(String url) async {
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
launchUrl(Uri.parse(url));
|
||||
} else {}
|
||||
}
|
||||
|
||||
Future<void> makePhoneCall(String phoneNumber) async {
|
||||
// 1. تنظيف الرقم (إزالة المسافات والفواصل)
|
||||
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
|
||||
|
||||
// 2. منطق التنسيق (مع الحفاظ على الأرقام القصيرة مثل 112 كما هي)
|
||||
if (formattedNumber.length > 6) {
|
||||
if (formattedNumber.startsWith('09')) {
|
||||
// إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي) -> +963
|
||||
formattedNumber = '+963${formattedNumber.substring(1)}';
|
||||
} else if (!formattedNumber.startsWith('+')) {
|
||||
// إذا لم يكن دولياً ولا محلياً معروفاً -> إضافة + فقط
|
||||
formattedNumber = '+$formattedNumber';
|
||||
}
|
||||
}
|
||||
// ملاحظة: الأرقام القصيرة (مثل 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 url;
|
||||
|
||||
if (Platform.isIOS) {
|
||||
switch (method) {
|
||||
case 'phone':
|
||||
url = 'tel:$contactInfo';
|
||||
break;
|
||||
case 'sms':
|
||||
url = 'sms:$contactInfo?body=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
case 'whatsapp':
|
||||
url =
|
||||
'https://api.whatsapp.com/send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
case 'email':
|
||||
url =
|
||||
'mailto:$contactInfo?subject=Subject&body=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else if (Platform.isAndroid) {
|
||||
switch (method) {
|
||||
case 'phone':
|
||||
url = 'tel:$contactInfo';
|
||||
break;
|
||||
|
||||
case 'sms':
|
||||
url = 'sms:$contactInfo?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=$contactInfo&text=${Uri.encodeComponent(message)}';
|
||||
} else {
|
||||
// Provide an alternative action, such as opening the WhatsApp Web API
|
||||
url =
|
||||
'https://api.whatsapp.com/send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
url =
|
||||
'mailto:$contactInfo?subject=Subject&body=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
await launchUrl(Uri.parse(url));
|
||||
} else {}
|
||||
}
|
||||
147
siro_rider/lib/controller/functions/location_controller.dart
Normal file
147
siro_rider/lib/controller/functions/location_controller.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
// import 'dart:async';
|
||||
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:location/location.dart';
|
||||
// import 'package:siro_rider/constant/box_name.dart';
|
||||
// import 'package:siro_rider/constant/links.dart';
|
||||
// import 'package:siro_rider/controller/functions/crud.dart';
|
||||
// import 'package:siro_rider/controller/home/payment/captain_wallet_controller.dart';
|
||||
// import 'package:siro_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;
|
||||
// }
|
||||
// }
|
||||
16
siro_rider/lib/controller/functions/location_permission.dart
Normal file
16
siro_rider/lib/controller/functions/location_permission.dart
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
201
siro_rider/lib/controller/functions/log_out.dart
Normal file
201
siro_rider/lib/controller/functions/log_out.dart
Normal file
@@ -0,0 +1,201 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_rider/onbording_page.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
import 'package:siro_rider/views/widgets/my_textField.dart';
|
||||
|
||||
import '../../constant/style.dart';
|
||||
import 'package:siro_rider/controller/home/map/map_socket_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/map_engine_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/location_search_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/nearby_drivers_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/ui_interactions_controller.dart';
|
||||
import 'package:siro_rider/controller/home/menu_controller.dart';
|
||||
import 'package:siro_rider/controller/home/points_for_rider_controller.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) => Get.snackbar('Deleted'.tr, 'Your Account is Deleted',
|
||||
backgroundColor: AppColor.redColor));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
Get.snackbar('Email Wrong'.tr, 'Email you inserted is Wrong.'.tr,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: AppColor.redColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
siro_rider/lib/controller/functions/network/net_guard.dart
Normal file
48
siro_rider/lib/controller/functions/network/net_guard.dart
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
358
siro_rider/lib/controller/functions/package_info.dart
Normal file
358
siro_rider/lib/controller/functions/package_info.dart
Normal file
@@ -0,0 +1,358 @@
|
||||
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:jailbreak_root_detection/jailbreak_root_detection.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);
|
||||
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.Intaleq.intaleq'
|
||||
: 'https://apps.apple.com/jo/app/intaleq-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 SecurityHelper {
|
||||
/// Performs security checks and handles potential risks
|
||||
static Future<void> performSecurityChecks() async {
|
||||
bool isNotTrust = false;
|
||||
bool isJailBroken = false;
|
||||
bool isRealDevice = true;
|
||||
bool isOnExternalStorage = false;
|
||||
bool checkForIssues = false;
|
||||
bool isDevMode = false;
|
||||
bool isTampered = false;
|
||||
String bundleId = "";
|
||||
|
||||
try {
|
||||
isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
|
||||
isJailBroken = await JailbreakRootDetection.instance.isJailBroken;
|
||||
isRealDevice = await JailbreakRootDetection.instance.isRealDevice;
|
||||
isOnExternalStorage =
|
||||
await JailbreakRootDetection.instance.isOnExternalStorage;
|
||||
|
||||
List<JailbreakIssue> issues =
|
||||
await JailbreakRootDetection.instance.checkForIssues;
|
||||
checkForIssues = issues.isNotEmpty;
|
||||
|
||||
isDevMode = await JailbreakRootDetection.instance.isDevMode;
|
||||
|
||||
// Get Bundle ID
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
bundleId = packageInfo.packageName;
|
||||
if (bundleId.isNotEmpty) {
|
||||
// Pass the CORRECT bundle ID to isTampered
|
||||
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Error during security checks: $e");
|
||||
// Consider handling specific exceptions, not just general errors.
|
||||
}
|
||||
|
||||
// Save values to storage (using GetStorage)
|
||||
await box.write('isNotTrust', isNotTrust); // Use await for write operations
|
||||
await box.write('isTampered', isTampered); // Use await
|
||||
await box.write('isJailBroken', isJailBroken); // Use await
|
||||
|
||||
debugPrint("Security Check Results:");
|
||||
debugPrint("isNotTrust: $isNotTrust");
|
||||
debugPrint("isJailBroken: $isJailBroken");
|
||||
debugPrint("isRealDevice: $isRealDevice");
|
||||
debugPrint("isOnExternalStorage: $isOnExternalStorage");
|
||||
debugPrint("checkForIssues: $checkForIssues");
|
||||
debugPrint("isDevMode: $isDevMode");
|
||||
debugPrint("isTampered: $isTampered");
|
||||
debugPrint("Bundle ID: $bundleId"); //Log.print the bundle ID
|
||||
|
||||
// Check for security risks and potentially show a warning
|
||||
if (isJailBroken || isRealDevice == false || isTampered) {
|
||||
// Log.print("security_warning".tr); //using easy_localization
|
||||
// Use a more robust approach to show a warning, like a dialog:
|
||||
_showSecurityWarning();
|
||||
} else {
|
||||
box.write(BoxName.security_check, 'passed');
|
||||
Log.print('Security checks passed successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes all app data
|
||||
static void _showSecurityWarning() {
|
||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
||||
RxInt secondsRemaining = 10.obs;
|
||||
|
||||
Get.dialog(
|
||||
CupertinoAlertDialog(
|
||||
title: Text("Security Warning".tr),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Obx(() => Text(
|
||||
"Potential security risks detected. The application will close in @seconds seconds."
|
||||
.trParams({
|
||||
// Use trParams for placeholders
|
||||
'seconds': secondsRemaining.value.toString(),
|
||||
}),
|
||||
// Wrap the Text widget in Obx
|
||||
)),
|
||||
SizedBox(height: 24), // More spacing before the progress bar
|
||||
Obx(() => SizedBox(
|
||||
width: double.infinity, // Make progress bar full width
|
||||
child: CupertinoActivityIndicator(
|
||||
// in case of loading
|
||||
radius: 15,
|
||||
animating: true,
|
||||
))),
|
||||
SizedBox(height: 8),
|
||||
Obx(() => ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8), // Rounded corners
|
||||
child: LinearProgressIndicator(
|
||||
value: secondsRemaining.value / 10,
|
||||
backgroundColor: Colors.grey.shade300, // Lighter background
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
CupertinoColors.systemRed), // iOS-style red
|
||||
minHeight: 8, // Slightly thicker progress bar
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
|
||||
Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
secondsRemaining.value--;
|
||||
if (secondsRemaining.value <= 0) {
|
||||
timer.cancel();
|
||||
// Get.back();
|
||||
_clearDataAndExit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _clearDataAndExit() async {
|
||||
await storage.deleteAll();
|
||||
await box.erase();
|
||||
exit(0); // Exit the app
|
||||
Log.print('exit');
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceHelper {
|
||||
static Future<String> getDeviceFingerprint() async {
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
8
siro_rider/lib/controller/functions/remove_account.dart
Normal file
8
siro_rider/lib/controller/functions/remove_account.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
// import 'package:ride/controller/functions/crud.dart';
|
||||
|
||||
// class RemoveAccount {
|
||||
|
||||
// void removeAccount()async{
|
||||
// var res=await CRUD().post(link: link)
|
||||
// }
|
||||
// }
|
||||
25
siro_rider/lib/controller/functions/scan_id_card.dart
Normal file
25
siro_rider/lib/controller/functions/scan_id_card.dart
Normal file
@@ -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();
|
||||
// }
|
||||
// }
|
||||
84
siro_rider/lib/controller/functions/secure_storage.dart
Normal file
84
siro_rider/lib/controller/functions/secure_storage.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/info.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
||||
|
||||
import '../../constant/char_map.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.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();
|
||||
}
|
||||
}
|
||||
|
||||
const List<String> keysToFetch = [
|
||||
'serverPHP',
|
||||
// 'seferAlexandriaServer',
|
||||
// 'seferPaymentServer',
|
||||
// 'seferCairoServer',
|
||||
// 'seferGizaServer',
|
||||
];
|
||||
|
||||
class AppInitializer {
|
||||
List<Map<String, dynamic>> links = [];
|
||||
|
||||
Future<void> initializeApp() async {
|
||||
if (box.read(BoxName.jwt) == null) {
|
||||
await LoginController().getJWT();
|
||||
} else {
|
||||
bool isTokenExpired = JwtDecoder.isExpired(
|
||||
r(box.read(BoxName.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 = jsonDecode(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(jsonDecode(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) {}
|
||||
}
|
||||
}
|
||||
53
siro_rider/lib/controller/functions/securty_check.dart
Normal file
53
siro_rider/lib/controller/functions/securty_check.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:siro_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.Intaleq.intaleq/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 true; // Treat platform errors as a compromised device (for safety)
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
168
siro_rider/lib/controller/functions/sms_controller.dart
Normal file
168
siro_rider/lib/controller/functions/sms_controller.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/api_key.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/info.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:siro_rider/env/env.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../print.dart';
|
||||
import '../auth/register_controller.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 = jsonDecode(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<RegisterController>().phoneController.text}'
|
||||
});
|
||||
box.write(BoxName.phoneDriver,
|
||||
'+2${Get.find<RegisterController>().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
siro_rider/lib/controller/functions/sss.dart
Normal file
18
siro_rider/lib/controller/functions/sss.dart
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
35
siro_rider/lib/controller/functions/toast.dart
Normal file
35
siro_rider/lib/controller/functions/toast.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_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,
|
||||
);
|
||||
}
|
||||
}
|
||||
51
siro_rider/lib/controller/functions/tts.dart
Normal file
51
siro_rider/lib/controller/functions/tts.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:siro_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) ?? 'en-US';
|
||||
bool isAvailable = await flutterTts.isLanguageAvailable(langCode);
|
||||
|
||||
// If language is unavailable, default to 'en-US'
|
||||
if (!isAvailable) {
|
||||
langCode = 'en-US';
|
||||
}
|
||||
|
||||
await flutterTts.setLanguage(langCode);
|
||||
await flutterTts.setSpeechRate(0.5); // Adjust speech rate
|
||||
await flutterTts.setVolume(1.0); // Set volume
|
||||
} catch (error) {
|
||||
Get.snackbar('Error', 'Failed to initialize TTS: $error');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to speak the given text
|
||||
Future<void> speakText(String text) async {
|
||||
try {
|
||||
await flutterTts.awaitSpeakCompletion(true);
|
||||
await flutterTts.speak(text);
|
||||
} catch (error) {
|
||||
Get.snackbar('Error', 'Failed to speak text: $error');
|
||||
}
|
||||
}
|
||||
}
|
||||
22
siro_rider/lib/controller/functions/twilio_service.dart
Normal file
22
siro_rider/lib/controller/functions/twilio_service.dart
Normal file
@@ -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) {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
106
siro_rider/lib/controller/functions/upload_image.dart
Normal file
106
siro_rider/lib/controller/functions/upload_image.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:siro_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 '../../constant/box_name.dart';
|
||||
import '../../constant/char_map.dart';
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/info.dart';
|
||||
import '../../main.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) {
|
||||
Get.snackbar('Image Upload Failed'.tr, e.toString(),
|
||||
backgroundColor: AppColor.redColor);
|
||||
} 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 ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).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 = jsonDecode(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();
|
||||
}
|
||||
}
|
||||
89
siro_rider/lib/controller/home/compare.sh
Normal file
89
siro_rider/lib/controller/home/compare.sh
Normal file
@@ -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|IntaleqMapController)$/) {
|
||||
# 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
|
||||
104
siro_rider/lib/controller/home/compare_precise.py
Normal file
104
siro_rider/lib/controller/home/compare_precise.py
Normal file
@@ -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|IntaleqMapController)\??\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()
|
||||
103
siro_rider/lib/controller/home/comparison_results.txt
Normal file
103
siro_rider/lib/controller/home/comparison_results.txt
Normal file
@@ -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
|
||||
121
siro_rider/lib/controller/home/contact_us_controller.dart
Normal file
121
siro_rider/lib/controller/home/contact_us_controller.dart
Normal file
@@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
62
siro_rider/lib/controller/home/decode_polyline_isolate.dart
Normal file
62
siro_rider/lib/controller/home/decode_polyline_isolate.dart
Normal file
@@ -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;
|
||||
// }
|
||||
43
siro_rider/lib/controller/home/deep_link_controller.dart
Normal file
43
siro_rider/lib/controller/home/deep_link_controller.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'dart:async';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:get/get.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();
|
||||
});
|
||||
|
||||
// الاستماع للروابط إذا كان التطبيق مغلقاً تماماً (Cold Start)
|
||||
try {
|
||||
final initialUri = await _appLinks.getInitialLink();
|
||||
if (initialUri != null) {
|
||||
Log.print('🔗 Received initial deep link (Cold Start): $initialUri');
|
||||
rawDeepLink.value = initialUri.toString();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error getting initial link: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_linkSubscription?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
127
siro_rider/lib/controller/home/device_performance.dart
Normal file
127
siro_rider/lib/controller/home/device_performance.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
class DevicePerformanceManager {
|
||||
/// القائمة البيضاء لموديلات الهواتف القوية (Flagships Only)
|
||||
/// أي هاتف يبدأ موديله بأحد هذه الرموز سيعتبر قوياً
|
||||
static const List<String> _highEndSamsungModels = [
|
||||
'SM-S', // سلسلة Galaxy S21, S22, S23, S24 (S901, S908, S911...)
|
||||
'SM-F', // سلسلة Fold و Flip (Z Fold, Z Flip)
|
||||
'SM-N9', // سلسلة Note 9, Note 10, Note 20
|
||||
'SM-G9', // سلسلة S10, S20 (G970, G980...)
|
||||
];
|
||||
|
||||
static const List<String> _highEndGoogleModels = [
|
||||
'Pixel 6',
|
||||
'Pixel 7',
|
||||
'Pixel 8',
|
||||
'Pixel 9',
|
||||
'Pixel Fold'
|
||||
];
|
||||
|
||||
static const List<String> _highEndHuaweiModels = [
|
||||
'ELS-', // P40 Pro
|
||||
'ANA-', // P40
|
||||
'HMA-', // Mate 20
|
||||
'LYA-', // Mate 20 Pro
|
||||
'VOG-', // P30 Pro
|
||||
'ELE-', // P30
|
||||
'NOH-', // Mate 40 Pro
|
||||
'AL00', // Mate X series (some)
|
||||
];
|
||||
|
||||
static const List<String> _highEndXiaomiModels = [
|
||||
'2201122', // Xiaomi 12 series patterns often look like this
|
||||
'2210132', // Xiaomi 13
|
||||
'2304FPN', // Xiaomi 13 Ultra
|
||||
'M2007J1', // Mi 10 series
|
||||
'M2102K1', // Mi 11 Ultra
|
||||
];
|
||||
|
||||
static const List<String> _highEndOnePlusModels = [
|
||||
'GM19', // OnePlus 7
|
||||
'HD19', // OnePlus 7T
|
||||
'IN20', // OnePlus 8
|
||||
'KB20', // OnePlus 8T
|
||||
'LE21', // OnePlus 9
|
||||
'NE22', // OnePlus 10
|
||||
'PHB110', // OnePlus 11
|
||||
'CPH', // Newer OnePlus models
|
||||
];
|
||||
|
||||
/// دالة الفحص الرئيسية
|
||||
static Future<bool> isHighEndDevice() async {
|
||||
// 1. الآيفون دائماً قوي (نظام الرسوميات فيه متفوق حتى في الموديلات القديمة)
|
||||
if (Platform.isIOS) return true;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
try {
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
|
||||
String manufacturer = androidInfo.manufacturer.toLowerCase();
|
||||
String model =
|
||||
androidInfo.model.toUpperCase(); // نحوله لحروف كبيرة للمقارنة
|
||||
String hardware = androidInfo.hardware.toLowerCase(); // المعالج
|
||||
|
||||
// --- الفحص العكسي (الحظر المباشر) ---
|
||||
// إذا كان المعالج من الفئات الضعيفة جداً المشهورة في الهواتف المقلدة
|
||||
// mt65xx, mt6735, sc77xx هي معالجات رخيصة جداً
|
||||
if (hardware.contains('mt65') ||
|
||||
hardware.contains('mt6735') ||
|
||||
hardware.contains('sc77')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- فحص القائمة البيضاء (Whitelist) ---
|
||||
|
||||
// 1. Samsung Flagships
|
||||
if (manufacturer.contains('samsung')) {
|
||||
for (var prefix in _highEndSamsungModels) {
|
||||
if (model.startsWith(prefix)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Google Pixel (6 and above)
|
||||
if (manufacturer.contains('google')) {
|
||||
for (var prefix in _highEndGoogleModels) {
|
||||
if (model.contains(prefix.toUpperCase())) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Huawei Flagships
|
||||
if (manufacturer.contains('huawei')) {
|
||||
for (var prefix in _highEndHuaweiModels) {
|
||||
if (model.startsWith(prefix)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. OnePlus Flagships
|
||||
if (manufacturer.contains('oneplus')) {
|
||||
for (var prefix in _highEndOnePlusModels) {
|
||||
if (model.startsWith(prefix)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Xiaomi Flagships
|
||||
if (manufacturer.contains('xiaomi') ||
|
||||
manufacturer.contains('redmi') ||
|
||||
manufacturer.contains('poco')) {
|
||||
// شاومي تسميتها معقدة، لذا سنعتمد على فحص الرام كعامل مساعد هنا فقط
|
||||
// لأن هواتف شاومي القوية عادة لا تزور الرام
|
||||
// الرام يجب أن يكون أكبر من 6 جيجا (بايت)
|
||||
double ramGB = (androidInfo.availableRamSize) / (1024 * 1024 * 1024);
|
||||
if (ramGB > 7.5)
|
||||
return true; // 8GB RAM or more is usually safe for Xiaomi high-end
|
||||
}
|
||||
|
||||
// إذا لم يكن من ضمن القوائم أعلاه، نعتبره جهازاً متوسطاً/ضعيفاً ونعرض الرسم البسيط
|
||||
return false;
|
||||
} catch (e) {
|
||||
// في حال حدوث خطأ في الفحص، نعود للوضع الآمن (الرسم البسيط)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
86
siro_rider/lib/controller/home/device_tier.dart
Normal file
86
siro_rider/lib/controller/home/device_tier.dart
Normal file
@@ -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';
|
||||
15
siro_rider/lib/controller/home/home_page_controller.dart
Normal file
15
siro_rider/lib/controller/home/home_page_controller.dart
Normal file
@@ -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:siro_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.Intaleq.intaleq",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
15
siro_rider/lib/controller/home/map/car_location.dart
Normal file
15
siro_rider/lib/controller/home/map/car_location.dart
Normal file
@@ -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,
|
||||
});
|
||||
}
|
||||
1052
siro_rider/lib/controller/home/map/location_search_controller.dart
Normal file
1052
siro_rider/lib/controller/home/map/location_search_controller.dart
Normal file
File diff suppressed because it is too large
Load Diff
809
siro_rider/lib/controller/home/map/map_engine_controller.dart
Normal file
809
siro_rider/lib/controller/home/map/map_engine_controller.dart
Normal file
@@ -0,0 +1,809 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' show cos, max, min, pi, pow, sqrt;
|
||||
import 'dart:typed_data';
|
||||
import 'package:siro_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 = {};
|
||||
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";
|
||||
|
||||
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 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();
|
||||
}
|
||||
|
||||
void onStyleLoaded() async {
|
||||
Log.print('🗺️ Intaleq 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');
|
||||
|
||||
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) return 120;
|
||||
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.clear();
|
||||
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 {
|
||||
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('segment_$s'),
|
||||
points: segCoords,
|
||||
color: color,
|
||||
width: 6,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
newPolylines.add(Polyline(
|
||||
polylineId: const PolylineId('route_primary'),
|
||||
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.fromStyleImage(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.fromStyleImage(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 {
|
||||
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('segment_$s'),
|
||||
points: segCoords,
|
||||
color: color,
|
||||
width: 6,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
newPolylines.add(Polyline(
|
||||
polylineId: const PolylineId('route_primary'),
|
||||
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.clear();
|
||||
polyLines.clear();
|
||||
polylineCoordinates.clear();
|
||||
|
||||
_animationTimers.forEach((key, timer) => timer.cancel());
|
||||
_animationTimers.clear();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_animationTimers.forEach((key, timer) => timer.cancel());
|
||||
_animationTimers.clear();
|
||||
mapController = null;
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
25
siro_rider/lib/controller/home/map/map_screen_binding.dart
Normal file
25
siro_rider/lib/controller/home/map/map_screen_binding.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
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';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
326
siro_rider/lib/controller/home/map/map_socket_controller.dart
Normal file
326
siro_rider/lib/controller/home/map/map_socket_controller.dart
Normal file
@@ -0,0 +1,326 @@
|
||||
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 '../../../main.dart'; // contains global 'box'
|
||||
import '../../../print.dart';
|
||||
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;
|
||||
Timer? _heartbeatTimer;
|
||||
DateTime? _lastSocketLocationTime;
|
||||
int _socketLocationUpdatesCount = 0;
|
||||
Timer? _watchdogTimer;
|
||||
|
||||
DateTime? get lastDriverLocationTime => _lastSocketLocationTime;
|
||||
int get socketLocationUpdatesCount => _socketLocationUpdatesCount;
|
||||
|
||||
void initConnectionWithSocket() {
|
||||
if (isSocketConnected) return;
|
||||
|
||||
String passengerId = box.read(BoxName.passengerID).toString();
|
||||
Log.print("🔌 Initializing Socket for Passenger: $passengerId");
|
||||
|
||||
socket = io_client.io(
|
||||
AppLink.serverSocket,
|
||||
io_client.OptionBuilder()
|
||||
.setTransports(['websocket'])
|
||||
.disableAutoConnect()
|
||||
.setQuery({'id': passengerId})
|
||||
.setReconnectionAttempts(20)
|
||||
.setReconnectionDelay(2000)
|
||||
.setReconnectionDelayMax(10000)
|
||||
.enableReconnection()
|
||||
.setTimeout(20000)
|
||||
.setExtraHeaders({'Connection': 'Upgrade'})
|
||||
.build(),
|
||||
);
|
||||
_isSocketInitialized = true;
|
||||
|
||||
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");
|
||||
}
|
||||
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 (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);
|
||||
});
|
||||
}
|
||||
|
||||
void _startHeartbeat() {
|
||||
_heartbeatTimer?.cancel();
|
||||
_heartbeatTimer = Timer.periodic(const Duration(seconds: 15), (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;
|
||||
|
||||
case 'cancelled':
|
||||
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'] ?? data['distance'];
|
||||
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,475 @@
|
||||
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') {
|
||||
noCarString = true;
|
||||
dataCarsLocationByPassenger = 'failure';
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
|
||||
noCarString = false;
|
||||
var responseData = jsonDecode(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) {
|
||||
final mapEngine = Get.find<MapEngineController>();
|
||||
if (carData['model'].toString().contains('دراجة')) {
|
||||
return mapEngine.motoIcon;
|
||||
} else if (carData['gender'] == 'Female') {
|
||||
return mapEngine.ladyIcon;
|
||||
} else {
|
||||
return mapEngine.carIcon;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
4558
siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
Normal file
4558
siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart
Normal file
File diff suppressed because it is too large
Load Diff
10
siro_rider/lib/controller/home/map/ride_state.dart
Normal file
10
siro_rider/lib/controller/home/map/ride_state.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
enum RideState {
|
||||
noRide, // لا يوجد رحلة جارية، عرض واجهة البحث
|
||||
cancelled, // تم إلغاء الرحلة
|
||||
preCheckReview, // يوجد رحلة منتهية، تحقق من التقييم
|
||||
searching, // جاري البحث عن كابتن
|
||||
driverApplied, // تم قبول الطلب
|
||||
driverArrived, // وصل السائق
|
||||
inProgress, // الرحلة بدأت بالفعل
|
||||
finished, // انتهت الرحلة (سيتم تحويلها إلى preCheckReview)
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
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 'package:intaleq_maps/intaleq_maps.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/mydialoug.dart';
|
||||
import '../../../views/widgets/my_textField.dart';
|
||||
import '../../../views/home/map_page_passenger.dart';
|
||||
import '../../../views/widgets/error_snakbar.dart';
|
||||
import '../../../models/model/painter_copoun.dart';
|
||||
import '../../functions/launch.dart';
|
||||
import '../../firebase/local_notification.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 'map_engine_controller.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: 'e.g. 0912345678 (Default +963)'.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 =
|
||||
formatSyrianPhoneNumber(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);
|
||||
}
|
||||
|
||||
String formatSyrianPhone(String phone) {
|
||||
phone = phone.replaceAll(' ', '').replaceAll('+', '');
|
||||
if (phone.startsWith('00963')) {
|
||||
phone = phone.replaceFirst('00963', '963');
|
||||
}
|
||||
if (phone.startsWith('0963')) {
|
||||
phone = phone.replaceFirst('0963', '963');
|
||||
}
|
||||
if (phone.startsWith('963')) {
|
||||
return phone;
|
||||
}
|
||||
if (phone.startsWith('09')) {
|
||||
return '963' + phone.substring(1);
|
||||
}
|
||||
if (phone.startsWith('9') && phone.length == 9) {
|
||||
return '963' + phone;
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
|
||||
String formatSyrianPhoneNumber(String phoneNumber) {
|
||||
String trimmedPhone = phoneNumber.trim();
|
||||
if (trimmedPhone.startsWith('09')) {
|
||||
return '963${trimmedPhone.substring(1)}';
|
||||
}
|
||||
if (trimmedPhone.startsWith('963')) {
|
||||
return trimmedPhone;
|
||||
}
|
||||
return '963$trimmedPhone';
|
||||
}
|
||||
|
||||
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 = formatSyrianPhone(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) {
|
||||
Get.snackbar("Alert".tr, "Wait for the trip to start first".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
var numberPhone = formatSyrianPhoneNumber(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 Intaleq 🚗
|
||||
|
||||
Track my ride here:
|
||||
$trackingLink
|
||||
|
||||
Driver: ${rideLifecycle.passengerName}
|
||||
Car: ${rideLifecycle.model} - ${rideLifecycle.licensePlate}
|
||||
Thank you for using Intaleq!
|
||||
""";
|
||||
|
||||
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 = formatSyrianPhoneNumber(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 Intaleq 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 = formatSyrianPhoneNumber(rawPhone);
|
||||
|
||||
var message = '''Dear Friend,
|
||||
|
||||
🚀 I have just started an exciting trip on Intaleq!
|
||||
Download the app to track my ride:
|
||||
|
||||
👉 Android: https://play.google.com/store/apps/details?id=com.Intaleq.intaleq&hl=en-US
|
||||
👉 iOS: https://apps.apple.com/st/app/intaleq-rider/id6748075179
|
||||
|
||||
See you there!
|
||||
Intaleq Team''';
|
||||
|
||||
launchCommunication('whatsapp', phone, message);
|
||||
},
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Cancel'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (res['status'] == 'success') {
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
|
||||
Get.snackbar("Success".tr, "The invitation was sent successfully".tr,
|
||||
backgroundColor: AppColor.greenColor, colorText: Colors.white);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
7751
siro_rider/lib/controller/home/map_passenger_controller.dart
Normal file
7751
siro_rider/lib/controller/home/map_passenger_controller.dart
Normal file
File diff suppressed because it is too large
Load Diff
14
siro_rider/lib/controller/home/menu_controller.dart
Normal file
14
siro_rider/lib/controller/home/menu_controller.dart
Normal file
@@ -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:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_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 = jsonDecode(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,75 @@
|
||||
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);
|
||||
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;
|
||||
// }
|
||||
}
|
||||
|
||||
@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;
|
||||
String cvvCode;
|
||||
|
||||
CreditCardModel({
|
||||
required this.cardNumber,
|
||||
required this.cardHolderName,
|
||||
required this.expiryDate,
|
||||
required this.cvvCode,
|
||||
});
|
||||
}
|
||||
155
siro_rider/lib/controller/home/points_for_rider_controller.dart
Normal file
155
siro_rider/lib/controller/home/points_for_rider_controller.dart
Normal file
@@ -0,0 +1,155 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:siro_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),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
159
siro_rider/lib/controller/home/precise_comparison_results.txt
Normal file
159
siro_rider/lib/controller/home/precise_comparison_results.txt
Normal file
@@ -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
|
||||
214
siro_rider/lib/controller/home/profile/complaint_controller.dart
Normal file
214
siro_rider/lib/controller/home/profile/complaint_controller.dart
Normal file
@@ -0,0 +1,214 @@
|
||||
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:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_rider/main.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}) {
|
||||
Get.snackbar(
|
||||
'', // العنوان سيتم التعامل معه عبر titleText
|
||||
'', // الرسالة سيتم التعامل معها عبر messageText
|
||||
titleText: Text(title.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
messageText: Text(message.tr,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14)),
|
||||
backgroundColor: isError
|
||||
? AppColor.redColor.withOpacity(0.95)
|
||||
: const Color.fromARGB(255, 6, 148, 79).withOpacity(0.95),
|
||||
icon: Icon(isError ? Icons.error_outline : Icons.check_circle_outline,
|
||||
color: Colors.white, size: 28),
|
||||
borderRadius: 12,
|
||||
margin: const EdgeInsets.all(15),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18),
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
duration: const Duration(seconds: 4),
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
// --- هذه الدالة تبقى كما هي لجلب بيانات الرحلة ---
|
||||
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 = jsonDecode(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
351
siro_rider/lib/controller/home/profile/invit_controller.dart
Normal file
351
siro_rider/lib/controller/home/profile/invit_controller.dart
Normal file
@@ -0,0 +1,351 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_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 Intaleq as a driver using my referral code!'.tr}
|
||||
${'Use code:'.tr} $driverCouponCode
|
||||
${'Download the Intaleq Driver app now and earn rewards!'.tr}
|
||||
''';
|
||||
await Share.share(shareText);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sharePassengerCode() async {
|
||||
if (couponCode != null) {
|
||||
final String shareText = '''
|
||||
${'Get a discount on your first Intaleq ride!'.tr}
|
||||
${'Use my referral code:'.tr} $couponCode
|
||||
${'Download the Intaleq app now and enjoy your ride!'.tr}
|
||||
''';
|
||||
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') {
|
||||
var data = jsonDecode(response);
|
||||
driverInvitationData = data['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') {
|
||||
var data = jsonDecode(response);
|
||||
driverInvitationDataToPassengers = data['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 {
|
||||
Get.snackbar('No contacts found'.tr,
|
||||
'No contacts with phone numbers were found on your device.'.tr);
|
||||
}
|
||||
} else {
|
||||
Get.snackbar('Permission denied'.tr,
|
||||
'Contact permission is required to pick contacts'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error picking contacts: $e');
|
||||
Get.snackbar(
|
||||
'Error'.tr, 'An error occurred while picking contacts: $e'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
void selectPhone(String phone) {
|
||||
invitePhoneController.text = phone;
|
||||
update();
|
||||
Get.back();
|
||||
}
|
||||
|
||||
/// **IMPROVEMENT**: A new robust function to format phone numbers specifically for Syria (+963).
|
||||
/// It handles various user inputs gracefully to produce a standardized international format.
|
||||
String _formatSyrianPhoneNumber(String phone) {
|
||||
// 1. Remove all non-digit characters to clean the input.
|
||||
String digitsOnly = phone.replaceAll(RegExp(r'\D'), '');
|
||||
|
||||
// 2. If it already starts with the country code, we assume it's correct.
|
||||
if (digitsOnly.startsWith('963')) {
|
||||
return '$digitsOnly';
|
||||
}
|
||||
|
||||
// 3. If it starts with '09' (common local format), remove the leading '0'.
|
||||
if (digitsOnly.startsWith('09')) {
|
||||
digitsOnly = digitsOnly.substring(1);
|
||||
}
|
||||
|
||||
// 4. Prepend the Syrian country code.
|
||||
return '963$digitsOnly';
|
||||
}
|
||||
|
||||
/// **IMPROVEMENT**: This method now uses the new phone formatting logic and
|
||||
/// sends a much-improved, user-friendly WhatsApp message.
|
||||
void sendInviteToPassenger() async {
|
||||
if (invitePhoneController.text.isEmpty ||
|
||||
invitePhoneController.text.length < 9) {
|
||||
mySnackeBarError('Please enter a correct phone'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use the new formatting function to ensure the number is correct.
|
||||
String formattedPhoneNumber =
|
||||
_formatSyrianPhoneNumber(invitePhoneController.text);
|
||||
|
||||
var response =
|
||||
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
|
||||
"driverId": box.read(BoxName.passengerID),
|
||||
"inviterPassengerPhone": formattedPhoneNumber,
|
||||
});
|
||||
|
||||
if (response != 'failure') {
|
||||
var d = response;
|
||||
Get.snackbar('Success'.tr, 'Invite sent successfully'.tr,
|
||||
backgroundColor: Colors.green, colorText: Colors.white);
|
||||
|
||||
// التحقق الديناميكي من مكان البيانات (V1 vs V3)
|
||||
var payload = d['data'] ?? d['message'];
|
||||
|
||||
// إذا كان الـ message نصاً وليس خريطة (Map)، نأخذ البيانات من المستوى الأعلى
|
||||
if (payload is String) {
|
||||
payload = d;
|
||||
}
|
||||
|
||||
String expirationTime = (payload['expirationTime'] ?? '').toString();
|
||||
String inviteCode = (payload['inviteCode'] ?? '').toString();
|
||||
|
||||
// New and improved WhatsApp message for better user engagement.
|
||||
String message =
|
||||
"👋 ${'Hello! I\'m inviting you to try Intaleq.'.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"
|
||||
"📲 ${'Download the app now:'.tr}\n"
|
||||
"• *Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n"
|
||||
"• *iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179\n\n"
|
||||
"${'See you on the road!'.tr} 🚗";
|
||||
|
||||
launchCommunication('whatsapp', formattedPhoneNumber, message);
|
||||
invitePhoneController.clear();
|
||||
update();
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Error'.tr, "This phone number has already been invited.".tr,
|
||||
backgroundColor: AppColor.redColor,
|
||||
duration: const Duration(seconds: 4));
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Error sending invite: $e");
|
||||
Get.snackbar(
|
||||
'Error'.tr, 'An unexpected error occurred. Please try again.'.tr,
|
||||
backgroundColor: AppColor.redColor);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
await Get.find<PaymentController>().addPassengersWallet('20');
|
||||
await CRUD().post(
|
||||
link: AppLink.updatePassengerGift,
|
||||
payload: {'id': invitation['id']},
|
||||
);
|
||||
NotificationCaptainController().addNotificationCaptain(
|
||||
invitation['passengerInviterId'].toString(),
|
||||
"You have got a gift for invitation".tr,
|
||||
'${"You have earned 20".tr} ${'LE'}',
|
||||
false,
|
||||
);
|
||||
fetchDriverStatsPassengers(); // Refresh list
|
||||
},
|
||||
);
|
||||
} 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,35 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_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 = jsonDecode(res);
|
||||
var rawData = jsonDecoded['data'] ?? jsonDecoded['message'];
|
||||
orderHistoryListPassenger = rawData is List ? rawData : [];
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_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 {
|
||||
var jsonDecoded = jsonDecode(res);
|
||||
|
||||
promoList = jsonDecoded['message'];
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
233
siro_rider/lib/controller/home/splash_screen_controlle.dart
Normal file
233
siro_rider/lib/controller/home/splash_screen_controlle.dart
Normal file
@@ -0,0 +1,233 @@
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:siro_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:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/onbording_page.dart';
|
||||
import 'package:siro_rider/views/auth/login_page.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:siro_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(
|
||||
'Intaleq', 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.', "tone1");
|
||||
} else {
|
||||
final rnd = Random();
|
||||
final raw = msgs[rnd.nextInt(msgs.length)];
|
||||
final parts = raw.split(':');
|
||||
final title = parts.isNotEmpty ? parts.first.trim() : 'Intaleq';
|
||||
final body = parts.length > 1
|
||||
? parts.sublist(1).join(':').trim()
|
||||
: 'مرحباً بك! تابع رحلاتك بأمان مع انطلق.';
|
||||
controller.scheduleNotificationsForSevenDays(
|
||||
title.isEmpty ? 'Intaleq' : 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();
|
||||
}
|
||||
}
|
||||
107
siro_rider/lib/controller/home/trip_monitor_controller.dart
Normal file
107
siro_rider/lib/controller/home/trip_monitor_controller.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_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 = jsonDecode(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();
|
||||
}
|
||||
}
|
||||
324
siro_rider/lib/controller/home/vip_waitting_page.dart
Normal file
324
siro_rider/lib/controller/home/vip_waitting_page.dart
Normal file
@@ -0,0 +1,324 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
import 'package:siro_rider/views/widgets/my_scafold.dart';
|
||||
import 'package:siro_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') {
|
||||
var decodedResponse = jsonDecode(res);
|
||||
if (decodedResponse['message'] is List) {
|
||||
tripData.value = decodedResponse['message'];
|
||||
} else {
|
||||
tripData.clear(); // Ensure empty list if no data
|
||||
// mySnackeBarError('No trip data found');
|
||||
}
|
||||
} else {
|
||||
tripData.clear();
|
||||
// mySnackeBarError('Failed to fetch trip data');
|
||||
}
|
||||
} catch (e) {
|
||||
tripData.clear();
|
||||
// mySnackeBarError('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,
|
||||
);
|
||||
}
|
||||
}
|
||||
78
siro_rider/lib/controller/local/local_controller.dart
Normal file
78
siro_rider/lib/controller/local/local_controller.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../main.dart';
|
||||
import '../themes/themes.dart';
|
||||
|
||||
class LocaleController extends GetxController {
|
||||
Locale? language;
|
||||
String countryCode = '';
|
||||
|
||||
ThemeMode themeMode = ThemeMode.system;
|
||||
|
||||
void changeLang(String langcode) {
|
||||
Locale newLocale;
|
||||
ThemeData newTheme;
|
||||
bool isArabic = langcode.startsWith('ar');
|
||||
|
||||
if (isArabic) {
|
||||
newLocale = const Locale("ar");
|
||||
newTheme = Get.isDarkMode ? darkThemeArabic : lightThemeArabic;
|
||||
} else {
|
||||
newLocale = const Locale("en");
|
||||
newTheme = Get.isDarkMode ? darkThemeEnglish : lightThemeEnglish;
|
||||
}
|
||||
|
||||
box.write(BoxName.lang, langcode);
|
||||
language = newLocale;
|
||||
Get.changeTheme(newTheme);
|
||||
Get.updateLocale(newLocale);
|
||||
update();
|
||||
}
|
||||
|
||||
void changeThemeMode(ThemeMode mode) {
|
||||
themeMode = mode;
|
||||
Get.changeThemeMode(mode);
|
||||
|
||||
// Explicitly update ThemeData to ensure immediate font and color changes
|
||||
bool goDark = mode == ThemeMode.dark ||
|
||||
(mode == ThemeMode.system && Get.isPlatformDarkMode);
|
||||
bool isArabic = (language?.languageCode ?? 'en').startsWith('ar');
|
||||
|
||||
ThemeData newTheme;
|
||||
if (isArabic) {
|
||||
newTheme = goDark ? darkThemeArabic : lightThemeArabic;
|
||||
} else {
|
||||
newTheme = goDark ? darkThemeEnglish : lightThemeEnglish;
|
||||
}
|
||||
Get.changeTheme(newTheme);
|
||||
|
||||
box.write(BoxName.themeMode, mode.toString());
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
String? storedLang = box.read(BoxName.lang);
|
||||
if (storedLang == null) {
|
||||
// Use device language if no language is stored
|
||||
storedLang = Get.deviceLocale!.languageCode;
|
||||
box.write(BoxName.lang, storedLang);
|
||||
}
|
||||
|
||||
String? storedTheme = box.read(BoxName.themeMode);
|
||||
if (storedTheme != null) {
|
||||
if (storedTheme == ThemeMode.light.toString()) {
|
||||
themeMode = ThemeMode.light;
|
||||
} else if (storedTheme == ThemeMode.dark.toString()) {
|
||||
themeMode = ThemeMode.dark;
|
||||
} else {
|
||||
themeMode = ThemeMode.system;
|
||||
}
|
||||
}
|
||||
|
||||
changeLang(storedLang);
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
7574
siro_rider/lib/controller/local/phone_intel/countries.dart
Normal file
7574
siro_rider/lib/controller/local/phone_intel/countries.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
import 'package:siro_rider/controller/local/phone_intel/helpers.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'countries.dart';
|
||||
|
||||
class PickerDialogStyle {
|
||||
final Color? backgroundColor;
|
||||
|
||||
final TextStyle? countryCodeStyle;
|
||||
|
||||
final TextStyle? countryNameStyle;
|
||||
|
||||
final Widget? listTileDivider;
|
||||
|
||||
final EdgeInsets? listTilePadding;
|
||||
|
||||
final EdgeInsets? padding;
|
||||
|
||||
final Color? searchFieldCursorColor;
|
||||
|
||||
final InputDecoration? searchFieldInputDecoration;
|
||||
|
||||
final EdgeInsets? searchFieldPadding;
|
||||
|
||||
final double? width;
|
||||
|
||||
PickerDialogStyle({
|
||||
this.backgroundColor,
|
||||
this.countryCodeStyle,
|
||||
this.countryNameStyle,
|
||||
this.listTileDivider,
|
||||
this.listTilePadding,
|
||||
this.padding,
|
||||
this.searchFieldCursorColor,
|
||||
this.searchFieldInputDecoration,
|
||||
this.searchFieldPadding,
|
||||
this.width,
|
||||
});
|
||||
}
|
||||
|
||||
class CountryPickerDialog extends StatefulWidget {
|
||||
final List<Country> countryList;
|
||||
final Country selectedCountry;
|
||||
final ValueChanged<Country> onCountryChanged;
|
||||
final String searchText;
|
||||
final List<Country> filteredCountries;
|
||||
final PickerDialogStyle? style;
|
||||
final String languageCode;
|
||||
|
||||
const CountryPickerDialog({
|
||||
Key? key,
|
||||
required this.searchText,
|
||||
required this.languageCode,
|
||||
required this.countryList,
|
||||
required this.onCountryChanged,
|
||||
required this.selectedCountry,
|
||||
required this.filteredCountries,
|
||||
this.style,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CountryPickerDialog> createState() => _CountryPickerDialogState();
|
||||
}
|
||||
|
||||
class _CountryPickerDialogState extends State<CountryPickerDialog> {
|
||||
late List<Country> _filteredCountries;
|
||||
late Country _selectedCountry;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selectedCountry = widget.selectedCountry;
|
||||
_filteredCountries = widget.filteredCountries.toList()
|
||||
..sort(
|
||||
(a, b) => a
|
||||
.localizedName(widget.languageCode)
|
||||
.compareTo(b.localizedName(widget.languageCode)),
|
||||
);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mediaWidth = MediaQuery.of(context).size.width;
|
||||
final width = widget.style?.width ?? mediaWidth;
|
||||
const defaultHorizontalPadding = 40.0;
|
||||
const defaultVerticalPadding = 24.0;
|
||||
return Dialog(
|
||||
insetPadding: EdgeInsets.symmetric(
|
||||
vertical: defaultVerticalPadding,
|
||||
horizontal: mediaWidth > (width + defaultHorizontalPadding * 2)
|
||||
? (mediaWidth - width) / 2
|
||||
: defaultHorizontalPadding),
|
||||
backgroundColor: widget.style?.backgroundColor,
|
||||
child: Container(
|
||||
padding: widget.style?.padding ?? const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding:
|
||||
widget.style?.searchFieldPadding ?? const EdgeInsets.all(0),
|
||||
child: TextField(
|
||||
cursorColor: widget.style?.searchFieldCursorColor,
|
||||
decoration: widget.style?.searchFieldInputDecoration ??
|
||||
InputDecoration(
|
||||
suffixIcon: const Icon(Icons.search),
|
||||
labelText: widget.searchText,
|
||||
),
|
||||
onChanged: (value) {
|
||||
_filteredCountries = widget.countryList.stringSearch(value)
|
||||
..sort(
|
||||
(a, b) => a
|
||||
.localizedName(widget.languageCode)
|
||||
.compareTo(b.localizedName(widget.languageCode)),
|
||||
);
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: _filteredCountries.length,
|
||||
itemBuilder: (ctx, index) => Column(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: kIsWeb
|
||||
? Image.asset(
|
||||
'assets/flags/${_filteredCountries[index].code.toLowerCase()}.png',
|
||||
package: 'intl_phone_field',
|
||||
width: 32,
|
||||
)
|
||||
: Text(
|
||||
_filteredCountries[index].flag,
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
contentPadding: widget.style?.listTilePadding,
|
||||
title: Text(
|
||||
_filteredCountries[index]
|
||||
.localizedName(widget.languageCode),
|
||||
style: widget.style?.countryNameStyle ??
|
||||
const TextStyle(fontWeight: FontWeight.w700),
|
||||
),
|
||||
trailing: Text(
|
||||
'+${_filteredCountries[index].dialCode}',
|
||||
style: widget.style?.countryCodeStyle ??
|
||||
const TextStyle(fontWeight: FontWeight.w700),
|
||||
),
|
||||
onTap: () {
|
||||
_selectedCountry = _filteredCountries[index];
|
||||
widget.onCountryChanged(_selectedCountry);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
widget.style?.listTileDivider ??
|
||||
const Divider(thickness: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
siro_rider/lib/controller/local/phone_intel/helpers.dart
Normal file
31
siro_rider/lib/controller/local/phone_intel/helpers.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'countries.dart';
|
||||
|
||||
bool isNumeric(String s) =>
|
||||
s.isNotEmpty && int.tryParse(s.replaceAll("+", "")) != null;
|
||||
|
||||
String removeDiacritics(String str) {
|
||||
var withDia =
|
||||
'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž';
|
||||
var withoutDia =
|
||||
'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz';
|
||||
|
||||
for (int i = 0; i < withDia.length; i++) {
|
||||
str = str.replaceAll(withDia[i], withoutDia[i]);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
extension CountryExtensions on List<Country> {
|
||||
List<Country> stringSearch(String search) {
|
||||
search = removeDiacritics(search.toLowerCase());
|
||||
return where(
|
||||
(country) => isNumeric(search) || search.startsWith("+")
|
||||
? country.dialCode.contains(search)
|
||||
: removeDiacritics(country.name.replaceAll("+", "").toLowerCase())
|
||||
.contains(search) ||
|
||||
country.nameTranslations.values.any((element) =>
|
||||
removeDiacritics(element.toLowerCase()).contains(search)),
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
library intl_phone_field;
|
||||
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import './countries.dart';
|
||||
import './phone_number.dart';
|
||||
import 'country_picker_dialog.dart';
|
||||
import 'helpers.dart';
|
||||
|
||||
class IntlPhoneField extends StatefulWidget {
|
||||
/// The TextFormField key.
|
||||
final GlobalKey<FormFieldState>? formFieldKey;
|
||||
|
||||
/// Whether to hide the text being edited (e.g., for passwords).
|
||||
final bool obscureText;
|
||||
|
||||
/// How the text should be aligned horizontally.
|
||||
final TextAlign textAlign;
|
||||
|
||||
/// How the text should be aligned vertically.
|
||||
final TextAlignVertical? textAlignVertical;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.readOnly}
|
||||
final bool readOnly;
|
||||
final FormFieldSetter<PhoneNumber>? onSaved;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.onChanged}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [inputFormatters], which are called before [onChanged]
|
||||
/// runs and can validate and change ("format") the input value.
|
||||
/// * [onEditingComplete], [onSubmitted], [onSelectionChanged]:
|
||||
/// which are more specialized input change notifications.
|
||||
final ValueChanged<PhoneNumber>? onChanged;
|
||||
|
||||
final ValueChanged<Country>? onCountryChanged;
|
||||
|
||||
/// An optional method that validates an input. Returns an error string to display if the input is invalid, or null otherwise.
|
||||
///
|
||||
/// A [PhoneNumber] is passed to the validator as argument.
|
||||
/// The validator can handle asynchronous validation when declared as a [Future].
|
||||
/// Or run synchronously when declared as a [Function].
|
||||
///
|
||||
/// By default, the validator checks whether the input number length is between selected country's phone numbers min and max length.
|
||||
/// If `disableLengthCheck` is not set to `true`, your validator returned value will be overwritten by the default validator.
|
||||
/// But, if `disableLengthCheck` is set to `true`, your validator will have to check phone number length itself.
|
||||
final FutureOr<String?> Function(PhoneNumber?)? validator;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.keyboardType}
|
||||
final TextInputType keyboardType;
|
||||
|
||||
/// Controls the text being edited.
|
||||
///
|
||||
/// If null, this widget will create its own [TextEditingController].
|
||||
final TextEditingController? controller;
|
||||
|
||||
/// Defines the keyboard focus for this widget.
|
||||
///
|
||||
/// The [focusNode] is a long-lived object that's typically managed by a
|
||||
/// [StatefulWidget] parent. See [FocusNode] for more information.
|
||||
///
|
||||
/// To give the keyboard focus to this widget, provide a [focusNode] and then
|
||||
/// use the current [FocusScope] to request the focus:
|
||||
///
|
||||
/// ```dart
|
||||
/// FocusScope.of(context).requestFocus(myFocusNode);
|
||||
/// ```
|
||||
///
|
||||
/// This happens automatically when the widget is tapped.
|
||||
///
|
||||
/// To be notified when the widget gains or loses the focus, add a listener
|
||||
/// to the [focusNode]:
|
||||
///
|
||||
/// ```dart
|
||||
/// focusNode.addListener(() { Log.print(myFocusNode.hasFocus); });
|
||||
/// ```
|
||||
///
|
||||
/// If null, this widget will create its own [FocusNode].
|
||||
///
|
||||
/// ## Keyboard
|
||||
///
|
||||
/// Requesting the focus will typically cause the keyboard to be shown
|
||||
/// if it's not showing already.
|
||||
///
|
||||
/// On Android, the user can hide the keyboard - without changing the focus -
|
||||
/// with the system back button. They can restore the keyboard's visibility
|
||||
/// by tapping on a text field. The user might hide the keyboard and
|
||||
/// switch to a physical keyboard, or they might just need to get it
|
||||
/// out of the way for a moment, to expose something it's
|
||||
/// obscuring. In this case requesting the focus again will not
|
||||
/// cause the focus to change, and will not make the keyboard visible.
|
||||
///
|
||||
/// This widget builds an [EditableText] and will ensure that the keyboard is
|
||||
/// showing when it is tapped by calling [EditableTextState.requestKeyboard()].
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.onSubmitted}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [EditableText.onSubmitted] for an example of how to handle moving to
|
||||
/// the next/previous field when using [TextInputAction.next] and
|
||||
/// [TextInputAction.previous] for [textInputAction].
|
||||
final void Function(String)? onSubmitted;
|
||||
|
||||
/// If false the widget is "disabled": it ignores taps, the [TextFormField]'s
|
||||
/// [decoration] is rendered in grey,
|
||||
/// [decoration]'s [InputDecoration.counterText] is set to `""`,
|
||||
/// and the drop down icon is hidden no matter [showDropdownIcon] value.
|
||||
///
|
||||
/// If non-null this property overrides the [decoration]'s
|
||||
/// [Decoration.enabled] property.
|
||||
final bool enabled;
|
||||
|
||||
/// The appearance of the keyboard.
|
||||
///
|
||||
/// This setting is only honored on iOS devices.
|
||||
///
|
||||
/// If unset, defaults to the brightness of [ThemeData.brightness].
|
||||
final Brightness? keyboardAppearance;
|
||||
|
||||
/// Initial Value for the field.
|
||||
/// This property can be used to pre-fill the field.
|
||||
final String? initialValue;
|
||||
|
||||
final String languageCode;
|
||||
|
||||
/// 2 letter ISO Code or country dial code.
|
||||
///
|
||||
/// ```dart
|
||||
/// initialCountryCode: 'IN', // India
|
||||
/// initialCountryCode: '+225', // Côte d'Ivoire
|
||||
/// ```
|
||||
final String? initialCountryCode;
|
||||
|
||||
/// List of Country to display see countries.dart for format
|
||||
final List<Country>? countries;
|
||||
|
||||
/// The decoration to show around the text field.
|
||||
///
|
||||
/// By default, draws a horizontal line under the text field but can be
|
||||
/// configured to show an icon, label, hint text, and error text.
|
||||
///
|
||||
/// Specify null to remove the decoration entirely (including the
|
||||
/// extra padding introduced by the decoration to save space for the labels).
|
||||
final InputDecoration decoration;
|
||||
|
||||
/// The style to use for the text being edited.
|
||||
///
|
||||
/// This text style is also used as the base style for the [decoration].
|
||||
///
|
||||
/// If null, defaults to the `subtitle1` text style from the current [Theme].
|
||||
final TextStyle? style;
|
||||
|
||||
/// Disable view Min/Max Length check
|
||||
final bool disableLengthCheck;
|
||||
|
||||
/// Won't work if [enabled] is set to `false`.
|
||||
final bool showDropdownIcon;
|
||||
|
||||
final BoxDecoration dropdownDecoration;
|
||||
|
||||
/// The style use for the country dial code.
|
||||
final TextStyle? dropdownTextStyle;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.inputFormatters}
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
|
||||
/// The text that describes the search input field.
|
||||
///
|
||||
/// When the input field is empty and unfocused, the label is displayed on top of the input field (i.e., at the same location on the screen where text may be entered in the input field).
|
||||
/// When the input field receives focus (or if the field is non-empty), the label moves above (i.e., vertically adjacent to) the input field.
|
||||
final String searchText;
|
||||
|
||||
/// Position of an icon [leading, trailing]
|
||||
final IconPosition dropdownIconPosition;
|
||||
|
||||
/// Icon of the drop down button.
|
||||
///
|
||||
/// Default is [Icon(Icons.arrow_drop_down)]
|
||||
final Icon dropdownIcon;
|
||||
|
||||
/// Whether this text field should focus itself if nothing else is already focused.
|
||||
final bool autofocus;
|
||||
|
||||
/// Autovalidate mode for text form field.
|
||||
///
|
||||
/// If [AutovalidateMode.onUserInteraction], this FormField will only auto-validate after its content changes.
|
||||
/// If [AutovalidateMode.always], it will auto-validate even without user interaction.
|
||||
/// If [AutovalidateMode.disabled], auto-validation will be disabled.
|
||||
///
|
||||
/// Defaults to [AutovalidateMode.onUserInteraction].
|
||||
final AutovalidateMode? autovalidateMode;
|
||||
|
||||
/// Whether to show or hide country flag.
|
||||
///
|
||||
/// Default value is `true`.
|
||||
final bool showCountryFlag;
|
||||
|
||||
/// Message to be displayed on autoValidate error
|
||||
///
|
||||
/// Default value is `Invalid Mobile Number`.
|
||||
final String? invalidNumberMessage;
|
||||
|
||||
/// The color of the cursor.
|
||||
final Color? cursorColor;
|
||||
|
||||
/// How tall the cursor will be.
|
||||
final double? cursorHeight;
|
||||
|
||||
/// How rounded the corners of the cursor should be.
|
||||
final Radius? cursorRadius;
|
||||
|
||||
/// How thick the cursor will be.
|
||||
final double cursorWidth;
|
||||
|
||||
/// Whether to show cursor.
|
||||
final bool? showCursor;
|
||||
|
||||
/// The padding of the Flags Button.
|
||||
///
|
||||
/// The amount of insets that are applied to the Flags Button.
|
||||
///
|
||||
/// If unset, defaults to [EdgeInsets.zero].
|
||||
final EdgeInsetsGeometry flagsButtonPadding;
|
||||
|
||||
/// The type of action button to use for the keyboard.
|
||||
final TextInputAction? textInputAction;
|
||||
|
||||
/// Optional set of styles to allow for customizing the country search
|
||||
/// & pick dialog
|
||||
final PickerDialogStyle? pickerDialogStyle;
|
||||
|
||||
/// The margin of the country selector button.
|
||||
///
|
||||
/// The amount of space to surround the country selector button.
|
||||
///
|
||||
/// If unset, defaults to [EdgeInsets.zero].
|
||||
final EdgeInsets flagsButtonMargin;
|
||||
|
||||
/// Enable the autofill hint for phone number.
|
||||
final bool disableAutoFillHints;
|
||||
|
||||
/// If null, default magnification configuration will be used.
|
||||
final TextMagnifierConfiguration? magnifierConfiguration;
|
||||
|
||||
const IntlPhoneField({
|
||||
Key? key,
|
||||
this.formFieldKey,
|
||||
this.initialCountryCode,
|
||||
this.languageCode = 'en',
|
||||
this.disableAutoFillHints = false,
|
||||
this.obscureText = false,
|
||||
this.textAlign = TextAlign.left,
|
||||
this.textAlignVertical,
|
||||
this.onTap,
|
||||
this.readOnly = false,
|
||||
this.initialValue,
|
||||
this.keyboardType = TextInputType.phone,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.decoration = const InputDecoration(),
|
||||
this.style,
|
||||
this.dropdownTextStyle,
|
||||
this.onSubmitted,
|
||||
this.validator,
|
||||
this.onChanged,
|
||||
this.countries,
|
||||
this.onCountryChanged,
|
||||
this.onSaved,
|
||||
this.showDropdownIcon = true,
|
||||
this.dropdownDecoration = const BoxDecoration(),
|
||||
this.inputFormatters,
|
||||
this.enabled = true,
|
||||
this.keyboardAppearance,
|
||||
@Deprecated('Use searchFieldInputDecoration of PickerDialogStyle instead')
|
||||
this.searchText = 'Search country',
|
||||
this.dropdownIconPosition = IconPosition.leading,
|
||||
this.dropdownIcon = const Icon(Icons.arrow_drop_down),
|
||||
this.autofocus = false,
|
||||
this.textInputAction,
|
||||
this.autovalidateMode = AutovalidateMode.onUserInteraction,
|
||||
this.showCountryFlag = true,
|
||||
this.cursorColor,
|
||||
this.disableLengthCheck = false,
|
||||
this.flagsButtonPadding = EdgeInsets.zero,
|
||||
this.invalidNumberMessage = 'Invalid Mobile Number',
|
||||
this.cursorHeight,
|
||||
this.cursorRadius = Radius.zero,
|
||||
this.cursorWidth = 2.0,
|
||||
this.showCursor = true,
|
||||
this.pickerDialogStyle,
|
||||
this.flagsButtonMargin = EdgeInsets.zero,
|
||||
this.magnifierConfiguration,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<IntlPhoneField> createState() => _IntlPhoneFieldState();
|
||||
}
|
||||
|
||||
class _IntlPhoneFieldState extends State<IntlPhoneField> {
|
||||
late List<Country> _countryList;
|
||||
late Country _selectedCountry;
|
||||
late List<Country> filteredCountries;
|
||||
late String number;
|
||||
|
||||
String? validatorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_countryList = widget.countries ?? countries;
|
||||
filteredCountries = _countryList;
|
||||
number = widget.initialValue ?? '';
|
||||
if (widget.initialCountryCode == null && number.startsWith('+')) {
|
||||
number = number.substring(1);
|
||||
// parse initial value
|
||||
_selectedCountry = countries.firstWhere(
|
||||
(country) => number.startsWith(country.fullCountryCode),
|
||||
orElse: () => _countryList.first);
|
||||
|
||||
// remove country code from the initial number value
|
||||
number = number.replaceFirst(
|
||||
RegExp("^${_selectedCountry.fullCountryCode}"), "");
|
||||
} else {
|
||||
_selectedCountry = _countryList.firstWhere(
|
||||
(item) => item.code == (widget.initialCountryCode ?? 'US'),
|
||||
orElse: () => _countryList.first);
|
||||
|
||||
// remove country code from the initial number value
|
||||
if (number.startsWith('+')) {
|
||||
number = number.replaceFirst(
|
||||
RegExp("^\\+${_selectedCountry.fullCountryCode}"), "");
|
||||
} else {
|
||||
number = number.replaceFirst(
|
||||
RegExp("^${_selectedCountry.fullCountryCode}"), "");
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.autovalidateMode == AutovalidateMode.always) {
|
||||
final initialPhoneNumber = PhoneNumber(
|
||||
countryISOCode: _selectedCountry.code,
|
||||
countryCode: '+${_selectedCountry.dialCode}',
|
||||
number: widget.initialValue ?? '',
|
||||
);
|
||||
|
||||
final value = widget.validator?.call(initialPhoneNumber);
|
||||
|
||||
if (value is String) {
|
||||
validatorMessage = value;
|
||||
} else {
|
||||
(value as Future).then((msg) {
|
||||
validatorMessage = msg;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _changeCountry() async {
|
||||
filteredCountries = _countryList;
|
||||
await showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (ctx, setState) => CountryPickerDialog(
|
||||
languageCode: widget.languageCode.toLowerCase(),
|
||||
style: widget.pickerDialogStyle,
|
||||
filteredCountries: filteredCountries,
|
||||
searchText: widget.searchText,
|
||||
countryList: _countryList,
|
||||
selectedCountry: _selectedCountry,
|
||||
onCountryChanged: (Country country) {
|
||||
_selectedCountry = country;
|
||||
widget.onCountryChanged?.call(country);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
key: widget.formFieldKey,
|
||||
initialValue: (widget.controller == null) ? number : null,
|
||||
autofillHints: widget.disableAutoFillHints
|
||||
? null
|
||||
: [AutofillHints.telephoneNumberNational],
|
||||
readOnly: widget.readOnly,
|
||||
obscureText: widget.obscureText,
|
||||
textAlign: widget.textAlign,
|
||||
textAlignVertical: widget.textAlignVertical,
|
||||
cursorColor: widget.cursorColor,
|
||||
onTap: widget.onTap,
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
cursorHeight: widget.cursorHeight,
|
||||
cursorRadius: widget.cursorRadius,
|
||||
cursorWidth: widget.cursorWidth,
|
||||
showCursor: widget.showCursor,
|
||||
onFieldSubmitted: widget.onSubmitted,
|
||||
magnifierConfiguration: widget.magnifierConfiguration,
|
||||
decoration: widget.decoration.copyWith(
|
||||
prefixIcon: _buildFlagsButton(),
|
||||
counterText: !widget.enabled ? '' : null,
|
||||
),
|
||||
style: widget.style,
|
||||
onSaved: (value) {
|
||||
widget.onSaved?.call(
|
||||
PhoneNumber(
|
||||
countryISOCode: _selectedCountry.code,
|
||||
countryCode:
|
||||
'+${_selectedCountry.dialCode}${_selectedCountry.regionCode}',
|
||||
number: value!,
|
||||
),
|
||||
);
|
||||
},
|
||||
onChanged: (value) async {
|
||||
final phoneNumber = PhoneNumber(
|
||||
countryISOCode: _selectedCountry.code,
|
||||
countryCode: '+${_selectedCountry.fullCountryCode}',
|
||||
number: value,
|
||||
);
|
||||
|
||||
if (widget.autovalidateMode != AutovalidateMode.disabled) {
|
||||
validatorMessage = await widget.validator?.call(phoneNumber);
|
||||
}
|
||||
|
||||
widget.onChanged?.call(phoneNumber);
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || !isNumeric(value)) return validatorMessage;
|
||||
if (!widget.disableLengthCheck) {
|
||||
return value.length >= _selectedCountry.minLength &&
|
||||
value.length <= _selectedCountry.maxLength
|
||||
? null
|
||||
: widget.invalidNumberMessage;
|
||||
}
|
||||
|
||||
return validatorMessage;
|
||||
},
|
||||
maxLength: widget.disableLengthCheck ? null : _selectedCountry.maxLength,
|
||||
keyboardType: widget.keyboardType,
|
||||
inputFormatters: widget.inputFormatters,
|
||||
enabled: widget.enabled,
|
||||
keyboardAppearance: widget.keyboardAppearance,
|
||||
autofocus: widget.autofocus,
|
||||
textInputAction: widget.textInputAction,
|
||||
autovalidateMode: widget.autovalidateMode,
|
||||
);
|
||||
}
|
||||
|
||||
Container _buildFlagsButton() {
|
||||
return Container(
|
||||
margin: widget.flagsButtonMargin,
|
||||
child: DecoratedBox(
|
||||
decoration: widget.dropdownDecoration,
|
||||
child: InkWell(
|
||||
borderRadius: widget.dropdownDecoration.borderRadius as BorderRadius?,
|
||||
onTap: widget.enabled ? _changeCountry : null,
|
||||
child: Padding(
|
||||
padding: widget.flagsButtonPadding,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
if (widget.enabled &&
|
||||
widget.showDropdownIcon &&
|
||||
widget.dropdownIconPosition == IconPosition.leading) ...[
|
||||
widget.dropdownIcon,
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
if (widget.showCountryFlag) ...[
|
||||
kIsWeb
|
||||
? Image.asset(
|
||||
'assets/flags/${_selectedCountry.code.toLowerCase()}.png',
|
||||
package: 'intl_phone_field',
|
||||
width: 32,
|
||||
)
|
||||
: Text(
|
||||
_selectedCountry.flag,
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
FittedBox(
|
||||
child: Text(
|
||||
'+${_selectedCountry.dialCode}',
|
||||
style: widget.dropdownTextStyle,
|
||||
),
|
||||
),
|
||||
if (widget.enabled &&
|
||||
widget.showDropdownIcon &&
|
||||
widget.dropdownIconPosition == IconPosition.trailing) ...[
|
||||
const SizedBox(width: 4),
|
||||
widget.dropdownIcon,
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum IconPosition {
|
||||
leading,
|
||||
trailing,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'countries.dart';
|
||||
|
||||
class NumberTooLongException implements Exception {}
|
||||
|
||||
class NumberTooShortException implements Exception {}
|
||||
|
||||
class InvalidCharactersException implements Exception {}
|
||||
|
||||
class PhoneNumber {
|
||||
String countryISOCode;
|
||||
String countryCode;
|
||||
String number;
|
||||
|
||||
PhoneNumber({
|
||||
required this.countryISOCode,
|
||||
required this.countryCode,
|
||||
required this.number,
|
||||
});
|
||||
|
||||
factory PhoneNumber.fromCompleteNumber({required String completeNumber}) {
|
||||
if (completeNumber == "") {
|
||||
return PhoneNumber(countryISOCode: "", countryCode: "", number: "");
|
||||
}
|
||||
|
||||
try {
|
||||
Country country = getCountry(completeNumber);
|
||||
String number;
|
||||
if (completeNumber.startsWith('+')) {
|
||||
number = completeNumber.substring(1 + country.dialCode.length + country.regionCode.length);
|
||||
} else {
|
||||
number = completeNumber.substring(country.dialCode.length + country.regionCode.length);
|
||||
}
|
||||
return PhoneNumber(
|
||||
countryISOCode: country.code, countryCode: country.dialCode + country.regionCode, number: number);
|
||||
} on InvalidCharactersException {
|
||||
rethrow;
|
||||
// ignore: unused_catch_clause
|
||||
} on Exception catch (e) {
|
||||
return PhoneNumber(countryISOCode: "", countryCode: "", number: "");
|
||||
}
|
||||
}
|
||||
|
||||
bool isValidNumber() {
|
||||
Country country = getCountry(completeNumber);
|
||||
if (number.length < country.minLength) {
|
||||
throw NumberTooShortException();
|
||||
}
|
||||
|
||||
if (number.length > country.maxLength) {
|
||||
throw NumberTooLongException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String get completeNumber {
|
||||
return countryCode + number;
|
||||
}
|
||||
|
||||
static Country getCountry(String phoneNumber) {
|
||||
if (phoneNumber == "") {
|
||||
throw NumberTooShortException();
|
||||
}
|
||||
|
||||
final validPhoneNumber = RegExp(r'^[+0-9]*[0-9]*$');
|
||||
|
||||
if (!validPhoneNumber.hasMatch(phoneNumber)) {
|
||||
throw InvalidCharactersException();
|
||||
}
|
||||
|
||||
if (phoneNumber.startsWith('+')) {
|
||||
return countries
|
||||
.firstWhere((country) => phoneNumber.substring(1).startsWith(country.dialCode + country.regionCode));
|
||||
}
|
||||
return countries.firstWhere((country) => phoneNumber.startsWith(country.dialCode + country.regionCode));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'PhoneNumber(countryISOCode: $countryISOCode, countryCode: $countryCode, number: $number)';
|
||||
}
|
||||
24414
siro_rider/lib/controller/local/translations.dart
Normal file
24414
siro_rider/lib/controller/local/translations.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/style.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import '../functions/crud.dart';
|
||||
|
||||
class NotificationCaptainController extends GetxController {
|
||||
bool isLoading = false;
|
||||
Map notificationData = {};
|
||||
|
||||
getNotifications() async {
|
||||
isLoading = true;
|
||||
update();
|
||||
var res = await CRUD().get(
|
||||
link: AppLink.getNotificationCaptain,
|
||||
payload: {'driverID': box.read(BoxName.driverID)});
|
||||
if (res == "failure") {
|
||||
Get.defaultDialog(
|
||||
title: 'There is no notification yet'.tr,
|
||||
titleStyle: AppStyle.title,
|
||||
middleText: '',
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Back',
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Get.back();
|
||||
}));
|
||||
}
|
||||
notificationData = jsonDecode(res);
|
||||
// sql.insertData(notificationData['message'], TableName.captainNotification);
|
||||
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
updateNotification(String id) async {
|
||||
await CRUD().post(
|
||||
link: AppLink.updateNotificationCaptain,
|
||||
payload: {'isShown': true, 'id': id},
|
||||
);
|
||||
}
|
||||
|
||||
addNotificationCaptain(String driverId, title, body, isPin) async {
|
||||
await CRUD().post(link: AppLink.addNotificationCaptain, payload: {
|
||||
'driverID': driverId,
|
||||
'title': title,
|
||||
'body': body,
|
||||
'isPin': isPin
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
getNotifications();
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/controller/firebase/firbase_messge.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import '../../views/widgets/mydialoug.dart';
|
||||
import '../firebase/notification_service.dart';
|
||||
import '../functions/crud.dart';
|
||||
|
||||
class PassengerNotificationController extends GetxController {
|
||||
bool isloading = false;
|
||||
Map notificationData = {};
|
||||
|
||||
getNotifications() async {
|
||||
isloading = true;
|
||||
update();
|
||||
var res = await CRUD().get(
|
||||
link: AppLink.getNotificationPassenger,
|
||||
payload: {'passenger_id': box.read(BoxName.passengerID)});
|
||||
if (res == "failure" || res == "error") {
|
||||
MyDialog().getDialog('There is no notification yet'.tr, '', () {
|
||||
Get.back();
|
||||
Get.back();
|
||||
});
|
||||
} else {
|
||||
final decoded = jsonDecode(res);
|
||||
// التحقق من وجود البيانات في 'data' أو 'message'
|
||||
var rawData = decoded['data'] ?? decoded['message'];
|
||||
|
||||
if (decoded['status'] == 'error' || decoded['status'] == 'failure' || rawData == "No notification data found") {
|
||||
notificationData = {'status': 'success', 'message': []}; // قائمة فارغة لمنع الخطأ في UI
|
||||
} else {
|
||||
// التأكد أننا نخزن قائمة
|
||||
notificationData = {
|
||||
'status': 'success',
|
||||
'message': rawData is List ? rawData : [rawData]
|
||||
};
|
||||
}
|
||||
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
// sql.insertData(notificationData['message'], TableName.captainNotification);
|
||||
}
|
||||
|
||||
updateNotification(String id) async {
|
||||
await CRUD().post(
|
||||
link: AppLink.updateNotificationPassenger,
|
||||
payload: {'isShown': 'true', 'id': id},
|
||||
);
|
||||
Get.back();
|
||||
getNotifications();
|
||||
}
|
||||
|
||||
addNotificationToPassenger(String title, body) async {
|
||||
var res = CRUD().post(link: AppLink.addNotificationPassenger, payload: {
|
||||
'title': title,
|
||||
'body': body,
|
||||
});
|
||||
// Get.find<FirebaseMessagesController>().sendNotificationToPassengerToken(
|
||||
// title,
|
||||
// body,
|
||||
// 'token',
|
||||
// [],
|
||||
// 'iphone_ringtone',
|
||||
// );
|
||||
await NotificationService.sendNotification(
|
||||
category: title,
|
||||
target: 'token'.toString(),
|
||||
title: title,
|
||||
body: body.tr,
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'cancel',
|
||||
driverList: [],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
getNotifications();
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/style.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/links.dart';
|
||||
import '../functions/crud.dart';
|
||||
|
||||
class RideAvailableController extends GetxController {
|
||||
bool isLoading = false;
|
||||
Map rideAvailableMap = {};
|
||||
getRideAvailable() async {
|
||||
isLoading = true;
|
||||
var res = await CRUD().get(link: AppLink.getRideWaiting, payload: {});
|
||||
if (res != 'failure') {
|
||||
rideAvailableMap = jsonDecode(res);
|
||||
isLoading = false;
|
||||
update();
|
||||
} else {
|
||||
Get.defaultDialog(
|
||||
title: 'No Rides now!'.tr,
|
||||
middleText: '',
|
||||
titleStyle: AppStyle.title,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Ok'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Get.back();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
getRideAvailable();
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user