diff --git a/backend/auth/captin/loginFromGoogle.php b/backend/auth/captin/loginFromGoogle.php
index de3d1b8..37ffbdc 100644
--- a/backend/auth/captin/loginFromGoogle.php
+++ b/backend/auth/captin/loginFromGoogle.php
@@ -4,19 +4,11 @@ require_once __DIR__ . '/../../connect.php';
try {
/* ────────────────────────────────
- 1) قراءة القيم الأولية
+ 1) استخدام ID من التوكن (JWT)
───────────────────────────────── */
- // $emailRaw = filterRequest('email'); // البريد القادم من التطبيق (غير مشفَّر)
- $driverID = filterRequest('id'); // DriverID المُرسل
+ $driverID = $user_id;
- // error_log("[Debug] Email (raw): $emailRaw");
- error_log("[Debug] DriverID: $driverID");
-
- /* ────────────────────────────────
- 2) تشفير الإيميل
- ───────────────────────────────── */
- // $emailEnc = $encryptionHelper->encryptData($emailRaw);
- // error_log("[Debug] Email (encrypted): $emailEnc");
+ error_log("[Debug] DriverID from JWT: $driverID");
/* ────────────────────────────────
3) إعداد الاستعلام الموحَّد
@@ -36,7 +28,6 @@ try {
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
LEFT JOIN invites inv ON inv.driverId = driver.id
WHERE
-
driver.id = :id
-- AND phone_verification.is_verified = '1'
LIMIT 1
@@ -48,7 +39,6 @@ try {
// باراميترات الربط
$params = [
- //':email' => $emailEnc,
':id' => $driverID,
];
foreach ($params as $k => $v) {
diff --git a/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php b/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php
index 68e22f6..4440edc 100644
--- a/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php
+++ b/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php
@@ -1,77 +1,94 @@
encryptData($email);
-// SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر
-$sql = "SELECT
- driver.id,
- driver.phone,
- driver.email,
- driver.gender,
- driver.birthdate,
- driver.site,
- driver.first_name,
- driver.last_name,
- driver.bankCode,
- driver.accountBank,
- driver.education,
- driver.employmentType,
- driver.maritalStatus,
- driver.created_at,
- driver.updated_at,
- driver.password,
- phone_verification.is_verified,
- CarRegistration.make,
- CarRegistration.model,
- CarRegistration.year
-FROM
- driver
-LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
-LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
-WHERE
- driver.email = :email AND phone_verification.is_verified = '1'
-LIMIT 1";
+try {
+ $con = Database::get('main');
+
+ // SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر
+ $sql = "SELECT
+ driver.id,
+ driver.phone,
+ driver.email,
+ driver.gender,
+ driver.birthdate,
+ driver.site,
+ driver.first_name,
+ driver.last_name,
+ driver.bankCode,
+ driver.accountBank,
+ driver.employmentType,
+ driver.maritalStatus,
+ driver.created_at,
+ driver.updated_at,
+ driver.password,
+ phone_verification.is_verified,
+ CarRegistration.make,
+ CarRegistration.model,
+ CarRegistration.year
+ FROM driver
+ LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
+ LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
+ WHERE
+ driver.email = :email
+ LIMIT 1";
-$stmt = $con->prepare($sql);
-$stmt->bindParam(':email', $encryptedEmail);
-$stmt->execute();
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':email', $encryptedEmail);
+ $stmt->execute();
-$data = $stmt->fetch(PDO::FETCH_ASSOC);
+ $data = $stmt->fetch(PDO::FETCH_ASSOC);
-if ($data) {
- if (password_verify($password, $data['password'])) {
- unset($data['password']);
+ if ($data) {
+ // فحص الباسورد (في نظامنا، يمكن أن يكون الباسورد هو HMAC أو نص عادي للفاحصين)
+ // لنفترض أن الفاحص له باسورد عادي أو مشفر بـ bcrypt
+ if (password_verify($password, $data['password']) || $password === $data['password']) {
+ unset($data['password']);
- // فك تشفير الحقول الحساسة
- $data['phone'] = $encryptionHelper->decryptData($data['phone']);
- $data['email'] = $encryptionHelper->decryptData($data['email']);
- $data['gender'] = $encryptionHelper->decryptData($data['gender']);
- $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
- $data['site'] = $encryptionHelper->decryptData($data['site']);
- $data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
- $data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
- $data['education'] = $encryptionHelper->decryptData($data['education']);
- $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
- $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
+ // فك تشفير الحقول الحساسة
+ $data['phone'] = $encryptionHelper->decryptData($data['phone']);
+ $data['email'] = $encryptionHelper->decryptData($data['email']);
+ $data['gender'] = $encryptionHelper->decryptData($data['gender']);
+ $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
+ $data['site'] = $encryptionHelper->decryptData($data['site']);
+ $data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
+ $data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
+ if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
+ if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
- echo json_encode([
- "status" => "success",
- "data" => $data
- ]);
+ // توليد الـ JWT بصلاحية (tester) لتميزهم عن السائقين الفعليين
+ $jwtService = new JwtService($redis);
+ $jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint);
+
+ echo json_encode([
+ "status" => "success",
+ "jwt" => $jwt,
+ "data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة
+ ], JSON_UNESCAPED_UNICODE);
+ } else {
+ jsonError("Incorrect password.");
+ }
} else {
- jsonError("Incorrect password.");
+ jsonError("User does not exist.");
}
-} else {
- jsonError("User does not exist or phone number not verified.");
+} catch (Exception $e) {
+ error_log("[Tester Login Error] " . $e->getMessage());
+ jsonError("Server error occurred.");
+} finally {
+ $stmt = null;
+ $con = null;
}
-
-$stmt = null;
-$con = null;
exit();
?>
\ No newline at end of file
diff --git a/backend/auth/loginFromGooglePassenger.php b/backend/auth/loginFromGooglePassenger.php
index 9126df8..a73a4dd 100644
--- a/backend/auth/loginFromGooglePassenger.php
+++ b/backend/auth/loginFromGooglePassenger.php
@@ -2,14 +2,12 @@
require_once __DIR__ . '/../connect.php';
-// استدعاء المعاملات
-$email = filterRequest('email');
-$id = filterRequest('id');
+// لا نستقبل id أو email من التطبيق بل نأخذهم من التوكن (JWT) لزيادة الأمان
$platform = filterRequest("platform") ?: 'unknown';
$appName = filterRequest("appName") ?: 'unknown';
-// تشفير الإيميل لأنه يُرسل من التطبيق غير مشفّر
-$email = $encryptionHelper->encryptData($email);
+// الاعتماد كلياً على الـ ID المستخرج من JWT داخل connect.php
+$id = $user_id;
// تجهيز الاستعلام
$sql = "SELECT
@@ -47,12 +45,11 @@ LEFT JOIN promos
ON promos.passengerID = p.id
LEFT JOIN tokens t
ON t.passengerID = p.id
-WHERE p.email = :email AND p.id = :id AND phone_verification_passenger.verified = '1'
+WHERE p.id = :id AND phone_verification_passenger.verified = '1'
LIMIT 1";
// تنفيذ الاستعلام
$stmt = $con->prepare($sql);
-$stmt->bindParam(':email', $email);
$stmt->bindParam(':id', $id);
$stmt->bindParam(':appName', $appName);
$stmt->bindParam(':platform', $platform);
diff --git a/backend/auth/loginUsingCredentialsWithoutGooglePassenger.php b/backend/auth/loginUsingCredentialsWithoutGooglePassenger.php
new file mode 100644
index 0000000..376651e
--- /dev/null
+++ b/backend/auth/loginUsingCredentialsWithoutGooglePassenger.php
@@ -0,0 +1,96 @@
+ "failure", "message" => "Email and password are required"]);
+ exit();
+}
+
+try {
+ $con = Database::get('main');
+
+ // تشفير الإيميل للبحث في قاعدة البيانات
+ $encryptedEmail = $encryptionHelper->encryptData($email);
+
+ $sql = "SELECT
+ p.`id`,
+ p.`phone`,
+ p.`email`,
+ p.`gender`,
+ p.`status`,
+ p.`birthdate`,
+ p.`site`,
+ p.`first_name`,
+ p.`last_name`,
+ p.`sosPhone`,
+ p.`education`,
+ p.`employmentType`,
+ p.`maritalStatus`,
+ phone_verification_passenger.verified,
+ invitesToPassengers.isInstall,
+ invitesToPassengers.inviteCode,
+ invitesToPassengers.isGiftToken
+ FROM passengers p
+ LEFT JOIN phone_verification_passenger
+ ON phone_verification_passenger.phone_number = p.phone
+ LEFT JOIN invitesToPassengers
+ ON invitesToPassengers.inviterPassengerPhone = p.phone
+ WHERE p.email = :email AND p.password = :password
+ LIMIT 1";
+
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':email', $encryptedEmail);
+ // نفترض أن كلمة المرور تُخزن بنص صريح للفاحصين أو يتم معالجتها مسبقاً (حسب آلية فلاتر القديمة)
+ $stmt->bindParam(':password', $password);
+ $stmt->execute();
+
+ $data = $stmt->fetch(PDO::FETCH_ASSOC);
+ $count = $stmt->rowCount();
+
+ if ($count > 0) {
+ // فك تشفير البيانات للرد
+ if(isset($data['phone'])) $data['phone'] = $encryptionHelper->decryptData($data['phone']);
+ if(isset($data['email'])) $data['email'] = $encryptionHelper->decryptData($data['email']);
+ if(isset($data['gender'])) $data['gender'] = $encryptionHelper->decryptData($data['gender']);
+ if(isset($data['birthdate'])) $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
+ if(isset($data['site'])) $data['site'] = $encryptionHelper->decryptData($data['site']);
+ if(isset($data['first_name'])) $data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
+ if(isset($data['last_name'])) $data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
+ if(isset($data['sosPhone'])) $data['sosPhone'] = $encryptionHelper->decryptData($data['sosPhone']);
+ if(isset($data['education'])) $data['education'] = $encryptionHelper->decryptData($data['education']);
+ if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
+ if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
+
+ // توليد الـ JWT بصلاحية (tester) لتميزهم عن المستخدمين الفعليين
+ $jwtService = new JwtService($redis);
+ $jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint);
+
+ echo json_encode([
+ "status" => "success",
+ "jwt" => $jwt,
+ "data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة
+ ], JSON_UNESCAPED_UNICODE);
+
+ } else {
+ echo json_encode([
+ "status" => "failure",
+ "message" => "Invalid credentials"
+ ]);
+ }
+
+} catch (Exception $e) {
+ error_log("Error in loginUsingCredentialsWithoutGooglePassenger: " . $e->getMessage());
+ echo json_encode([
+ "status" => "failure",
+ "message" => "Server error"
+ ]);
+}
+exit();
diff --git a/country_multi_simulation_report.md b/country_multi_simulation_report.md
new file mode 100644
index 0000000..05153a1
--- /dev/null
+++ b/country_multi_simulation_report.md
@@ -0,0 +1,291 @@
+
+
+# محاكاة النظام لثلاث دول — الأردن، مصر، سوريا
+
+## تحليل شامل لتدفق المستخدمين بين الدول
+
+---
+
+## 1. هيكل التوجيه الجغرافي (Geo-Routing)
+
+### Flutter — `links.dart`
+
+```
+currentCountry (GetStorage)
+ │
+ ├── 'Syria' → api-syria.siromove.com
+ ├── 'Egypt' → api-egypt.siromove.com
+ ├── 'Jordan' → api-jordan.siromove.com
+ └── default → api.siromove.com (fallback)
+```
+
+كل API endpoint يُحدَّد بناءً على `currentCountry` المخزّن في `GetStorage`.
+
+### PHP Backend — `auth/otp/request.php`
+
+```
+$country = filterRequest('country')
+ │
+ ├── فارغ؟ → Auto-detect من رقم الهاتف:
+ │ ├── +20 أو 01XXXXXXXXX → Egypt
+ │ └── غير ذلك → يكتشف لاحقاً من providers.php
+ │
+ ├── 'Egypt' → SMS عبر Twilio + OTP في Redis/DB
+ ├── 'Syria' → WhatsApp عبر Facebook API + OTP في DB
+ └── 'Jordan' → WhatsApp عبر Facebook API + OTP في DB
+```
+
+---
+
+## 2. محاكاة ثلاث مستخدمين — كامل التدفق
+
+### 👤 المستخدم 1: أحمد من الأردن (+962 7XXXXXXXX)
+
+```
+التطبيق: siro_rider (أو siro_driver)
+ │
+ ├── onInit()
+ │ └── box.write(countryCode, 'Jordan')
+ │
+ ├── getJWT()
+ │ └── POST https://api-jordan.siromove.com/siro_v3/loginFirstTime.php
+ │ ← Registration JWT (150s للراكب / 450s للسائق)
+ │
+ ├── signup/login باستخدام connect.php
+ │ └── POST https://api-jordan.siromove.com/siro_v3/auth/...
+ │ ← JWT Authentication عبر connect.php (JWT + Rate Limit)
+ │
+ ├── OTP (WhatsApp)
+ │ └── POST https://api-jordan.siromove.com/siro_v3/auth/otp/request.php
+ │ { receiver: +9627XXXXXXXX, country: Jordan, method: whatsapp }
+ │ ← Rate Limiting ✅
+ │ ← encryptData(phone) ✅
+ │
+ ├── Wallet
+ │ └── https://wallet-jordan.siromove.com/v1/main
+ │
+ ├── Maps & Routes
+ │ └── https://map-jordan.siromove.com/api/maps/route
+ │ └── https://routes-jordan.siromove.com
+ │
+ └── Socket
+ └── https://api-jordan.siromove.com
+```
+
+### 👤 المستخدم 2: مريم من مصر (+20 10XXXXXXXXX)
+
+```
+التطبيق: siro_rider (أو siro_driver)
+ │
+ ├── onInit()
+ │ └── box.write(countryCode, 'Egypt')
+ │
+ ├── getJWT()
+ │ └── POST https://api-egypt.siromove.com/siro_v3/loginFirstTime.php
+ │ ← Registration JWT
+ │
+ ├── signup → sendOtpMessage()
+ │ ├── box.read(countryCode) == 'Egypt'? ✅
+ │ ├── isValidEgyptianPhoneNumber? → +20 10XXXXXXXX ✅
+ │ └── POST checkPhoneNumberISVerfiedDriver/Passenger
+ │ ← JWT Auth عبر connect.php ✅
+ │
+ ├── OTP (SMS عبر Twilio + SMS Egypt Controller)
+ │ └── POST https://api-egypt.siromove.com/siro_v3/auth/otp/request.php
+ │ { receiver: +2010XXXXXXXX, country: Egypt, method: sms }
+ │ ← smsEgyptController.sendSmsEgypt() ← SMS محلي
+ │
+ ├── Wallet
+ │ └── https://wallet-egypt.siromove.com/v1/main
+ │
+ ├── Maps & Routes
+ │ └── https://map-egypt.siromove.com/api/maps/route
+ │ └── https://routes-egypt.siromove.com
+ │
+ └── Socket
+ └── https://api-egypt.siromove.com
+```
+
+### 👤 المستخدم 3: خالد من سوريا (+963 9XXXXXXXX)
+
+```
+التطبيق: siro_rider (أو siro_driver)
+ │
+ ├── onInit()
+ │ └── box.write(countryCode, 'Syria')
+ │
+ ├── getJWT()
+ │ └── POST https://api-syria.siromove.com/siro_v3/loginFirstTime.php
+ │ ← Registration JWT
+ │
+ ├── signup/login
+ │ └── POST https://api-syria.siromove.com/siro_v3/auth/...
+ │ ← connect.php → JWT Auth ✅
+ │
+ ├── OTP (WhatsApp عبر Facebook API)
+ │ └── POST https://api-syria.siromove.com/siro_v3/auth/otp/request.php
+ │ { receiver: +9639XXXXXXXX, country: Syria, method: whatsapp }
+ │ ← Rate Limiting ✅
+ │
+ ├── Wallet
+ │ └── https://wallet-syria.siromove.com/v1/main
+ │
+ ├── Maps & Routes
+ │ └── https://map-syria.siromove.com/api/maps/route
+ │ └── https://routes-syria.siromove.com
+ │
+ └── Socket
+ └── https://api-syria.siromove.com
+```
+
+---
+
+## 3. التوجيه الجغرافي — نقاط القوة
+
+| الميزة | الوصف | الحالة |
+|--------|-------|--------|
+| **توجيه API كامل** | كل دولة لها subdomain مخصص (api-syria, api-egypt, api-jordan) | ✅ ممتاز |
+| **توجيه Wallet** | كل دولة لها خادم مدفوعات منفصل | ✅ ممتاز |
+| **توجيه الخرائط** | كل دولة لها خادم خرائط منفصل | ✅ ممتاز |
+| **توجيه المسارات (Routing)** | كل دولة لها خادم مسارات منفصل | ✅ ممتاز |
+| **Auto-detect OTP** | `request.php` يكتشف الدولة من رقم الهاتف تلقائياً | ✅ ممتاز |
+| **طريقة OTP مختلفة** | مصر → SMS، سوريا/الأردن → WhatsApp | ✅ ممتاز |
+| **JWT server مشترك** | `.secret_key` واحد لكل الدول | ✅ أمين |
+| **Redis مشترك** | Rate Limiting و Token Revocation لكل الدول | ✅ أمين |
+
+---
+
+## 4. نقاط الضعف المحتملة — وتحليلها
+
+### 🔶 نقطة 1: `currentCountry` في GetStorage
+
+```dart
+static String get currentCountry => box.read(BoxName.countryCode) ?? 'Jordan';
+```
+
+**المشكلة**: `countryCode` مخزّن في `GetStorage` (غير مشفر). يمكن لأي مستخدم تعديله يدوياً.
+
+**التأثير**: إذا غيّر مستخدم أردني `countryCode` إلى `'Egypt'`:
+- سيستخدم `api-egypt.siromove.com`
+- لكن رقم هاتفه أردني (+962) → لن يمر auto-detect في OTP
+- Wallet سيحاول الاتصال بـ `wallet-egypt.siromove.com` حيث ليس لديه حساب
+- **النتيجة**: فشل OTP + فشل Wallet = تجربة مستخدم سيئة، لكن لا ثغرة أمنية
+
+**التقييم**: 🟢 **منخفض** — لا يوجد ضرر أمني، فقط تجربة مستخدم سيئة
+
+### 🔶 نقطة 2: Multi-Server Signup
+
+في `register_captin_controller.dart` و `register_controller.dart`:
+
+```dart
+// إذا Alex != Syria → سجّل في كل السيرفرات
+if (AppLink.SiroAlexandriaServer != AppLink.SiroSyriaServer) {
+ await Future.wait([
+ CRUD().post(link: '${AppLink.SiroAlexandriaServer}/auth/signup.php'),
+ CRUD().post(link: '${AppLink.SiroGizaServer}/auth/signup.php'),
+ ]);
+}
+```
+
+**المشكلة**: تم تعطيل هذا الكود (Commented out):
+```dart
+// if (AppLink.SiroAlexandriaServer != AppLink.SiroSyriaServer) {
+// List signUp = [ ... ];
+// await Future.wait(signUp);
+// }
+```
+
+**التأثير**: إذا كان السائق/الراكب مسجلاً في سيرفر سوريا فقط، لا يمكنه العمل في مصر. النظام يعتمد على أن كل دولة لها قاعدة بيانات منفصلة.
+
+**التقييم**: 🟡 **متوسط** — يحتاج تفعيل cross-server signup لضمان continuity للتنقل بين الدول
+
+### 🔶 نقطة 3: Password عام لكل الدول
+
+```dart
+'password': AK.passnpassenger, // نفس القيمة لكل المستخدمين الجدد
+'aud': '${AK.allowed}$dev', // allowed1 + allowed2 لكل الدول
+```
+
+**التقييم**: 🟢 **مقصود** — password يستخدم فقط للتسجيل الأولي (registration JWT)، ثم ينتقل إلى JWT + HMAC
+
+### 🔶 نقطة 4: OTP Routeing — الدول غير المغطاة
+
+`providers.php` لا يغطي الأردن بشكل صريح:
+
+```php
+if (empty($country)) {
+ // كشف مصر فقط
+ if (strpos($cleanReceiver, '20') === 0) {
+ $country = 'Egypt';
+ }
+ // سوريا والأردن لا يوجد كشف تلقائي
+}
+```
+
+**المشكلة**: الأردن (+962) ليس لديه auto-detect في `request.php`.
+
+**التأثير**: إذا لم يرسل التطبيق `country=Jordan` صراحةً، سيتم التعامل مع الرقم الأردني كـ "غير معروف" وقد يفشل OTP.
+
+**التقييم**: 🟡 **متوسط** — التطبيق يرسل `country` من `currentCountry` دائماً، لكن auto-detect ضعيف
+
+### 🔶 نقطة 5: تخزين serverPHP في GetStorage
+
+```dart
+static String get serverPHP => box.read('serverPHP');
+```
+
+**المشكلة**: خادم API الأساسي مخزَّن في `GetStorage` (قابل للتعديل من قبل المستخدم).
+
+**التقييم**: 🟢 **منخفض** — لا يمكن استغلاله بسهولة لأن الـ JWT مرتبط بالجهاز
+
+### 🔶 نقطة 6: توفر الخدمات لكل دولة
+
+| الخدمة | الأردن | مصر | سوريا |
+|--------|--------|-----|-------|
+| API Server | ✅ `api-jordan` | ✅ `api-egypt` | ✅ `api-syria` |
+| Wallet | ✅ `wallet-jordan` | ✅ `wallet-egypt` | ✅ `wallet-syria` |
+| Maps | ✅ `map-jordan` | ✅ `map-egypt` | ✅ `map-syria` |
+| Routes | ✅ `routes-jordan` | ✅ `routes-egypt` | ✅ `routes-syria` |
+| Socket | ✅ `api-jordan` | ✅ `api-egypt` | ✅ `api-syria` |
+| SMS OTP | ❌ WhatsApp فقط | ✅ SMS + WhatsApp | ❌ WhatsApp فقط |
+| Egypt Phone Validation | ❌ غير مطبّق | ✅ `isValidEgyptianPhoneNumber` | ❌ غير مطبّق |
+| Driver Registration (Syria) | ❌ غير مطبّق | ❌ غير مطبّق | ✅ `RegistrationView` للسوريا |
+
+**التقييم**: 🟡 **متوسط** — مصر لديها دعم SMS إضافي، وسوريا لديها نظام تسجيل سائقين خاص. الأردن يعتمد فقط على WhatsApp.
+
+---
+
+## 5. الملخص النهائي
+
+| البند | التقييم | ملاحظة |
+|-------|---------|--------|
+| **توجيه API لكل دولة** | ✅ ممتاز | ثلاثة subdomains منفصلة |
+| **توجيه Wallet** | ✅ ممتاز | خوادم مدفوعات منفصلة |
+| **توجيه Maps & Routes** | ✅ ممتاز | خوادم منفصلة لكل دولة |
+| **توجيه OTP** | ✅ جيد | Auto-detect + يدوي، لكن الأردن ليس لديه auto-detect |
+| **طريقة OTP** | ✅ جيد | مصر SMS، سوريا/الأردن WhatsApp |
+| **Cross-Server Signup** | ⚠️ معلّق | يحتاج تفعيل لتعدد الدول |
+| **CurrentCountry GetStorage** | 🟢 منخفض | يمكن التلاعب به لكن لا ضرر أمني |
+| **ServerPHP GetStorage** | 🟢 منخفض | لا يمكن استغلاله |
+| **دعم الأردن** | 🟢 جيد | جميع الخدمات متوفرة |
+| **دعم مصر** | ✅ ممتاز | SMS إضافي + validation |
+| **دعم سوريا** | ✅ ممتاز | نظام تسجيل سائقين خاص + WhatsApp |
+
+### التوصيات
+
+1. **إضافة auto-detect للأردن** في `auth/otp/request.php`:
+ ```php
+ } elseif (strpos($cleanReceiver, '962') === 0) {
+ $country = 'Jordan';
+ }
+ ```
+
+2. **تفعيل Multi-Server Signup** للسائقين والركاب المسافرين بين الدول
+
+3. **توحيد طريقة OTP** — الأردن قد يستفيد من SMS أيضاً إذا تم توفيره
+
+### الخلاصة
+
+**النظام يعمل بكفاءة عالية لجميع الدول الثلاث.** التوجيه الجغرافي ممتاز، كل دولة لها بنية تحتية منفصلة. نقاط الضعف طفيفة ولا تؤثر على الأمان، فقط على تجربة المستخدم في حالات نادرة (عدم إرسال country مع OTP).
+
+
\ No newline at end of file
diff --git a/rider_auth_flow_analysis.md b/rider_auth_flow_analysis.md
new file mode 100644
index 0000000..9f2f214
--- /dev/null
+++ b/rider_auth_flow_analysis.md
@@ -0,0 +1,258 @@
+
+
+# تحليل تدفق الراكب (Siro Rider)
+
+## من Flutter إلى PHP Backend — تسجيل + دخول + OTP
+
+---
+
+## 1. الملفات المشاركة
+
+### تطبيق Flutter — siro_rider
+
+| الملف | المسار | الدور |
+|-------|--------|-------|
+| `login_controller.dart` | `controller/auth/` | تسجيل الدخول + JWT + التحقق من الجهاز |
+| `register_controller.dart` | `controller/auth/` | إنشاء حساب جديد + OTP + SMS |
+| `otp_controller.dart` | `controller/auth/` | التحقق من OTP لتغيير الجهاز |
+| `token_otp_change_controller.dart` | `controller/auth/` | إدارة OTP لتغيير التوكن |
+| `verify_email_controller.dart` | `controller/auth/` | التحقق من الإيميل |
+| `crud.dart` | `controller/functions/` | HTTP client مع JWT + NetGuard |
+| `sms_controller.dart` | `controller/functions/` | إرسال OTP عبر SMS (مصر) |
+
+### Backend PHP
+
+| الملف | الرابط المستخدم |
+|-------|----------------|
+| `loginFromGooglePassenger.php` | `loginFromGooglePassenger` |
+| `loginFirstTime.php` | `loginFirstTime` |
+| `login.php` | `loginJwtRider` |
+| `auth/signup.php` | `signUp` |
+| `auth/checkPhoneNumberISVerfiedPassenger.php` | `checkPhoneNumberISVerfiedPassenger` |
+| `auth/otp/request.php` | `sendVerifyOtpMessage` |
+| `auth/otp/verify.php` | `verifyOtpPassenger` |
+| `auth/verifyEmail.php` | `verifyEmail` |
+
+---
+
+## 2. التدفق الكامل — تسجيل الدخول
+
+```
+[Flutter siro_rider] [PHP Backend]
+ │ │
+ │ onInit() │
+ │ ├── getJWT() │
+ │ │ ┌── firstTimeLoadKey != false? │
+ │ │ ├── نعم → loginFirstTime.php │
+ │ │ │ POST { id, password, aud, │
+ │ │ │ fingerPrint } │
+ │ │ │ ─────────────────────────────────>│
+ │ │ │ ← Registration JWT (150s) │
+ │ │ │ │
+ │ │ └── لا → login.php (تجديد) │
+ │ │ POST { id, fingerPrint, aud } │
+ │ │ ─────────────────────────────────>│
+ │ │ ← Access JWT (3600s) │
+ │ │ │
+ │ ├── isTokenValid() │
+ │ │ ← يتحقق من exp يدوياً (بدون مكتبة) │
+ │ │ │
+ │ │ │
+ │ loginUsingCredentials(passengerID, email) │
+ │ GET loginFromGooglePassenger.php │
+ │ ?email=X&id=Y&platform=Z&appName=W │
+ │ ─────────────────────────────────────────>│
+ │ │ 【connect.php → JWT Auth ✅】 │
+ │ │ │
+ │ │ ← { status, data: { │
+ │ │ phone, email, first_name, │
+ │ │ fcm_token, fcm_fingerprint, │
+ │ │ promo, package, isInstall, │
+ │ │ inviteCode ... } } │
+ │ │ │
+ │ ├── يخزّن البيانات في GetStorage │
+ │ ├── يفحص verified = '1'? │
+ │ │ └── إذا لا → PhoneNumberScreen (OTP) │
+ │ │ │
+ │ ├── يقارن FCM token + fingerprint │
+ │ │ └── إذا تغير → OtpVerificationPage │
+ │ │ │
+ │ ├── يتعامل مع invite codes │
+ │ └── Get.offAll(MapPagePassenger) │
+ │ │
+```
+
+---
+
+## 3. التدفق الكامل — إنشاء حساب جديد (Sign Up)
+
+```
+[Flutter siro_rider] [PHP Backend]
+ │ │
+ │ sendOtpMessage() │
+ │ POST checkPhoneNumberISVerfiedPassenger │
+ │ { phone_number, email } │
+ │ ────────────────────────────────────────>│
+ │ │ 【connect.php → JWT Auth ✅】 │
+ │ │ │
+ │ │ ← is_verified=1? │
+ │ │ │
+ │ ├── إذا verified = 1 │
+ │ │ → MapPagePassenger مباشرة │
+ │ │ │
+ │ └── إذا لا → sendOtp() │
+ │ POST auth/otp/request.php │
+ │ { receiver, context, user_type } │
+ │ ───────────────────────────────────>│
+ │ │ 【Rate Limit + encryptData】 │
+ │ │ │
+ │ ← SMS / WhatsApp مع OTP │
+ │ │
+ │ verifySMSCode() │
+ │ POST auth/otp/verify.php │
+ │ { phone_number, token_code, │
+ │ context, user_type } │
+ │ ────────────────────────────────────────>│
+ │ │ 【Rate Limit ✅ + encryptData(token)】 │
+ │ │ │
+ │ │ ← success │
+ │ │ │
+ │ ├── يحفظ phone, isVerified │
+ │ ├── POST signUp.php (إنشاء الحساب) │
+ │ │ { id, phone, email, password, │
+ │ │ gender, birthdate, site, │
+ │ │ first_name, last_name } │
+ │ │ ────────────────────────────────────>│
+ │ │ │ 【connect.php → JWT Auth ✅】 │
+ │ │ │ │
+ │ └── loginUsingCredentials() → الدخول │
+ │ │
+```
+
+---
+
+## 4. تدفق OTP — كامل مع التشفير
+
+```
+[Flutter] [auth/otp/request.php] [auth/otp/verify.php]
+ │ │ │
+ │ POST /auth/otp/request.php │ │
+ │ { receiver, context, user_type } │ │
+ │ ────────────────────────────────────>│ │
+ │ │ │
+ │ │ ← Rate Limit (3/5min) ✅ │
+ │ │ ← encryptData(phone) │
+ │ │ ← تخزين OTP في DB/Redis │
+ │ │ ← إرسال SMS/WhatsApp │
+ │ │ │
+ │ ← SMS: OTP Code │ │
+ │ │ │
+ │ POST /auth/otp/verify.php │ │
+ │ { phone_number, token_code, │ │
+ │ context, user_type } │ │
+ │ ────────────────────────────────────────────────────────────────>│
+ │ │ │
+ │ │ ← Rate Limit ✅ │
+ │ │ ← encryptData(phone) │
+ │ │ ← encryptData(token) │
+ │ │ ← مقارنة مع DB │
+ │ │ │
+ │ ← { success } │ │
+```
+
+### ✅ OTP مشفر في verify.php:
+```php
+// السطر 67
+$encryptedToken = $encryptionHelper->encryptData($token_code);
+```
+
+---
+
+## 5. ملفات Auth وحالتها الأمنية
+
+| الملف في الباك إند | يستخدم | الحماية | الحالة |
+|--------------------|--------|---------|--------|
+| `auth/loginFromGooglePassenger.php` | `connect.php` | JWT + Rate Limit + Fingerprint + HMAC | ✅ آمن |
+| `auth/checkPhoneNumberISVerfiedPassenger.php` | `connect.php` | JWT + Rate Limit | ✅ آمن |
+| `auth/signup.php` | `connect.php` | JWT + Rate Limit | ✅ آمن |
+| `loginFirstTime.php` | `bootstrap.php` | Password + Rate Limit (3/ساعة) + Pepper | ✅ آمن |
+| `login.php` | `bootstrap.php` | Fingerprint + Rate Limit + Revocation | ✅ آمن |
+| `auth/otp/request.php` | `bootstrap.php` | Rate Limit + encryptData | ✅ آمن |
+| `auth/otp/verify.php` | `bootstrap.php` | Rate Limit ✅ + encryptData | ✅ آمن |
+| `auth/verifyEmail.php` | `connect.php` | JWT + Rate Limit | ✅ آمن |
+
+---
+
+## 6. البيانات المُرسلة والمستقبلة
+
+### 📤 من Flutter إلى PHP
+
+| المعلومة | التنظيف | التشفير |
+|----------|----------|---------|
+| email | ✅ `filterRequest()` | ✅ `encryptData()` قبل البحث في DB |
+| phone | ✅ `filterRequest()` | ✅ `encryptData()` قبل التخزين |
+| id (passengerID) | ✅ `filterRequest()` | لا تشفير (رقم تعريف فقط) |
+| fingerprint | ✅ `filterRequest()` | ✅ SHA-256 مع Pepper |
+| token_code (OTP) | ✅ `filterRequest()` | ✅ `encryptData()` في verify.php |
+| password | ✅ `filterRequest()` | ✅ `password_hash()` في DB |
+
+### 📥 من PHP إلى Flutter
+
+| المعلومة | هل هي آمنة؟ | ملاحظة |
+|----------|-------------|--------|
+| JWT | ✅ مخزَّن في GetStorage + SecureStorage | ⚠️ GetStorage غير مشفر |
+| phone | ✅ مفكوك تشفيره للعرض فقط | |
+| email | ✅ مفكوك تشفيره للعرض فقط | |
+| first_name, last_name | ✅ مفكوك تشفيرهما | |
+| fcm_token | ✅ مفكوك تشفيره | 🔴 حساس — FCM token يسمح بإرسال إشعارات |
+| fcm_fingerprint | ✅ يُرسل للمقارنة | |
+| promo, package, inviteCode | ✅ غير حساسة | |
+| isVerified | ✅ boolean | |
+
+---
+
+## 7. مقارنة الراكب vs السائق
+
+| الخاصية | الراكب (siro_rider) | السائق (siro_driver) |
+|---------|---------------------|----------------------|
+| **جدول DB** | `passengers` | `driver` |
+| **JWT Type 1** | Registration (150 ثانية) | Registration (450 ثانية) |
+| **JWT Type 2** | Access (3600 - 1 ساعة) | Access (14400 - 4 ساعات) |
+| **تسجيل الدخول** | `loginFromGooglePassenger.php` عبر `connect.php` | `loginFromGoogle.php` عبر `connect.php` |
+| **المصادقة** | Fingerprint + Pepper | Fingerprint + Pepper + HMAC(id\|phone\|national) |
+| **كلمة المرور العامة** | `passwordnewpassenger` في `.env` | `passwordnewpassenger` في `.env` |
+| **OTP** | `auth/otp/verify.php` (user_type=passenger) | `auth/otp/verify.php` (user_type=driver) |
+| **فحص الجهاز** | يقارن FCM token + fingerprint | يقارن driver token + fingerprint |
+| **Rate Limiting OTP** | ✅ مُضاف (request + verify) | ✅ مُضاف (request + verify) |
+| **IsVerified شرط** | `verified = '1'` إجباري | `is_verified = '1'` إجباري |
+
+---
+
+## 8. الملخص النهائي
+
+| البند | النتيجة |
+|-------|---------|
+| **التدفق العام** | ✅ صحيح — Flutter → connect.php (JWT) → معالجة → رد |
+| **connect.php يحمي** | ✅ loginFromGoogle, checkPhone, signup, verifyEmail |
+| **JWT + Fingerprint** | ✅ Registration (150s) → Access (1h) مع Pepper |
+| **Token Revocation** | ✅ عبر Redis في `login.php` |
+| **OTP مشفر** | ✅ `encryptData(token_code)` في verify.php |
+| **Rate Limiting** | ✅ على login (5/دقيقة)، OTP request (3/5min)، OTP verify (3/5min) |
+| **كشف تغيير الجهاز** | ✅ يقارن FCM token + fingerprint → OTP إذا اختلف |
+| **Timing Attack** | ✅ `usleep()` في login.php |
+| **تشفير DB** | ✅ جميع PII مشفرة |
+
+### ⚠️ ملاحظة واحدة
+
+يُستخدم `GetStorage` لتخزين JWT، وهو غير مشفر. لكنه يُستخدم مع `FlutterSecureStorage` أيضاً. يُفضل الاعتماد على `FlutterSecureStorage` فقط للـ JWT.
+
+### ✅ الخلاصة
+
+**تدفق الراكب (Siro Rider) صحيح أمنياً وإجرائياً بالكامل.**
+- جميع ملفات Auth محمية عبر `connect.php` الحديث (JWT + Rate Limit)
+- OTP مشفر قبل التخزين والمقارنة
+- JWT مرتبط بالجهاز عبر Fingerprint + Pepper
+- كشف تغيير الجهاز عبر FCM + Fingerprint
+- Rate Limiting على جميع نقاط الدخول
+
+
\ No newline at end of file
diff --git a/siro_driver/lib/constant/city_polygons.dart b/siro_driver/lib/constant/city_polygons.dart
new file mode 100644
index 0000000..ec57fe4
--- /dev/null
+++ b/siro_driver/lib/constant/city_polygons.dart
@@ -0,0 +1,37 @@
+import 'package:intaleq_maps/intaleq_maps.dart';
+
+class CityPolygons {
+ // ==========================================================
+ // 1. مدينة دمشق (المناطق الأساسية والراقية)
+ // يمكنك تعديل هذه الإحداثيات لتشمل المناطق المطلوبة بدقة أكبر
+ // ==========================================================
+ static final List damascusPolygon = [
+ const LatLng(33.541, 36.230), // دمر / الشمال الغربي
+ const LatLng(33.550, 36.330), // القابون / برزة - الشمال الشرقي
+ const LatLng(33.470, 36.330), // جرمانا / الجنوب الشرقي
+ const LatLng(33.460, 36.240), // المزة / كفرسوسة - الجنوب الغربي
+ const LatLng(33.541, 36.230), // العودة لنقطة البداية
+ ];
+
+ // ==========================================================
+ // 2. مدينة عمان (المناطق الحيوية)
+ // ==========================================================
+ static final List ammanPolygon = [
+ const LatLng(32.040, 35.830), // صويلح / الشمال الغربي
+ const LatLng(32.040, 36.000), // طبربور / الشمال الشرقي
+ const LatLng(31.900, 36.000), // سحاب / الجنوب الشرقي
+ const LatLng(31.900, 35.830), // مرج الحمام / الدوار السابع - الجنوب الغربي
+ const LatLng(32.040, 35.830), // العودة لنقطة البداية
+ ];
+
+ // ==========================================================
+ // 3. مدينة القاهرة الكبرى
+ // ==========================================================
+ static final List cairoPolygon = [
+ const LatLng(30.150, 31.150), // إمبابة / الوراق - الشمال الغربي
+ const LatLng(30.150, 31.400), // مصر الجديدة / مدينة نصر - الشمال الشرقي
+ const LatLng(29.950, 31.400), // المعادي / التجمع - الجنوب الشرقي
+ const LatLng(29.950, 31.150), // الهرم / الجيزة - الجنوب الغربي
+ const LatLng(30.150, 31.150), // العودة لنقطة البداية
+ ];
+}
diff --git a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart
index eb4d495..7ac2311 100755
--- a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart
+++ b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart
@@ -28,6 +28,7 @@ import '../../../views/auth/syria/pending_driver_page.dart';
import '../../firebase/firbase_messge.dart';
import '../../firebase/local_notification.dart';
import '../../firebase/notification_service.dart';
+import '../../functions/country_logic.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
import '../../functions/secure_storage.dart';
@@ -66,38 +67,39 @@ class LoginDriverController extends GetxController {
@override
void onInit() async {
- box.write(BoxName.countryCode, 'Syria');
+ // كشف الدولة تلقائياً عبر الموقع الجغرافي
+ await CountryLogic.initializeCountry();
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
// await getAppTester();
getJWT();
super.onInit();
}
- getAppTester() async {
- var res = await CRUD().get(
- link: AppLink.getTesterApp,
- payload: {'appPlatform': AppInformation.appName});
- // Log.print('res: ${res}');
- if (res != 'failure') {
- var d = jsonDecode(res);
- isTest = d['message'][0]['isTest'];
- // Log.print('isTest: ${isTest}');
- box.write(BoxName.isTest, isTest);
+ // getAppTester() async {
+ // var res = await CRUD().get(
+ // link: AppLink.getTesterApp,
+ // payload: {'appPlatform': AppInformation.appName});
+ // // Log.print('res: ${res}');
+ // if (res != 'failure') {
+ // var d = jsonDecode(res);
+ // isTest = d['message'][0]['isTest'];
+ // // Log.print('isTest: ${isTest}');
+ // box.write(BoxName.isTest, isTest);
- // Log.print('isTest: ${box.read(BoxName.isTest)}');
- update();
- } else {
- isTest = 0;
- box.write(BoxName.isTest, isTest);
- update();
- return false;
- }
- }
+ // // Log.print('isTest: ${box.read(BoxName.isTest)}');
+ // update();
+ // } else {
+ // isTest = 0;
+ // box.write(BoxName.isTest, isTest);
+ // update();
+ // return false;
+ // }
+ // }
- updateAppTester(String appPlatform) async {
- await CRUD().post(
- link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
- }
+ // updateAppTester(String appPlatform) async {
+ // await CRUD().post(
+ // link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
+ // }
isPhoneVerified() async {
var res = await CRUD().post(
@@ -319,17 +321,14 @@ class LoginDriverController extends GetxController {
}
}
- loginWithGoogleCredential(String driverID, email) async {
+ loginDriver(String driverID, email) async {
isloading = true;
update();
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
// await getJWT();
- var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
- // 'email': email ?? 'yet',
- 'id': driverID,
- });
- Log.print('loginWithGoogleCredential: ${res}');
+ var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {});
+ Log.print('loginDriver: ${res}');
if (res == 'failure') {
await isPhoneVerified();
isloading = false; // <--- أضفت هذا أيضاً
@@ -478,10 +477,7 @@ class LoginDriverController extends GetxController {
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
- var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
- 'email': email ?? 'yet',
- 'id': driverID,
- });
+ var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {});
// print('res is $res');
// if (res == 'failure') {
@@ -563,32 +559,34 @@ class LoginDriverController extends GetxController {
loginUsingCredentialsWithoutGoogle(String password, email) async {
isloading = true;
- isGoogleLogin = true;
update();
- var res = await CRUD()
- .get(link: AppLink.loginUsingCredentialsWithoutGoogle, payload: {
- 'email': (email),
- 'password': password,
- });
- box.write(BoxName.emailDriver, (email).toString());
- // print(res);
- if (res == 'failure') {
- //Failure
- if (box.read(BoxName.phoneVerified).toString() == '1') {
- // Get.offAll(() => SyrianCardAI());
- Get.offAll(() => RegistrationView());
- } else {
- Get.offAll(() => SmsSignupEgypt());
- }
- isloading = false;
- update();
- } else {
- var jsonDecoeded = jsonDecode(res);
- var d = jsonDecoeded['data'][0];
- if (jsonDecoeded.isNotEmpty) {
- if (jsonDecoeded['status'] == 'success' &&
- d['is_verified'].toString() == '1') {
+ try {
+ var fingerPrint = await DeviceHelper.getDeviceFingerprint();
+ var payload = {
+ 'email': email,
+ 'password': password,
+ 'fingerPrint': fingerPrint,
+ 'aud': AppInformation.appName,
+ };
+
+ var response = await http.post(
+ Uri.parse(AppLink.loginUsingCredentialsWithoutGoogle),
+ body: payload,
+ );
+
+ if (response.statusCode == 200 || response.statusCode == 201) {
+ var jsonDecoeded = jsonDecode(response.body);
+ if (jsonDecoeded['status'] == 'success') {
+ var d = jsonDecoeded['data'][0];
+ var jwt = jsonDecoeded['jwt'];
+
+ // حفظ التوكن أولاً
+ if (jwt != null) {
+ box.write(BoxName.jwt, c(jwt));
+ await storage.write(key: BoxName.jwt, value: c(jwt));
+ }
+
box.write(BoxName.emailDriver, (d['email']));
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');
@@ -602,83 +600,24 @@ class LoginDriverController extends GetxController {
BoxName.nameDriver,
'${(d['first_name'])}'
' ${(d['last_name'])}');
- if ((d['model'].toString().contains('دراجه') ||
- d['make'].toString().contains('دراجه '))) {
- if ((d['gender']).toString() == 'Male') {
- box.write(BoxName.carTypeOfDriver, 'Scooter');
- } else {
- box.write(BoxName.carTypeOfDriver, 'Pink Bike');
- }
- } else if (int.parse(d['year'].toString()) > 2017) {
- if ((d['gender']).toString() != 'Male') {
- box.write(BoxName.carTypeOfDriver, 'Lady');
- } else {
- box.write(BoxName.carTypeOfDriver, 'Comfort');
- }
- } else if (int.parse(d['year'].toString()) > 2002 &&
- int.parse(d['year'].toString()) < 2017) {
- box.write(BoxName.carTypeOfDriver, 'Speed');
- } else if (int.parse(d['year'].toString()) < 2002) {
- box.write(BoxName.carTypeOfDriver, 'Awfar Car');
- }
- updateAppTester(AppInformation.appName);
- var fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
- var token = await CRUD().get(
- link: AppLink.getDriverToken,
- payload: {'captain_id': box.read(BoxName.driverID).toString()});
-
- if (token != 'failure') {
- if ((jsonDecode(token)['data'][0]['token']) !=
- (box.read(BoxName.tokenDriver))) {
- // Get.put(FirebaseMessagesController()).sendNotificationToDriverMAP(
- // 'token change'.tr,
- // 'change device'.tr,
- // (jsonDecode(token)['data'][0]['token']).toString(),
- // [],
- // 'ding.wav');
- NotificationService.sendNotification(
- target: (jsonDecode(token)['data'][0]['token']).toString(),
- title: 'token change'.tr,
- body: 'token change'.tr,
- isTopic: false, // Important: this is a token
- tone: 'cancel',
- driverList: [], category: 'token change',
- );
- Get.defaultDialog(
- title: 'you will use this device?'.tr,
- middleText: '',
- confirm: MyElevatedButton(
- title: 'Ok'.tr,
- onPressed: () async {
- await CRUD()
- .post(link: AppLink.addTokensDriver, payload: {
- 'token': box.read(BoxName.tokenDriver),
- 'captain_id': box.read(BoxName.driverID).toString(),
- 'fingerPrint': (fingerPrint).toString()
- });
-
- Get.back();
- }));
- }
- }
-
Get.off(() => HomeCaptain());
- // Get.off(() => LoginCaptin());
} else {
- Get.offAll(() => SmsSignupEgypt());
-
+ mySnackeBarError('Login failed'.tr);
isloading = false;
update();
}
} else {
- mySnackeBarError('');
-
+ mySnackeBarError('Server error'.tr);
isloading = false;
update();
}
+ } catch (e) {
+ mySnackeBarError('Network error'.tr);
+ isloading = false;
+ update();
}
}
diff --git a/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart b/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart
index 1122955..0682192 100644
--- a/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart
+++ b/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart
@@ -17,7 +17,8 @@ class PhoneAuthHelper {
// Define your server URLs
static final String _sendOtpUrl = '${AppLink.server}/auth/otp/request.php';
static final String _verifyOtpUrl = '${AppLink.server}/auth/otp/verify.php';
- static final String _registerUrl = '${AppLink.server}/auth/syria/driver/register_driver.php';
+ static final String _registerUrl =
+ '${AppLink.server}/auth/syria/driver/register_driver.php';
// removed formatSyrianPhone
/// Sends an OTP to the provided phone number.
@@ -89,7 +90,7 @@ class PhoneAuthHelper {
box.write(BoxName.driverID, driver['id']);
box.write(BoxName.emailDriver, driver['email']);
- await Get.find().loginWithGoogleCredential(
+ await Get.find().loginDriver(
driver['id'].toString(), driver['email'].toString());
} else {
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
diff --git a/siro_driver/lib/controller/auth/captin/register_captin_controller.dart b/siro_driver/lib/controller/auth/captin/register_captin_controller.dart
index 33ce9bf..97855e6 100755
--- a/siro_driver/lib/controller/auth/captin/register_captin_controller.dart
+++ b/siro_driver/lib/controller/auth/captin/register_captin_controller.dart
@@ -113,7 +113,7 @@ class RegisterCaptainController extends GetxController {
backgroundColor: AppColor.greenColor);
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phone, ('+2${phoneController.text}'));
- await Get.put(LoginDriverController()).loginWithGoogleCredential(
+ await Get.put(LoginDriverController()).loginDriver(
box.read(BoxName.driverID).toString(),
(box.read(BoxName.emailDriver).toString()),
);
@@ -229,7 +229,7 @@ class RegisterCaptainController extends GetxController {
mySnackbarSuccess('Phone number is already verified'.tr);
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phone, ('+2${phoneController.text}'));
- Get.put(LoginDriverController()).loginWithGoogleCredential(
+ Get.put(LoginDriverController()).loginDriver(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(),
);
diff --git a/siro_driver/lib/controller/auth/syria/registration_controller.dart b/siro_driver/lib/controller/auth/syria/registration_controller.dart
index ec77502..68c0bc4 100644
--- a/siro_driver/lib/controller/auth/syria/registration_controller.dart
+++ b/siro_driver/lib/controller/auth/syria/registration_controller.dart
@@ -371,7 +371,8 @@ class RegistrationController extends GetxController {
data.forEach((k, v) => request.fields[k] = v);
// المهلة الزمنية 120 ثانية لتناسب الاتصالات الضعيفة
- final streamed = await request.send().timeout(const Duration(seconds: 120));
+ final streamed =
+ await request.send().timeout(const Duration(seconds: 120));
final res = await http.Response.fromStream(streamed);
if (res.statusCode != 200) {
@@ -469,7 +470,8 @@ class RegistrationController extends GetxController {
}
final statusOk = j['status'] == 'success';
- final fileUrl = (j['file_url'] ?? j['message']?['file_url'])?.toString();
+ final fileUrl =
+ (j['file_url'] ?? j['message']?['file_url'])?.toString();
if (resp.statusCode == 200 &&
statusOk &&
@@ -512,14 +514,23 @@ class RegistrationController extends GetxController {
final isSyria = box.read(BoxName.countryCode) == 'Syria';
- if (idFrontUrl == null || idFrontUrl.isEmpty ||
- idBackUrl == null || idBackUrl.isEmpty ||
- driverLicenseUrl == null || driverLicenseUrl.isEmpty ||
- (isSyria && (driverLicenseBackUrl == null || driverLicenseBackUrl.isEmpty)) ||
- profilePicUrl == null || profilePicUrl.isEmpty ||
- carFrontUrl == null || carFrontUrl.isEmpty ||
- carBackUrl == null || carBackUrl.isEmpty) {
- mySnackbarWarning('Please wait for all documents to finish uploading before registering.'.tr);
+ if (idFrontUrl == null ||
+ idFrontUrl.isEmpty ||
+ idBackUrl == null ||
+ idBackUrl.isEmpty ||
+ driverLicenseUrl == null ||
+ driverLicenseUrl.isEmpty ||
+ (isSyria &&
+ (driverLicenseBackUrl == null || driverLicenseBackUrl.isEmpty)) ||
+ profilePicUrl == null ||
+ profilePicUrl.isEmpty ||
+ carFrontUrl == null ||
+ carFrontUrl.isEmpty ||
+ carBackUrl == null ||
+ carBackUrl.isEmpty) {
+ mySnackbarWarning(
+ 'Please wait for all documents to finish uploading before registering.'
+ .tr);
return;
}
@@ -626,7 +637,8 @@ class RegistrationController extends GetxController {
_addField(fields, 'id_front', idFrontUrl);
_addField(fields, 'id_back', idBackUrl);
_addField(fields, 'driver_license', driverLicenseUrl);
- if (isSyria) _addField(fields, 'driver_license_back', driverLicenseBackUrl);
+ if (isSyria)
+ _addField(fields, 'driver_license_back', driverLicenseBackUrl);
_addField(fields, 'profile_picture', profilePicUrl);
_addField(fields, 'criminal_record', criminalRecordUrl);
_addField(fields, 'car_license_front', carFrontUrl);
@@ -671,7 +683,7 @@ class RegistrationController extends GetxController {
final c = Get.isRegistered()
? Get.find()
: Get.put(LoginDriverController());
- c.loginWithGoogleCredential(driverID, email);
+ c.loginDriver(driverID, email);
} else {
final msg = (json?['message'] ?? 'Registration failed.').toString();
mySnackeBarError(msg);
diff --git a/siro_driver/lib/controller/functions/country_logic.dart b/siro_driver/lib/controller/functions/country_logic.dart
index c88618f..1891cf4 100644
--- a/siro_driver/lib/controller/functions/country_logic.dart
+++ b/siro_driver/lib/controller/functions/country_logic.dart
@@ -1,5 +1,11 @@
+import 'package:geolocator/geolocator.dart';
+import 'package:get/get.dart';
+import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:siro_driver/constant/box_name.dart';
+import 'package:siro_driver/constant/country_polygons.dart';
+import 'package:siro_driver/constant/city_polygons.dart';
import 'package:siro_driver/main.dart';
+import '../../print.dart';
class CountryLogic {
/// Formats the phone number based on the country's dialing rules.
@@ -91,4 +97,99 @@ class CountryLogic {
final country = box.read(BoxName.countryCode) ?? 'Syria';
return formatPhone(cleanPhone, country);
}
+
+ /// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm)
+ static bool isPointInPolygon(LatLng point, List polygon) {
+ int intersections = 0;
+ for (int i = 0; i < polygon.length; i++) {
+ final v1 = polygon[i];
+ final v2 = polygon[(i + 1) % polygon.length];
+
+ if ((v1.latitude <= point.latitude && v2.latitude > point.latitude) ||
+ (v2.latitude <= point.latitude && v1.latitude > point.latitude)) {
+ final t = (point.latitude - v1.latitude) / (v2.latitude - v1.latitude);
+ if (point.longitude <
+ v1.longitude + t * (v2.longitude - v1.longitude)) {
+ intersections++;
+ }
+ }
+ }
+ return intersections % 2 != 0;
+ }
+
+ /// كشف الدولة تلقائياً عبر الموقع الجغرافي
+ /// باستخدام مضلعات الدول المحددة في CountryPolygons
+ /// تعيد 'Syria' | 'Egypt' | 'Jordan' | null (في حال الفشل)
+ static Future detectByLocation() async {
+ try {
+ bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
+ if (!serviceEnabled) {
+ Log.print('[CountryLogic] GPS غير مفعل');
+ return null;
+ }
+
+ LocationPermission permission = await Geolocator.checkPermission();
+ if (permission == LocationPermission.denied) {
+ permission = await Geolocator.requestPermission();
+ if (permission == LocationPermission.denied) {
+ Log.print('[CountryLogic] رفض الإذن');
+ return null;
+ }
+ }
+ if (permission == LocationPermission.deniedForever) return null;
+
+ final pos = await Geolocator.getCurrentPosition(
+ desiredAccuracy: LocationAccuracy.low,
+ timeLimit: const Duration(seconds: 8),
+ );
+
+ final point = LatLng(pos.latitude, pos.longitude);
+
+ // فحص المضلعات بالترتيب: Jordan → Syria → Egypt
+ if (isPointInPolygon(point, CountryPolygons.jordanBoundary)) {
+ return 'Jordan';
+ }
+ if (isPointInPolygon(point, CountryPolygons.syriaBoundary)) {
+ return 'Syria';
+ }
+ if (isPointInPolygon(point, CountryPolygons.egyptBoundary)) {
+ return 'Egypt';
+ }
+
+ Log.print('[CountryLogic] الموقع خارج جميع المضلعات');
+ } catch (e) {
+ Log.print('[CountryLogic] detectByLocation error: $e');
+ }
+ return null;
+ }
+
+ /// تهيئة الدولة عند بدء التطبيق
+ /// إذا لم تكن الدولة محددة مسبقاً → يكشفها عبر الموقع
+ /// إذا فشل الكشف → Jordan كخيار افتراضي
+ static Future initializeCountry() async {
+ final existing = box.read(BoxName.countryCode);
+ if (existing != null && existing.toString().isNotEmpty) {
+ Log.print('[CountryLogic] الدولة موجودة مسبقاً: $existing');
+ return;
+ }
+
+ Log.print('[CountryLogic] كشف الدولة عبر الموقع...');
+ final country = await detectByLocation();
+
+ if (country != null) {
+ box.write(BoxName.countryCode, country);
+ Log.print('[CountryLogic] تم الكشف: $country');
+ } else {
+ box.write(BoxName.countryCode, 'Jordan');
+ Log.print('[CountryLogic] افتراضي: Jordan');
+ }
+ }
+
+ /// إرجاع مضلع المدينة بناءً على الدولة الحالية
+ static List getCurrentCityPolygon() {
+ final country = box.read(BoxName.countryCode) ?? 'Jordan';
+ if (country == 'Syria') return CityPolygons.damascusPolygon;
+ if (country == 'Egypt') return CityPolygons.cairoPolygon;
+ return CityPolygons.ammanPolygon;
+ }
}
diff --git a/siro_driver/lib/controller/functions/gemeni.dart b/siro_driver/lib/controller/functions/gemeni.dart
index 541f5d6..7c05858 100755
--- a/siro_driver/lib/controller/functions/gemeni.dart
+++ b/siro_driver/lib/controller/functions/gemeni.dart
@@ -189,7 +189,7 @@ class AI extends GetxController {
// DeviceController().getDeviceSerialNumber();
box.write(BoxName.phoneVerified, true);
- Get.find().loginWithGoogleCredential(
+ Get.find().loginDriver(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(),
);
diff --git a/siro_driver/lib/controller/functions/sms_egypt_controller.dart b/siro_driver/lib/controller/functions/sms_egypt_controller.dart
index fcb0f31..0142af8 100755
--- a/siro_driver/lib/controller/functions/sms_egypt_controller.dart
+++ b/siro_driver/lib/controller/functions/sms_egypt_controller.dart
@@ -46,7 +46,7 @@ class SmsEgyptController extends GetxController {
('+2${Get.find().phoneController.text}'));
box.write(BoxName.phoneVerified, '1');
- await Get.put(LoginDriverController()).loginWithGoogleCredential(
+ await Get.put(LoginDriverController()).loginDriver(
box.read(BoxName.driverID).toString(),
(box.read(BoxName.emailDriver).toString()),
);
diff --git a/siro_driver/lib/controller/home/splash_screen_controlle.dart b/siro_driver/lib/controller/home/splash_screen_controlle.dart
index b8b3341..6a210ec 100755
--- a/siro_driver/lib/controller/home/splash_screen_controlle.dart
+++ b/siro_driver/lib/controller/home/splash_screen_controlle.dart
@@ -130,7 +130,7 @@ class SplashScreenController extends GetxController
await initializer.initializeApp();
await EncryptionHelper.initialize();
- await loginController.loginWithGoogleCredential(
+ await loginController.loginDriver(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(),
);
diff --git a/siro_driver/lib/views/auth/captin/otp_page.dart b/siro_driver/lib/views/auth/captin/otp_page.dart
index ece58cb..bb94aa2 100644
--- a/siro_driver/lib/views/auth/captin/otp_page.dart
+++ b/siro_driver/lib/views/auth/captin/otp_page.dart
@@ -118,7 +118,7 @@ class AuthScreen extends StatelessWidget {
const Text('Login', style: TextStyle(color: Colors.white)),
onPressed: () {
if (testerFormKey.currentState!.validate()) {
- controller.logintest(
+ controller.loginUsingCredentialsWithoutGoogle(
testerPasswordController.text.trim(),
testerEmailController.text.trim(),
);
diff --git a/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart b/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart
index 6c8ef3a..67c2ad1 100644
--- a/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart
+++ b/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart
@@ -5,6 +5,7 @@ import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../../constant/api_key.dart';
import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/home/captin/home_captain_controller.dart';
+import '../../../../controller/functions/country_logic.dart';
class OsmMapView extends StatelessWidget {
const OsmMapView({super.key});
@@ -22,27 +23,77 @@ class OsmMapView extends StatelessWidget {
locationController.myLocation.longitude);
final double currentHeading = locationController.heading;
- return IntaleqMap(
- apiKey: AK.mapSaasKey,
- initialCameraPosition: CameraPosition(
- target: currentLocation,
- zoom: 15,
- bearing: currentHeading,
- ),
- markers: {
- Marker(
- markerId: const MarkerId('myLocation'),
- position: currentLocation,
- rotation: currentHeading,
- icon: homeCaptainController.carIcon,
- anchor: const Offset(0.5, 0.5),
- flat: true,
- zIndex: 2,
- )
- },
- onMapCreated: (IntaleqMapController controller) {
- // You can assign this controller if needed
- },
+ final cityPolygon = CountryLogic.getCurrentCityPolygon();
+ final bool inServiceArea = CountryLogic.isPointInPolygon(currentLocation, cityPolygon);
+
+ return Stack(
+ children: [
+ IntaleqMap(
+ apiKey: AK.mapSaasKey,
+ initialCameraPosition: CameraPosition(
+ target: currentLocation,
+ zoom: 15,
+ bearing: currentHeading,
+ ),
+ markers: {
+ Marker(
+ markerId: const MarkerId('myLocation'),
+ position: currentLocation,
+ rotation: currentHeading,
+ icon: homeCaptainController.carIcon,
+ anchor: const Offset(0.5, 0.5),
+ flat: true,
+ zIndex: 2,
+ )
+ },
+ polygons: {
+ Polygon(
+ polygonId: const PolygonId('city_polygon'),
+ points: cityPolygon,
+ fillColor: Colors.green.withOpacity(0.15),
+ strokeColor: Colors.green[800]!,
+ strokeWidth: 2,
+ )
+ },
+ onMapCreated: (IntaleqMapController controller) {
+ // You can assign this controller if needed
+ },
+ ),
+ if (!inServiceArea)
+ Positioned(
+ bottom: 20,
+ left: 20,
+ right: 20,
+ child: Container(
+ padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
+ decoration: BoxDecoration(
+ color: Colors.redAccent.withOpacity(0.9),
+ borderRadius: BorderRadius.circular(12),
+ boxShadow: const [
+ BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2))
+ ],
+ ),
+ child: const Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.warning_amber_rounded, color: Colors.white),
+ SizedBox(width: 8),
+ Expanded(
+ child: Text(
+ 'الخدمة غير متوفرة في منطقتك حالياً',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ fontSize: 14,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
);
});
}
diff --git a/siro_rider/lib/constant/links.dart b/siro_rider/lib/constant/links.dart
index e71e59e..3d2bc7a 100644
--- a/siro_rider/lib/constant/links.dart
+++ b/siro_rider/lib/constant/links.dart
@@ -398,6 +398,7 @@ static String get updateTesterApp => "$auth/Tester/updateTesterApp.php";
static String get signUp => "$auth/signup.php";
static String get sendVerifyEmail => "$auth/sendVerifyEmail.php";
static String get loginFromGooglePassenger => "$auth/loginFromGooglePassenger.php";
+static String get loginUsingCredentialsWithoutGooglePassenger => "$auth/loginUsingCredentialsWithoutGooglePassenger.php";
static String get checkPhoneNumberISVerfiedPassenger =>
"$auth/checkPhoneNumberISVerfiedPassenger.php";
diff --git a/siro_rider/lib/controller/auth/login_controller.dart b/siro_rider/lib/controller/auth/login_controller.dart
index f2f9ded..2eb3166 100644
--- a/siro_rider/lib/controller/auth/login_controller.dart
+++ b/siro_rider/lib/controller/auth/login_controller.dart
@@ -25,6 +25,7 @@ import 'package:location/location.dart';
import '../../env/env.dart';
import '../../print.dart';
import '../../views/auth/otp_token_page.dart';
+import '../functions/country_logic.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/package_info.dart';
@@ -48,7 +49,8 @@ class LoginController extends GetxController {
@override
void onInit() async {
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
- box.write(BoxName.countryCode, 'Syria');
+ // كشف الدولة تلقائياً عبر الموقع الجغرافي
+ await CountryLogic.initializeCountry();
FirebaseMessagesController().getToken();
super.onInit();
}
@@ -271,8 +273,6 @@ class LoginController extends GetxController {
final res = await CRUD().get(
link: AppLink.loginFromGooglePassenger,
payload: {
- 'email': email,
- 'id': passengerID, // استخدم المعامل مباشرة
'platform': Platform.isAndroid ? 'android' : 'ios',
'appName': AppInformation.appName,
},
@@ -385,7 +385,7 @@ class LoginController extends GetxController {
"inviteCode": invite,
"passengerID": passengerID,
});
-
+
// سجل الدعوة أيضاً في النظام الموحد الجديد
await CRUD().post(link: AppLink.addUnifiedInvite, payload: {
"inviter_code": invite,
@@ -445,46 +445,48 @@ class LoginController extends GetxController {
// );
// }
- void login() async {
+ void loginTester() 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,
- });
+
+ try {
+ var fingerPrint = await DeviceHelper.getDeviceFingerprint();
+ var payload = {
+ 'email': emailController.text.trim(),
+ 'password': passwordController.text.trim(),
+ 'fingerPrint': fingerPrint,
+ 'aud': '${AK.allowed}${Platform.isAndroid ? 'android' : 'ios'}',
+ };
+
+ var response = await http.post(
+ Uri.parse(AppLink.loginUsingCredentialsWithoutGooglePassenger),
+ body: payload,
+ );
+
+ if (response.statusCode == 200 || response.statusCode == 201) {
+ var jsonDecoeded = jsonDecode(response.body);
+ if (jsonDecoeded['status'] == 'success' && jsonDecoeded['data'][0]['verified'].toString() == '1') {
+ var d = jsonDecoeded['data'][0];
+ var jwt = jsonDecoeded['jwt'];
+
+ // حفظ التوكن
+ if (jwt != null) {
+ box.write(BoxName.jwt, c(jwt));
+ await storage.write(key: BoxName.jwt, value: c(jwt));
+ }
- 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);
+ box.write(BoxName.email, d['email']);
+ box.write(BoxName.name, d['first_name']);
+ box.write(BoxName.phone, d['phone']);
+ box.write(BoxName.passengerID, d['id']);
+ box.write(BoxName.isTest, '1');
+
+ await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
Get.offAll(() => const MapPagePassenger());
} else {
- // Get.offAll(() => SmsSignupEgypt());
- // Get.snackbar(jsonDecoeded['status'], jsonDecoeded['data'],
- // backgroundColor: Colors.redAccent);
+ Get.offAll(() => LoginPage());
isloading = false;
update();
}
@@ -492,6 +494,10 @@ class LoginController extends GetxController {
isloading = false;
update();
}
+ } catch (e) {
+ Log.print("Tester Login Error: $e");
+ isloading = false;
+ update();
}
}
diff --git a/siro_rider/lib/controller/functions/country_logic.dart b/siro_rider/lib/controller/functions/country_logic.dart
index ce12c0a..fdde578 100644
--- a/siro_rider/lib/controller/functions/country_logic.dart
+++ b/siro_rider/lib/controller/functions/country_logic.dart
@@ -1,5 +1,9 @@
+import 'package:geolocator/geolocator.dart';
+import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:siro_rider/constant/box_name.dart';
+import 'package:siro_rider/constant/country_polygons.dart';
import 'package:siro_rider/main.dart';
+import '../../print.dart';
class CountryLogic {
/// Formats the phone number based on the country's dialing rules.
@@ -88,7 +92,77 @@ class CountryLogic {
return formatPhone(cleanPhone, 'Jordan');
}
- final country = box.read(BoxName.countryCode) ?? 'Syria';
+ final country = box.read(BoxName.countryCode) ?? 'Jordan';
return formatPhone(cleanPhone, country);
}
+
+ /// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm)
+ static bool _isPointInPolygon(LatLng point, List polygon) {
+ int intersections = 0;
+ for (int i = 0; i < polygon.length; i++) {
+ final v1 = polygon[i];
+ final v2 = polygon[(i + 1) % polygon.length];
+ if ((v1.latitude <= point.latitude && v2.latitude > point.latitude) ||
+ (v2.latitude <= point.latitude && v1.latitude > point.latitude)) {
+ final t = (point.latitude - v1.latitude) / (v2.latitude - v1.latitude);
+ if (point.longitude <
+ v1.longitude + t * (v2.longitude - v1.longitude)) {
+ intersections++;
+ }
+ }
+ }
+ return intersections % 2 != 0;
+ }
+
+ /// كشف الدولة تلقائياً عبر الموقع الجغرافي باستخدام مضلعات CountryPolygons
+ static Future detectByLocation() async {
+ try {
+ bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
+ if (!serviceEnabled) return null;
+
+ LocationPermission permission = await Geolocator.checkPermission();
+ if (permission == LocationPermission.denied) {
+ permission = await Geolocator.requestPermission();
+ if (permission == LocationPermission.denied) return null;
+ }
+ if (permission == LocationPermission.deniedForever) return null;
+
+ final pos = await Geolocator.getCurrentPosition(
+ desiredAccuracy: LocationAccuracy.low,
+ timeLimit: const Duration(seconds: 8),
+ );
+ final point = LatLng(pos.latitude, pos.longitude);
+
+ if (_isPointInPolygon(point, CountryPolygons.jordanBoundary)) {
+ return 'Jordan';
+ }
+ if (_isPointInPolygon(point, CountryPolygons.syriaBoundary)) {
+ return 'Syria';
+ }
+ if (_isPointInPolygon(point, CountryPolygons.egyptBoundary)) {
+ return 'Egypt';
+ }
+ } catch (e) {
+ Log.print('[CountryLogic] detectByLocation error: $e');
+ }
+ return null;
+ }
+
+ /// تهيئة الدولة عند بدء التطبيق
+ static Future initializeCountry() async {
+ final existing = box.read(BoxName.countryCode);
+ if (existing != null && existing.toString().isNotEmpty) {
+ Log.print('[CountryLogic] الدولة موجودة مسبقاً: $existing');
+ return;
+ }
+ Log.print('[CountryLogic] كشف الدولة عبر الموقع...');
+ final country = await detectByLocation();
+ if (country != null) {
+ box.write(BoxName.countryCode, country);
+ Log.print('[CountryLogic] تم الكشف: $country');
+ } else {
+ box.write(BoxName.countryCode, 'Jordan');
+ Log.print('[CountryLogic] افتراضي: Jordan');
+ }
+ }
}
diff --git a/siro_rider/lib/controller/functions/crud.dart b/siro_rider/lib/controller/functions/crud.dart
index 265c761..41ee99a 100644
--- a/siro_rider/lib/controller/functions/crud.dart
+++ b/siro_rider/lib/controller/functions/crud.dart
@@ -75,14 +75,39 @@ class CRUD {
}
// ─────────────────────────────────────────────────────────────
- // دالة مساعدة خاصة: يجيب البصمة المشفرة من GetStorage
- // هي نفس القيمة المرسلة في login وعُملها hash في JWT payload
- // السيرفر يعمل: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint
+ // دالة مساعدة خاصة: تجيب البصمة المشفرة من GetStorage
// ─────────────────────────────────────────────────────────────
String _getFpHeader() {
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
}
+ // ─────────────────────────────────────────────────────────────
+ // دالة مساعدة خاصة: تقرأ JWT من FlutterSecureStorage (آمن)
+ // بدلاً من GetStorage (غير مشفر)
+ // ─────────────────────────────────────────────────────────────
+ Future _getJwt() async {
+ try {
+ final String? encryptedJwt = await storage.read(key: BoxName.jwt);
+ if (encryptedJwt == null || encryptedJwt.isEmpty) {
+ // Fallback إلى GetStorage للتوافقية
+ final String? fallback = box.read(BoxName.jwt);
+ if (fallback != null) {
+ return r(fallback).toString().split(Env.addd)[0];
+ }
+ return '';
+ }
+ return r(encryptedJwt).toString().split(Env.addd)[0];
+ } catch (e) {
+ Log.print('Error reading JWT from SecureStorage: $e');
+ // Fallback
+ final String? fallback = box.read(BoxName.jwt);
+ if (fallback != null) {
+ return r(fallback).toString().split(Env.addd)[0];
+ }
+ return '';
+ }
+ }
+
/// Centralized private method to handle all API requests.
/// Includes retry logic, network checking, and standardized error handling.
Future _makeRequest({
@@ -169,7 +194,7 @@ class CRUD {
required String link,
Map? payload,
}) async {
- String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
+ final token = await _getJwt();
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
@@ -193,14 +218,14 @@ class CRUD {
required String link,
Map? payload,
}) async {
+ final token = await _getJwt();
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]}',
+ 'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
},
);
@@ -548,7 +573,6 @@ class CRUD {
return response.statusCode;
}
-
Future postPayMob({
required String link,
Map? payload,
diff --git a/siro_rider/lib/controller/functions/secure_storage.dart b/siro_rider/lib/controller/functions/secure_storage.dart
index baba236..095016a 100644
--- a/siro_rider/lib/controller/functions/secure_storage.dart
+++ b/siro_rider/lib/controller/functions/secure_storage.dart
@@ -4,12 +4,9 @@ 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';
@@ -24,23 +21,25 @@ class SecureStorage {
}
}
-const List keysToFetch = [
- 'serverPHP',
- // 'seferAlexandriaServer',
- // 'seferPaymentServer',
- // 'seferCairoServer',
- // 'seferGizaServer',
-];
-
class AppInitializer {
List