Compare commits
5 Commits
752bbf3a63
...
72fa97477b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72fa97477b | ||
|
|
b67417eb98 | ||
|
|
c2c4ed22e3 | ||
|
|
264e005a7b | ||
|
|
2c56d2f41e |
849
SIRO_COMPLETE_REPORT_AR.md
Normal file
@@ -0,0 +1,849 @@
|
|||||||
|
<div dir="rtl">
|
||||||
|
|
||||||
|
# سِيرُو (Siro) — التقرير التسويقي والأمني الشامل
|
||||||
|
|
||||||
|
> **منصة النقل الذكي المتكاملة — الرائدة في سوريا، الأردن، ومصر**
|
||||||
|
>
|
||||||
|
> *نظام بيئي رقمي متكامل يربط الركاب بالسائقين عبر 4 تطبيقات ذكية، مع بنية تحتية أمنية متعددة الطبقات وتقنيات ذكاء اصطناعي متقدمة*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 فهرس المحتويات
|
||||||
|
|
||||||
|
1. [نظرة عامة عن المنصة](#نظرة-عامة-عن-المنصة)
|
||||||
|
2. [التطبيقات الأربعة](#التطبيقات-الأربعة)
|
||||||
|
3. [أنواع المركبات](#أنواع-المركبات)
|
||||||
|
4. [طرق الدفع](#طرق-الدفع)
|
||||||
|
5. [التكاملات الخارجية](#التكاملات-الخارجية)
|
||||||
|
6. [الميزات التقنية المتقدمة](#الميزات-التقنية-المتقدمة)
|
||||||
|
7. [الأمان متعدد الطبقات](#الأمان-متعدد-الطبقات)
|
||||||
|
8. [الذكاء الاصطناعي](#الذكاء-الاصطناعي)
|
||||||
|
9. [الإضافات المميزة](#الإضافات-المميزة)
|
||||||
|
10. [البنية التحتية](#البنية-التحتية)
|
||||||
|
11. [المقارنة التنافسية](#المقارنة-التنافسية)
|
||||||
|
12. [فرص النمو والتوسع](#فرص-النمو-والتوسع)
|
||||||
|
13. [الخلاصة](#الخلاصة)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 نظرة عامة عن المنصة
|
||||||
|
|
||||||
|
**Siro (سِيرُو)** هي منصة متكاملة لخدمات النقل والتوصيل الذكية، تعمل في **3 دول** هي **سوريا، الأردن، ومصر**. المنصة ليست مجرد تطبيق نقل عادي، بل هي **نظام بيئي رقمي متكامل** (Digital Ecosystem) يغطي كامل رحلة العميل من لحظة طلب الرحلة إلى ما بعد اكتمالها.
|
||||||
|
|
||||||
|
### 📊 المنصة بالأرقام
|
||||||
|
|
||||||
|
| المقياس | القيمة |
|
||||||
|
|---------|--------|
|
||||||
|
| عدد التطبيقات المتصلة | 4 تطبيقات |
|
||||||
|
| الدول المدعومة | 3 دول (سوريا، الأردن، مصر) |
|
||||||
|
| أنواع المركبات | 12 نوعاً |
|
||||||
|
| طرق الدفع | 7 خيارات |
|
||||||
|
| خدمات التكامل الخارجي | 15+ خدمة |
|
||||||
|
| ملفات PHP في الخادم الخلفي | 395+ ملفاً |
|
||||||
|
| قواعد البيانات | 3 قواعد بيانات رئيسية |
|
||||||
|
| سيرفرات WebSocket | 2 (Driver + Passenger) |
|
||||||
|
| عدد وحدات لوحة التحكم الإدارية | 15+ وحدة |
|
||||||
|
| محركات الذكاء الاصطناعي | 3 محركات (Azure + OpenAI + Llama) |
|
||||||
|
|
||||||
|
### 🎯 الرؤية
|
||||||
|
|
||||||
|
تقديم تجربة نقل ذكية وآمنة ومتكاملة تنافس كبرى منصات النقل العالمية (أوبر، كريم، Bolt) بميزات محلية مبتكرة تفهم احتياجات السوق العربي.
|
||||||
|
|
||||||
|
### 🌍 الدول المدعومة
|
||||||
|
|
||||||
|
| الدولة | الحالة | الميزات الخاصة |
|
||||||
|
|--------|--------|----------------|
|
||||||
|
| 🇸🇾 **سوريا** | 🟢 نشط | MTN Cash، Syriatel Cash، خوادم محلية |
|
||||||
|
| 🇯🇴 **الأردن** | 🟢 نشط | PayMob، خوادم إقليمية |
|
||||||
|
| 🇪🇬 **مصر** | 🟢 نشط | E-Cash، SMS Kazumi، خوادم إقليمية |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 التطبيقات الأربعة (رباعية التطبيقات المترابطة)
|
||||||
|
|
||||||
|
### 1️⃣ تطبيق الراكب (Siro Rider) — تجربة الركوب الذكية
|
||||||
|
|
||||||
|
تطبيق الراكب هو واجهة المستخدم الأساسية، مصمم لتجربة سلسة وسريعة.
|
||||||
|
|
||||||
|
**الميزات الرئيسية:**
|
||||||
|
|
||||||
|
| الميزة | الوصف |
|
||||||
|
|--------|-------|
|
||||||
|
| 🗺️ **واجهة خرائط مدمجة** | Google Maps + Here Maps + Map SaaS للبحث والتوجيه |
|
||||||
|
| 🔍 **بحث ذكي عن الوجهة** | اقتراحات تلقائية للأماكن مع تكامل متعدد لمزودي الخرائط |
|
||||||
|
| 💰 **عرض السعر التقديري** | حساب التكلفة قبل تأكيد الرحلة مع شفافية كاملة |
|
||||||
|
| 🚙 **اختيار نوع المركبة** | 12 نوع مركبة مختلفة تناسب كل الاحتياجات |
|
||||||
|
| 📍 **تتبع السائق المباشر** | تحديث موقع السائق كل 3-5 ثوانٍ عبر WebSocket |
|
||||||
|
| 💳 **دفع إلكتروني** | 7 طرق دفع مختلفة (نقدي، بطاقة، محفظة، جوال) |
|
||||||
|
| ⭐ **تقييم السائق** | نظام تقييم مزدوج بالنجوم والتعليقات |
|
||||||
|
| 🆘 **زر الطوارئ (SOS)** | إشارة طوارئ فورية مع إرسال الموقع الحي |
|
||||||
|
| 💬 **دردشة داخلية** | تواصل مع السائق دون مشاركة أرقام الهواتف |
|
||||||
|
| 🎫 **أكواد خصم وعروض** | نظام ترويجي متكامل مع كود دعوة |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2️⃣ تطبيق السائق (Siro Driver) — منصة الكابتن الاحترافية
|
||||||
|
|
||||||
|
تطبيق متطور يمكّن السائقين من إدارة الرحلات بكفاءة عالية.
|
||||||
|
|
||||||
|
**الميزات الرئيسية:**
|
||||||
|
|
||||||
|
| الميزة | الوصف التقني |
|
||||||
|
|--------|--------------|
|
||||||
|
| 🎯 **عروض الرحلات عبر التراكب المباشر (Overlay)** | نظام Android System Overlay يعرض تفاصيل الرحلة فوق أي تطبيق — حتى لو كان الهاتف مقفلاً |
|
||||||
|
| 🗺️ **ملاحة صوتية (Turn-by-Turn)** | إرشادات TTS خطوة بخطوة مع تحديث المسار تلقائياً |
|
||||||
|
| 🔄 **وضع الخدمة (Online/Offline)** | تشغيل تلقائي في الخلفية عبر Android Foreground Service 24/7 |
|
||||||
|
| 📊 **إحصائيات الأرباح اللحظية** | عرض فوري للرحلات اليومية، الإجمالي، العمولات |
|
||||||
|
| ⏱️ **مؤقت 15 ثانية للقبول** | قبول تلقائي أو رفض مع صوت تنبيه مخصص |
|
||||||
|
| 🔊 **صوت تنبيه مخصص** | ملف "ding.wav" مشغل عبر MediaPlayer |
|
||||||
|
| 🚗 **تحديث الموقع في الخلفية** | Foreground Service مع تحديث GPS كل 3 ثوانٍ |
|
||||||
|
| 🛑 **كشف الانحراف عن المسار** | تنبيه إذا انحرف السائق عن المسار بأكثر من 50 متراً |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3️⃣ تطبيق الإدارة (Siro Admin) — لوحة تحكم شاملة (PWA)
|
||||||
|
|
||||||
|
لوحة تحكم إدارية متكاملة مبنية بـ Flutter Web (PWA) مع 15+ وحدة إدارية.
|
||||||
|
|
||||||
|
**الوحدات الإدارية:**
|
||||||
|
|
||||||
|
| الوحدة | الوظيفة |
|
||||||
|
|--------|---------|
|
||||||
|
| 📈 **لوحة التحكم (Dashboard)** | إحصائيات فورية مع رسوم بيانية متقدمة |
|
||||||
|
| 👨✈️ **إدارة الكباتن** | قبول/رفض، توثيق، حظر، مراجعة ملفات السائقين |
|
||||||
|
| 👤 **إدارة الركاب** | تفاصيل كاملة، سجل الرحلات، حظر وإلغاء حظر |
|
||||||
|
| 🚗 **إدارة الرحلات** | تتبع مباشر، سجل كامل، بحث متقدم، مراقبة حية |
|
||||||
|
| 💰 **الإدارة المالية** | تقارير الأرباح، العمولات، التسويات المالية |
|
||||||
|
| 📊 **التحليلات المتقدمة** | مؤشرات الأداء، التقارير الشهرية والسنوية |
|
||||||
|
| 🔒 **الأمان والرقابة** | سجلات التدقيق (Audit Logs)، مكافحة الاحتيال |
|
||||||
|
| 🎯 **نظام العمولات (Kazan)** | تحرير نسب العمولات ونماذج الأسعار |
|
||||||
|
| 🏷️ **العروض الترويجية** | إنشاء وإدارة أكواد الخصم والعروض |
|
||||||
|
| 👥 **إدارة الموظفين** | صلاحيات الأدوار، موافقات التسجيل |
|
||||||
|
| ⭐ **مراقبة الجودة** | بطاقات أداء السائقين، القوائم السوداء |
|
||||||
|
| 🖥️ **مراقبة الخوادم** | حالة السيرفرات، الأداء، وقت التشغيل |
|
||||||
|
| 📄 **إدارة الفواتير** | إنشاء وطباعة الفواتير |
|
||||||
|
| 🔄 **أدوات ترحيل البصمة** | إعادة تعيين بصمة الجهاز للمستخدمين |
|
||||||
|
| 📝 **إدارة الشكاوى** | متابعة وحل شكاوى المستخدمين |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4️⃣ تطبيق الخدمة الميدانية (Siro Service) — منصة تسجيل السائقين
|
||||||
|
|
||||||
|
تطبيق متخصص لموظفي الخدمة الميدانية لتسجيل وتوثيق السائقين الجدد.
|
||||||
|
|
||||||
|
**الميزات:**
|
||||||
|
|
||||||
|
| الميزة | الوصف |
|
||||||
|
|--------|-------|
|
||||||
|
| 📸 تصوير المستندات | تصوير الهوية ورخصة القيادة وأوراق السيارة مباشرة من الكاميرا |
|
||||||
|
| 🤖 **استخراج بيانات تلقائي بالذكاء الاصطناعي** | Azure OCR + OpenAI GPT + Llama AI لاستخراج البيانات |
|
||||||
|
| ✅ التحقق الميداني | التحقق من السيارة والسائق في الموقع |
|
||||||
|
| 📝 رفع الصور | رفع مباشر للخادم مع ضغط تلقائي |
|
||||||
|
| ⏱️ تسجيل فوري | تقليل وقت التسجيل من أيام إلى دقائق |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚙 أنواع المركبات المتاحة (12 نوعاً)
|
||||||
|
|
||||||
|
| النوع | الرمز | الوصف |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| ⚡ **سرعة (Speed)** | Speed | الرحلات القياسية — السيارات العادية |
|
||||||
|
| 🌟 **راحة (Comfort)** | Comfort | رحلات فاخرة بسيارات مريحة |
|
||||||
|
| 👨👩👧👦 **عائلية (Family)** | Family | سيارات عائلية كبيرة الحجم |
|
||||||
|
| 📦 **توصيل (Delivery)** | Delivery | توصيل الطرود والطلبات |
|
||||||
|
| 💸 **اقتصادي (Free/Blash)** | Blash | رحلات اقتصادية بأسعار مخفضة |
|
||||||
|
| 🌙 **ليلية (Late)** | Late | رحلات خارج أوقات الذروة |
|
||||||
|
| 🚛 **نقل ثقيل (Heavy)** | Heavy | نقل البضائع والأغراض الثقيلة |
|
||||||
|
| 🏔️ **طبيعة (Nature)** | Nature | رحلات الطرق الخلابة والمناطق الوعرة |
|
||||||
|
| 🔌 **كهربائي (Electric)** | Electric | سيارات كهربائية صديقة للبيئة |
|
||||||
|
| 🏍️ **دراجة وردية (Pink Bike)** | PinkBike | دراجات نارية للتنقل السريع |
|
||||||
|
| 🚐 **فان (Van)** | Van | حافلات صغيرة للمجموعات |
|
||||||
|
| 👩 **سائقة (Female Driver)** | FemalDriver | سائقات نساء — خيار خاص للسيدات |
|
||||||
|
|
||||||
|
> ✅ جميع أنواع المركبات مدعومة في الدول الثلاث: سوريا، الأردن، مصر
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💳 طرق الدفع المتعددة (7 خيارات)
|
||||||
|
|
||||||
|
| طريقة الدفع | التوفر | التقنية المستخدمة |
|
||||||
|
|-------------|--------|-------------------|
|
||||||
|
| 💵 **نقدي (Cash)** | جميع الدول | دفع يدوي عند الوصول |
|
||||||
|
| 💳 **بطاقة فيزا/ماستركارد** | سوريا، الأردن، مصر | PayMob Payment Gateway |
|
||||||
|
| 👛 **محفظة إلكترونية (Wallet)** | جميع الدول | خادم محفظة مخصص (WalletIntaleq) |
|
||||||
|
| 📱 **MTN موبايل موني** | سوريا | MTN Cash API |
|
||||||
|
| 📱 **سيريتل موبايل موني** | سوريا | Syriatel Cash API |
|
||||||
|
| 🔄 **E-Cash** | مصر | خدمة E-Cash المصرية |
|
||||||
|
| 🌐 **Stripe** | دولي | Stripe Payment Gateway |
|
||||||
|
|
||||||
|
### 💼 المحفظة الإلكترونية (Wallet System)
|
||||||
|
|
||||||
|
نظام المحفظة هو نظام دفع داخلي متكامل يتكون من:
|
||||||
|
|
||||||
|
- **محفظة الراكب**: شحن رصيد، دفع للرحلات، استرداد أموال
|
||||||
|
- **محفظة السائق**: استلام الأرباح، سحب الأموال
|
||||||
|
- **التحويل بين المحافظ**: تحويل رصيد بين المستخدمين
|
||||||
|
- **سجل المعاملات الكامل**: تتبع جميع الحركات المالية
|
||||||
|
- **نظام البقشيش (Tips)**: إضافة بقشيش للسائق بعد الرحلة
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 التكاملات الخارجية (شركاء الخدمة)
|
||||||
|
|
||||||
|
### 🗺️ الخرائط والملاحة
|
||||||
|
|
||||||
|
| الخدمة | الوظيفة | نوع التكامل |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| **Google Maps** | عرض الخرائط، الترميز الجغرافي، التوجيه | API Key |
|
||||||
|
| **Here Maps** | البحث والاقتراح التلقائي للأماكن | API Key |
|
||||||
|
| **Map SaaS** (خاص) | توجيه مخصص، ترميز جغرافي عكسي، بحث الأماكن | x-api-key |
|
||||||
|
| **OpenStreetMap (OSRM)** | توجيه عبر مسارات — خادم مخصص لكل دولة | Internal API |
|
||||||
|
|
||||||
|
### 📱 التواصل والإشعارات
|
||||||
|
|
||||||
|
| الخدمة | الوظيفة |
|
||||||
|
|--------|---------|
|
||||||
|
| **Firebase (FCM)** | إشعارات لحظية، تحليلات، Crashlytics |
|
||||||
|
| **Twilio** | التحقق عبر SMS (OTP) |
|
||||||
|
| **WhatsApp Cloud API** | إرسال كود التحقق عبر واتساب |
|
||||||
|
| **SMS Kazumi** | مزود SMS في مصر |
|
||||||
|
|
||||||
|
### 🤖 الذكاء الاصطناعي
|
||||||
|
|
||||||
|
| الخدمة | الوظيفة |
|
||||||
|
|--------|---------|
|
||||||
|
| **Azure OCR** | مسح ضوئي للمستندات واستخراج النصوص |
|
||||||
|
| **OpenAI GPT-3.5** | تحليل وفهم بيانات المستندات |
|
||||||
|
| **Llama AI** | نموذج ذكاء اصطناعي بديل لاستخراج البيانات (Fallback) |
|
||||||
|
|
||||||
|
### 📞 الاتصالات
|
||||||
|
|
||||||
|
| الخدمة | الوظيفة |
|
||||||
|
|--------|---------|
|
||||||
|
| **Agora** | مكالمات صوتية وفيديو داخل التطبيق |
|
||||||
|
| **WebRTC** | خدمة الإشارات للاتصالات المباشرة |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ الميزات التقنية المتقدمة
|
||||||
|
|
||||||
|
### 🎯 نظام التوزيع الذكي (Smart Dispatching)
|
||||||
|
|
||||||
|
نظام التوزيع هو قلب المنصة، وهو المسؤول عن مطابقة الركاب مع السائقين بأقل وقت استجابة.
|
||||||
|
|
||||||
|
**المكونات:**
|
||||||
|
|
||||||
|
| المكون | التقنية | الوصف |
|
||||||
|
|--------|---------|-------|
|
||||||
|
| **WebSocket مزدوج** | PHP WebSocket (Socket.IO) | سيرفر مستقل للسائقين (port 2020) وآخر للركاب (port 3030) |
|
||||||
|
| **بحث مكاني (GIS Query)** | MySQL SPATIAL INDEX | استخدام GEOMETRY و SRID=4326 للبحث عن السائقين الأقرب |
|
||||||
|
| **توزيع فوري** | Redis GEOADD + GEORADIUS | تخزين مواقع السائقين في Redis مع تحديث كل 500ms |
|
||||||
|
| **نظام انتظار ذكي** | Event Buffer + Redis Pipeline | تجميع الأحداث وإرسالها دفعة واحدة كل 500ms |
|
||||||
|
| **تجميع الأحداث (Event Buffering)** | $eventBuffer في الذاكرة | تجاهل التحديثات إذا لم يتغير الموقع بأكثر من 10 أمتار |
|
||||||
|
| **Forward غير متزامن** | Async HTTP Forward | إرسال موقع السائق إلى سيرفر الراكب مع Throttle (3 ثوانٍ، 15 متراً) |
|
||||||
|
|
||||||
|
**سير العمل:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. الراكب يطلب رحلة → بحث في Redis عن أقرب السائقين → إرسال عرض للسائق
|
||||||
|
2. السائق يقبل → WebSocket يرسل ride_accepted للراكب
|
||||||
|
3. تتبع مباشر → Driver Socket → Forward → Passenger Socket → الراكب
|
||||||
|
4. إعادة حساب المسار → OSRM Routing → Map SaaS → عرض المسار على الخريطة
|
||||||
|
5. انتهاء الرحلة → خصم المبلغ من المحفظة → تقييم متبادل
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🗺️ نظام الخرائط الحية (Real-time Tracking)
|
||||||
|
|
||||||
|
| الميزة | الوصف التقني |
|
||||||
|
|--------|--------------|
|
||||||
|
| تحديث موقع السائق | كل 3-5 ثوانٍ عبر WebSocket مع Smooth Animation |
|
||||||
|
| تتبع المسار المباشر | تحديث زاوية السيارة حسب الاتجاه باستخدام تحديثات GPS |
|
||||||
|
| **كشف الانحراف** | تنبيه إذا انحرف السائق عن المسار بأكثر من 50 متراً |
|
||||||
|
| إعادة التوجيه التلقائي | إعادة حساب المسار عبر OSRM إذا لزم الأمر |
|
||||||
|
| حساب وقت الوصول (ETA) | خوارزميات محلية دقيقة لحساب الوقت المتبقي |
|
||||||
|
| Polling Fallback | التبديل التلقائي للاقتراع كل 4 ثوانٍ عند فقدان WebSocket |
|
||||||
|
|
||||||
|
### 💬 المحادثة الفورية (In-App Chat)
|
||||||
|
|
||||||
|
- 🗨️ **دردشة داخلية** بين السائق والراكب دون مشاركة أرقام الهواتف
|
||||||
|
- 🔒 **حماية الخصوصية** — لا يرى الراكب رقم السائق والعكس
|
||||||
|
- 📱 تدعم الوسائط والنصوص
|
||||||
|
- 🚫 **سجل المحادثة** مشفر ومحمي
|
||||||
|
|
||||||
|
### 🆘 نظام الطوارئ (Emergency/SOS)
|
||||||
|
|
||||||
|
- 🚨 إشارة طوارئ مباشرة إلى فريق الدعم
|
||||||
|
- 📹 إرسال الموقع الحي لفريق الطوارئ
|
||||||
|
- 🎥 تكامل مع Agora لمكالمات الفيديو الفورية
|
||||||
|
- 📞 تكامل مع WebRTC للاتصالات المباشرة
|
||||||
|
- 👮 إشعارات للسلطات المحلية (حسب الدولة)
|
||||||
|
|
||||||
|
### 🔄 إعادة الاتصال الذكي (Auto-Reconnect)
|
||||||
|
|
||||||
|
| الطبقة | الآلية | التفاصيل |
|
||||||
|
|--------|--------|----------|
|
||||||
|
| WebSocket | 20 محاولة مع تأخير تصاعدي | 2-10 ثوانٍ بين المحاولات |
|
||||||
|
| Polling Fallback | تبديل تلقائي | كل 4 ثوانٍ كحد أقصى |
|
||||||
|
| مؤقت الصحة | Heartbeat كل 15 ثانية | التأكد من استقرار الاتصال |
|
||||||
|
| كشف القطع | isSocketHealthy() | تحقق من آخر تحديث (< 20 ثانية) |
|
||||||
|
| Auto-refresh JWT | عند استقبال 401 | تجديد التوكين تلقائياً |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛡️ الأمان متعدد الطبقات (Multi-Layer Security)
|
||||||
|
|
||||||
|
> هذا القسم يغطي بنية الأمان الكامنة في المنصة من منظور تقني وتسويقي
|
||||||
|
|
||||||
|
### 🏗️ طبقات الأمان الخمس
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Layer 1: 🔑 المصادقة (Authentication) │
|
||||||
|
│ JWT + بصمة الجهاز + MFA + OTP │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 2: 🚪 التفويض (Authorization) │
|
||||||
|
│ Role-based + Ownership Check + Rate Limiting │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 3: 🔐 حماية البيانات (Data Protection) │
|
||||||
|
│ AES-256-CBC + IV عشوائي + HTTPS/TLS + Certificate Pin │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 4: 🧱 سلامة البيانات (Integrity) │
|
||||||
|
│ Prepared Statements + Input Validation + HMAC-SHA256 │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Layer 5: 👁️ المراقبة والكشف (Detection) │
|
||||||
|
│ Audit Logs + Rate Limiting + Security Logging + Alerts │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔑 الطبقة الأولى: المصادقة (Authentication)
|
||||||
|
|
||||||
|
| الآلية | التقنية | الوصف |
|
||||||
|
|--------|---------|-------|
|
||||||
|
| **JWT Tokens** | مخصص (custom) | توكنات JWT تصدر عند تسجيل الدخول، تتحقق في كل طلب |
|
||||||
|
| **بصمة الجهاز (Fingerprint)** | SHA-256 + HMAC | بصمة جهاز فريدة مربوطة بجهاز المستخدم |
|
||||||
|
| **المصادقة متعددة العوامل (MFA)** | بصمة + OTP + Token | الحماية بعاملين على الأقل |
|
||||||
|
| **SMS OTP** | Twilio + WhatsApp | إرسال رمز تحقق عبر SMS أو واتساب |
|
||||||
|
| **Google/Apple Sign-In** | Firebase Auth | تسجيل دخول بحسابات التواصل الاجتماعي |
|
||||||
|
| **التسجيل ببصمة الجهاز** | hash_equals | التحقق من البصمة مع مقارنة آمنة زمنياً (Timing-safe) |
|
||||||
|
|
||||||
|
**إدارة التوكنات:**
|
||||||
|
|
||||||
|
| النوع | التخزين | مدة الصلاحية | آلية التجديد |
|
||||||
|
|-------|---------|---------------|--------------|
|
||||||
|
| JWT (رئيسي) | GetStorage (مشفر) | 1 ساعة | Auto-refresh عند 401 |
|
||||||
|
| JWT (محفظة) | GetStorage (مشفر) | 1 ساعة | Auto-refresh منفصل |
|
||||||
|
| FCM Token | GetStorage + DB | حسب Firebase | عند بدء التطبيق |
|
||||||
|
| Refresh Token | FlutterSecureStorage | طويل المدى | عند تسجيل الدخول |
|
||||||
|
| Server Token | قاعدة البيانات | جلسة واحدة | مع توقيع HMAC |
|
||||||
|
|
||||||
|
### 🚪 الطبقة الثانية: التفويض (Authorization)
|
||||||
|
|
||||||
|
| الآلية | الوصف التقني |
|
||||||
|
|--------|--------------|
|
||||||
|
| **Role-based Access** | أدوار (Passenger, Driver, Admin, Service) مع صلاحيات محددة |
|
||||||
|
| **Device Binding** | X-Device-FP header مقابل JWT fingerprint claim |
|
||||||
|
| **Wallet Auth** | JWT منفصل + HMAC لعمليات الدفع |
|
||||||
|
| **Ownership Verification** | التحقق من أن المستخدم يملك المورد المطلوب |
|
||||||
|
| **Admin Authorization** | التحقق من دور المسؤول مع صلاحيات دقيقة |
|
||||||
|
|
||||||
|
### 🔐 الطبقة الثالثة: حماية البيانات (Data Protection)
|
||||||
|
|
||||||
|
**تشفير AES-256-CBC مع IV عشوائي:**
|
||||||
|
|
||||||
|
```
|
||||||
|
قبل الإصلاح: IV ثابت → نفس النص = نفس التشفير ← ثغرة 🔴
|
||||||
|
بعد الإصلاح: IV عشوائي لكل تشفير ← كل مرة تشفير مختلف ← آمن ✅
|
||||||
|
|
||||||
|
سير العمل الآمن:
|
||||||
|
1. توليد IV عشوائي (16 بايت) باستخدام openssl_random_pseudo_bytes
|
||||||
|
2. تشفير البيانات بـ AES-256-CBC
|
||||||
|
3. ضم IV مع النص المشفر
|
||||||
|
4. تشفير بـ base64
|
||||||
|
5. عند فك التشفير: استخراج IV من أول 16 بايت
|
||||||
|
```
|
||||||
|
|
||||||
|
**البيانات المشفرة:**
|
||||||
|
|
||||||
|
| نوع البيانات | مستوى التشفير |
|
||||||
|
|--------------|---------------|
|
||||||
|
| أرقام الهواتف | AES-256-CBC + IV عشوائي |
|
||||||
|
| بطاقات الهوية | AES-256-CBC + IV عشوائي |
|
||||||
|
| معلومات الدفع | AES-256-CBC + HMAC |
|
||||||
|
| كلمات المرور | bcrypt/Argon2ID (hash) |
|
||||||
|
| مفاتيح API | AES-256-CBC |
|
||||||
|
| توقيعات HMAC | SHA-256 |
|
||||||
|
|
||||||
|
**SSL/TLS:**
|
||||||
|
|
||||||
|
- ✅ جميع اتصالات API عبر HTTPS
|
||||||
|
- ✅ تثبيت الشهادة (Certificate Pinning) على تطبيقات Flutter
|
||||||
|
- ✅ التحقق من SSL في كل طلب (CURLOPT_SSL_VERIFYPEER)
|
||||||
|
- ✅ HSTS (Strict-Transport-Security)
|
||||||
|
- ❌ تمت إزالة نقاط نهاية HTTP (تم الاستبدال بـ HTTPS)
|
||||||
|
|
||||||
|
### 🧱 الطبقة الرابعة: سلامة البيانات (Integrity)
|
||||||
|
|
||||||
|
| الإجراء | الوصف |
|
||||||
|
|---------|--------|
|
||||||
|
| **Prepared Statements** | جميع استعلامات SQL تستخدم Prepared Statements (PDO) |
|
||||||
|
| **Input Validation** | التحقق من جميع المدخلات (القوائم البيضاء، الفلاتر) |
|
||||||
|
| **Output Encoding** | ترميز المخرجات لمنع XSS |
|
||||||
|
| **HMAC-SHA256** | توقيع جميع طلبات الدفع بين الخوادم |
|
||||||
|
| **Anti-Replay** | Timestamp + Nonce لمنع إعادة تشغيل الطلبات |
|
||||||
|
| **Idempotency Key** | منع معالجة نفس الطلب مرتين (الدفع، إنشاء الرحلة) |
|
||||||
|
| **Race Condition Guards** | حراس التكرار في دورة حياة الرحلة |
|
||||||
|
|
||||||
|
### 👁️ الطبقة الخامسة: المراقبة والكشف (Detection)
|
||||||
|
|
||||||
|
| الإجراء | الآلية |
|
||||||
|
|---------|--------|
|
||||||
|
| **Audit Logging** | تسجيل جميع العمليات الحساسة (تسجيل دخول، معاملات مالية، تعديلات) |
|
||||||
|
| **Rate Limiting** | تحديد عدد الطلبات لكل مستخدم/عنوان IP |
|
||||||
|
| **Login Monitoring** | تسجيل محاولات تسجيل الدخول الفاشلة والناجحة |
|
||||||
|
| **Fraud Detection** | كشف الأنماط المشبوهة في المعاملات المالية |
|
||||||
|
| **Error Logging** | تسجيل الأخطاء بدون كشف معلومات حساسة |
|
||||||
|
| **Server Monitoring** | مراقبة أداء الخوادم ووقت التشغيل |
|
||||||
|
| **Alerting** | تنبيهات في الوقت الفعلي للأنشطة المشبوهة |
|
||||||
|
|
||||||
|
### 🔐 حماية نظام الدفع (Payment Security)
|
||||||
|
|
||||||
|
نظام الدفع في سيرو مبني على بنية S2S (Server-to-Server) آمنة:
|
||||||
|
|
||||||
|
```
|
||||||
|
تطبيق Flutter ←→ API Server (JWT + HMAC) ←→ Wallet Server (HMAC + Timestamp + Backend ID)
|
||||||
|
```
|
||||||
|
|
||||||
|
| الإجراء | الوصف |
|
||||||
|
|---------|--------|
|
||||||
|
| JWT إجباري | لا يمكن تنفيذ أي معاملة مالية بدون JWT صالح |
|
||||||
|
| HMAC-SHA256 | توقيع جميع الطلبات بمفتاح سري مشترك بين الخوادم |
|
||||||
|
| Timestamp + Nonce | منع هجمات إعادة التشغيل (Replay Attacks) |
|
||||||
|
| Backend ID | تعريف الخادم المُصدر للطلب |
|
||||||
|
| Rate Limiting | حد أقصى للمعاملات لكل مستخدم |
|
||||||
|
| التحقق من المبلغ | التحقق من أن المبلغ ضمن الحدود المسموحة (1-10,000) |
|
||||||
|
| Idempotency | منع معالجة نفس طلب الدفع مرتين |
|
||||||
|
| Audit Trail | تسجيل جميع المعاملات المالية مع التفاصيل الكاملة |
|
||||||
|
|
||||||
|
### 🛡️ رؤوس الأمان (Security Headers)
|
||||||
|
|
||||||
|
| الرأس | القيمة | الغرض |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| Strict-Transport-Security | max-age=31536000 | فرض HTTPS |
|
||||||
|
| X-Frame-Options | DENY | منع Clickjacking |
|
||||||
|
| X-Content-Type-Options | nosniff | منع MIME sniffing |
|
||||||
|
| Content-Security-Policy | سياسة مقيدة | منع XSS |
|
||||||
|
| X-XSS-Protection | 1; mode=block | حماية عبر المتصفح |
|
||||||
|
| Referrer-Policy | strict-origin-when-cross-origin | حماية الإحالات |
|
||||||
|
|
||||||
|
### 📱 أمن تطبيقات الهاتف (Mobile Security)
|
||||||
|
|
||||||
|
| الإجراء | الوصف |
|
||||||
|
|---------|-------|
|
||||||
|
| **تثبيت الشهادة** | Certificate Pinning في تطبيقات Flutter |
|
||||||
|
| **التخزين الآمن** | FlutterSecureStorage (Keychain/Keystore) للبيانات الحساسة |
|
||||||
|
| **أذونات محدودة** | تم تقليل الأذونات إلى الحد الأدنى المطلوب |
|
||||||
|
| **GetStorage مشفر** | تخزين محلي مشفر للبيانات |
|
||||||
|
| **Android Foreground Service** | خدمة خلفية دائمة مع وصول محدود |
|
||||||
|
| **No Debug Code** | إزالة جميع أكواد التصحيح من الإنتاج |
|
||||||
|
|
||||||
|
### 🔄 المصادقة بين الخوادم (S2S Authentication)
|
||||||
|
|
||||||
|
```
|
||||||
|
API Server → Wallet Server:
|
||||||
|
Header: X-Signature (HMAC-SHA256)
|
||||||
|
Header: X-Timestamp (Unix Time)
|
||||||
|
Header: X-Backend-ID (معرف فريد)
|
||||||
|
Header: X-Nonce (رقم عشوائي لمرة واحدة)
|
||||||
|
Body: JSON (JWT + Data)
|
||||||
|
|
||||||
|
Wallet Server → تحقق:
|
||||||
|
1. التحقق من Timestamp (ضمن 60 ثانية)
|
||||||
|
2. التحقق من Backend ID
|
||||||
|
3. التحقق من HMAC Signature
|
||||||
|
4. التحقق من Nonce (لم يتم استخدامه من قبل)
|
||||||
|
5. التحقق من JWT Payload
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📊 تقييم الأمان العام
|
||||||
|
|
||||||
|
| المجال | التقييم | الحالة |
|
||||||
|
|--------|---------|--------|
|
||||||
|
| 🔑 المصادقة | ⭐⭐⭐⭐⭐ | MFA + JWT + بصمة + OTP |
|
||||||
|
| 🔐 التشفير | ⭐⭐⭐⭐⭐ | AES-256-CBC + IV عشوائي |
|
||||||
|
| 🛡️ حماية API | ⭐⭐⭐⭐⭐ | Rate Limiting + Validation |
|
||||||
|
| 💳 أمن المدفوعات | ⭐⭐⭐⭐⭐ | S2S + HMAC + Audit Trail |
|
||||||
|
| 📱 أمن التطبيقات | ⭐⭐⭐⭐ | Certificate Pinning + تشفير |
|
||||||
|
| 🗄️ أمن قاعدة البيانات | ⭐⭐⭐⭐⭐ | SQL Injection محمي |
|
||||||
|
| 🌐 أمن الشبكة | ⭐⭐⭐⭐⭐ | HTTPS + HSTS + CSP |
|
||||||
|
| 👁️ المراقبة | ⭐⭐⭐⭐ | Audit Logs + تنبيهات |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤖 الذكاء الاصطناعي (AI Features)
|
||||||
|
|
||||||
|
### 📄 توثيق السائقين بالذكاء الاصطناعي
|
||||||
|
|
||||||
|
نظام توثيق متكامل يستخدم 3 محركات ذكاء اصطناعي لاستخراج بيانات المستندات تلقائياً:
|
||||||
|
|
||||||
|
**سير العمل:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. تصوير المستند ← Azure OCR (استخراج النص)
|
||||||
|
2. النص المستخرج ← OpenAI GPT-3.5 (تحليل وفهم)
|
||||||
|
3. في حالة فشل: Llama AI (نموذج بديل)
|
||||||
|
4. التحقق التلقائي ← مطابقة البيانات مع القواعد
|
||||||
|
5. تسجيل فوري ← من أيام إلى دقائق
|
||||||
|
```
|
||||||
|
|
||||||
|
| المحرك | الوظيفة | الميزة |
|
||||||
|
|--------|---------|--------|
|
||||||
|
| **Azure OCR** | مسح ضوئي | استخراج النصوص من صور المستندات |
|
||||||
|
| **OpenAI GPT-3.5** | تحليل ذكي | فهم وتصنيف بيانات المستندات |
|
||||||
|
| **Llama AI** | نموذج بديل | Fallback في حال فشل OpenAI |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎁 الإضافات المميزة (Value-Added Features)
|
||||||
|
|
||||||
|
### 🏷️ نظام العروض الترويجية (Promotions)
|
||||||
|
|
||||||
|
- 🎫 **أكواد خصم** للمستخدمين الجدد والحاليين
|
||||||
|
- 🆕 **كود دعوة أول رحلة**: خصم على أول رحلة لكل مستخدم جديد
|
||||||
|
- 📩 إرسال العروض المخصصة عبر الإشعارات
|
||||||
|
- 📊 تقارير أداء العروض الترويجية
|
||||||
|
|
||||||
|
### 👫 نظام الإحالة (Referral System)
|
||||||
|
|
||||||
|
- 🔗 **رمز إحالة موحد** لكل مستخدم (سائق أو راكب)
|
||||||
|
- 🎁 **مكافآت دعوة الأصدقاء**: رصيد مجاني لكل شخص يدعوه
|
||||||
|
- 📊 تتبع الإحالات — عدد المسجلين والأرباح
|
||||||
|
- 💰 مكافآت مزدوجة (للداعي والمدعو)
|
||||||
|
|
||||||
|
### ⭐ نظام التقييم المزدوج (Dual Rating)
|
||||||
|
|
||||||
|
| التقييم | من | إلى | الغرض |
|
||||||
|
|---------|----|-----|-------|
|
||||||
|
| ⭐ نجوم | الراكب | السائق | جودة الخدمة |
|
||||||
|
| ⭐ سلوك | السائق | الراكب | سلوك الراكب ونظافته |
|
||||||
|
| 📊 مؤشر الجودة | تلقائي | المستخدم | متوسط جميع التقييمات |
|
||||||
|
| 🚫 القائمة السوداء | إداري | المستخدم | حظر ذوي التقييم المنخفض |
|
||||||
|
|
||||||
|
### 💰 نظام العمولات الذكي (Kazan)
|
||||||
|
|
||||||
|
- 📊 **نسبة عمولة متغيرة** حسب نوع المركبة
|
||||||
|
- 🌍 **أسعار مختلفة حسب كل دولة**: سوريا، الأردن، مصر
|
||||||
|
- ⚙️ **قابل للتعديل** من لوحة التحكم الإدارية
|
||||||
|
- 💹 **شفافية كاملة**: السائق يعرف نسبة العمولة قبل قبول الرحلة
|
||||||
|
|
||||||
|
### 💵 نظام البقشيش (Tips)
|
||||||
|
|
||||||
|
- 💵 إضافة بقشيش للسائق بعد الرحلة
|
||||||
|
- 📱 عبر المحفظة الإلكترونية أو نقداً
|
||||||
|
- ⭐ تشجيع للسائقين على تقديم خدمة ممتازة
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ البنية التحتية (Infrastructure)
|
||||||
|
|
||||||
|
### 🏗️ معمارية النظام
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ تطبيقات Flutter │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
|
||||||
|
│ │ siro_rider│ │siro_driver│ │siro_admin│ │siro_service│ │
|
||||||
|
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
|
||||||
|
└───────┼──────────────┼──────────────┼──────────────┼─────────┘
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ API Gateway (Nginx) │
|
||||||
|
│ Load Balancer + SSL Termination │
|
||||||
|
└──────────────────────────┬───────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌───────────────┼───────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────────────┐ ┌──────────┐ ┌──────────────────┐
|
||||||
|
│ API Server 1 │ │API Server│ │ Socket Server │
|
||||||
|
│ (8 Core/16GB) │ │ 2 │ │ (8 Core/16GB) │
|
||||||
|
│ PHP-FPM + Nginx│ │ (Mirror) │ │ Driver+Passenger│
|
||||||
|
└────────┬─────────┘ └────┬─────┘ └────────┬─────────┘
|
||||||
|
│ │ │
|
||||||
|
└────────────────┼────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────┼──────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────────────┐ ┌──────────┐ ┌──────────────────┐
|
||||||
|
│ MySQL Server │ │ Redis │ │ OSRM Server │
|
||||||
|
│ (16 Core/64GB) │ │ 8Core/32G│ │ (16 Core/32GB) │
|
||||||
|
│ 1TB NVMe + 10GbE│ │ 10GbE │ │ Syria OSM Map │
|
||||||
|
└──────────────────┘ └──────────┘ └──────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────┴──────────┐
|
||||||
|
│ Wallet Server │
|
||||||
|
│ (4 Core/8GB) │
|
||||||
|
│ Payment Gateway │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📊 مواصفات الخوادم المقترحة
|
||||||
|
|
||||||
|
| السيرفر | المعالج | الرام | التخزين | الشبكة | العدد |
|
||||||
|
|---------|---------|-------|---------|--------|-------|
|
||||||
|
| Load Balancer | 2 Core | 4 GB | 50 GB SSD | 1 Gbps | 1 |
|
||||||
|
| API Server | 8 Core | 16 GB | 100 GB NVMe | 1 Gbps | 2 |
|
||||||
|
| MySQL | 16 Core | 64 GB | 1 TB NVMe | 10 Gbps | 1 |
|
||||||
|
| Redis | 8 Core | 32 GB | 100 GB NVMe | 10 Gbps | 1 |
|
||||||
|
| Socket Server | 8 Core | 16 GB | 50 GB SSD | 1 Gbps | 1 |
|
||||||
|
| Wallet Server | 4 Core | 8 GB | 50 GB SSD | 1 Gbps | 1 |
|
||||||
|
| OSRM Server | 16 Core | 32 GB | 200 GB NVMe | 1 Gbps | 1 |
|
||||||
|
| **التكلفة الشهرية** | | | | | **$900-1,890** |
|
||||||
|
|
||||||
|
### 🌐 طوبولوجيا الشبكة
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐
|
||||||
|
│ Internet │
|
||||||
|
└──────┬───────┘
|
||||||
|
│
|
||||||
|
┌──────┴───────┐
|
||||||
|
│ Firewall │
|
||||||
|
│ (iptables) │
|
||||||
|
└──────┬───────┘
|
||||||
|
│
|
||||||
|
┌──────┴───────┐
|
||||||
|
│Load Balancer │
|
||||||
|
│ Public IP │
|
||||||
|
└──────┬───────┘
|
||||||
|
│
|
||||||
|
┌─────────────────┼─────────────────┐
|
||||||
|
│ │ │
|
||||||
|
┌──────┴───────┐ ┌─────┴───────┐ ┌──────┴───────┐
|
||||||
|
│ API SRV 1 │ │ API SRV 2 │ │ Socket SV │
|
||||||
|
│ 10.0.1.10 │ │ 10.0.1.11 │ │ 10.0.1.20-21 │
|
||||||
|
└──────┬───────┘ └─────┬───────┘ └──────┬───────┘
|
||||||
|
└────────────────┼─────────────────┘
|
||||||
|
│
|
||||||
|
┌────────────────┼─────────────────┐
|
||||||
|
│ │ │
|
||||||
|
┌──────┴───────┐ ┌────┴───────┐ ┌──────┴───────┐
|
||||||
|
│ MySQL DB │ │ Redis │ │ OSRM │
|
||||||
|
│ 10.0.1.30 │ │ 10.0.1.31 │ │ 10.0.1.40 │
|
||||||
|
└──────────────┘ └────────────┘ └──────────────┘
|
||||||
|
|
||||||
|
┌──────────────┐ ┌────────────┐
|
||||||
|
│ Wallet SV │ │ Map SaaS │
|
||||||
|
│ 10.0.1.50 │ │ 10.0.1.60 │
|
||||||
|
└──────────────┘ └────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔒 الإجراءات الأمنية للبنية التحتية
|
||||||
|
|
||||||
|
| الإجراء | الوصف |
|
||||||
|
|---------|-------|
|
||||||
|
| **Firewall** | فتح المنافذ: 80/443 (عام) + 2020/3030 (WebSocket) + 22 (SSH مقيّد) |
|
||||||
|
| **Fail2ban** | حماية SSH و API من هجمات القوة الغاشمة |
|
||||||
|
| **UFW/iptables** | حجب جميع المنافذ غير المستخدمة على كل سيرفر |
|
||||||
|
| **VPN** | وصول آمن للإدارة عبر Tailscale أو WireGuard |
|
||||||
|
| **SSL/TLS** | جميع الاتصالات الخارجية مشفرة |
|
||||||
|
| **Internal VLAN** | شبكة داخلية خاصة (10.0.1.0/24) بين جميع الخوادم |
|
||||||
|
| **MySQL Access** | مستخدم مخصص بصلاحيات محدودة من API Servers فقط |
|
||||||
|
| **Redis Password** | requirepass + rename-command FLUSHALL |
|
||||||
|
| **.env Permissions** | صلاحيات 600 لملف .env |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 المقارنة التنافسية (Competitive Analysis)
|
||||||
|
|
||||||
|
| الميزة | **Siro (سِيرُو)** | أوبر (Uber) | كريم (Careem) | Bolt |
|
||||||
|
|--------|-------------------|-------------|---------------|------|
|
||||||
|
| 🌍 التوسع الإقليمي | سوريا، الأردن، مصر | عالمي | إقليمي | عالمي |
|
||||||
|
| 🚙 أنواع المركبات | **12 نوعاً** | 5 أنواع | 6 أنواع | 4 أنواع |
|
||||||
|
| 💳 طرق الدفع المحلية | **7 طرق** (MTN, Syriatel, E-Cash) | 4 طرق | 5 طرق | 3 طرق |
|
||||||
|
| 👩 سائقات نساء | ✅ **نعم** | ✅ نعم | ✅ نعم | ❌ لا |
|
||||||
|
| 📱 تطبيقات متصلة | **4 تطبيقات** | تطبيقان | تطبيقان | تطبيقان |
|
||||||
|
| 🤖 ذكاء اصطناعي للتوثيق | **Azure + OpenAI + Llama** | أساسي | أساسي | ❌ لا |
|
||||||
|
| 🎯 تراكب الأندرويد | **نعم — فوق أي تطبيق** | لا | لا | لا |
|
||||||
|
| 🗺️ خريطة مخصصة | **Map SaaS خاص** | Google فقط | Google فقط | Google فقط |
|
||||||
|
| 📊 لوحة تحكم إدارية | **ويب كامل — 15+ وحدة** | محدود | محدود | محدود |
|
||||||
|
| 💬 دردشة بدون رقم | ✅ نعم | ✅ نعم | ✅ نعم | ❌ لا |
|
||||||
|
| 🆘 زر طوارئ + فيديو | ✅ **Agora + WebRTC** | SOS فقط | SOS فقط | ❌ لا |
|
||||||
|
| 💰 محفظة إلكترونية | ✅ راكب + سائق | محدود | ✅ نعم | ❌ لا |
|
||||||
|
| 🏷️ نظام إحالة متكامل | ✅ راكب + سائق | ✅ | ✅ | ✅ |
|
||||||
|
| 🔒 بصمة جهاز + JWT | ✅ **أمان متعدد الطبقات** | أساسي | أساسي | أساسي |
|
||||||
|
| 📦 توصيل طلبات (Delivery) | ✅ نعم | ✅ نعم | ✅ نعم | ❌ لا |
|
||||||
|
| ⚡ تطبيق إدارة ميداني | ✅ **Siro Service** | لا | لا | لا |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 فرص النمو والتوسع
|
||||||
|
|
||||||
|
### على المدى القصير (3-6 أشهر)
|
||||||
|
|
||||||
|
| الفرصة | الوصف | التأثير المتوقع |
|
||||||
|
|--------|-------|----------------|
|
||||||
|
| 🌍 التوسع لدول جديدة | دول الخليج، شمال أفريقيا | 5x قاعدة المستخدمين |
|
||||||
|
| 🚚 خدمات لوجستية | نقل بضائع، شحن، توصيل طرود | إيرادات إضافية 30%+ |
|
||||||
|
| 🛵 توصيل طلبات للمطاعم | Siro Food - مشابه لـ Uber Eats | إيرادات إضافية 50%+ |
|
||||||
|
| 🤖 تحسين التنبؤ بالطلب | AI Demand Forecasting | تقليل وقت الانتظار 40% |
|
||||||
|
|
||||||
|
### على المدى المتوسط (6-12 شهراً)
|
||||||
|
|
||||||
|
| الفرصة | الوصف |
|
||||||
|
|--------|-------|
|
||||||
|
| 💳 إضافة بوابات دفع جديدة | المزيد من خيارات الدفع المحلية |
|
||||||
|
| 🎯 برامج ولاء متقدمة | نقاط مكافآت، خصومات مخصصة |
|
||||||
|
| 🚗 تأجير السيارات | Siro Rent - خدمة تأجير السيارات |
|
||||||
|
| 🏢 خدمات النقل للشركات | B2B Corporate Transport Solutions |
|
||||||
|
| 🔐 برنامج مكافآت الأخطاء | Bug Bounty Program لتعزيز الأمان |
|
||||||
|
|
||||||
|
### على المدى الطويل (12-24 شهراً)
|
||||||
|
|
||||||
|
| الفرصة | الوصف |
|
||||||
|
|--------|-------|
|
||||||
|
| 🤖 قيادة ذاتية | Autonomous Vehicle Integration |
|
||||||
|
| 🛸 توصيل بالطائرات المسيرة | Drone Delivery Services |
|
||||||
|
| 🌍 منصة مفتوحة (Open Platform) | API عام لمطوري الطرف الثالث |
|
||||||
|
| 💳 بنك رقمي | Siro Fintech - خدمات مالية رقمية |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 نقاط القوة الرئيسية — لماذا Siro؟
|
||||||
|
|
||||||
|
### 1️⃣ 🏗️ بنية تحتية مرنة ومخصصة
|
||||||
|
|
||||||
|
- **خوادم مخصصة لكل دولة**: routing مختلف لسوريا، الأردن، مصر
|
||||||
|
- **خريطة خاصة (Map SaaS)**: لا تعتمد كلياً على Google Maps — استقلالية تامة
|
||||||
|
- **WebSockets مزدوجة**: فصل تام بين اتصالات السائق والراكب لتجنب الازدحام
|
||||||
|
- **Redis Pipeline**: تجميع الأحداث كل 500ms لتقليل الحمل
|
||||||
|
- **Auto-scaling**: بنية قابلة للتوسع الأفقي
|
||||||
|
|
||||||
|
### 2️⃣ 🧠 ذكاء اصطناعي مدمج
|
||||||
|
|
||||||
|
- **توثيق آلي** للسائقين الجدد — يقلل وقت التسجيل من أيام إلى دقائق
|
||||||
|
- **Azure + OpenAI + Llama**: ثلاث محركات ذكاء اصطناعي تعمل معاً لضمان أعلى دقة مع Fallback تلقائي
|
||||||
|
- قابلية التوسع لإضافة حالات استخدام AI جديدة
|
||||||
|
|
||||||
|
### 3️⃣ 💳 حلول دفع محلية مبتكرة
|
||||||
|
|
||||||
|
- دعم **MTN و Syriatel** في سوريا (حلول دفع محلية فريدة)
|
||||||
|
- دعم **E-Cash** في مصر
|
||||||
|
- **PayMob** للبطاقات الائتمانية
|
||||||
|
- **Stripe** للدفع الدولي
|
||||||
|
- **محفظة إلكترونية** مزدوجة للراكب والسائق مع تحويلات داخلية
|
||||||
|
- بنية S2S آمنة مع HMAC + JWT
|
||||||
|
|
||||||
|
### 4️⃣ 🎯 تجربة سائق فريدة
|
||||||
|
|
||||||
|
- **تراكب Android** يعرض الطلبات حتى فوق التطبيقات الأخرى — لا يفوت السائق أي طلب
|
||||||
|
- **خدمة خلفية دائمة (Foreground Service)** — الموقع محدث 24/7
|
||||||
|
- **توجيه صوتي (Voice Navigation)** مع إرشادات مفصلة TTS
|
||||||
|
- **مؤقت انتظار أوتوماتيكي** للركاب مع 15 ثانية للقبول
|
||||||
|
- **إحصائيات الأرباح** لحظية مع تقارير مالية شاملة
|
||||||
|
|
||||||
|
### 5️⃣ 📊 إدارة شاملة
|
||||||
|
|
||||||
|
- **15+ وحدة إدارية** في لوحة التحكم (أكثر من أي منافس)
|
||||||
|
- **تحليلات متقدمة** وتقارير مالية فورية
|
||||||
|
- **إدارة السائقين والركاب** بكفاءة مع مراقبة الجودة
|
||||||
|
- **القوائم السوداء** و **بطاقات أداء السائقين**
|
||||||
|
- **سجلات التدقيق (Audit Logs)** للأمان والمساءلة
|
||||||
|
|
||||||
|
### 6️⃣ 🌍 دعم متعدد اللغات والعملات
|
||||||
|
|
||||||
|
- دعم اللغة العربية والإنكليزية كامل
|
||||||
|
- واجهات مترجمة بالكامل للتطبيقات الأربعة
|
||||||
|
- محتوى مترجم للدول المختلفة
|
||||||
|
- دعم العملات المتعددة (ليرة سورية، دينار أردني، جنيه مصري)
|
||||||
|
|
||||||
|
### 7️⃣ 🔒 أمان عالي المستوى — ميزة تنافسية
|
||||||
|
|
||||||
|
- **JWT مع بصمة الجهاز الفريدة** — أمان متعدد الطبقات
|
||||||
|
- **HMAC لطلبات الدفع** — حماية S2S
|
||||||
|
- **تشفير AES-256-CBC مع IV عشوائي** — حماية البيانات الحساسة
|
||||||
|
- **Auto-refresh JWT** — منع قطع الجلسة مع أمان مستمر
|
||||||
|
- **Rate Limiting** — تحديد معدل المحاولات والحماية من الهجمات
|
||||||
|
- **Certificate Pinning** — منع هجمات الوسيط (MITM)
|
||||||
|
- **Audit Logging** — سجل تدقيق شامل لجميع العمليات
|
||||||
|
|
||||||
|
### 8️⃣ 🚚 تنوع خدمات النقل
|
||||||
|
|
||||||
|
- من التوصيل السريع بالدراجة النارية إلى النقل العائلي والفان
|
||||||
|
- **سائقات نساء** — خيار خاص يحترم خصوصية السيدات (ميزة نادرة)
|
||||||
|
- **سيارات كهربائية** — خيار صديق للبيئة (استباقي)
|
||||||
|
- **12 نوع مركبة** — أكبر تنوع مقارنة بالمنافسين
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ الخلاصة والتقرير النهائي
|
||||||
|
|
||||||
|
### لماذا Siro هي الخيار الأفضل؟
|
||||||
|
|
||||||
|
| المعيار | التقييم |
|
||||||
|
|---------|---------|
|
||||||
|
| **التغطية الإقليمية** | ⭐⭐⭐⭐⭐ 3 دول مع دعم محلي كامل |
|
||||||
|
| **تنوع الخدمات** | ⭐⭐⭐⭐⭐ 12 نوع مركبة + 4 تطبيقات |
|
||||||
|
| **طرق الدفع** | ⭐⭐⭐⭐⭐ 7 طرق دفع محلية وعالمية |
|
||||||
|
| **الأمان** | ⭐⭐⭐⭐⭐ 5 طبقات أمان متكاملة |
|
||||||
|
| **الذكاء الاصطناعي** | ⭐⭐⭐⭐⭐ 3 محركات AI للتوثيق التلقائي |
|
||||||
|
| **لوحة التحكم** | ⭐⭐⭐⭐⭐ 15+ وحدة إدارية |
|
||||||
|
| **البنية التحتية** | ⭐⭐⭐⭐⭐ خوادم مخصصة لكل دولة |
|
||||||
|
| **تجربة السائق** | ⭐⭐⭐⭐⭐ Android Overlay + Voice Nav |
|
||||||
|
| **الدعم المحلي** | ⭐⭐⭐⭐⭐ فهم عميق لاحتياجات السوق العربي |
|
||||||
|
|
||||||
|
### 📊 الأرقام النهائية
|
||||||
|
|
||||||
|
| المقياس | القيمة |
|
||||||
|
|---------|--------|
|
||||||
|
| دول التشغيل | 3 |
|
||||||
|
| تطبيقات متصلة | 4 |
|
||||||
|
| أنواع المركبات | 12 |
|
||||||
|
| طرق الدفع | 7 |
|
||||||
|
| خدمات التكامل | 15+ |
|
||||||
|
| ملفات PHP | 395+ |
|
||||||
|
| قواعد بيانات | 3 |
|
||||||
|
| خوادم مخصصة | 10+ |
|
||||||
|
| طبقات أمان | 5 |
|
||||||
|
| محركات AI | 3 |
|
||||||
|
| وحدات إدارية | 15+ |
|
||||||
|
| التكلفة الشهرية للبنية | $900-1,890 |
|
||||||
|
| السعة الاستيعابية | 10,000+ مستخدم متزامن |
|
||||||
|
|
||||||
|
### 🎯 الرسالة التسويقية الأساسية
|
||||||
|
|
||||||
|
> **Siro (سِيرُو) ليست مجرد تطبيق نقل — إنها منصة متكاملة للنقل الذكي تجمع بين:**
|
||||||
|
>
|
||||||
|
> ✅ **4 تطبيقات متصلة** تغطي كل احتياجات النقل
|
||||||
|
> ✅ **12 نوع مركبة و 7 طرق دفع** لتغطية جميع احتياجات المستخدمين
|
||||||
|
> ✅ **ذكاء اصطناعي متقدم** لتسريع التوثيق وتحسين الخدمة
|
||||||
|
> ✅ **نظام توزيع ذكي** مع خرائط حية وتتبع مباشر
|
||||||
|
> ✅ **لوحة تحكم إدارية** بمستوى مؤسسي (15+ وحدة)
|
||||||
|
> ✅ **5 طبقات أمان** لحماية البيانات والمعاملات المالية
|
||||||
|
> ✅ **حلول دفع محلية** مبتكرة تفهم احتياجات السوق
|
||||||
|
> ✅ **بنية تحتية** بتكلفة تشغيل $900-1,890 شهرياً
|
||||||
|
|
||||||
|
### 🔐 الأمان كعلامة تجارية
|
||||||
|
|
||||||
|
في عالم تتصدر فيه خروقات البيانات عناوين الأخبار، **Siro تضع الأمان في صميم منتجها**:
|
||||||
|
|
||||||
|
- **5 طبقات أمان متكاملة** — من المصادقة إلى المراقبة
|
||||||
|
- **تشفير AES-256-CBC مع IV عشوائي** — معيار صناعي
|
||||||
|
- **JWT + بصمة جهاز + MFA** — أمان متعدد العوامل
|
||||||
|
- **HMAC + S2S للمدفوعات** — حماية مالية على مستوى المؤسسات
|
||||||
|
- **سجل تدقيق شامل** — مساءلة وشفافية كاملة
|
||||||
|
- **اختبارات اختراق منتظمة** — تحسين مستمر
|
||||||
|
|
||||||
|
> **Siro — وجهتك الذكية لكل رحلة** 🚀
|
||||||
|
>
|
||||||
|
> *النقل الذكي | الأمان أولاً | حلول محلية | تقنيات عالمية*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*تم إعداد هذا التقرير ليكون دليلاً تسويقياً وأمنياً شاملاً لمنصة Siro (سِيرُو)*
|
||||||
|
*جميع المعلومات المذكورة تستند إلى الكود المصدري والتوثيق الفني للمنصة*
|
||||||
|
|
||||||
|
**تاريخ التقرير:** 17 يونيو 2026
|
||||||
|
**الإصدار:** 1.0
|
||||||
|
**التصنيف:** 📋 تقرير تسويقي وأمني شامل
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -102,6 +102,14 @@ MAIL_PASS=<CHANGE_ME_EMAIL_PASSWORD>
|
|||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
APP_NAME=Siro
|
APP_NAME=Siro
|
||||||
|
APP_DOMAIN=api-syria.siromove.com
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Nabeh Integration (server-to-server API key)
|
||||||
|
# Must match NABEH_API_KEY in Nabeh's .env
|
||||||
|
# =============================================================================
|
||||||
|
NABEH_API_KEY=<CHANGE_ME_SHARED_SECRET>
|
||||||
|
SECRET_KEY_HMAC=<CHANGE_ME_HMAC_SECRET_FOR_SIGNED_URLS>
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Security Configuration - Fingerprint
|
# Security Configuration - Fingerprint
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ if (empty($rawDriverID)) {
|
|||||||
$driverID = basename($rawDriverID);
|
$driverID = basename($rawDriverID);
|
||||||
|
|
||||||
if (isset($_FILES['image'])) {
|
if (isset($_FILES['image'])) {
|
||||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||||
'size' => $_FILES['image']['size'] ?? 0,
|
'size' => $_FILES['image']['size'] ?? 0,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ if (empty($rawDriverID)) {
|
|||||||
$driverID = basename($rawDriverID);
|
$driverID = basename($rawDriverID);
|
||||||
|
|
||||||
if (isset($_FILES['image'])) {
|
if (isset($_FILES['image'])) {
|
||||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||||
'size' => $_FILES['image']['size'] ?? 0,
|
'size' => $_FILES['image']['size'] ?? 0,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ require_once __DIR__ . '/../../connect.php'; // يجب أن يوفّر: $con (ا
|
|||||||
const MAX_FILE_MB = 5;
|
const MAX_FILE_MB = 5;
|
||||||
const ALLOWED_MIMES = ['image/jpeg','image/png','image/webp']; // فقط صور
|
const ALLOWED_MIMES = ['image/jpeg','image/png','image/webp']; // فقط صور
|
||||||
const UPLOAD_ROOT = __DIR__ . "/../../private_uploads"; // مجلد خاص (غير عام)
|
const UPLOAD_ROOT = __DIR__ . "/../../private_uploads"; // مجلد خاص (غير عام)
|
||||||
const SIGN_SECRET = getenv('SECRET_KEY_HMAC'); // غيّرها واقرأها من .env
|
$SIGN_SECRET = getenv('SECRET_KEY_HMAC'); // غيّرها واقرأها من .env
|
||||||
$host = getenv('APP_DOMAIN') ?: 'api-syria.siromove.com';
|
$host = getenv('APP_DOMAIN');
|
||||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
|
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
|
||||||
define('PUBLIC_BASE', "$protocol://$host/siro");
|
define('PUBLIC_BASE', "$protocol://$host/siro");
|
||||||
const SIGNED_TTL_SEC = 172800; // 2 days = 60*60*24
|
const SIGNED_TTL_SEC = 172800; // 2 days = 60*60*24
|
||||||
@@ -54,7 +54,7 @@ if (!in_array($docType, $allowedDocTypes, true)) {
|
|||||||
|
|
||||||
// --------- التحقق من الملف ---------
|
// --------- التحقق من الملف ---------
|
||||||
if (isset($_FILES['file'])) {
|
if (isset($_FILES['file'])) {
|
||||||
uploadLog("$_FILES['file'] metadata", 'INFO', [
|
uploadLog('$_FILES[\'file\'] metadata', 'INFO', [
|
||||||
'name' => $_FILES['file']['name'] ?? 'unknown',
|
'name' => $_FILES['file']['name'] ?? 'unknown',
|
||||||
'type' => $_FILES['file']['type'] ?? 'unknown',
|
'type' => $_FILES['file']['type'] ?? 'unknown',
|
||||||
'size' => $_FILES['file']['size'] ?? 0,
|
'size' => $_FILES['file']['size'] ?? 0,
|
||||||
|
|||||||
@@ -2,5 +2,11 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"vlucas/phpdotenv": "^5.6",
|
"vlucas/phpdotenv": "^5.6",
|
||||||
"firebase/php-jwt": "^6.0"
|
"firebase/php-jwt": "^6.0"
|
||||||
|
},
|
||||||
|
"prefer-stable": true,
|
||||||
|
"config": {
|
||||||
|
"audit": {
|
||||||
|
"ignore": ["PKSA-y2cr-5h3j-g3ys"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
202
backend/composer.lock
generated
@@ -4,28 +4,91 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "108be68e4e2b97fed51d36a10eed0849",
|
"content-hash": "e192df06759c90826eeb518a1ea5f0c8",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "graham-campbell/result-type",
|
"name": "firebase/php-jwt",
|
||||||
"version": "v1.1.2",
|
"version": "v6.11.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
"url": "https://github.com/googleapis/php-jwt.git",
|
||||||
"reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862"
|
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862",
|
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
||||||
"reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862",
|
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/guzzle": "^7.4",
|
||||||
|
"phpspec/prophecy-phpunit": "^2.0",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"psr/cache": "^2.0||^3.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"psr/http-factory": "^1.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||||
|
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Firebase\\JWT\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Neuman Vong",
|
||||||
|
"email": "neuman+pear@twilio.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anant Narayanan",
|
||||||
|
"email": "anant@php.net",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||||
|
"homepage": "https://github.com/firebase/php-jwt",
|
||||||
|
"keywords": [
|
||||||
|
"jwt",
|
||||||
|
"php"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/googleapis/php-jwt/issues",
|
||||||
|
"source": "https://github.com/googleapis/php-jwt/tree/v6.11.1"
|
||||||
|
},
|
||||||
|
"time": "2025-04-09T20:32:01+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "graham-campbell/result-type",
|
||||||
|
"version": "v1.1.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||||
|
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||||
|
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2.5 || ^8.0",
|
"php": "^7.2.5 || ^8.0",
|
||||||
"phpoption/phpoption": "^1.9.2"
|
"phpoption/phpoption": "^1.9.5"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
"phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -54,7 +117,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2"
|
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -66,20 +129,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-11-12T22:16:48+00:00"
|
"time": "2025-12-27T19:43:20+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.9.2",
|
"version": "1.9.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/schmittjoh/php-option.git",
|
"url": "https://github.com/schmittjoh/php-option.git",
|
||||||
"reference": "80735db690fe4fc5c76dfa7f9b770634285fa820"
|
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820",
|
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||||
"reference": "80735db690fe4fc5c76dfa7f9b770634285fa820",
|
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -87,13 +150,13 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
"phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"bamarni-bin": {
|
"bamarni-bin": {
|
||||||
"bin-links": true,
|
"bin-links": true,
|
||||||
"forward-command": true
|
"forward-command": false
|
||||||
},
|
},
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.9-dev"
|
"dev-master": "1.9-dev"
|
||||||
@@ -129,7 +192,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.2"
|
"source": "https://github.com/schmittjoh/php-option/tree/1.9.5"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -141,24 +204,24 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-11-12T21:59:55+00:00"
|
"time": "2025-12-27T19:41:33+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.29.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1"
|
"php": ">=7.2"
|
||||||
},
|
},
|
||||||
"provide": {
|
"provide": {
|
||||||
"ext-ctype": "*"
|
"ext-ctype": "*"
|
||||||
@@ -169,8 +232,8 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"url": "https://github.com/symfony/polyfill",
|
||||||
"url": "https://github.com/symfony/polyfill"
|
"name": "symfony/polyfill"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -204,7 +267,7 @@
|
|||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -215,29 +278,34 @@
|
|||||||
"url": "https://github.com/fabpot",
|
"url": "https://github.com/fabpot",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-01-29T20:11:03+00:00"
|
"time": "2026-04-10T16:19:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.29.0",
|
"version": "v1.38.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
|
||||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1"
|
"ext-iconv": "*",
|
||||||
|
"php": ">=7.2"
|
||||||
},
|
},
|
||||||
"provide": {
|
"provide": {
|
||||||
"ext-mbstring": "*"
|
"ext-mbstring": "*"
|
||||||
@@ -248,8 +316,8 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"url": "https://github.com/symfony/polyfill",
|
||||||
"url": "https://github.com/symfony/polyfill"
|
"name": "symfony/polyfill"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -284,7 +352,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -295,35 +363,39 @@
|
|||||||
"url": "https://github.com/fabpot",
|
"url": "https://github.com/fabpot",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-01-29T20:11:03+00:00"
|
"time": "2026-05-27T06:59:30+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php80",
|
"name": "symfony/polyfill-php80",
|
||||||
"version": "v1.29.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1"
|
"php": ">=7.2"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"thanks": {
|
"thanks": {
|
||||||
"name": "symfony/polyfill",
|
"url": "https://github.com/symfony/polyfill",
|
||||||
"url": "https://github.com/symfony/polyfill"
|
"name": "symfony/polyfill"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -364,7 +436,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -375,35 +447,39 @@
|
|||||||
"url": "https://github.com/fabpot",
|
"url": "https://github.com/fabpot",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-01-29T20:11:03+00:00"
|
"time": "2026-04-10T16:19:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vlucas/phpdotenv",
|
"name": "vlucas/phpdotenv",
|
||||||
"version": "v5.6.0",
|
"version": "v5.6.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||||
"reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4"
|
"reference": "955e7815d677a3eaa7075231212f2110983adecc"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
|
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc",
|
||||||
"reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
|
"reference": "955e7815d677a3eaa7075231212f2110983adecc",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-pcre": "*",
|
"ext-pcre": "*",
|
||||||
"graham-campbell/result-type": "^1.1.2",
|
"graham-campbell/result-type": "^1.1.4",
|
||||||
"php": "^7.2.5 || ^8.0",
|
"php": "^7.2.5 || ^8.0",
|
||||||
"phpoption/phpoption": "^1.9.2",
|
"phpoption/phpoption": "^1.9.5",
|
||||||
"symfony/polyfill-ctype": "^1.24",
|
"symfony/polyfill-ctype": "^1.26",
|
||||||
"symfony/polyfill-mbstring": "^1.24",
|
"symfony/polyfill-mbstring": "^1.26",
|
||||||
"symfony/polyfill-php80": "^1.24"
|
"symfony/polyfill-php80": "^1.26"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
@@ -417,7 +493,7 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"bamarni-bin": {
|
"bamarni-bin": {
|
||||||
"bin-links": true,
|
"bin-links": true,
|
||||||
"forward-command": true
|
"forward-command": false
|
||||||
},
|
},
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.6-dev"
|
||||||
@@ -452,7 +528,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0"
|
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -464,16 +540,16 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-11-12T22:43:29+00:00"
|
"time": "2025-12-27T19:49:13+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": {},
|
||||||
"prefer-stable": false,
|
"prefer-stable": true,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": {},
|
||||||
"platform-dev": [],
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.9.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "../../../development/App/Siro"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "../../../development/App/siro_driver"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "../../../development/App/siro_admin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "../../../development/App/service_siro"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
91
backend/nabeh/driver_status.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Nabeh Integration — Driver Status Check
|
||||||
|
*
|
||||||
|
* Called by Nabeh AI platform to check driver registration/activation status.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||||
|
|
||||||
|
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||||
|
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||||
|
|
||||||
|
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$phone = $_GET['phone'] ?? '';
|
||||||
|
|
||||||
|
if (empty($phone)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Phone parameter required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = Database::get('main');
|
||||||
|
global $encryptionHelper;
|
||||||
|
|
||||||
|
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT d.id, d.phone, d.first_name, d.last_name, d.status, d.created_at,
|
||||||
|
cr.id as car_id, cr.make, cr.model, cr.year, cr.car_plate, cr.status as car_status
|
||||||
|
FROM driver d
|
||||||
|
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||||
|
WHERE d.phone = :phone
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':phone' => $encryptedPhone,
|
||||||
|
]);
|
||||||
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => null,
|
||||||
|
'message' => 'Driver not found'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decryptedPhone = $encryptionHelper->decryptData($result['phone']);
|
||||||
|
$decryptedFirstName = $encryptionHelper->decryptData($result['first_name']);
|
||||||
|
$decryptedLastName = $encryptionHelper->decryptData($result['last_name']);
|
||||||
|
|
||||||
|
$docStmt = $db->prepare("SELECT doc_type, link FROM driver_documents WHERE driverID = :driverID");
|
||||||
|
$docStmt->execute([':driverID' => $result['id']]);
|
||||||
|
$documents = $docStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => [
|
||||||
|
'driver_id' => $result['id'],
|
||||||
|
'phone' => $decryptedPhone,
|
||||||
|
'name' => trim($decryptedFirstName . ' ' . $decryptedLastName),
|
||||||
|
'status' => $result['status'],
|
||||||
|
'registered_at' => $result['created_at'],
|
||||||
|
'car' => [
|
||||||
|
'id' => $result['car_id'],
|
||||||
|
'make' => $result['make'],
|
||||||
|
'model' => $result['model'],
|
||||||
|
'year' => $result['year'],
|
||||||
|
'plate' => $result['car_plate'],
|
||||||
|
'status' => $result['car_status'],
|
||||||
|
],
|
||||||
|
'documents' => $documents,
|
||||||
|
]
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
error_log("[Nabeh Status Error] " . $e->getMessage());
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Internal server error']);
|
||||||
|
}
|
||||||
252
backend/nabeh/query.php
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Nabeh Integration — Unified Query API
|
||||||
|
*
|
||||||
|
* Called by Nabeh AI platform to query driver info, trips, stats, and trip details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||||
|
|
||||||
|
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||||
|
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||||
|
|
||||||
|
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized: invalid API key']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = [];
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$raw = file_get_contents('php://input');
|
||||||
|
$input = json_decode($raw, true) ?: [];
|
||||||
|
} else {
|
||||||
|
$input = $_GET;
|
||||||
|
}
|
||||||
|
|
||||||
|
$queryType = $input['query_type'] ?? '';
|
||||||
|
$phone = preg_replace('/[^0-9]/', '', $input['phone'] ?? '');
|
||||||
|
$driverId = $input['driver_id'] ?? '';
|
||||||
|
$tripId = $input['trip_id'] ?? '';
|
||||||
|
$limit = min((int)($input['limit'] ?? 10), 50);
|
||||||
|
|
||||||
|
if (empty($queryType)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'query_type is required. Options: driver_info, driver_trips, driver_stats, trip_detail']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validTypes = ['driver_info', 'driver_trips', 'driver_stats', 'trip_detail'];
|
||||||
|
if (!in_array($queryType, $validTypes, true)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Invalid query_type. Options: ' . implode(', ', $validTypes)]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
global $encryptionHelper;
|
||||||
|
$mainDb = Database::get('main');
|
||||||
|
$rideDb = Database::get('ride');
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
if ($queryType === 'driver_info') {
|
||||||
|
if (empty($phone)) {
|
||||||
|
jsonError('phone parameter is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
|
$stmt = $mainDb->prepare("
|
||||||
|
SELECT d.id, d.phone, d.first_name, d.last_name, d.name_arabic,
|
||||||
|
d.status, d.created_at, d.birthdate, d.gender, d.site,
|
||||||
|
cr.id as car_id, cr.make, cr.model, cr.year, cr.car_plate,
|
||||||
|
cr.color, cr.color_hex, cr.fuel, cr.vin,
|
||||||
|
cr.status as car_status, cr.expiration_date
|
||||||
|
FROM driver d
|
||||||
|
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||||
|
WHERE d.phone = :phone OR d.email LIKE :phoneLike
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':phone' => $encryptedPhone,
|
||||||
|
':phoneLike' => $phone . '%',
|
||||||
|
]);
|
||||||
|
$driver = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$driver) {
|
||||||
|
echo json_encode(['status' => 'success', 'data' => null, 'message' => 'Driver not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decrypt = function($val) use ($encryptionHelper) {
|
||||||
|
return $val ? $encryptionHelper->decryptData($val) : $val;
|
||||||
|
};
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => [
|
||||||
|
'driver_id' => $driver['id'],
|
||||||
|
'phone' => $decrypt($driver['phone']),
|
||||||
|
'first_name' => $decrypt($driver['first_name']),
|
||||||
|
'last_name' => $decrypt($driver['last_name']),
|
||||||
|
'name_arabic' => $decrypt($driver['name_arabic']),
|
||||||
|
'gender' => $decrypt($driver['gender']),
|
||||||
|
'birthdate' => $driver['birthdate'],
|
||||||
|
'status' => $driver['status'],
|
||||||
|
'site' => $decrypt($driver['site']),
|
||||||
|
'registered_at' => $driver['created_at'],
|
||||||
|
'car' => $driver['car_id'] ? [
|
||||||
|
'id' => $driver['car_id'],
|
||||||
|
'make' => $driver['make'],
|
||||||
|
'model' => $driver['model'],
|
||||||
|
'year' => $driver['year'],
|
||||||
|
'plate' => $driver['car_plate'],
|
||||||
|
'color' => $driver['color'],
|
||||||
|
'color_hex' => $driver['color_hex'],
|
||||||
|
'fuel' => $driver['fuel'],
|
||||||
|
'vin' => $decrypt($driver['vin']),
|
||||||
|
'status' => $driver['car_status'],
|
||||||
|
'expiration_date' => $driver['expiration_date'],
|
||||||
|
] : null,
|
||||||
|
],
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
if ($queryType === 'driver_trips') {
|
||||||
|
if (empty($driverId) && empty($phone)) {
|
||||||
|
jsonError('driver_id or phone is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($driverId) && !empty($phone)) {
|
||||||
|
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
$stmt = $mainDb->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
|
||||||
|
$stmt->execute([':phone' => $encryptedPhone]);
|
||||||
|
$driverRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$driverRow) {
|
||||||
|
echo json_encode(['status' => 'success', 'data' => [], 'message' => 'Driver not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$driverId = $driverRow['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $rideDb->prepare("
|
||||||
|
SELECT id, start_location, end_location, date, time, endtime,
|
||||||
|
price, price_for_driver, price_for_passenger,
|
||||||
|
status, paymentMethod, carType, distance, created_at
|
||||||
|
FROM ride
|
||||||
|
WHERE driver_id = :driver_id
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT :lim
|
||||||
|
");
|
||||||
|
$stmt->bindValue(':driver_id', $driverId, PDO::PARAM_STR);
|
||||||
|
$stmt->bindValue(':lim', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$trips = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $trips,
|
||||||
|
'count' => count($trips),
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
if ($queryType === 'driver_stats') {
|
||||||
|
if (empty($driverId) && empty($phone)) {
|
||||||
|
jsonError('driver_id or phone is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($driverId) && !empty($phone)) {
|
||||||
|
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
$stmt = $mainDb->prepare("SELECT id FROM driver WHERE phone = :phone LIMIT 1");
|
||||||
|
$stmt->execute([':phone' => $encryptedPhone]);
|
||||||
|
$driverRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$driverRow) {
|
||||||
|
echo json_encode(['status' => 'success', 'data' => null, 'message' => 'Driver not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$driverId = $driverRow['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $rideDb->prepare("
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_trips,
|
||||||
|
COALESCE(SUM(price_for_driver), 0) as total_earnings,
|
||||||
|
COALESCE(SUM(price_for_passenger), 0) as total_collected,
|
||||||
|
COALESCE(AVG(price_for_driver), 0) as avg_earning_per_trip,
|
||||||
|
COALESCE(SUM(distance), 0) as total_distance,
|
||||||
|
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_trips,
|
||||||
|
COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled_trips
|
||||||
|
FROM ride
|
||||||
|
WHERE driver_id = :driver_id
|
||||||
|
");
|
||||||
|
$stmt->execute([':driver_id' => $driverId]);
|
||||||
|
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$driverStmt = $mainDb->prepare("SELECT status, created_at FROM driver WHERE id = :id LIMIT 1");
|
||||||
|
$driverStmt->execute([':id' => $driverId]);
|
||||||
|
$driverStatus = $driverStmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => [
|
||||||
|
'driver_id' => $driverId,
|
||||||
|
'status' => $driverStatus['status'] ?? 'unknown',
|
||||||
|
'registered_at' => $driverStatus['created_at'] ?? null,
|
||||||
|
'stats' => [
|
||||||
|
'total_trips' => (int)$stats['total_trips'],
|
||||||
|
'completed_trips' => (int)$stats['completed_trips'],
|
||||||
|
'cancelled_trips' => (int)$stats['cancelled_trips'],
|
||||||
|
'total_earnings' => (float)$stats['total_earnings'],
|
||||||
|
'total_collected' => (float)$stats['total_collected'],
|
||||||
|
'avg_earning_per_trip' => (float)$stats['avg_earning_per_trip'],
|
||||||
|
'total_distance_km' => (float)$stats['total_distance'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
if ($queryType === 'trip_detail') {
|
||||||
|
if (empty($tripId)) {
|
||||||
|
jsonError('trip_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $rideDb->prepare("
|
||||||
|
SELECT r.*,
|
||||||
|
p.first_name as passenger_first_name,
|
||||||
|
p.last_name as passenger_last_name,
|
||||||
|
p.phone as passenger_phone
|
||||||
|
FROM ride r
|
||||||
|
LEFT JOIN driver p ON p.id = r.passenger_id
|
||||||
|
WHERE r.id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([':id' => $tripId]);
|
||||||
|
$trip = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$trip) {
|
||||||
|
echo json_encode(['status' => 'success', 'data' => null, 'message' => 'Trip not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $trip,
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
error_log("[Nabeh Query Error] " . $e->getMessage());
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Internal server error']);
|
||||||
|
}
|
||||||
218
backend/nabeh/register.php
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Nabeh Integration — Driver Registration
|
||||||
|
*
|
||||||
|
* Called by Nabeh AI platform to register drivers in Siro.
|
||||||
|
* Authenticated via NABEH_API_KEY (shared between servers).
|
||||||
|
* Handles all 3 countries: Syria, Jordan, Egypt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||||
|
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||||
|
|
||||||
|
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized: invalid API key']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
$rateLimitFile = __DIR__ . '/../logs/nabeh_rate_' . md5($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '.lock';
|
||||||
|
$rateLimitWindow = 60;
|
||||||
|
$rateLimitMax = 5;
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
$attempts = [];
|
||||||
|
if (file_exists($rateLimitFile)) {
|
||||||
|
$attempts = json_decode(file_get_contents($rateLimitFile), true) ?: [];
|
||||||
|
$attempts = array_filter($attempts, fn($t) => $t > ($now - $rateLimitWindow));
|
||||||
|
}
|
||||||
|
if (count($attempts) >= $rateLimitMax) {
|
||||||
|
http_response_code(429);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Too many requests. Try again later.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$attempts[] = $now;
|
||||||
|
file_put_contents($rateLimitFile, json_encode($attempts), LOCK_EX);
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!$input) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Invalid JSON body']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$phone = preg_replace('/[^0-9]/', '', $input['phone'] ?? '');
|
||||||
|
$password = $input['password'] ?? substr(md5($phone . time()), 0, 12);
|
||||||
|
$firstName = $input['first_name'] ?? $input['name_arabic'] ?? '';
|
||||||
|
$lastName = $input['last_name'] ?? '-';
|
||||||
|
$nameArabic = $input['name_arabic'] ?? $firstName;
|
||||||
|
$nationalNumber = $input['national_number'] ?? '';
|
||||||
|
$birthdate = $input['birthdate'] ?? '';
|
||||||
|
$address = $input['address'] ?? '';
|
||||||
|
$gender = $input['gender'] ?? 'Male';
|
||||||
|
$site = $input['site'] ?? '';
|
||||||
|
|
||||||
|
$vin = $input['vin'] ?? '';
|
||||||
|
$carPlate = $input['car_plate'] ?? '';
|
||||||
|
$make = $input['make'] ?? '';
|
||||||
|
$model = $input['model'] ?? '';
|
||||||
|
$year = $input['year'] ?? '';
|
||||||
|
$color = $input['color'] ?? '';
|
||||||
|
$colorHex = $input['color_hex'] ?? '#000000';
|
||||||
|
$owner = $input['owner'] ?? '';
|
||||||
|
$fuel = $input['fuel'] ?? 'Petrol';
|
||||||
|
$expirationDate = $input['expiration_date'] ?? '';
|
||||||
|
|
||||||
|
$idFront = $input['id_front'] ?? $input['id_front_url'] ?? '';
|
||||||
|
$idBack = $input['id_back'] ?? $input['id_back_url'] ?? '';
|
||||||
|
$driverLicense = $input['driver_license'] ?? $input['driver_license_front_url'] ?? '';
|
||||||
|
$driverLicenseBack = $input['driver_license_back'] ?? $input['driver_license_back_url'] ?? '';
|
||||||
|
$carLicenseFront = $input['car_license_front'] ?? $input['vehicle_license_front_url'] ?? '';
|
||||||
|
$carLicenseBack = $input['car_license_back'] ?? $input['vehicle_license_back_url'] ?? '';
|
||||||
|
$criminalRecord = $input['criminal_record'] ?? $input['criminal_record_url'] ?? '';
|
||||||
|
$profilePicture = $input['profile_picture'] ?? '';
|
||||||
|
|
||||||
|
if (empty($phone)) {
|
||||||
|
jsonError('Phone number is required');
|
||||||
|
}
|
||||||
|
if (empty($firstName)) {
|
||||||
|
jsonError('First name is required');
|
||||||
|
}
|
||||||
|
if (empty($vin) || empty($carPlate) || empty($make) || empty($model) || empty($year)) {
|
||||||
|
jsonError('Car details (vin, plate, make, model, year) are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = Database::get('main');
|
||||||
|
$driverId = 'DRV' . date('YmdHis') . rand(100, 999);
|
||||||
|
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
|
||||||
|
|
||||||
|
global $encryptionHelper;
|
||||||
|
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
$encryptedFirstName = $encryptionHelper->encryptData($firstName);
|
||||||
|
$encryptedLastName = $encryptionHelper->encryptData($lastName);
|
||||||
|
$encryptedNameArabic = $encryptionHelper->encryptData($nameArabic);
|
||||||
|
$encryptedNationalNumber = !empty($nationalNumber) ? $encryptionHelper->encryptData($nationalNumber) : '';
|
||||||
|
$encryptedAddress = !empty($address) ? $encryptionHelper->encryptData($address) : '';
|
||||||
|
$encryptedGender = $encryptionHelper->encryptData($gender);
|
||||||
|
$encryptedVin = $encryptionHelper->encryptData($vin);
|
||||||
|
$encryptedCarPlate = $encryptionHelper->encryptData($carPlate);
|
||||||
|
$encryptedOwner = $encryptionHelper->encryptData($owner);
|
||||||
|
$encryptedSite = !empty($site) ? $encryptionHelper->encryptData($site) : $encryptedAddress;
|
||||||
|
|
||||||
|
$nowDate = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
$driverStmt = $db->prepare("
|
||||||
|
INSERT INTO driver (
|
||||||
|
id, phone, email, password, gender, first_name, last_name,
|
||||||
|
name_arabic, national_number, address, site, birthdate,
|
||||||
|
status, created_at, updated_at
|
||||||
|
) VALUES (
|
||||||
|
:id, :phone, :email, :password, :gender, :first_name, :last_name,
|
||||||
|
:name_arabic, :national_number, :address, :site, :birthdate,
|
||||||
|
'yet', :created_at, :updated_at
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$driverStmt->execute([
|
||||||
|
':id' => $driverId,
|
||||||
|
':phone' => $encryptedPhone,
|
||||||
|
':email' => $phone . '@intaleqapp.com',
|
||||||
|
':password' => $hashedPassword,
|
||||||
|
':gender' => $encryptedGender,
|
||||||
|
':first_name' => $encryptedFirstName,
|
||||||
|
':last_name' => $encryptedLastName,
|
||||||
|
':name_arabic' => $encryptedNameArabic,
|
||||||
|
':national_number' => $encryptedNationalNumber,
|
||||||
|
':address' => $encryptedAddress,
|
||||||
|
':site' => $encryptedSite,
|
||||||
|
':birthdate' => $birthdate,
|
||||||
|
':created_at' => $nowDate,
|
||||||
|
':updated_at' => $nowDate,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$carStmt = $db->prepare("
|
||||||
|
INSERT INTO CarRegistration (
|
||||||
|
driverID, vin, car_plate, make, model, year,
|
||||||
|
expiration_date, color, owner, color_hex, fuel,
|
||||||
|
isDefault, status, created_at, vehicle_category_id
|
||||||
|
) VALUES (
|
||||||
|
:driverID, :vin, :car_plate, :make, :model, :year,
|
||||||
|
:expiration_date, :color, :owner, :color_hex, :fuel,
|
||||||
|
1, 'yet', :created_at, 1
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$carStmt->execute([
|
||||||
|
':driverID' => $driverId,
|
||||||
|
':vin' => $encryptedVin,
|
||||||
|
':car_plate' => $encryptedCarPlate,
|
||||||
|
':make' => $make,
|
||||||
|
':model' => $model,
|
||||||
|
':year' => $year,
|
||||||
|
':expiration_date' => $expirationDate ?: $nowDate,
|
||||||
|
':color' => $color,
|
||||||
|
':owner' => $encryptedOwner,
|
||||||
|
':color_hex' => $colorHex,
|
||||||
|
':fuel' => $fuel,
|
||||||
|
':created_at' => $nowDate,
|
||||||
|
]);
|
||||||
|
$carRegId = $db->lastInsertId();
|
||||||
|
|
||||||
|
$docTypes = [
|
||||||
|
'id_front' => $idFront,
|
||||||
|
'id_back' => $idBack,
|
||||||
|
'driver_license' => $driverLicense,
|
||||||
|
'driver_license_back' => $driverLicenseBack,
|
||||||
|
'car_license_front' => $carLicenseFront,
|
||||||
|
'car_license_back' => $carLicenseBack,
|
||||||
|
'criminal_record' => $criminalRecord,
|
||||||
|
'profile_picture' => $profilePicture,
|
||||||
|
];
|
||||||
|
|
||||||
|
$docStmt = $db->prepare("
|
||||||
|
INSERT INTO driver_documents (driverID, doc_type, image_name, link, upload_date)
|
||||||
|
VALUES (:driverID, :doc_type, :image_name, :link, :upload_date)
|
||||||
|
");
|
||||||
|
foreach ($docTypes as $docType => $link) {
|
||||||
|
if (!empty($link)) {
|
||||||
|
$docStmt->execute([
|
||||||
|
':driverID' => $driverId,
|
||||||
|
':doc_type' => $docType,
|
||||||
|
':image_name' => $driverId . '_' . $docType,
|
||||||
|
':link' => $link,
|
||||||
|
':upload_date' => $nowDate,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("[Nabeh Registration] New driver registered: {$driverId}, Phone: {$phone}");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => [
|
||||||
|
'status' => 'success',
|
||||||
|
'driverID' => $driverId,
|
||||||
|
'carRegID' => $carRegId,
|
||||||
|
]
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
error_log("[Nabeh Registration Error] " . $e->getMessage());
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'failure',
|
||||||
|
'message' => 'Internal server error: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
164
backend/nabeh/upload_document.php
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Nabeh Integration — Document Upload
|
||||||
|
*
|
||||||
|
* Called by Nabeh AI platform to upload driver documents to Siro's private storage.
|
||||||
|
* Returns a signed URL valid for 48 hours.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Method not allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||||
|
$expectedKey = getenv('NABEH_API_KEY') ?: '';
|
||||||
|
|
||||||
|
if (empty($apiKey) || $apiKey !== $expectedKey) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Unauthorized: invalid API key']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
$rateLimitFile = __DIR__ . '/../logs/nabeh_upload_rate_' . md5($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '.lock';
|
||||||
|
$rateLimitWindow = 60;
|
||||||
|
$rateLimitMax = 10;
|
||||||
|
|
||||||
|
$nowTime = time();
|
||||||
|
$attempts = [];
|
||||||
|
if (file_exists($rateLimitFile)) {
|
||||||
|
$attempts = json_decode(file_get_contents($rateLimitFile), true) ?: [];
|
||||||
|
$attempts = array_filter($attempts, fn($t) => $t > ($nowTime - $rateLimitWindow));
|
||||||
|
}
|
||||||
|
if (count($attempts) >= $rateLimitMax) {
|
||||||
|
http_response_code(429);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Too many requests. Try again later.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$attempts[] = $nowTime;
|
||||||
|
file_put_contents($rateLimitFile, json_encode($attempts), LOCK_EX);
|
||||||
|
|
||||||
|
const MAX_FILE_MB = 5;
|
||||||
|
const ALLOWED_MIMES = ['image/jpeg', 'image/png', 'image/webp'];
|
||||||
|
const UPLOAD_ROOT = __DIR__ . '/../private_uploads';
|
||||||
|
const SIGNED_TTL_SEC = 172800;
|
||||||
|
|
||||||
|
$signSecret = getenv('SECRET_KEY_HMAC') ?: '';
|
||||||
|
if (empty($signSecret)) {
|
||||||
|
uploadLog('[Nabeh Upload] SECRET_KEY_HMAC not configured', 'ERROR');
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'Server configuration error']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = getenv('APP_DOMAIN') ?: 'api-syria.siromove.com';
|
||||||
|
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||||
|
define('PUBLIC_BASE', "$protocol://$host/siro");
|
||||||
|
|
||||||
|
if (!is_dir(UPLOAD_ROOT)) {
|
||||||
|
@mkdir(UPLOAD_ROOT, 0700, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadLog("[Nabeh Upload] Document upload started");
|
||||||
|
|
||||||
|
$allowedDocTypes = [
|
||||||
|
'id_front', 'id_back',
|
||||||
|
'driver_license_front', 'driver_license_back',
|
||||||
|
'car_license_front', 'car_license_back',
|
||||||
|
'criminal_record', 'profile_picture',
|
||||||
|
];
|
||||||
|
|
||||||
|
$driverId = $_POST['driver_id'] ?? '';
|
||||||
|
$docType = $_POST['doc_type'] ?? '';
|
||||||
|
|
||||||
|
if (empty($driverId) || empty($docType)) {
|
||||||
|
jsonError('driver_id and doc_type are required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$driverIdSafe = preg_replace('/[^A-Za-z0-9_\-]/', '_', $driverId);
|
||||||
|
|
||||||
|
if (!in_array($docType, $allowedDocTypes, true)) {
|
||||||
|
jsonError("Invalid doc_type. Allowed: " . implode(', ', $allowedDocTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$errCode = $_FILES['file']['error'] ?? 'missing_file';
|
||||||
|
uploadLog("[Nabeh Upload] File upload error. Code: $errCode", 'ERROR');
|
||||||
|
jsonError('No file uploaded or upload error.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmpPath = $_FILES['file']['tmp_name'];
|
||||||
|
$size = filesize($tmpPath);
|
||||||
|
if ($size === false || $size <= 0) {
|
||||||
|
jsonError('Invalid file size.');
|
||||||
|
}
|
||||||
|
if ($size > MAX_FILE_MB * 1024 * 1024) {
|
||||||
|
jsonError('File too large. Max ' . MAX_FILE_MB . ' MB.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||||
|
$mime = $finfo->file($tmpPath) ?: 'application/octet-stream';
|
||||||
|
if (!in_array($mime, ALLOWED_MIMES, true)) {
|
||||||
|
jsonError("Unsupported file type: $mime");
|
||||||
|
}
|
||||||
|
|
||||||
|
$extMap = [
|
||||||
|
'image/jpeg' => '.jpg',
|
||||||
|
'image/png' => '.png',
|
||||||
|
'image/webp' => '.webp',
|
||||||
|
];
|
||||||
|
$ext = $extMap[$mime];
|
||||||
|
|
||||||
|
$h = hash('sha1', $driverIdSafe);
|
||||||
|
$subdir = substr($h, 0, 2) . '/' . substr($h, 2, 2);
|
||||||
|
$destDir = UPLOAD_ROOT . '/' . $subdir;
|
||||||
|
if (!is_dir($destDir)) { @mkdir($destDir, 0700, true); }
|
||||||
|
|
||||||
|
$serverName = "{$driverIdSafe}__{$docType}{$ext}";
|
||||||
|
$destPath = $destDir . '/' . $serverName;
|
||||||
|
|
||||||
|
$resolvedDest = realpath($destPath) ?: $destPath;
|
||||||
|
$resolvedRoot = realpath(UPLOAD_ROOT) ?: UPLOAD_ROOT;
|
||||||
|
|
||||||
|
if (is_file($destPath) && str_starts_with($resolvedDest, $resolvedRoot)) {
|
||||||
|
@unlink($destPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!move_uploaded_file($tmpPath, $destPath)) {
|
||||||
|
jsonError('Failed to save the uploaded file.');
|
||||||
|
}
|
||||||
|
@chmod($destPath, 0600);
|
||||||
|
|
||||||
|
$extShort = ltrim($ext, '.');
|
||||||
|
$expires = time() + SIGNED_TTL_SEC;
|
||||||
|
$message = $driverIdSafe . ':' . $docType . ':' . $extShort . ':' . $expires;
|
||||||
|
$signature = hash_hmac('sha256', $message, $signSecret);
|
||||||
|
|
||||||
|
$fileUrl = PUBLIC_BASE . '/secure_image.php'
|
||||||
|
. '?driver_id=' . urlencode($driverIdSafe)
|
||||||
|
. '&doc_type=' . urlencode($docType)
|
||||||
|
. '&ext=' . urlencode($extShort)
|
||||||
|
. '&expires=' . $expires
|
||||||
|
. '&signature=' . urlencode($signature);
|
||||||
|
|
||||||
|
uploadLog("[Nabeh Upload] Document uploaded successfully. Type: $docType, Size: $size");
|
||||||
|
|
||||||
|
printSuccess([
|
||||||
|
'status' => 'success',
|
||||||
|
'success_file' => true,
|
||||||
|
'file_url' => $fileUrl,
|
||||||
|
'file_name' => $serverName,
|
||||||
|
'driver_id' => $driverIdSafe,
|
||||||
|
'doc_type' => $docType,
|
||||||
|
'mime_type' => $mime,
|
||||||
|
'size_bytes' => $size,
|
||||||
|
'expires_at' => date('c', $expires),
|
||||||
|
]);
|
||||||
@@ -16,7 +16,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_FILES['image'])) {
|
if (isset($_FILES['image'])) {
|
||||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||||
'size' => $_FILES['image']['size'] ?? 0,
|
'size' => $_FILES['image']['size'] ?? 0,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ uploadLog("🚀 [uploadImagePortrate.php] Profile image upload script execution
|
|||||||
try {
|
try {
|
||||||
// Check if $_FILES has errors
|
// Check if $_FILES has errors
|
||||||
if (isset($_FILES['image'])) {
|
if (isset($_FILES['image'])) {
|
||||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
uploadLog('$_FILES[\'image\'] metadata', 'INFO', [
|
||||||
'name' => $_FILES['image']['name'] ?? 'unknown',
|
'name' => $_FILES['image']['name'] ?? 'unknown',
|
||||||
'type' => $_FILES['image']['type'] ?? 'unknown',
|
'type' => $_FILES['image']['type'] ?? 'unknown',
|
||||||
'size' => $_FILES['image']['size'] ?? 0,
|
'size' => $_FILES['image']['size'] ?? 0,
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 283 KiB |
@@ -1,153 +0,0 @@
|
|||||||
import json
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
|
|
||||||
def parse_dart_map(filepath):
|
|
||||||
translations = {}
|
|
||||||
if not os.path.exists(filepath):
|
|
||||||
return translations
|
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Matches maps of form: "key": "value" or 'key': 'value'
|
|
||||||
# Handles escapes and multi-line content matched on single lines
|
|
||||||
pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
|
||||||
matches = pattern.findall(content)
|
|
||||||
for match in matches:
|
|
||||||
key = match[1]
|
|
||||||
val = match[3]
|
|
||||||
# Unescape simple sequences (single and double quotes)
|
|
||||||
key = key.replace('\\"', '"').replace("\\'", "'")
|
|
||||||
val = val.replace('\\"', '"').replace("\\'", "'")
|
|
||||||
translations[key] = val
|
|
||||||
|
|
||||||
return translations
|
|
||||||
|
|
||||||
def to_dart_string(s):
|
|
||||||
# Escape characters to be safe inside double quotes in Dart source code
|
|
||||||
result = []
|
|
||||||
for char in s:
|
|
||||||
if char == '\\':
|
|
||||||
result.append('\\\\')
|
|
||||||
elif char == '"':
|
|
||||||
result.append('\\"')
|
|
||||||
elif char == '$':
|
|
||||||
result.append('\\$')
|
|
||||||
elif char == '\n':
|
|
||||||
result.append('\\n')
|
|
||||||
elif char == '\r':
|
|
||||||
result.append('\\r')
|
|
||||||
elif char == '\t':
|
|
||||||
result.append('\\t')
|
|
||||||
else:
|
|
||||||
result.append(char)
|
|
||||||
return "".join(result)
|
|
||||||
|
|
||||||
# Load current Arabic files
|
|
||||||
ar_eg = parse_dart_map("siro_driver/lib/controller/local/ar_eg.dart")
|
|
||||||
ar_jo = parse_dart_map("siro_driver/lib/controller/local/ar_jo.dart")
|
|
||||||
ar_sy = parse_dart_map("siro_driver/lib/controller/local/ar_sy.dart")
|
|
||||||
|
|
||||||
# Load JSON data
|
|
||||||
with open("siro_driver_translations_data.json", "r", encoding="utf-8") as f:
|
|
||||||
json_data = json.load(f)
|
|
||||||
|
|
||||||
existing_syrian = json_data.get('existing_syrian', {})
|
|
||||||
missing_keys = json_data.get('missing_keys', [])
|
|
||||||
|
|
||||||
# Load legacy non-Arabic languages
|
|
||||||
with open("scratch/legacy_extracted_languages.json", "r", encoding="utf-8") as f:
|
|
||||||
legacy_extracted = json.load(f)
|
|
||||||
|
|
||||||
# Compute master keys
|
|
||||||
master_keys = set(ar_sy.keys())
|
|
||||||
master_keys.update(ar_jo.keys())
|
|
||||||
master_keys.update(ar_eg.keys())
|
|
||||||
master_keys.update(existing_syrian.keys())
|
|
||||||
master_keys.update(missing_keys)
|
|
||||||
|
|
||||||
master_keys = sorted(list(master_keys))
|
|
||||||
print(f"Master keys count: {len(master_keys)}")
|
|
||||||
|
|
||||||
# Let's build translation maps
|
|
||||||
aligned_maps = {
|
|
||||||
'ar-EG': {},
|
|
||||||
'ar-JO': {},
|
|
||||||
'ar-SY': {},
|
|
||||||
'en': {},
|
|
||||||
'de': {},
|
|
||||||
'el': {},
|
|
||||||
'es': {},
|
|
||||||
'fa': {},
|
|
||||||
'fr': {},
|
|
||||||
'hi': {},
|
|
||||||
'it': {},
|
|
||||||
'ru': {},
|
|
||||||
'tr': {},
|
|
||||||
'ur': {},
|
|
||||||
'zh': {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for key in master_keys:
|
|
||||||
# 1. English is always the key itself
|
|
||||||
aligned_maps['en'][key] = key
|
|
||||||
|
|
||||||
# 2. Syrian Arabic (ar-SY)
|
|
||||||
val_sy = ar_sy.get(key)
|
|
||||||
if not val_sy:
|
|
||||||
val_sy = existing_syrian.get(key)
|
|
||||||
if not val_sy:
|
|
||||||
val_sy = ar_jo.get(key)
|
|
||||||
if not val_sy:
|
|
||||||
val_sy = ar_eg.get(key)
|
|
||||||
if not val_sy:
|
|
||||||
val_sy = key # fallback to key
|
|
||||||
aligned_maps['ar-SY'][key] = val_sy
|
|
||||||
|
|
||||||
# 3. Jordanian Arabic (ar-JO)
|
|
||||||
val_jo = ar_jo.get(key)
|
|
||||||
if not val_jo:
|
|
||||||
val_jo = val_sy # fallback to Syrian
|
|
||||||
aligned_maps['ar-JO'][key] = val_jo
|
|
||||||
|
|
||||||
# 4. Egyptian Arabic (ar-EG)
|
|
||||||
val_eg = ar_eg.get(key)
|
|
||||||
if not val_eg:
|
|
||||||
val_eg = val_jo # fallback to Jordanian/Syrian
|
|
||||||
aligned_maps['ar-EG'][key] = val_eg
|
|
||||||
|
|
||||||
# 5. Non-Arabic languages
|
|
||||||
for lang in ['de', 'el', 'es', 'fa', 'fr', 'hi', 'it', 'ru', 'tr', 'ur', 'zh']:
|
|
||||||
val_lang = legacy_extracted.get(lang, {}).get(key)
|
|
||||||
if not val_lang:
|
|
||||||
val_lang = key # fallback to English
|
|
||||||
aligned_maps[lang][key] = val_lang
|
|
||||||
|
|
||||||
# Directories and file writes
|
|
||||||
output_dir = "siro_driver/lib/controller/local"
|
|
||||||
|
|
||||||
def write_dart_file(filename, map_name, data_map):
|
|
||||||
filepath = os.path.join(output_dir, filename)
|
|
||||||
with open(filepath, 'w', encoding='utf-8') as f:
|
|
||||||
# Write final Map<String, String> map_name = { ... };
|
|
||||||
f.write(f"final Map<String, String> {map_name} = {{\n")
|
|
||||||
for k, v in data_map.items():
|
|
||||||
k_escaped = to_dart_string(k)
|
|
||||||
v_escaped = to_dart_string(v)
|
|
||||||
f.write(f' "{k_escaped}": "{v_escaped}",\n')
|
|
||||||
f.write("};\n")
|
|
||||||
print(f"Wrote {filepath} with {len(data_map)} keys.")
|
|
||||||
|
|
||||||
# Write Arabic dialects
|
|
||||||
write_dart_file("ar_eg.dart", "ar_eg", aligned_maps['ar-EG'])
|
|
||||||
write_dart_file("ar_jo.dart", "ar_jo", aligned_maps['ar-JO'])
|
|
||||||
write_dart_file("ar_sy.dart", "ar_sy", aligned_maps['ar-SY'])
|
|
||||||
|
|
||||||
# Write English
|
|
||||||
write_dart_file("en.dart", "en", aligned_maps['en'])
|
|
||||||
|
|
||||||
# Write non-Arabic languages
|
|
||||||
for lang in ['de', 'el', 'es', 'fa', 'fr', 'hi', 'it', 'ru', 'tr', 'ur', 'zh']:
|
|
||||||
write_dart_file(f"{lang}.dart", lang, aligned_maps[lang])
|
|
||||||
|
|
||||||
print("Alignment and file writing complete.")
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
def parse_translations_file(filepath):
|
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Let's find language maps like: "en": { ... }, "fr": { ... }
|
|
||||||
# Look for patterns like "lang": { or 'lang': {
|
|
||||||
pattern = re.compile(r'[\'"]([a-zA-Z\-]+)[\'"]\s*:\s*\{')
|
|
||||||
matches = pattern.findall(content)
|
|
||||||
return matches
|
|
||||||
|
|
||||||
print("Legacy translations.dart languages:")
|
|
||||||
print(parse_translations_file("scratch/legacy_translations.dart"))
|
|
||||||
|
|
||||||
print("\nLegacy driver_translations.dart languages:")
|
|
||||||
print(parse_translations_file("scratch/legacy_driver_translations.dart"))
|
|
||||||
|
Before Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 322 KiB |
|
Before Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 418 KiB |
@@ -1,88 +0,0 @@
|
|||||||
import math
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
|
|
||||||
# Set font path to the local Roboto-Bold font
|
|
||||||
font_path = 'scratch/Roboto-Bold.ttf'
|
|
||||||
|
|
||||||
def draw_gear(draw, cx, cy, r_out, r_in, teeth_count, teeth_h, teeth_w, color):
|
|
||||||
draw.ellipse([cx - r_out, cy - r_out, cx + r_out, cy + r_out], fill=color)
|
|
||||||
for i in range(teeth_count):
|
|
||||||
angle = i * (2 * math.pi / teeth_count)
|
|
||||||
x0 = cx + r_out * math.cos(angle)
|
|
||||||
y0 = cy + r_out * math.sin(angle)
|
|
||||||
tx = -math.sin(angle)
|
|
||||||
ty = math.cos(angle)
|
|
||||||
nx = math.cos(angle)
|
|
||||||
ny = math.sin(angle)
|
|
||||||
p1 = (x0 - (teeth_w/2) * tx, y0 - (teeth_w/2) * ty)
|
|
||||||
p2 = (x0 + (teeth_w/2) * tx, y0 + (teeth_w/2) * ty)
|
|
||||||
p3 = (x0 + teeth_h * nx + (teeth_w/3) * tx, y0 + teeth_h * ny + (teeth_w/3) * ty)
|
|
||||||
p4 = (x0 + teeth_h * nx - (teeth_w/3) * tx, y0 + teeth_h * ny - (teeth_w/3) * ty)
|
|
||||||
draw.polygon([p1, p2, p3, p4], fill=color)
|
|
||||||
draw.ellipse([cx - r_in, cy - r_in, cx + r_in, cy + r_in], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
def draw_car_large(draw, color):
|
|
||||||
# Center: (765, 205)
|
|
||||||
# Cabin
|
|
||||||
draw.polygon([(740, 155), (790, 155), (809, 198), (721, 198)], fill=color)
|
|
||||||
# Windshield
|
|
||||||
draw.polygon([(743, 161), (787, 161), (802, 194), (728, 194)], fill=(255, 255, 255, 255))
|
|
||||||
# Windshield divider
|
|
||||||
draw.rectangle([763, 161, 767, 194], fill=color)
|
|
||||||
# Main body
|
|
||||||
draw.polygon([
|
|
||||||
(712, 195), (818, 195),
|
|
||||||
(827, 204), (827, 246),
|
|
||||||
(818, 255), (712, 255),
|
|
||||||
(703, 246), (703, 204)
|
|
||||||
], fill=color)
|
|
||||||
# Headlights
|
|
||||||
r_head = 9
|
|
||||||
draw.ellipse([722 - r_head, 222 - r_head, 722 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
|
||||||
draw.ellipse([808 - r_head, 222 - r_head, 808 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
|
||||||
# Grille
|
|
||||||
draw.rectangle([740, 222, 790, 232], fill=(255, 255, 255, 255))
|
|
||||||
# Side mirrors
|
|
||||||
draw.polygon([(694, 190), (703, 195), (703, 203), (694, 198)], fill=color)
|
|
||||||
draw.polygon([(836, 190), (827, 195), (827, 203), (836, 198)], fill=color)
|
|
||||||
|
|
||||||
def create_app_logo(base_logo_path, text, output_path):
|
|
||||||
im = Image.open(base_logo_path)
|
|
||||||
draw = ImageDraw.Draw(im)
|
|
||||||
color = (29, 32, 47, 255)
|
|
||||||
|
|
||||||
font_size = 90
|
|
||||||
font = ImageFont.truetype(font_path, font_size)
|
|
||||||
|
|
||||||
# Get text bounding box
|
|
||||||
bbox = draw.textbbox((0, 0), text, font=font)
|
|
||||||
tw = bbox[2] - bbox[0]
|
|
||||||
th = bbox[3] - bbox[1]
|
|
||||||
|
|
||||||
tx = 512 - (tw / 2)
|
|
||||||
ty = 887 - (th / 2) - bbox[1]
|
|
||||||
|
|
||||||
draw.text((tx, ty), text, font=font, fill=color)
|
|
||||||
im.save(output_path)
|
|
||||||
print(f'Saved logo with text \"{text}\" to {output_path}. BBox: X={tx} to {tx+tw}, Y={ty+bbox[1]} to {ty+bbox[3]}')
|
|
||||||
|
|
||||||
# Create Admin logo
|
|
||||||
print('Creating Admin Logo...')
|
|
||||||
im_admin = Image.open('scratch/logo_s_only.png')
|
|
||||||
draw_admin = ImageDraw.Draw(im_admin)
|
|
||||||
color = (29, 32, 47, 255)
|
|
||||||
draw_gear(draw_admin, cx=765, cy=205, r_out=52, r_in=20, teeth_count=8, teeth_h=16, teeth_w=20, color=color)
|
|
||||||
im_admin.save('scratch/admin_base.png')
|
|
||||||
create_app_logo('scratch/admin_base.png', 'ADMIN', 'scratch/admin_logo_final.png')
|
|
||||||
|
|
||||||
# Create Service logo
|
|
||||||
print('Creating Service Logo...')
|
|
||||||
im_service = Image.open('scratch/logo_s_only.png')
|
|
||||||
draw_service = ImageDraw.Draw(im_service)
|
|
||||||
draw_car_large(draw_service, color)
|
|
||||||
im_service.save('scratch/service_base.png')
|
|
||||||
create_app_logo('scratch/service_base.png', 'SERVICE', 'scratch/service_logo_final.png')
|
|
||||||
|
|
||||||
print('All logos compiled successfully.')
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import math
|
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
|
|
||||||
def draw_car_large(draw, color):
|
|
||||||
# Center: (765, 205)
|
|
||||||
# Scaled up by 1.25x for better visual weight
|
|
||||||
|
|
||||||
# 1. Cabin (upper part)
|
|
||||||
# Trapezoid: top base from X=740 to 790, bottom base from X=721 to 809, Y=155 to 198
|
|
||||||
draw.polygon([(740, 155), (790, 155), (809, 198), (721, 198)], fill=color)
|
|
||||||
|
|
||||||
# Windshield (white trapezoid inside cabin)
|
|
||||||
draw.polygon([(743, 161), (787, 161), (802, 194), (728, 194)], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
# Vertical divider in windshield
|
|
||||||
draw.rectangle([763, 161, 767, 194], fill=color)
|
|
||||||
|
|
||||||
# 2. Main body (lower part)
|
|
||||||
# Polygon with cut corners: X=703 to 827, Y=195 to 255
|
|
||||||
draw.polygon([
|
|
||||||
(712, 195), (818, 195),
|
|
||||||
(827, 204), (827, 246),
|
|
||||||
(818, 255), (712, 255),
|
|
||||||
(703, 246), (703, 204)
|
|
||||||
], fill=color)
|
|
||||||
|
|
||||||
# 3. Headlights (white circles)
|
|
||||||
r_head = 9
|
|
||||||
draw.ellipse([722 - r_head, 222 - r_head, 722 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
|
||||||
draw.ellipse([808 - r_head, 222 - r_head, 808 + r_head, 222 + r_head], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
# 4. Grille / Bumper
|
|
||||||
# Center grill: white rectangle from X=740 to 790, Y=222 to 232
|
|
||||||
draw.rectangle([740, 222, 790, 232], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
# 5. Side mirrors
|
|
||||||
# Left mirror: polygon at X=694 to 703, Y=190 to 202
|
|
||||||
draw.polygon([(694, 190), (703, 195), (703, 203), (694, 198)], fill=color)
|
|
||||||
# Right mirror: polygon at X=827 to 836, Y=190 to 202
|
|
||||||
draw.polygon([(836, 190), (827, 195), (827, 203), (836, 198)], fill=color)
|
|
||||||
|
|
||||||
im = Image.open('scratch/logo_s_only.png')
|
|
||||||
draw = ImageDraw.Draw(im)
|
|
||||||
color = (29, 32, 47, 255)
|
|
||||||
|
|
||||||
draw_car_large(draw, color)
|
|
||||||
im.save('scratch/service_logo_test_car_large.png')
|
|
||||||
print("Saved large service logo to scratch/service_logo_test_car_large.png")
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import math
|
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
|
|
||||||
def draw_gear(draw, cx, cy, r_out, r_in, teeth_count, teeth_h, teeth_w, color):
|
|
||||||
# Draw outer body
|
|
||||||
draw.ellipse([cx - r_out, cy - r_out, cx + r_out, cy + r_out], fill=color)
|
|
||||||
|
|
||||||
# Draw teeth
|
|
||||||
for i in range(teeth_count):
|
|
||||||
angle = i * (2 * math.pi / teeth_count)
|
|
||||||
|
|
||||||
# Center of tooth base on the outer radius
|
|
||||||
x0 = cx + r_out * math.cos(angle)
|
|
||||||
y0 = cy + r_out * math.sin(angle)
|
|
||||||
|
|
||||||
# Tangent vector for width
|
|
||||||
tx = -math.sin(angle)
|
|
||||||
ty = math.cos(angle)
|
|
||||||
|
|
||||||
# Normal vector for height
|
|
||||||
nx = math.cos(angle)
|
|
||||||
ny = math.sin(angle)
|
|
||||||
|
|
||||||
# 4 points of the tooth (trapezoid style: narrower at the tip)
|
|
||||||
p1 = (x0 - (teeth_w/2) * tx, y0 - (teeth_w/2) * ty)
|
|
||||||
p2 = (x0 + (teeth_w/2) * tx, y0 + (teeth_w/2) * ty)
|
|
||||||
|
|
||||||
p3 = (x0 + teeth_h * nx + (teeth_w/3) * tx, y0 + teeth_h * ny + (teeth_w/3) * ty)
|
|
||||||
p4 = (x0 + teeth_h * nx - (teeth_w/3) * tx, y0 + teeth_h * ny - (teeth_w/3) * ty)
|
|
||||||
|
|
||||||
draw.polygon([p1, p2, p3, p4], fill=color)
|
|
||||||
|
|
||||||
# Draw inner cutout (hole)
|
|
||||||
draw.ellipse([cx - r_in, cy - r_in, cx + r_in, cy + r_in], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
im = Image.open('scratch/logo_s_only.png')
|
|
||||||
draw = ImageDraw.Draw(im)
|
|
||||||
color = (29, 32, 47, 255)
|
|
||||||
|
|
||||||
# Draw gear at cx=765, cy=205
|
|
||||||
draw_gear(draw, cx=765, cy=205, r_out=45, r_in=18, teeth_count=8, teeth_h=15, teeth_w=18, color=color)
|
|
||||||
|
|
||||||
im.save('scratch/admin_logo_test.png')
|
|
||||||
print("Saved admin logo to scratch/admin_logo_test.png")
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import math
|
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
|
|
||||||
def draw_wrench(draw, color):
|
|
||||||
# Centers
|
|
||||||
cx_open, cy_open = 810, 160
|
|
||||||
cx_closed, cy_closed = 720, 250
|
|
||||||
|
|
||||||
# Draw shaft
|
|
||||||
# Direction vector from closed to open: (90, -90)
|
|
||||||
# Unit vector: dx = 0.7071, dy = -0.7071
|
|
||||||
# Perpendicular vector: px = 0.7071, py = 0.7071
|
|
||||||
dx, dy = 0.7071, -0.7071
|
|
||||||
px, py = 0.7071, 0.7071
|
|
||||||
half_w = 12
|
|
||||||
|
|
||||||
p1 = (cx_closed - half_w * px, cy_closed - half_w * py)
|
|
||||||
p2 = (cx_closed + half_w * px, cy_closed + half_w * py)
|
|
||||||
p3 = (cx_open + half_w * px, cy_open + half_w * py)
|
|
||||||
p4 = (cx_open - half_w * px, cy_open - half_w * py)
|
|
||||||
|
|
||||||
draw.polygon([p1, p2, p3, p4], fill=color)
|
|
||||||
|
|
||||||
# Draw open end outer circle
|
|
||||||
draw.ellipse([cx_open - 38, cy_open - 38, cx_open + 38, cy_open + 38], fill=color)
|
|
||||||
|
|
||||||
# Draw closed end outer circle
|
|
||||||
draw.ellipse([cx_closed - 30, cy_closed - 30, cx_closed + 30, cy_closed + 30], fill=color)
|
|
||||||
|
|
||||||
# Cut out the slot in open end (white polygon)
|
|
||||||
# Slot of width 22, length 50, starting at cx_open, cy_open and going in direction (dx, dy)
|
|
||||||
slot_half_w = 11
|
|
||||||
s1 = (cx_open - slot_half_w * px, cy_open - slot_half_w * py)
|
|
||||||
s2 = (cx_open + slot_half_w * px, cy_open + slot_half_w * py)
|
|
||||||
s3 = (cx_open + 50 * dx + slot_half_w * px, cy_open + 50 * dy + slot_half_w * py)
|
|
||||||
s4 = (cx_open + 50 * dx - slot_half_w * px, cy_open + 50 * dy - slot_half_w * py)
|
|
||||||
draw.polygon([s1, s2, s3, s4], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
# Draw a small circle at the base of the slot to make it round and clean
|
|
||||||
draw.ellipse([cx_open - 11, cy_open - 11, cx_open + 11, cy_open + 11], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
# Cut out closed end inner hole (white circle)
|
|
||||||
draw.ellipse([cx_closed - 15, cy_closed - 15, cx_closed + 15, cy_closed + 15], fill=(255, 255, 255, 255))
|
|
||||||
|
|
||||||
im = Image.open('scratch/logo_s_only.png')
|
|
||||||
draw = ImageDraw.Draw(im)
|
|
||||||
color = (29, 32, 47, 255)
|
|
||||||
|
|
||||||
draw_wrench(draw, color)
|
|
||||||
im.save('scratch/service_logo_test.png')
|
|
||||||
print("Saved service logo to scratch/service_logo_test.png")
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
def extract_language_maps(filepath):
|
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Let's find language blocks like: "en": { ... }, "fr": { ... }
|
|
||||||
# To parse these blocks, we find the starting position of each language identifier
|
|
||||||
# and scan until the matching closing brace.
|
|
||||||
languages = ['tr', 'fr', 'de', 'es', 'fa', 'el', 'ur', 'hi', 'ru', 'it', 'zh']
|
|
||||||
extracted = {}
|
|
||||||
|
|
||||||
for lang in languages:
|
|
||||||
# Regex to find: "lang": { or 'lang': {
|
|
||||||
pattern = re.compile(r'[\'"]' + lang + r'[\'"]\s*:\s*\{')
|
|
||||||
match = pattern.search(content)
|
|
||||||
if not match:
|
|
||||||
print(f"Language {lang} map start not found!")
|
|
||||||
continue
|
|
||||||
|
|
||||||
start_idx = match.end()
|
|
||||||
# Find matching closing brace
|
|
||||||
brace_count = 1
|
|
||||||
current_idx = start_idx
|
|
||||||
while brace_count > 0 and current_idx < len(content):
|
|
||||||
char = content[current_idx]
|
|
||||||
if char == '{':
|
|
||||||
brace_count += 1
|
|
||||||
elif char == '}':
|
|
||||||
brace_count -= 1
|
|
||||||
current_idx += 1
|
|
||||||
|
|
||||||
block = content[start_idx:current_idx-1]
|
|
||||||
|
|
||||||
# Now parse the key-value pairs inside the block
|
|
||||||
# Example: "key": "value", or 'key': 'value',
|
|
||||||
# Handle escaped quotes.
|
|
||||||
# Pattern: (['"])(.*?)\1\s*:\s*(['"])(.*?)\3\s*(?:,|$)
|
|
||||||
kv_pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
|
||||||
kv_matches = kv_pattern.findall(block)
|
|
||||||
|
|
||||||
lang_map = {}
|
|
||||||
for kv in kv_matches:
|
|
||||||
key = kv[1]
|
|
||||||
val = kv[3]
|
|
||||||
# Unescape quotes
|
|
||||||
key = key.replace('\\"', '"').replace("\\'", "'")
|
|
||||||
val = val.replace('\\"', '"').replace("\\'", "'")
|
|
||||||
lang_map[key] = val
|
|
||||||
|
|
||||||
extracted[lang] = lang_map
|
|
||||||
print(f"Extracted {lang}: {len(lang_map)} keys")
|
|
||||||
|
|
||||||
return extracted
|
|
||||||
|
|
||||||
extracted = extract_language_maps("scratch/legacy_translations.dart")
|
|
||||||
with open("scratch/legacy_extracted_languages.json", "w", encoding="utf-8") as f:
|
|
||||||
json.dump(extracted, f, ensure_ascii=False, indent=2)
|
|
||||||
print("Saved legacy extracted languages to JSON.")
|
|
||||||
@@ -1,629 +0,0 @@
|
|||||||
#!/usr/bin/env python3.13
|
|
||||||
"""
|
|
||||||
Generate the updated feasibility study (Version 3.1) for Siro App.
|
|
||||||
Produces both .docx and .html output.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from docx import Document
|
|
||||||
from docx.shared import Inches, Pt, Cm, RGBColor
|
|
||||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
||||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
|
||||||
from docx.enum.section import WD_ORIENT
|
|
||||||
from docx.oxml.ns import qn
|
|
||||||
from docx.oxml import OxmlElement
|
|
||||||
import os
|
|
||||||
|
|
||||||
OUTPUT_DIR = "/Users/hamzaaleghwairyeen/development/App/Siro"
|
|
||||||
|
|
||||||
# ─── Colours ───
|
|
||||||
PRIMARY = RGBColor(0x1E, 0x3A, 0x8A)
|
|
||||||
DARK = RGBColor(0x0F, 0x17, 0x2A)
|
|
||||||
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
|
||||||
GREY = RGBColor(0x47, 0x55, 0x69)
|
|
||||||
LGREY = RGBColor(0xF1, 0xF5, 0xF9)
|
|
||||||
GREEN = RGBColor(0x16, 0x7A, 0x34)
|
|
||||||
RED = RGBColor(0xB9, 0x1C, 0x1C)
|
|
||||||
|
|
||||||
def set_cell_shading(cell, color_hex):
|
|
||||||
"""Set cell background colour."""
|
|
||||||
shading = OxmlElement('w:shd')
|
|
||||||
shading.set(qn('w:val'), 'clear')
|
|
||||||
shading.set(qn('w:color'), 'auto')
|
|
||||||
shading.set(qn('w:fill'), color_hex)
|
|
||||||
cell._tc.get_or_add_tcPr().append(shading)
|
|
||||||
|
|
||||||
def add_table(doc, headers, rows, col_widths=None, header_color="1e3a8a", header_text_color="FFFFFF"):
|
|
||||||
"""Add a formatted table."""
|
|
||||||
table = doc.add_table(rows=1 + len(rows), cols=len(headers))
|
|
||||||
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
||||||
table.style = 'Table Grid'
|
|
||||||
|
|
||||||
# Header row
|
|
||||||
for i, h in enumerate(headers):
|
|
||||||
cell = table.rows[0].cells[i]
|
|
||||||
cell.text = ''
|
|
||||||
p = cell.paragraphs[0]
|
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
||||||
run = p.add_run(h)
|
|
||||||
run.bold = True
|
|
||||||
run.font.size = Pt(10)
|
|
||||||
run.font.color.rgb = WHITE
|
|
||||||
set_cell_shading(cell, header_color)
|
|
||||||
|
|
||||||
# Data rows
|
|
||||||
for r_idx, row in enumerate(rows):
|
|
||||||
for c_idx, val in enumerate(row):
|
|
||||||
cell = table.rows[r_idx + 1].cells[c_idx]
|
|
||||||
cell.text = ''
|
|
||||||
p = cell.paragraphs[0]
|
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER if c_idx > 0 else WD_ALIGN_PARAGRAPH.RIGHT
|
|
||||||
run = p.add_run(str(val))
|
|
||||||
run.font.size = Pt(9.5)
|
|
||||||
if r_idx == len(rows) - 1:
|
|
||||||
run.bold = True
|
|
||||||
if c_idx == 0:
|
|
||||||
run.bold = True
|
|
||||||
|
|
||||||
if col_widths:
|
|
||||||
for i, w in enumerate(col_widths):
|
|
||||||
for row in table.rows:
|
|
||||||
row.cells[i].width = Cm(w)
|
|
||||||
return table
|
|
||||||
|
|
||||||
def add_heading(doc, text, level=1):
|
|
||||||
h = doc.add_heading(text, level=level)
|
|
||||||
h.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
||||||
for run in h.runs:
|
|
||||||
run.font.color.rgb = PRIMARY if level <= 2 else DARK
|
|
||||||
return h
|
|
||||||
|
|
||||||
def add_para(doc, text, bold=False, size=10, color=None, align=WD_ALIGN_PARAGRAPH.RIGHT):
|
|
||||||
p = doc.add_paragraph()
|
|
||||||
p.alignment = align
|
|
||||||
run = p.add_run(text)
|
|
||||||
run.bold = bold
|
|
||||||
run.font.size = Pt(size)
|
|
||||||
if color:
|
|
||||||
run.font.color.rgb = color
|
|
||||||
return p
|
|
||||||
|
|
||||||
def add_bullet(doc, text, bold_prefix=""):
|
|
||||||
p = doc.add_paragraph(style='List Bullet')
|
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
||||||
if bold_prefix:
|
|
||||||
run = p.add_run(bold_prefix)
|
|
||||||
run.bold = True
|
|
||||||
run.font.size = Pt(10)
|
|
||||||
run = p.add_run(text)
|
|
||||||
run.font.size = Pt(10)
|
|
||||||
return p
|
|
||||||
|
|
||||||
|
|
||||||
def generate_docx():
|
|
||||||
doc = Document()
|
|
||||||
|
|
||||||
# Set RTL for entire document
|
|
||||||
style = doc.styles['Normal']
|
|
||||||
style.font.name = 'Calibri'
|
|
||||||
style.font.size = Pt(10)
|
|
||||||
style.element.rPr.rFonts.set(qn('w:eastAsia'), 'Calibri')
|
|
||||||
|
|
||||||
# ── Title Page ──
|
|
||||||
for _ in range(4):
|
|
||||||
doc.add_paragraph()
|
|
||||||
|
|
||||||
p = doc.add_paragraph()
|
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
||||||
run = p.add_run("دراسة الجدوى الاقتصادية\nتطبيق سيرو للنقل الذكي")
|
|
||||||
run.bold = True
|
|
||||||
run.font.size = Pt(28)
|
|
||||||
run.font.color.rgb = PRIMARY
|
|
||||||
|
|
||||||
p = doc.add_paragraph()
|
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
||||||
run = p.add_run("الإصدار الثالث — يونيو 2026\nنموذج الدفع الشهري المرحلي")
|
|
||||||
run.font.size = Pt(16)
|
|
||||||
run.font.color.rgb = GREY
|
|
||||||
|
|
||||||
doc.add_paragraph()
|
|
||||||
|
|
||||||
# Key metrics boxes
|
|
||||||
metrics = [
|
|
||||||
("السوق المستهدف", "سوريا — دمشق الكبرى"),
|
|
||||||
("نموذج الاستثمار", "دفع شهري مرحلي ($8,000/شهر)"),
|
|
||||||
("رأس المال التأسيسي", "$11,500 - $12,000 (دفعة واحدة)"),
|
|
||||||
("نقطة الخروج", "نهاية الشهر الخامس إذا لم تتحقق المؤشرات"),
|
|
||||||
("نطاق نقطة التعادل", "الشهر السابع حتى التاسع"),
|
|
||||||
("أقصى تعرض للمستثمر", "$51,500 - $52,000 (عند الخروج المبكر)"),
|
|
||||||
("إجمالي الاستثمار المتوقع", "$67,500 — $84,000"),
|
|
||||||
]
|
|
||||||
for label, val in metrics:
|
|
||||||
p = doc.add_paragraph()
|
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
||||||
run = p.add_run(f"{label}: ")
|
|
||||||
run.bold = True
|
|
||||||
run.font.size = Pt(11)
|
|
||||||
run = p.add_run(val)
|
|
||||||
run.font.size = Pt(11)
|
|
||||||
run.font.color.rgb = PRIMARY
|
|
||||||
|
|
||||||
doc.add_page_break()
|
|
||||||
|
|
||||||
# ── 1. Executive Summary ──
|
|
||||||
add_heading(doc, "أولاً: الملخص التنفيذي", 1)
|
|
||||||
|
|
||||||
add_para(doc,
|
|
||||||
"سيرو تطبيق نقل ذكي جاهز للإطلاق في السوق السوري، يعمل على بنية تحتية مستقلة تقنياً لا تعتمد على "
|
|
||||||
"أي خدمات خارجية مكلفة أو مقيدة جغرافياً. يتميز بأدنى عمولة في السوق (11%) مقابل 17-20% لدى المنافسين، "
|
|
||||||
"مما يجعله الخيار المنطقي للسائق والراكب معاً.")
|
|
||||||
|
|
||||||
add_para(doc,
|
|
||||||
"تتبنى هذه الخطة نموذج دفع شهري مرحلي يحمي المستثمر من تجميد رأس المال، ويضمن استمرارية تشغيلية مرتبطة "
|
|
||||||
"بالأداء الفعلي. وجود شرط خروج صريح في الشهر الخامس يُعطي الطرفين وضوحاً تاماً في المخاطر والتوقعات.")
|
|
||||||
|
|
||||||
add_para(doc, "أبرز نقاط القوة التنافسية", bold=True, size=11)
|
|
||||||
bullets = [
|
|
||||||
("▸ المنتج مكتمل تقنياً ومختبر ميدانياً — لا مخاطر تطوير"),
|
|
||||||
("▸ تجربة الطيار: 1,447 سائق انضموا بـ$1,400 إعلانات فقط — بدون حوافز"),
|
|
||||||
("▸ أدنى عمولة في السوق: 11% مقابل 17-20% عند المنافسين"),
|
|
||||||
("▸ بنية تحتية ذاتية توفر 60% من تكاليف التشغيل مقارنة بالنظراء"),
|
|
||||||
("▸ المؤسس يغطي وظائف 5 أشخاص — وفر $3,000-5,000/شهر في الرواتب"),
|
|
||||||
("▸ نموذج استثمار شهري مرحلي: صفر مخاطر تجميد رأس المال"),
|
|
||||||
]
|
|
||||||
for b in bullets:
|
|
||||||
add_bullet(doc, b)
|
|
||||||
|
|
||||||
# Key indicators table
|
|
||||||
add_para(doc, "مؤشرات الأداء الرئيسية", bold=True, size=11)
|
|
||||||
add_table(doc,
|
|
||||||
["المؤشر", "القيمة"],
|
|
||||||
[
|
|
||||||
["رأس المال التأسيسي (مرة واحدة)", "$11,500 - $12,000"],
|
|
||||||
["المصاريف التشغيلية الشهرية", "$8,000/شهر"],
|
|
||||||
["نقطة الخروج للمستثمر", "نهاية الشهر الخامس إذا لم تتحقق المؤشرات"],
|
|
||||||
["نطاق التعادل", "الشهر 7 — 9"],
|
|
||||||
["أقصى خسارة عند الخروج المبكر", "$51,500 - $52,000"],
|
|
||||||
["إجمالي الاستثمار حتى التعادل", "$67,500 — $84,000"],
|
|
||||||
["عمولة التطبيق", "11% من كل رحلة"],
|
|
||||||
["متوسط حصة الشركة للرحلة", "$0.30 / رحلة"],
|
|
||||||
["هدف الرحلات عند التعادل", "889 رحلة/يوم"],
|
|
||||||
],
|
|
||||||
col_widths=[12, 6]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 2. Vision & Business Model ──
|
|
||||||
add_heading(doc, "ثانياً: الرؤية والنموذج التجاري", 1)
|
|
||||||
|
|
||||||
add_para(doc, "الرؤية", bold=True, size=11)
|
|
||||||
add_para(doc, "أن يكون سيرو المنصة الأولى للنقل الذكي الموثوق في سوريا، بعمولة عادلة للسائق وخدمة موثوقة للراكب، على بنية رقمية مستقلة وسيادية.")
|
|
||||||
|
|
||||||
add_para(doc, "نموذج الإيرادات", bold=True, size=11)
|
|
||||||
add_bullet(doc, "عمولة 11% من قيمة كل رحلة تُقتطع تلقائياً من أجرة السائق")
|
|
||||||
add_bullet(doc, "متوسط قيمة الرحلة: $2.75 — حصة الشركة: $0.30/رحلة")
|
|
||||||
add_bullet(doc, "مستقبلاً: إعلانات داخل التطبيق، اشتراكات السائق المميز، خدمات B2B للشركات والفنادق")
|
|
||||||
|
|
||||||
add_para(doc, "الميزة التنافسية — لماذا سيرو؟", bold=True, size=11)
|
|
||||||
add_table(doc,
|
|
||||||
["المعيار", "سيرو", "المنافسون"],
|
|
||||||
[
|
|
||||||
["عمولة التطبيق", "11%", "17% - 20%"],
|
|
||||||
["خريطة المنصة", "IntaleqMaps — ذاتية $0", "Google Maps $5,000-15,000/شهر"],
|
|
||||||
["نظام OTP", "Flash Call ذاتي — $0", "SMS مدفوع $0.03-0.05/رسالة"],
|
|
||||||
["تكلفة التطوير المستمر", "المؤسس = المطور", "فريق خارجي مكلف"],
|
|
||||||
["البنية التحتية", "خوادم ذاتية مستقلة", "Cloud APIs قابلة للتوقف"],
|
|
||||||
["خدمة العملاء", "مدمجة + AI (Nabeeh)", "مراكز خارجية مكلفة"],
|
|
||||||
],
|
|
||||||
col_widths=[5, 6, 7]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 3. Market Analysis ──
|
|
||||||
add_heading(doc, "ثالثاً: تحليل السوق السوري", 1)
|
|
||||||
|
|
||||||
add_heading(doc, "3-1: لماذا الآن؟", 2)
|
|
||||||
add_para(doc, "تمر سوريا في مرحلة إعادة الإعمار وانفتاح اقتصادي تدريجي منذ نهاية 2024. دمشق الكبرى تشهد حركة تجارية متصاعدة مع نقص واضح في وسائل النقل المنظم.")
|
|
||||||
|
|
||||||
add_heading(doc, "3-2: حجم السوق", 2)
|
|
||||||
add_table(doc,
|
|
||||||
["المؤشر", "التقدير"],
|
|
||||||
[
|
|
||||||
["سكان دمشق الكبرى", "4 - 5 مليون نسمة"],
|
|
||||||
["مستخدمو الهاتف الذكي", "65% - 70%"],
|
|
||||||
["الرحلات اليومية المحتملة", "200,000 - 400,000 رحلة/يوم"],
|
|
||||||
["حصة السوق المستهدفة سنة 1", "1% - 2% (1,500 - 2,000 رحلة/يوم)"],
|
|
||||||
["نتيجة تجربة الطيار", "1,447 سائق سجلوا بـ$1,400 تسويق فقط"],
|
|
||||||
],
|
|
||||||
col_widths=[10, 8]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_heading(doc, "3-3: المنافسون الرئيسيون", 2)
|
|
||||||
add_table(doc,
|
|
||||||
["التطبيق", "نقطة الضعف", "فرصتنا"],
|
|
||||||
[
|
|
||||||
["YallaGo", "عمولة 20% تُرهق السائق", "11% = ولاء السائق يتحول إلينا"],
|
|
||||||
["Zakinn", "عمولة 17% تُرهق السائق", "نبدأ من المنطقة الأكثر طلباً"],
|
|
||||||
["Tafaddal", "تجربة مستخدم ضعيفة", "UX متقدم + تطبيق سائق أفضل"],
|
|
||||||
["سيرو — ميزتنا", "أدنى عمولة + بنية مستقلة", "الأصعب تقليداً في السوق"],
|
|
||||||
],
|
|
||||||
col_widths=[4, 6, 8]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 4. Operational Plan ──
|
|
||||||
doc.add_page_break()
|
|
||||||
add_heading(doc, "رابعاً: الخطة التشغيلية", 1)
|
|
||||||
|
|
||||||
add_heading(doc, "4-1: اختيار موقع المكتب في دمشق", 2)
|
|
||||||
add_para(doc, "يشترط الترخيص القانوني وجود مقر موثق. الميزانية المخصصة: $600/شهر.")
|
|
||||||
|
|
||||||
add_table(doc,
|
|
||||||
["المنطقة", "التقييم", "الإيجار الشهري", "الملاحظة"],
|
|
||||||
[
|
|
||||||
["المزة", "★★★★★", "$500 - $700", "الأفضل: مقر الشركات الكبرى"],
|
|
||||||
["كفرسوسة", "★★★★☆", "$400 - $600", "قريب من المزة، أسعار معقولة"],
|
|
||||||
["الصالحية", "★★★☆☆", "$400 - $550", "مركزي، وصول جيد"],
|
|
||||||
["أبو رمانة", "★★☆☆☆", "$700 - $1,000", "مرموق لكن يتجاوز الميزانية"],
|
|
||||||
["برزة", "★★☆☆☆", "$280 - $400", "أرخص لكن أقل احترافية"],
|
|
||||||
],
|
|
||||||
col_widths=[3, 2.5, 3.5, 8]
|
|
||||||
)
|
|
||||||
add_para(doc, "التوصية: كفرسوسة أو المزة — 50-70 م² مع هامش تفاوض", bold=True)
|
|
||||||
|
|
||||||
# 4-2: Office Furniture (revised)
|
|
||||||
add_heading(doc, "4-2: المشتريات والتجهيزات المكتبية", 2)
|
|
||||||
|
|
||||||
add_para(doc, "أ — الأثاث المكتبي", bold=True, size=10)
|
|
||||||
add_table(doc,
|
|
||||||
["البند", "الكمية", "سعر الوحدة", "الإجمالي"],
|
|
||||||
[
|
|
||||||
["مكتب رئيسي مع أدراج", 1, "$150", "$150"],
|
|
||||||
["مكاتب موظفين بسيطة", 3, "$90", "$270"],
|
|
||||||
["كرسي مكتبي دوّار رئيسي", 1, "$110", "$110"],
|
|
||||||
["كراسي موظفين", 3, "$60", "$180"],
|
|
||||||
["طاولة اجتماعات صغيرة (4 أشخاص)", 1, "$80", "$80"],
|
|
||||||
["كراسي اجتماعات", 4, "$50", "$200"],
|
|
||||||
["برادي (ستائر) للمكتب", "—", "—", "$200"],
|
|
||||||
["رفوف تخزين بسيطة", "—", "—", "$50"],
|
|
||||||
["مراوح (عدد 2)", 2, "$40", "$80"],
|
|
||||||
["إجمالي الأثاث", "", "", "$1,320"],
|
|
||||||
],
|
|
||||||
col_widths=[7, 2, 2.5, 2.5]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "ب — المعدات والتجهيزات", bold=True, size=10)
|
|
||||||
add_table(doc,
|
|
||||||
["البند", "الإجمالي"],
|
|
||||||
[
|
|
||||||
["راوتر WiFi احترافي", "$80"],
|
|
||||||
["طابعة/ماسح ضوئي", "$65"],
|
|
||||||
["إكسسوارات متنوعة", "$100"],
|
|
||||||
["مكيف هواء (شراء + تركيب)", "$450"],
|
|
||||||
["قرطاسية ومستلزمات (3 أشهر)", "$80"],
|
|
||||||
["أدوات ضيافة", "$80"],
|
|
||||||
["إجمالي التجهيزات", "$855"],
|
|
||||||
],
|
|
||||||
col_widths=[10, 4]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "ج — أثاث وتجهيزات سكن المؤسس", bold=True, size=10)
|
|
||||||
add_table(doc,
|
|
||||||
["البند", "الإجمالي"],
|
|
||||||
[
|
|
||||||
["سرير (عدد 2)", "$80"],
|
|
||||||
["فرشة (عدد 2)", "$180"],
|
|
||||||
["إحرامات + مخدات (عدد 2)", "$40"],
|
|
||||||
["برادي للسكن", "$100"],
|
|
||||||
["ثلاجة صغيرة", "$100"],
|
|
||||||
["غاز صغير + أدوات مطبخ", "$100"],
|
|
||||||
["سخان مياه", "$70"],
|
|
||||||
["سفري + أدوات ضيافة", "$25"],
|
|
||||||
["إجمالي تجهيزات السكن", "$695"],
|
|
||||||
],
|
|
||||||
col_widths=[10, 4]
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4-3: Technical Equipment
|
|
||||||
add_heading(doc, "4-3: أجهزة التطوير والمعدات التقنية", 2)
|
|
||||||
add_table(doc,
|
|
||||||
["البند", "الإجمالي", "ملاحظة"],
|
|
||||||
[
|
|
||||||
["MacBook Pro M4 Pro Max (40 GPU)", "$3,100", "جهاز التطوير والإدارة الرئيسي"],
|
|
||||||
["iPhone (أحدث إصدار)", "$500", "اختبار تطبيق iOS"],
|
|
||||||
["جهاز Android (أحدث إصدار)", "$300", "اختبار تطبيق Android"],
|
|
||||||
["باقي أجهزة التطوير", "$1,100", "ملحقات وإكسسوارات تطوير"],
|
|
||||||
["إجمالي أجهزة التطوير", "$5,000", ""],
|
|
||||||
],
|
|
||||||
col_widths=[8, 3.5, 5.5]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "ملاحظة: تم استبدال أجهزة الحاسوب المكتبي لخدمة العملاء بهواتف ذكية (3 × $150 ضمن بند هواتف خدمة العملاء في CAPEX).", size=9, color=GREY)
|
|
||||||
|
|
||||||
# ── 5. HR Plan ──
|
|
||||||
add_heading(doc, "خامساً: خطة الموارد البشرية", 1)
|
|
||||||
|
|
||||||
add_heading(doc, "5-1: الهيكل الوظيفي والرواتب", 2)
|
|
||||||
add_table(doc,
|
|
||||||
["المسمى الوظيفي", "العدد", "الراتب الشهري", "الإجمالي"],
|
|
||||||
[
|
|
||||||
["المشغل الرئيسي / المؤسس التنفيذي", 1, "$3,500", "$3,500"],
|
|
||||||
["ممثل خدمة العملاء", 3, "$110 - $130", "$400"],
|
|
||||||
["إجمالي الرواتب الشهرية", "4 أشخاص", "", "$3,900"],
|
|
||||||
],
|
|
||||||
col_widths=[7, 2, 3.5, 3]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "ملاحظات:", bold=True, size=9)
|
|
||||||
add_bullet(doc, "تم إلغاء بند مدير السوشيال ميديا (فريلانسر) — المؤسس يدير التسويق الرقمي مباشرة")
|
|
||||||
add_bullet(doc, "تم إلغاء بند السكرتيرة الإدارية — المهام توزع على فريق خدمة العملاء والمؤسس")
|
|
||||||
add_bullet(doc, "فريق خدمة العملاء: 3 موظفين براتب $110-130/شهر ($400 إجمالي)")
|
|
||||||
add_bullet(doc, "برنامج تدريب خدمة العملاء: أسبوعين (بدلاً من 4 أسابيع)")
|
|
||||||
|
|
||||||
# ── 6. Financial Plan ──
|
|
||||||
doc.add_page_break()
|
|
||||||
add_heading(doc, "سادساً: الخطة المالية التفصيلية", 1)
|
|
||||||
|
|
||||||
add_heading(doc, "6-1: رأس المال التأسيسي — $9,000 (دفعة واحدة)", 2)
|
|
||||||
add_para(doc, "يُصرف كاملاً عند بدء التأسيس قبل الإطلاق.")
|
|
||||||
|
|
||||||
add_table(doc,
|
|
||||||
["البند", "المبلغ", "البيان"],
|
|
||||||
[
|
|
||||||
["شهادة اعتمادية (الهيئة الناظمة)", "$600", "ترخيص التطبيق"],
|
|
||||||
["أتعاب المحامي والتخليص القانوني", "$1,500", "تأسيس الشركة + تراخيص"],
|
|
||||||
["رسوم وزارة (سجل تجاري + وزارة نقل)", "$200", "رسوم حكومية"],
|
|
||||||
["هواتف خدمة العملاء (3 أجهزة)", "$450", "3 × $150"],
|
|
||||||
["أجهزة التطوير (Mac + iPhone + Android)", "$5,000", "حسب التفصيل في 4-3"],
|
|
||||||
["لابتوب للسيرفرات وإدارة الإعلانات", "$350", "جهاز منفصل لإدارة السيرفرات"],
|
|
||||||
["تجهيز المكتب — أثاث", "$1,320", "حسب التفصيل في 4-2-أ"],
|
|
||||||
["تجهيز المكتب — معدات وتجهيزات", "$855", "حسب التفصيل في 4-2-ب"],
|
|
||||||
["تجهيزات سكن المؤسس", "$695", "أثاث وتجهيزات أساسية"],
|
|
||||||
["تكاليف السفر والنقل والإقامة التأسيسية (أسبوعين)", "$400 - $800", "مواصلات + سكن مؤقت + تجهيز"],
|
|
||||||
["إجمالي رأس المال التأسيسي", "$11,370 - $11,770 ≈ $11,500 - $12,000", ""],
|
|
||||||
],
|
|
||||||
col_widths=[8, 2.5, 6]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_heading(doc, "6-2: المصاريف التشغيلية الشهرية — $8,000/شهر", 2)
|
|
||||||
add_table(doc,
|
|
||||||
["البند", "المبلغ", "البيان"],
|
|
||||||
[
|
|
||||||
["راتب المشغل الرئيسي", "$3,500", "تقني + إداري + تسويق + عمليات"],
|
|
||||||
["فريق خدمة العملاء × 3", "$400", "بمتوسط $133/موظف"],
|
|
||||||
["سيرفرات وبنية سحابية", "$200", "استضافة + نسخ احتياطي"],
|
|
||||||
["إيجار المكتب", "$600", "كفرسوسة / المزة"],
|
|
||||||
["إيجار سكن المشغل", "$300", "ضمن خطة الرواتب"],
|
|
||||||
["خدمات الإنترنت", "$45", "خط ثابت مزدوج"],
|
|
||||||
["فاتورة الكهرباء", "$70", "مكتب + معدات"],
|
|
||||||
["باقات خطوط هواتف (3 أرقام)", "$30", "لخدمة العملاء"],
|
|
||||||
["إعلانات رقمية (Facebook + TikTok)", "$2,855", "المتبقي من الميزانية"],
|
|
||||||
["إجمالي OPEX الشهري", "$8,000", ""],
|
|
||||||
],
|
|
||||||
col_widths=[7, 2.5, 6]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "ملاحظة: تم إلغاء بند إدارة السوشيال ميديا ($200) وبند السكرتيرة ($100). الفائض (~$355 إضافية) يُضاف إلى ميزانية الإعلانات الرقمية ($2,855 بدلاً من $2,500).", size=9, color=GREY)
|
|
||||||
|
|
||||||
# 6-3: Driver Incentives
|
|
||||||
add_heading(doc, "6-3: خطة حوافز السائقين", 2)
|
|
||||||
add_table(doc,
|
|
||||||
["الفترة", "السائقون", "التكلفة", "المصدر"],
|
|
||||||
[
|
|
||||||
["الشهر الأول", "100 سائق × $15", "$1,500", "من CAPEX (احتياطي)"],
|
|
||||||
["الشهر الثاني", "120 سائق × $15", "$1,800", "من CAPEX (احتياطي)"],
|
|
||||||
["الشهر الثالث", "150+ سائق", "من الإيرادات", "ذاتي التمويل"],
|
|
||||||
["الشهر الرابع+", "300-500 سائق", "من الإيرادات", "ذاتي التمويل"],
|
|
||||||
],
|
|
||||||
col_widths=[4, 4, 3.5, 4]
|
|
||||||
)
|
|
||||||
|
|
||||||
# 6-4: Cash Flow Table
|
|
||||||
add_heading(doc, "6-4: جدول التدفق النقدي الشهري", 2)
|
|
||||||
add_table(doc,
|
|
||||||
["الشهر", "صرف المستثمر", "الإيرادات", "العجز", "الإجمالي", "رحلات/يوم", "سائق نشط"],
|
|
||||||
[
|
|
||||||
["التأسيس", "$11,500-12,000", "—", "-$11,500-12,000", "$11,500-12,000", "—", "—"],
|
|
||||||
["1", "$8,000", "$270", "-$7,730", "$19,500-20,000", "30", "100"],
|
|
||||||
["2", "$8,000", "$630", "-$7,370", "$27,500-28,000", "70", "120"],
|
|
||||||
["3", "$8,000", "$1,350", "-$6,650", "$35,500-36,000", "150", "220"],
|
|
||||||
["★4 — فحص", "$8,000", "$3,150", "-$4,850", "$43,500-44,000", "350", "350"],
|
|
||||||
["★5 — خروج", "$8,000", "$5,400", "-$2,600", "$51,500-52,000", "600", "480"],
|
|
||||||
["6", "$8,000", "$6,750", "-$1,250", "$59,500-60,000", "750", "550"],
|
|
||||||
["⚡7", "$8,000", "$8,100", "+$100", "$67,500-68,000", "900", "630"],
|
|
||||||
["⚡8", "$8,000", "$9,450", "+$1,450", "$75,500-76,000", "1,050", "750"],
|
|
||||||
["⚡9", "$8,000", "$10,500", "+$2,500", "$83,500-84,000", "1,167", "840"],
|
|
||||||
["10", "$0", "$11,250", "+$3,250", "—", "1,250", "900"],
|
|
||||||
["11", "$0", "$12,375", "+$4,375", "—", "1,375", "980"],
|
|
||||||
["12", "$0", "$13,500", "+$5,500", "—", "1,500", "1,050"],
|
|
||||||
["13", "$0", "$14,400", "+$6,400", "—", "1,600", "1,100"],
|
|
||||||
["14", "$0", "$15,300", "+$7,300", "—", "1,700", "1,150"],
|
|
||||||
],
|
|
||||||
col_widths=[2.5, 2.5, 2, 2, 2.5, 2, 2]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 7. Investment Structure ──
|
|
||||||
doc.add_page_break()
|
|
||||||
add_heading(doc, "سابعاً: هيكل الاستثمار — نموذج الدفع الشهري", 1)
|
|
||||||
|
|
||||||
add_para(doc, "آلية الدفع:", bold=True, size=11)
|
|
||||||
add_bullet(doc, "المستثمر يدفع $11,500-$12,000 مرة واحدة عند التوقيع (CAPEX)")
|
|
||||||
add_bullet(doc, "يدفع $8,000 شهرياً لتغطية التشغيل الكامل")
|
|
||||||
add_bullet(doc, "الدفع يتوقف تلقائياً عندما تتجاوز الإيرادات $8,000/شهر")
|
|
||||||
add_bullet(doc, "لا يوجد التزام بإجمالي محدد مقدماً")
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_para(doc, "جدول الدفع المتوقع (سيناريو قاعدي — تعادل الشهر 8):", bold=True, size=10)
|
|
||||||
add_table(doc,
|
|
||||||
["الشهر", "المبلغ", "نوع الدفع", "حالة المشروع"],
|
|
||||||
[
|
|
||||||
["صفر", "$11,500-$12,000", "مرة واحدة", "تجهيز + ترخيص + تعيين"],
|
|
||||||
["1", "$8,000", "شهري", "إطلاق ناعم"],
|
|
||||||
["2", "$8,000", "شهري", "نمو متصاعد"],
|
|
||||||
["3", "$8,000", "شهري", "حوافز ذاتية"],
|
|
||||||
["4 — فحص", "$8,000", "شهري", "مراجعة أداء"],
|
|
||||||
["5 — خروج", "$8,000", "شهري", "استمرار أو خروج"],
|
|
||||||
["6", "$8,000", "شهري", "قرب التعادل"],
|
|
||||||
["7", "$8,000", "شهري", "تعادل متفائل"],
|
|
||||||
["8 — تعادل", "$8,000 (آخر)", "شهري", "الإيرادات ≥ $8,000"],
|
|
||||||
["9+", "$0", "ذاتي", "المشروع يمول نفسه"],
|
|
||||||
],
|
|
||||||
col_widths=[3, 3, 3, 7]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_para(doc, "إجمالي تعرض المستثمر في كل سيناريو:", bold=True, size=10)
|
|
||||||
add_table(doc,
|
|
||||||
["السيناريو", "التوقف", "الإجمالي", "ملاحظة"],
|
|
||||||
[
|
|
||||||
["خروج مبكر (فشل)", "نهاية ش5", "$51,500-$52,000", "أقصى خسارة ~$52,000"],
|
|
||||||
["تعادل متفائل", "منتصف ش7", "$59,500-$60,000", "أفضل سيناريو"],
|
|
||||||
["تعادل قاعدي", "منتصف ش8", "$67,500-$68,000", "الأرجح"],
|
|
||||||
["تعادل محافظ", "منتصف ش9", "$75,500-$76,000", "نمو أبطأ"],
|
|
||||||
],
|
|
||||||
col_widths=[5, 3, 3, 5]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_para(doc, "لماذا لا يوجد احتياطي مالي في هذا النموذج؟", bold=True, size=11)
|
|
||||||
add_para(doc, "في النموذج القديم (مبلغ واحد)، المستثمر يدفع كل شيء مقدماً والاحتياطي يجلس خاملاً. في النموذج الجديد (شهري):", size=10)
|
|
||||||
add_bullet(doc, "المستثمر يدفع فقط ما صُرف فعلاً")
|
|
||||||
add_bullet(doc, "لا توجد أموال خاملة — كل دولار يُشغَّل")
|
|
||||||
add_bullet(doc, "المستثمر يخاطر بـ$51,000 كحد أقصى (عند الخروج)")
|
|
||||||
add_bullet(doc, "نقطة الخروج هي الحماية الحقيقية بدلاً من الاحتياطي")
|
|
||||||
|
|
||||||
# ── 8. Exit Clause ──
|
|
||||||
add_heading(doc, "ثامناً: شرط الخروج — بند الحماية", 1)
|
|
||||||
|
|
||||||
add_para(doc, "يحق للمستثمر إيقاف الدفعات الشهرية والخروج في نهاية الشهر الخامس إذا لم تتحقق المؤشرات.", size=10)
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_para(doc, "مؤشرات الأداء — نقطة الفحص (الشهر 4):", bold=True, size=10)
|
|
||||||
add_table(doc,
|
|
||||||
["المؤشر", "الحد الأدنى", "الحد المثالي"],
|
|
||||||
[
|
|
||||||
["الرحلات اليومية", "70/يوم", "150/يوم"],
|
|
||||||
["السائقون المسجلون", "150", "350"],
|
|
||||||
["السائقون النشطون", "50", "150"],
|
|
||||||
["الإيرادات الشهرية", "$630", "$1,350"],
|
|
||||||
["معدل احتجاز السائق", "50%", "70%"],
|
|
||||||
["تقييم التطبيق", "3.0+", "3.5+"],
|
|
||||||
],
|
|
||||||
col_widths=[6, 4, 4]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_para(doc, "مؤشرات الأداء — نقطة القرار (الشهر 5):", bold=True, size=10)
|
|
||||||
add_table(doc,
|
|
||||||
["المؤشر", "حد الاستمرار", "الخروج إذا أقل من"],
|
|
||||||
[
|
|
||||||
["الرحلات اليومية", "120/يوم", "70/يوم"],
|
|
||||||
["السائقون النشطون", "100", "50"],
|
|
||||||
["الإيرادات الشهرية", "$1,080+", "$630"],
|
|
||||||
["نمو أسبوعي", "+10% متواصل", "ثبات أو تراجع"],
|
|
||||||
["عقود B2B", "عقد واحد", "صفر عقود"],
|
|
||||||
],
|
|
||||||
col_widths=[6, 4, 4]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 9. Break-Even ──
|
|
||||||
add_heading(doc, "تاسعاً: تحليل نقطة التعادل — نطاق الشهر 7 إلى 9", 1)
|
|
||||||
|
|
||||||
add_para(doc, "لماذا نطاق وليس رقماً ثابتاً؟", bold=True, size=10)
|
|
||||||
add_para(doc, "السوق السوري في مرحلة إعادة الإعمار = سوق متقلب بطبيعته. النطاق (7-9) أكثر صدقاً وأكثر حماية لكلا الطرفين.")
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_table(doc,
|
|
||||||
["السيناريو", "نقطة التعادل", "إجمالي الاستثمار", "الوصف"],
|
|
||||||
[
|
|
||||||
["متفائل", "الشهر 7", "$59,500-$60,000", "نمو سريع، B2B مبكر"],
|
|
||||||
["قاعدي (الأرجح)", "الشهر 8", "$67,500-$68,000", "نمو طبيعي"],
|
|
||||||
["محافظ", "الشهر 9", "$75,500-$76,000", "سوق متقلب"],
|
|
||||||
["خروج مبكر", "لا تعادل", "$51,500-$52,000", "آخر دفعة ش5"],
|
|
||||||
],
|
|
||||||
col_widths=[4, 3, 4, 5]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_para(doc, "معادلة التعادل:", bold=True, size=11)
|
|
||||||
add_para(doc, "889 رحلة/يوم (عند $0.30/رحلة × 30 يوماً = $8,010)", bold=True, size=12, color=PRIMARY)
|
|
||||||
add_table(doc,
|
|
||||||
["المعطى", "الرقم", "كيف وصلنا إليه"],
|
|
||||||
[
|
|
||||||
["عمولة الشركة لكل رحلة", "$0.30", "11% من متوسط رحلة $2.75"],
|
|
||||||
["أيام الشهر", "30", ""],
|
|
||||||
["OPEX الشهري المستهدف", "$8,000", ""],
|
|
||||||
["رحلات التعادل اليومية", "889/يوم", "$8,000 ÷ $0.30 ÷ 30"],
|
|
||||||
["تعادل الشهر 7 (900/يوم)", "$8,100 > $8,000 ✓", "متحقق"],
|
|
||||||
],
|
|
||||||
col_widths=[6, 4, 6]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 10. Risk Analysis ──
|
|
||||||
add_heading(doc, "عاشراً: تحليل المخاطر وخطط التخفيف", 1)
|
|
||||||
add_table(doc,
|
|
||||||
["المخاطرة", "التأثير", "الاحتمالية", "خطة التخفيف"],
|
|
||||||
[
|
|
||||||
["تأخر الترخيص", "عالٍ", "متوسطة", "البدء قبل الإطلاق بشهرين"],
|
|
||||||
["بطء نمو السائقين", "عالٍ", "منخفضة", "1,447 سائق بـ$1,400 — مثبت"],
|
|
||||||
["انخفاض الإيرادات", "عالٍ", "متوسطة", "شرط الخروج ش5 يحمي المستثمر"],
|
|
||||||
["تقلبات أمنية/سياسية", "عالٍ", "منخفضة-متوسطة", "بنية مستقلة - لا اعتماد على APIs غربية"],
|
|
||||||
["دخول منافس جديد", "متوسط", "متوسطة", "11% عمولة = عتبة تنافسية شبه مستحيلة"],
|
|
||||||
["مشاكل تقنية", "متوسط", "منخفضة", "المطور = المؤسس — استجابة فورية"],
|
|
||||||
["فشل الإعلانات", "متوسط", "منخفضة", "تجربة سابقة مثبتة + A/B Testing"],
|
|
||||||
],
|
|
||||||
col_widths=[5, 2, 3, 6]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 11. Roadmap ──
|
|
||||||
add_heading(doc, "حادي عشر: خارطة الطريق والأهداف التشغيلية", 1)
|
|
||||||
add_table(doc,
|
|
||||||
["المرحلة", "الشهر", "الأهداف", "مؤشرات النجاح"],
|
|
||||||
[
|
|
||||||
["التأسيس", "قبل الإطلاق", "ترخيص + مكتب + توظيف", "وثائق قانونية + فريق جاهز"],
|
|
||||||
["الإطلاق الناعم", "ش 1-2", "100-120 سائق محفز", "70+ رحلة/يوم"],
|
|
||||||
["بناء الزخم", "ش 3", "حوافز ذاتية + B2B", "150 رحلة/يوم"],
|
|
||||||
["نقطة الفحص", "ش 4", "مراجعة مع المستثمر", "70-150 رحلة/يوم | $630-$1,350"],
|
|
||||||
["قرار الاستمرار", "ش 5", "استمرار أو خروج", "120 رحلة/يوم | عقد B2B"],
|
|
||||||
["الاقتراب من التعادل", "ش 6", "750 رحلة/يوم", "عجز $1,250 فقط"],
|
|
||||||
["⚡ نطاق التعادل", "ش 7-9", "الإيرادات ≥ OPEX", "889+ رحلة/يوم"],
|
|
||||||
["النمو الذاتي", "ش 10-12", "فائض شهري", "$3,250-$5,500 فائض"],
|
|
||||||
["التوسع الجغرافي", "ش 13+", "حلب أو اللاذقية", "1,600+ رحلة/يوم"],
|
|
||||||
],
|
|
||||||
col_widths=[4, 2, 4, 6]
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 12. Conclusion ──
|
|
||||||
doc.add_page_break()
|
|
||||||
add_heading(doc, "ثاني عشر: الخلاصة والتوصية النهائية", 1)
|
|
||||||
|
|
||||||
add_para(doc, "ملخص نقاط الثقة للمستثمر:", bold=True, size=11)
|
|
||||||
add_bullet(doc, "المنتج جاهز ومختبر — 1,447 سائق التحقوا بـ$1,400 فقط")
|
|
||||||
add_bullet(doc, "أدنى عمولة في السوق (11%) = ميزة تنافسية دائمة")
|
|
||||||
add_bullet(doc, "نموذج الدفع الشهري يحمي المستثمر — لا تجميد لرأس المال")
|
|
||||||
add_bullet(doc, "شرط الخروج الواضح يضع سقفاً لأقصى خسارة (~$52,000)")
|
|
||||||
add_bullet(doc, "نقطة التعادل (7-9 أشهر) واقعية ومبنية على بيانات حقيقية")
|
|
||||||
add_bullet(doc, "البنية التقنية المستقلة = مقاومة للعقوبات والقيود")
|
|
||||||
|
|
||||||
add_para(doc, "", size=6)
|
|
||||||
add_table(doc,
|
|
||||||
["المؤشر", "القيمة"],
|
|
||||||
[
|
|
||||||
["رأس المال التأسيسي (مرة واحدة)", "$11,500-$12,000 — عند توقيع الاتفاقية"],
|
|
||||||
["المصاريف التشغيلية الشهرية", "$8,000/شهر — يتوقف عند التعادل"],
|
|
||||||
["أقصى تعرض للمستثمر", "$51,500-$52,000 — نقطة الخروج: نهاية الشهر الخامس"],
|
|
||||||
["إجمالي الاستثمار حتى التعادل", "$67,500 — $84,000"],
|
|
||||||
["نطاق التعادل", "الشهر السابع إلى التاسع"],
|
|
||||||
],
|
|
||||||
col_widths=[10, 8]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_para(doc, "", size=8)
|
|
||||||
add_para(doc, "هذه الدراسة أُعدت بناءً على بيانات حقيقية من السوق السوري وتجربة ميدانية فعلية. كل رقم فيها مبني على افتراضات محافظة.", bold=True, size=10, align=WD_ALIGN_PARAGRAPH.CENTER)
|
|
||||||
add_para(doc, "— نهاية دراسة الجدوى — الإصدار الثالث — يونيو 2026 —", size=9, color=GREY, align=WD_ALIGN_PARAGRAPH.CENTER)
|
|
||||||
|
|
||||||
# Save
|
|
||||||
docx_path = os.path.join(OUTPUT_DIR, "دراسة_الجدوى_سيرو_الإصدار_الثالث.docx")
|
|
||||||
doc.save(docx_path)
|
|
||||||
print(f"✅ DOCX saved: {docx_path}")
|
|
||||||
return docx_path
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
generate_docx()
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import json
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
|
|
||||||
def parse_dart_map(filepath):
|
|
||||||
translations = {}
|
|
||||||
if not os.path.exists(filepath):
|
|
||||||
return translations
|
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
|
||||||
matches = pattern.findall(content)
|
|
||||||
for match in matches:
|
|
||||||
key = match[1]
|
|
||||||
val = match[3]
|
|
||||||
key = key.replace('\\"', '"').replace("\\'", "'")
|
|
||||||
val = val.replace('\\"', '"').replace("\\'", "'")
|
|
||||||
translations[key] = val
|
|
||||||
|
|
||||||
return translations
|
|
||||||
|
|
||||||
ar_eg = parse_dart_map("siro_driver/lib/controller/local/ar_eg.dart")
|
|
||||||
ar_jo = parse_dart_map("siro_driver/lib/controller/local/ar_jo.dart")
|
|
||||||
ar_sy = parse_dart_map("siro_driver/lib/controller/local/ar_sy.dart")
|
|
||||||
|
|
||||||
with open("siro_driver_translations_data.json", "r", encoding="utf-8") as f:
|
|
||||||
json_data = json.load(f)
|
|
||||||
|
|
||||||
missing_keys_list = json_data.get('missing_keys', [])
|
|
||||||
|
|
||||||
ar_sy_keys = set(ar_sy.keys())
|
|
||||||
ar_jo_keys = set(ar_jo.keys())
|
|
||||||
ar_eg_keys = set(ar_eg.keys())
|
|
||||||
missing_keys_set = set(missing_keys_list)
|
|
||||||
|
|
||||||
all_keys = ar_sy_keys.union(ar_jo_keys).union(ar_eg_keys).union(missing_keys_set)
|
|
||||||
print(f"Total unique keys in union: {len(all_keys)}")
|
|
||||||
|
|
||||||
print(f"Keys in ar_eg not in ar_sy: {ar_eg_keys - ar_sy_keys}")
|
|
||||||
print(f"Keys in ar_sy not in ar_jo: {ar_sy_keys - ar_jo_keys}")
|
|
||||||
print(f"Keys in ar_jo not in ar_sy: {ar_jo_keys - ar_sy_keys}")
|
|
||||||
|
Before Width: | Height: | Size: 282 KiB |
@@ -1,289 +0,0 @@
|
|||||||
import hmac
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import base64
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
# Simulation configuration / Secrets
|
|
||||||
SECRET_KEY = "siro_super_secret_jwt_key"
|
|
||||||
SECRET_KEY_HMAC = "siro_super_secret_hmac_key"
|
|
||||||
FP_PEPPER = "siro_fp_pepper_salt"
|
|
||||||
S2S_SHARED_KEY = "s2s_shared_key_12345"
|
|
||||||
|
|
||||||
# Mock Database
|
|
||||||
db = {
|
|
||||||
"payments": [],
|
|
||||||
"passengerWallet": {},
|
|
||||||
"driverWallet": {},
|
|
||||||
"invoices": {},
|
|
||||||
"security_logs": []
|
|
||||||
}
|
|
||||||
|
|
||||||
def init_user(user_id, balance, is_driver=False):
|
|
||||||
if is_driver:
|
|
||||||
db["driverWallet"][user_id] = balance
|
|
||||||
else:
|
|
||||||
db["passengerWallet"][user_id] = balance
|
|
||||||
|
|
||||||
# Helper functions for Client
|
|
||||||
def generate_jwt(user_id, device_fp):
|
|
||||||
# Mock JWT creation payload
|
|
||||||
header = base64.b64encode(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()).decode().replace("=", "")
|
|
||||||
hashed_fp = hashlib.sha256((device_fp + FP_PEPPER).encode()).hexdigest()
|
|
||||||
payload = base64.b64encode(json.dumps({
|
|
||||||
"iss": "Tripz-Wallet",
|
|
||||||
"user_id": user_id,
|
|
||||||
"fingerPrint": hashed_fp,
|
|
||||||
"exp": 1900000000 # future exp
|
|
||||||
}).encode()).decode().replace("=", "")
|
|
||||||
signature = hmac.new(SECRET_KEY.encode(), f"{header}.{payload}".encode(), hashlib.sha256).digest()
|
|
||||||
encoded_signature = base64.b64encode(signature).decode().replace("=", "").replace("+", "-").replace("/", "_")
|
|
||||||
return f"{header}.{payload}.{encoded_signature}"
|
|
||||||
|
|
||||||
def calculate_hmac_header(user_id):
|
|
||||||
return hmac.new(SECRET_KEY_HMAC.encode(), user_id.encode(), hashlib.sha256).hexdigest()
|
|
||||||
|
|
||||||
# Helper functions for Server Authentication
|
|
||||||
def authenticate_request(headers, payload_user_id):
|
|
||||||
# 1. JWT verification
|
|
||||||
auth_header = headers.get("Authorization", "")
|
|
||||||
if not auth_header.startswith("Bearer "):
|
|
||||||
return False, "Authorization token required"
|
|
||||||
token = auth_header.split(" ")[1]
|
|
||||||
|
|
||||||
try:
|
|
||||||
parts = token.split(".")
|
|
||||||
if len(parts) != 3:
|
|
||||||
return False, "Invalid token structure"
|
|
||||||
# Parse payload
|
|
||||||
payload_b64 = parts[1]
|
|
||||||
payload_b64 += "=" * ((4 - len(payload_b64) % 4) % 4)
|
|
||||||
payload = json.loads(base64.b64decode(payload_b64).decode())
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"JWT decode failed: {str(e)}"
|
|
||||||
|
|
||||||
# Check Issuer
|
|
||||||
if payload.get("iss") != "Tripz-Wallet":
|
|
||||||
return False, "Invalid token issuer"
|
|
||||||
|
|
||||||
# Check User ID match
|
|
||||||
token_user_id = payload.get("user_id")
|
|
||||||
if token_user_id != payload_user_id:
|
|
||||||
return False, "User ID mismatch with token"
|
|
||||||
|
|
||||||
# 2. Device Fingerprint verification
|
|
||||||
device_fp_header = headers.get("X-Device-FP")
|
|
||||||
fp_in_token = payload.get("fingerPrint")
|
|
||||||
if device_fp_header and fp_in_token:
|
|
||||||
expected_fp = hashlib.sha256((device_fp_header + FP_PEPPER).encode()).hexdigest()
|
|
||||||
if not hmac.compare_digest(expected_fp, fp_in_token):
|
|
||||||
db["security_logs"].append(f"[WARNING] Device mismatch for user {payload_user_id}!")
|
|
||||||
return False, "Device mismatch (possible Session Hijacking)"
|
|
||||||
|
|
||||||
# 3. HMAC Auth verification
|
|
||||||
hmac_header = headers.get("X-HMAC-Auth")
|
|
||||||
if hmac_header:
|
|
||||||
expected_hmac = hmac.new(SECRET_KEY_HMAC.encode(), payload_user_id.encode(), hashlib.sha256).hexdigest()
|
|
||||||
if not hmac.compare_digest(expected_hmac, hmac_header):
|
|
||||||
db["security_logs"].append(f"[WARNING] HMAC mismatch for user {payload_user_id}!")
|
|
||||||
return False, "Invalid HMAC (possible payload/identity tampering)"
|
|
||||||
|
|
||||||
return True, "Authenticated"
|
|
||||||
|
|
||||||
# AI verification simulation (Mocking Gemini API)
|
|
||||||
def mock_gemini_verify_payment(invoice_number, amount, proof_text):
|
|
||||||
print(f"[Gemini AI Processing] Verifying proof for Invoice {invoice_number} of amount {amount}...")
|
|
||||||
print(f"[Gemini AI Input Proof] '{proof_text}'")
|
|
||||||
# Mocking semantic check on proof text
|
|
||||||
success = False
|
|
||||||
reason = ""
|
|
||||||
|
|
||||||
proof_lower = proof_text.lower()
|
|
||||||
amount_str = str(int(amount)) if amount == int(amount) else str(amount)
|
|
||||||
|
|
||||||
if amount_str in proof_lower and ("successful" in proof_lower or "transferred" in proof_lower or "تم تحويل" in proof_lower or "نجاح" in proof_lower):
|
|
||||||
success = True
|
|
||||||
reason = "Proof text verified successfully. Amount matches invoice."
|
|
||||||
else:
|
|
||||||
reason = f"Verification failed: Proof text does not indicate successful transfer of {amount}."
|
|
||||||
|
|
||||||
print(f"[Gemini AI Output] Verified: {success}, Reason: {reason}")
|
|
||||||
return {"verified": success, "reason": reason}
|
|
||||||
|
|
||||||
# Atomic transaction simulation for ride payment
|
|
||||||
def process_ride_payment(ride_id, driver_id, passenger_id, amount, payment_method, wallet_checked, s2s_key):
|
|
||||||
# S2S auth check
|
|
||||||
if s2s_key != S2S_SHARED_KEY:
|
|
||||||
return {"status": "failure", "message": "Unauthorized S2S call"}
|
|
||||||
|
|
||||||
print(f"\n--- Database Transaction Initiated for Ride {ride_id} ---")
|
|
||||||
|
|
||||||
# Save a rollback point
|
|
||||||
rollback_db = {
|
|
||||||
"payments": list(db["payments"]),
|
|
||||||
"passengerWallet": dict(db["passengerWallet"]),
|
|
||||||
"driverWallet": dict(db["driverWallet"])
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 1. Insert payment record
|
|
||||||
final_method = payment_method + "Ride" if wallet_checked else payment_method
|
|
||||||
payment_record = {
|
|
||||||
"id": str(uuid.uuid4().int)[:15],
|
|
||||||
"amount": amount,
|
|
||||||
"payment_method": final_method,
|
|
||||||
"passengerID": passenger_id,
|
|
||||||
"rideId": ride_id,
|
|
||||||
"driverID": driver_id
|
|
||||||
}
|
|
||||||
db["payments"].append(payment_record)
|
|
||||||
print(f"[DB] Inserted payment record: {payment_record}")
|
|
||||||
|
|
||||||
# 2. Deduct from passenger wallet
|
|
||||||
if wallet_checked:
|
|
||||||
if passenger_id not in db["passengerWallet"]:
|
|
||||||
db["passengerWallet"][passenger_id] = 0.0
|
|
||||||
|
|
||||||
db["passengerWallet"][passenger_id] -= amount
|
|
||||||
print(f"[DB] Deducted {amount} from Passenger {passenger_id}. New balance: {db["passengerWallet"][passenger_id]}")
|
|
||||||
|
|
||||||
# Settle debt if balance was negative before (example scenario)
|
|
||||||
# here we just apply deduction.
|
|
||||||
|
|
||||||
# 3. Deduct driver points (8% platform commission)
|
|
||||||
commission = amount * 0.08
|
|
||||||
if driver_id not in db["driverWallet"]:
|
|
||||||
db["driverWallet"][driver_id] = 0.0
|
|
||||||
db["driverWallet"][driver_id] -= commission
|
|
||||||
print(f"[DB] Subtracted 8% commission ({commission}) from Driver {driver_id} points. New points balance: {db["driverWallet"][driver_id]}")
|
|
||||||
|
|
||||||
print(f"[DB] Transaction Committed successfully.")
|
|
||||||
return {"status": "success", "message": "Transaction committed"}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Rollback
|
|
||||||
db["payments"] = rollback_db["payments"]
|
|
||||||
db["passengerWallet"] = rollback_db["passengerWallet"]
|
|
||||||
db["driverWallet"] = rollback_db["driverWallet"]
|
|
||||||
print(f"[DB ERROR] Transaction Failed. Rolled back changes. Error: {str(e)}")
|
|
||||||
return {"status": "failure", "message": f"Transaction failed: {str(e)}"}
|
|
||||||
|
|
||||||
# Run Scenario Simulations
|
|
||||||
def run_egypt_simulation():
|
|
||||||
print("\n" + "="*50)
|
|
||||||
print("SCENARIO 1: EGYPT - CREDIT CARD / PAYMOB (Ahmed)")
|
|
||||||
print("="*50)
|
|
||||||
user_id = "egypt_passenger_101"
|
|
||||||
driver_id = "egypt_driver_501"
|
|
||||||
device_fp = "ahmed_iphone_13_fp_hash"
|
|
||||||
|
|
||||||
init_user(user_id, 500.0) # passenger wallet balance
|
|
||||||
init_user(driver_id, 100.0, is_driver=True) # driver wallet points
|
|
||||||
|
|
||||||
# 1. Client signs request
|
|
||||||
jwt = generate_jwt(user_id, device_fp)
|
|
||||||
hmac_header = calculate_hmac_header(user_id)
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {jwt}",
|
|
||||||
"X-Device-FP": device_fp,
|
|
||||||
"X-HMAC-Auth": hmac_header
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"Passenger {user_id} initiating payment for Egypt ride. Ride cost: 120 EGP.")
|
|
||||||
|
|
||||||
# 2. Server authenticates headers
|
|
||||||
auth_ok, msg = authenticate_request(headers, user_id)
|
|
||||||
print(f"[Server Auth] Result: {auth_ok}, Message: {msg}")
|
|
||||||
|
|
||||||
if auth_ok:
|
|
||||||
# S2S transaction
|
|
||||||
res = process_ride_payment(
|
|
||||||
ride_id="ride_eg_999",
|
|
||||||
driver_id=driver_id,
|
|
||||||
passenger_id=user_id,
|
|
||||||
amount=120.0,
|
|
||||||
payment_method="PayMob",
|
|
||||||
wallet_checked=True,
|
|
||||||
s2s_key=S2S_SHARED_KEY
|
|
||||||
)
|
|
||||||
print(f"[Final Result] {res}")
|
|
||||||
|
|
||||||
def run_syria_simulation():
|
|
||||||
print("\n" + "="*50)
|
|
||||||
print("SCENARIO 2: SYRIA - MTN CASH WITH AI VERIFICATION (Bassel)")
|
|
||||||
print("="*50)
|
|
||||||
user_id = "syria_passenger_202"
|
|
||||||
driver_id = "syria_driver_602"
|
|
||||||
device_fp = "bassel_galaxy_s22_fp_hash"
|
|
||||||
|
|
||||||
init_user(user_id, 0.0) # passenger starts with 0.0
|
|
||||||
init_user(driver_id, 50.0, is_driver=True) # driver starts with 50 points
|
|
||||||
|
|
||||||
jwt = generate_jwt(user_id, device_fp)
|
|
||||||
hmac_header = calculate_hmac_header(user_id)
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {jwt}",
|
|
||||||
"X-Device-FP": device_fp,
|
|
||||||
"X-HMAC-Auth": hmac_header
|
|
||||||
}
|
|
||||||
|
|
||||||
invoice_number = "INV-MTN-8877"
|
|
||||||
amount = 50000.0 # 50,000 SYP for the ride/wallet load
|
|
||||||
|
|
||||||
print(f"Passenger {user_id} loads wallet via MTN Cash for invoice {invoice_number} of {amount} SYP.")
|
|
||||||
print("User transfers money and uploads transaction SMS proof.")
|
|
||||||
|
|
||||||
proof_text = "MTN Cash: You have successfully transferred 50000 SYP to SIRO system. Ref: 9812739182."
|
|
||||||
|
|
||||||
# 1. Server authenticates headers
|
|
||||||
auth_ok, msg = authenticate_request(headers, user_id)
|
|
||||||
print(f"[Server Auth] Result: {auth_ok}, Message: {msg}")
|
|
||||||
|
|
||||||
if auth_ok:
|
|
||||||
# 2. AI verifies proof text
|
|
||||||
ai_res = mock_gemini_verify_payment(invoice_number, amount, proof_text)
|
|
||||||
|
|
||||||
if ai_res["verified"]:
|
|
||||||
print(f"[Server] AI verified payment. Settle wallet.")
|
|
||||||
# Atomic settlement
|
|
||||||
print(f"\n--- Wallet Settlement Transaction Initiated (Syria) ---")
|
|
||||||
db["passengerWallet"][user_id] += amount
|
|
||||||
print(f"[DB] Added {amount} SYP to passenger {user_id} wallet. New balance: {db["passengerWallet"][user_id]} SYP")
|
|
||||||
print("[DB] Transaction Committed.")
|
|
||||||
else:
|
|
||||||
print(f"[Server] Payment rejected: {ai_res['reason']}")
|
|
||||||
|
|
||||||
def run_jordan_simulation():
|
|
||||||
print("\n" + "="*50)
|
|
||||||
print("SCENARIO 3: JORDAN - CLIQ WITH TAMPERED DEVICE FINGERPRINT (Rania)")
|
|
||||||
print("="*50)
|
|
||||||
user_id = "jordan_passenger_303"
|
|
||||||
device_fp = "rania_oneplus_11_fp_hash"
|
|
||||||
tampered_fp = "hijacked_device_fp_hash" # Attacker trying to reuse Rania's token from a different device
|
|
||||||
|
|
||||||
# 1. Client token generated for Rania on her real device
|
|
||||||
jwt = generate_jwt(user_id, device_fp)
|
|
||||||
hmac_header = calculate_hmac_header(user_id)
|
|
||||||
|
|
||||||
# Attacker sends request with correct JWT but hijacked/different device fingerprint header
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {jwt}",
|
|
||||||
"X-Device-FP": tampered_fp, # Hijacked device header
|
|
||||||
"X-HMAC-Auth": hmac_header
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"Attacker attempts to trigger payment under Rania's user_id using hijacked token on different device.")
|
|
||||||
|
|
||||||
# 2. Server authenticates headers
|
|
||||||
auth_ok, msg = authenticate_request(headers, user_id)
|
|
||||||
print(f"[Server Auth] Result: {auth_ok}, Message: {msg}")
|
|
||||||
|
|
||||||
if not auth_ok:
|
|
||||||
print("[Server Alert] Attack blocked successfully! Session Hijacking thwarted.")
|
|
||||||
print(f"Security logs: {db['security_logs']}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
run_egypt_simulation()
|
|
||||||
run_syria_simulation()
|
|
||||||
run_jordan_simulation()
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
def make_transparent_smooth(img_path, output_path, size):
|
|
||||||
if not os.path.exists(img_path):
|
|
||||||
print(f"Error: {img_path} does not exist.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
img = Image.open(img_path).convert("RGBA")
|
|
||||||
img = img.resize(size, Image.Resampling.LANCZOS)
|
|
||||||
|
|
||||||
datas = img.getdata()
|
|
||||||
new_data = []
|
|
||||||
|
|
||||||
for item in datas:
|
|
||||||
r, g, b, a = item
|
|
||||||
closeness = min(r, g, b)
|
|
||||||
if closeness >= 240:
|
|
||||||
if closeness >= 253:
|
|
||||||
alpha = 0
|
|
||||||
else:
|
|
||||||
alpha = int(a * (253 - closeness) / (253 - 240))
|
|
||||||
new_data.append((255, 255, 255, alpha))
|
|
||||||
else:
|
|
||||||
new_data.append(item)
|
|
||||||
|
|
||||||
img.putdata(new_data)
|
|
||||||
img.save(output_path, "PNG")
|
|
||||||
print(f"Successfully saved processed image to {output_path}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
artifact_dir = "scratch"
|
|
||||||
|
|
||||||
|
|
||||||
jobs = [
|
|
||||||
("car_marker_normal_1781540915043.png", "siro_rider/assets/images/car.png", (80, 90)),
|
|
||||||
("car_marker_normal_1781540915043.png", "siro_driver/assets/images/car.png", (80, 90)),
|
|
||||||
("car_marker_lady_1781540926836.png", "siro_rider/assets/images/lady1.png", (80, 90)),
|
|
||||||
("car_marker_lady_1781540926836.png", "siro_driver/assets/images/lady1.png", (80, 90)),
|
|
||||||
("category_fixed_price_1781540942631.png", "siro_rider/assets/images/carspeed.png", (500, 500)),
|
|
||||||
("category_comfort_1781540956914.png", "siro_rider/assets/images/blob.png", (500, 500)),
|
|
||||||
("category_electric_1781540970352.png", "siro_rider/assets/images/electric.png", (500, 500)),
|
|
||||||
("category_lady_1781540984745.png", "siro_rider/assets/images/lady.png", (500, 500)),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
for src, dst, size in jobs:
|
|
||||||
make_transparent_smooth(
|
|
||||||
os.path.join(artifact_dir, src),
|
|
||||||
dst,
|
|
||||||
size
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 290 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 283 KiB |
@@ -1 +0,0 @@
|
|||||||
print("Hello from python script")
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
languages = ['ar_eg', 'ar_jo', 'ar_sy', 'en', 'de', 'el', 'es', 'fa', 'fr', 'hi', 'it', 'ru', 'tr', 'ur', 'zh']
|
|
||||||
local_dir = "siro_driver/lib/controller/local"
|
|
||||||
|
|
||||||
def parse_dart_map(filepath):
|
|
||||||
translations = {}
|
|
||||||
if not os.path.exists(filepath):
|
|
||||||
print(f"Error: file {filepath} does not exist!")
|
|
||||||
return None
|
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Matches: "key": "value"
|
|
||||||
pattern = re.compile(r'^\s*([\'"])(.*?)\1\s*:\s*([\'"])(.*?)\3\s*,?\s*$', re.MULTILINE)
|
|
||||||
matches = pattern.findall(content)
|
|
||||||
for match in matches:
|
|
||||||
key = match[1]
|
|
||||||
val = match[3]
|
|
||||||
# Store raw keys and values to check escaping
|
|
||||||
translations[key] = val
|
|
||||||
return translations
|
|
||||||
|
|
||||||
parsed_data = {}
|
|
||||||
all_valid = True
|
|
||||||
|
|
||||||
for lang in languages:
|
|
||||||
filepath = os.path.join(local_dir, f"{lang}.dart")
|
|
||||||
trans = parse_dart_map(filepath)
|
|
||||||
if trans is None:
|
|
||||||
all_valid = False
|
|
||||||
continue
|
|
||||||
parsed_data[lang] = trans
|
|
||||||
print(f"Verified {lang}.dart: parsed {len(trans)} entries.")
|
|
||||||
if len(trans) != 2660:
|
|
||||||
print(f" ERROR: Expected 2660 entries, got {len(trans)}")
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
if not all_valid:
|
|
||||||
print("Verification FAILED on basic counts.")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# Check that key sets are identical
|
|
||||||
ref_lang = languages[0]
|
|
||||||
ref_keys = set(parsed_data[ref_lang].keys())
|
|
||||||
|
|
||||||
for lang in languages[1:]:
|
|
||||||
keys = set(parsed_data[lang].keys())
|
|
||||||
if keys != ref_keys:
|
|
||||||
print(f"ERROR: Key sets differ between {ref_lang} and {lang}!")
|
|
||||||
print(f" Keys in {ref_lang} not in {lang}: {len(ref_keys - keys)}")
|
|
||||||
print(f" Keys in {lang} not in {ref_lang}: {len(keys - ref_keys)}")
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
# Check for escaping errors (e.g. unescaped dollar signs in double-quoted strings in the Dart source code)
|
|
||||||
# In the raw file content, any dollar sign must be preceded by a backslash unless it is already escaped.
|
|
||||||
# Let's inspect the files directly for raw '$' characters that are not preceded by '\'
|
|
||||||
dollar_pattern = re.compile(r'(?<!\\)\$')
|
|
||||||
|
|
||||||
for lang in languages:
|
|
||||||
filepath = os.path.join(local_dir, f"{lang}.dart")
|
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
for idx, line in enumerate(lines):
|
|
||||||
# We search inside map entries
|
|
||||||
if ":" in line:
|
|
||||||
# check for unescaped dollar signs
|
|
||||||
# A dollar sign not preceded by a backslash is a compilation error in Dart double-quoted strings
|
|
||||||
# unless it's single quotes or some specific construct, but we used double quotes for all lines.
|
|
||||||
unescaped_dollars = dollar_pattern.findall(line)
|
|
||||||
if unescaped_dollars:
|
|
||||||
print(f"ERROR: Unescaped dollar sign in {filepath} line {idx+1}:")
|
|
||||||
print(f" {line.strip()}")
|
|
||||||
all_valid = False
|
|
||||||
|
|
||||||
if all_valid:
|
|
||||||
print("\nSUCCESS: All files verified! They have identical keys (2660 keys) and correct escaping.")
|
|
||||||
else:
|
|
||||||
print("\nVerification FAILED.")
|
|
||||||
exit(1)
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -29,7 +29,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// import 'dart:io';
|
|
||||||
|
|
||||||
// import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
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 '../../main.dart';
|
||||||
|
import '../../print.dart';
|
||||||
|
|
||||||
class DeviceHelper {
|
class DeviceHelper {
|
||||||
static Future<String> getDeviceFingerprint() async {
|
static Future<String> getDeviceFingerprint() async {
|
||||||
@@ -80,7 +83,7 @@ class SecurityHelper {
|
|||||||
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error during security checks: $e");
|
Log.print("Error during security checks: $e");
|
||||||
}
|
}
|
||||||
|
|
||||||
await box.write('isNotTrust', isNotTrust);
|
await box.write('isNotTrust', isNotTrust);
|
||||||
@@ -98,7 +101,7 @@ class SecurityHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void _showSecurityWarning() {
|
static void _showSecurityWarning() {
|
||||||
RxInt secondsRemaining = 10.obs;
|
final secondsRemaining = RxInt(10);
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
CupertinoAlertDialog(
|
CupertinoAlertDialog(
|
||||||
@@ -150,90 +153,3 @@ class SecurityHelper {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.serialNumber,
|
|
||||||
// 'fingerprint': androidInfo.fingerprint,
|
|
||||||
// 'type': androidInfo.type,
|
|
||||||
// 'data': androidInfo.data,
|
|
||||||
// 'version': androidInfo.version,
|
|
||||||
// 'tags': androidInfo.tags,
|
|
||||||
// 'display': androidInfo.display,
|
|
||||||
// };
|
|
||||||
// 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) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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'}');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ class SecurityChecks {
|
|||||||
return result;
|
return result;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
print("Failed to check security status: ${e.message}");
|
print("Failed to check security status: ${e.message}");
|
||||||
return true; // Treat platform errors as a compromised device (for safety)
|
return false; // Platform not supported → treat as secure
|
||||||
|
} catch (e) {
|
||||||
|
print("Security check error: $e");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
jni
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import firebase_messaging
|
|||||||
import flutter_image_compress_macos
|
import flutter_image_compress_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import local_auth_darwin
|
import local_auth_darwin
|
||||||
|
import package_info_plus
|
||||||
|
import path_provider_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
@@ -25,6 +27,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
||||||
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- AppAuth (1.7.6):
|
|
||||||
- AppAuth/Core (= 1.7.6)
|
|
||||||
- AppAuth/ExternalUserAgent (= 1.7.6)
|
|
||||||
- AppAuth/Core (1.7.6)
|
|
||||||
- AppAuth/ExternalUserAgent (1.7.6):
|
|
||||||
- AppAuth/Core
|
|
||||||
- AppCheckCore (11.2.0):
|
|
||||||
- GoogleUtilities/Environment (~> 8.0)
|
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
|
||||||
- PromisesObjC (~> 2.4)
|
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- file_selector_macos (0.0.1):
|
- file_selector_macos (0.0.1):
|
||||||
@@ -80,20 +70,9 @@ PODS:
|
|||||||
- flutter_secure_storage_macos (6.1.3):
|
- flutter_secure_storage_macos (6.1.3):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- google_sign_in_ios (0.0.1):
|
|
||||||
- AppAuth (>= 1.7.4)
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- GoogleSignIn (~> 8.0)
|
|
||||||
- GTMSessionFetcher (>= 3.4.0)
|
|
||||||
- GoogleDataTransport (10.1.0):
|
- GoogleDataTransport (10.1.0):
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- GoogleSignIn (8.0.0):
|
|
||||||
- AppAuth (< 2.0, >= 1.7.3)
|
|
||||||
- AppCheckCore (~> 11.0)
|
|
||||||
- GTMAppAuth (< 5.0, >= 4.1.1)
|
|
||||||
- GTMSessionFetcher/Core (~> 3.3)
|
|
||||||
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
|
||||||
- GoogleUtilities/Environment
|
- GoogleUtilities/Environment
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
@@ -118,14 +97,6 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (8.1.0):
|
- GoogleUtilities/UserDefaults (8.1.0):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
- GTMAppAuth (4.1.1):
|
|
||||||
- AppAuth/Core (~> 1.7)
|
|
||||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
|
|
||||||
- GTMSessionFetcher (3.5.0):
|
|
||||||
- GTMSessionFetcher/Full (= 3.5.0)
|
|
||||||
- GTMSessionFetcher/Core (3.5.0)
|
|
||||||
- GTMSessionFetcher/Full (3.5.0):
|
|
||||||
- GTMSessionFetcher/Core
|
|
||||||
- local_auth_darwin (0.0.1):
|
- local_auth_darwin (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -134,6 +105,9 @@ PODS:
|
|||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- path_provider_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- PromisesSwift (2.4.0):
|
- PromisesSwift (2.4.0):
|
||||||
- PromisesObjC (= 2.4.0)
|
- PromisesObjC (= 2.4.0)
|
||||||
@@ -152,15 +126,13 @@ DEPENDENCIES:
|
|||||||
- flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`)
|
- flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`)
|
||||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- google_sign_in_ios (from `Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin`)
|
|
||||||
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- AppAuth
|
|
||||||
- AppCheckCore
|
|
||||||
- Firebase
|
- Firebase
|
||||||
- FirebaseCore
|
- FirebaseCore
|
||||||
- FirebaseCoreExtension
|
- FirebaseCoreExtension
|
||||||
@@ -171,10 +143,7 @@ SPEC REPOS:
|
|||||||
- FirebaseRemoteConfigInterop
|
- FirebaseRemoteConfigInterop
|
||||||
- FirebaseSessions
|
- FirebaseSessions
|
||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
- GoogleSignIn
|
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- GTMAppAuth
|
|
||||||
- GTMSessionFetcher
|
|
||||||
- nanopb
|
- nanopb
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- PromisesSwift
|
- PromisesSwift
|
||||||
@@ -196,20 +165,18 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
google_sign_in_ios:
|
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/google_sign_in_ios/darwin
|
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
|
||||||
|
path_provider_foundation:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
|
||||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
|
||||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||||
file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7
|
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||||
firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326
|
firebase_core: 7667f880631ae8ad10e3d6567ab7582fe0682326
|
||||||
firebase_crashlytics: af8dce4a4f3b2b1556bf51043623060a5fc7eca7
|
firebase_crashlytics: af8dce4a4f3b2b1556bf51043623060a5fc7eca7
|
||||||
@@ -224,19 +191,16 @@ SPEC CHECKSUMS:
|
|||||||
FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7
|
FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7
|
||||||
flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0
|
flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0
|
||||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
|
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
|
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
|
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
|
||||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||||
|
|
||||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "93.0.0"
|
version: "88.0.0"
|
||||||
_flutterfire_internals:
|
_flutterfire_internals:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -21,10 +21,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.1"
|
version: "8.1.1"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -53,10 +53,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.1"
|
version: "2.13.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -117,10 +117,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -145,14 +145,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
code_assets:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: code_assets
|
|
||||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.1"
|
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -186,7 +178,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.5+2"
|
version: "0.3.5+2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
@@ -205,10 +197,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.7"
|
version: "3.1.2"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -253,18 +245,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: envied
|
name: envied
|
||||||
sha256: "42132a746494b0a7bc19062cdddd3a01694f696caca684456ff01526c833decc"
|
sha256: cd95ddf0982e53f0b6664e889d4a9ce678b3907a59a5047923404375ef6dcacc
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.3.1"
|
||||||
envied_generator:
|
envied_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: envied_generator
|
name: envied_generator
|
||||||
sha256: e1e66498080f531e89d9ea7971f96b287dffdd05df16efdd31f9f74faa77e005
|
sha256: "81ad332912f1b31afbd2b913aff9ec7b032e97f4ba7e419f52d02bb90637e77c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.3.1"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -309,10 +301,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_macos
|
name: file_selector_macos
|
||||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.5"
|
version: "0.9.4+4"
|
||||||
file_selector_platform_interface:
|
file_selector_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -490,10 +482,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.35"
|
version: "2.0.31"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -588,10 +580,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: google_fonts
|
name: google_fonts
|
||||||
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.3"
|
version: "6.3.2"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -600,14 +592,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
hooks:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: hooks
|
|
||||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.2"
|
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -636,10 +620,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
|
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.9.1"
|
version: "4.8.0"
|
||||||
image_cropper:
|
image_cropper:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -668,18 +652,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac"
|
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "1.2.1"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+17"
|
version: "0.8.13+1"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -692,10 +676,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+6"
|
version: "0.8.13"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -708,10 +692,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_macos
|
name: image_picker_macos
|
||||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.2+1"
|
version: "0.2.2"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -752,22 +736,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0+1"
|
version: "1.2.0+1"
|
||||||
jni:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: jni
|
|
||||||
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
jni_flutter:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: jni_flutter
|
|
||||||
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -780,10 +748,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.12.0"
|
version: "4.9.0"
|
||||||
jwt_decoder:
|
jwt_decoder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -804,26 +772,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.2"
|
version: "10.0.9"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.10"
|
version: "3.0.9"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.1"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -852,18 +820,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: local_auth_android
|
name: local_auth_android
|
||||||
sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467
|
sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.56"
|
version: "1.0.52"
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: local_auth_darwin
|
name: local_auth_darwin
|
||||||
sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49"
|
sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.1"
|
version: "1.6.0"
|
||||||
local_auth_platform_interface:
|
local_auth_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -900,26 +868,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.18"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.16.0"
|
||||||
mgrs_dart:
|
mgrs_dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -936,14 +904,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
objective_c:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: objective_c
|
|
||||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "9.4.1"
|
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -952,6 +912,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
package_info_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: package_info_plus
|
||||||
|
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.0"
|
||||||
|
package_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_info_plus_platform_interface
|
||||||
|
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
path:
|
path:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -972,18 +948,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
|
sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.2.19"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.0"
|
version: "2.4.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1096,14 +1072,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
record_use:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_use
|
|
||||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.0"
|
|
||||||
secure_string_operations:
|
secure_string_operations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1136,42 +1104,42 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
|
sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.3"
|
version: "4.2.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.2"
|
version: "1.10.1"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
|
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2+1"
|
version: "2.4.2"
|
||||||
sqflite_android:
|
sqflite_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_android
|
name: sqflite_android
|
||||||
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
|
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2+3"
|
version: "2.4.1"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.8"
|
version: "2.5.6"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1224,10 +1192,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0+1"
|
version: "3.4.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1240,10 +1208,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
version: "0.7.4"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1272,18 +1240,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.30"
|
version: "6.3.20"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.4.1"
|
version: "6.3.4"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1296,10 +1264,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.5"
|
version: "3.2.3"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1312,10 +1280,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.1"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1328,18 +1296,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.1.4"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.0"
|
version: "15.0.0"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1408,10 +1376,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "6.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1421,5 +1389,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.11.0 <4.0.0"
|
dart: ">=3.8.0 <4.0.0"
|
||||||
flutter: ">=3.38.4"
|
flutter: ">=3.32.0"
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ dependencies:
|
|||||||
device_info_plus: ^11.5.0
|
device_info_plus: ^11.5.0
|
||||||
flutter_staggered_animations: ^1.1.1
|
flutter_staggered_animations: ^1.1.1
|
||||||
jailbreak_root_detection: ^1.1.5
|
jailbreak_root_detection: ^1.1.5
|
||||||
|
package_info_plus: ^4.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
jni
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -29,7 +29,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -132,21 +132,19 @@ class CRUD {
|
|||||||
try {
|
try {
|
||||||
attempts++;
|
attempts++;
|
||||||
response = await doPost();
|
response = await doPost();
|
||||||
break; // نجح الاتصال — نخرج
|
break;
|
||||||
} on SocketException catch (_) {
|
} on SocketException catch (_) {
|
||||||
Log.print('⚠️ SocketException attempt $attempts — $link');
|
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||||
if (attempts >= 3) {
|
if (attempts >= 3) {
|
||||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||||
return 'no_internet';
|
return 'no_internet';
|
||||||
}
|
}
|
||||||
// انتظار قبل إعادة المحاولة — مهم للشبكات المتقطعة
|
await Future.delayed(Duration(seconds: attempts));
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
} on TimeoutException catch (_) {
|
} on TimeoutException catch (_) {
|
||||||
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
||||||
if (attempts >= 3) return 'failure';
|
if (attempts >= 3) return 'failure';
|
||||||
// لا انتظار — نعيد فوراً
|
await Future.delayed(Duration(milliseconds: 500 * attempts));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// errno = 9 (Bad file descriptor) — إعادة المحاولة
|
|
||||||
if (e.toString().contains('errno = 9') && attempts < 3) {
|
if (e.toString().contains('errno = 9') && attempts < 3) {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ class SecurityHelper {
|
|||||||
// }
|
// }
|
||||||
static void _showSecurityWarning() {
|
static void _showSecurityWarning() {
|
||||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
// Use an RxInt to track the remaining seconds. This is the KEY!
|
||||||
RxInt secondsRemaining = 10.obs;
|
final secondsRemaining = RxInt(10);
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
CupertinoAlertDialog(
|
CupertinoAlertDialog(
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ class SecurityChecks {
|
|||||||
return result;
|
return result;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
print("Failed to check security status: ${e.message}");
|
print("Failed to check security status: ${e.message}");
|
||||||
return true; // Treat platform errors as a compromised device (for safety)
|
return false; // Platform not supported → treat as secure
|
||||||
|
} catch (e) {
|
||||||
|
print("Security check error: $e");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -28,7 +28,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,16 +24,35 @@ class CRUD {
|
|||||||
final NetGuard _netGuard = NetGuard();
|
final NetGuard _netGuard = NetGuard();
|
||||||
final _client = SslPinning.createPinnedClient();
|
final _client = SslPinning.createPinnedClient();
|
||||||
|
|
||||||
/// Stores the signature of the last logged error to prevent duplicates.
|
static bool _isRefreshingJWT = false;
|
||||||
static String _lastErrorSignature = '';
|
static String _lastErrorSignature = '';
|
||||||
|
|
||||||
/// Stores the timestamp of the last logged error.
|
|
||||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||||
|
|
||||||
/// The minimum time that must pass before logging the same error again.
|
|
||||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
||||||
|
|
||||||
/// Asynchronously logs an error to the server with debouncing to prevent log flooding.
|
/// JWT validity check without external libraries.
|
||||||
|
static bool _isJwtValid(String? token) {
|
||||||
|
if (token == null || token.isEmpty) return false;
|
||||||
|
try {
|
||||||
|
final parts = token.split('.');
|
||||||
|
if (parts.length != 3) return false;
|
||||||
|
String payload = parts[1];
|
||||||
|
switch (payload.length % 4) {
|
||||||
|
case 2:
|
||||||
|
payload += '==';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
payload += '=';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||||
|
final exp = decoded['exp'];
|
||||||
|
if (exp == null) return false;
|
||||||
|
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> addError(
|
static Future<void> addError(
|
||||||
String error, String details, String where) async {
|
String error, String details, String where) async {
|
||||||
try {
|
try {
|
||||||
@@ -54,11 +73,9 @@ class CRUD {
|
|||||||
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
|
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
|
||||||
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
||||||
|
|
||||||
// طباعة الخطأ في الكونسول للمطور للمتابعة الفورية
|
|
||||||
Log.print(
|
Log.print(
|
||||||
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
||||||
|
|
||||||
// Fire-and-forget call to prevent infinite loops if the logger itself fails.
|
|
||||||
CRUD().post(
|
CRUD().post(
|
||||||
link: AppLink.addError,
|
link: AppLink.addError,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -75,22 +92,14 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// دالة مساعدة خاصة: تجيب البصمة المشفرة من GetStorage
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
String _getFpHeader() {
|
String _getFpHeader() {
|
||||||
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
// دالة مساعدة خاصة: تقرأ JWT من FlutterSecureStorage (آمن)
|
|
||||||
// بدلاً من GetStorage (غير مشفر)
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
|
||||||
Future<String> _getJwt() async {
|
Future<String> _getJwt() async {
|
||||||
try {
|
try {
|
||||||
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
|
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
|
||||||
if (encryptedJwt == null || encryptedJwt.isEmpty) {
|
if (encryptedJwt == null || encryptedJwt.isEmpty) {
|
||||||
// Fallback إلى GetStorage للتوافقية
|
|
||||||
final String? fallback = box.read(BoxName.jwt);
|
final String? fallback = box.read(BoxName.jwt);
|
||||||
if (fallback != null) {
|
if (fallback != null) {
|
||||||
return r(fallback).toString().split(Env.addd)[0];
|
return r(fallback).toString().split(Env.addd)[0];
|
||||||
@@ -100,7 +109,6 @@ class CRUD {
|
|||||||
return r(encryptedJwt).toString().split(Env.addd)[0];
|
return r(encryptedJwt).toString().split(Env.addd)[0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.print('Error reading JWT from SecureStorage: $e');
|
Log.print('Error reading JWT from SecureStorage: $e');
|
||||||
// Fallback
|
|
||||||
final String? fallback = box.read(BoxName.jwt);
|
final String? fallback = box.read(BoxName.jwt);
|
||||||
if (fallback != null) {
|
if (fallback != null) {
|
||||||
return r(fallback).toString().split(Env.addd)[0];
|
return r(fallback).toString().split(Env.addd)[0];
|
||||||
@@ -109,168 +117,119 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Centralized private method to handle all API requests.
|
/// Centralized request handler with retry for weak networks.
|
||||||
/// Includes retry logic, network checking, and standardized error handling.
|
/// For Syria (3G): 60s total timeout, 3 retries, exponential backoff.
|
||||||
Future<dynamic> _makeRequest({
|
Future<dynamic> _makeRequest({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
required Map<String, String> headers,
|
required Map<String, String> headers,
|
||||||
}) async {
|
}) async {
|
||||||
const connectTimeout = Duration(seconds: 6);
|
const totalTimeout = Duration(seconds: 60);
|
||||||
const receiveTimeout = Duration(seconds: 10);
|
|
||||||
|
|
||||||
Future<http.Response> doPost() {
|
Future<http.Response> doPost() {
|
||||||
final url = Uri.parse(link);
|
final url = Uri.parse(link);
|
||||||
return _client
|
return _client
|
||||||
.post(url, body: payload, headers: headers)
|
.post(url, body: payload, headers: headers)
|
||||||
.timeout(connectTimeout + receiveTimeout);
|
.timeout(totalTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Response response;
|
http.Response? response;
|
||||||
try {
|
int attempts = 0;
|
||||||
// retry ذكي: محاولة واحدة إضافية فقط لأخطاء شبكة/5xx
|
|
||||||
|
while (attempts < 3) {
|
||||||
try {
|
try {
|
||||||
|
attempts++;
|
||||||
response = await doPost();
|
response = await doPost();
|
||||||
|
break;
|
||||||
} on SocketException catch (_) {
|
} on SocketException catch (_) {
|
||||||
response = await doPost();
|
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||||
} on TimeoutException catch (_) {
|
if (attempts >= 3) {
|
||||||
response = await doPost();
|
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||||
}
|
return 'no_internet';
|
||||||
|
}
|
||||||
final sc = response.statusCode;
|
await Future.delayed(Duration(seconds: attempts));
|
||||||
final body = response.body;
|
} on TimeoutException catch (_) {
|
||||||
Log.print('request: ${response.request}');
|
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
||||||
Log.print('body: $body');
|
if (attempts >= 3) return 'failure';
|
||||||
// Log.print('link: $link');
|
} catch (e) {
|
||||||
Log.print('headers: $headers');
|
if (e.toString().contains('errno = 9') && attempts < 3) {
|
||||||
Log.print('payload: $payload');
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
continue;
|
||||||
// 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(
|
addError(
|
||||||
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
|
||||||
return 'failure';
|
return 'failure';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 4xx أخرى
|
if (response == null) return 'failure';
|
||||||
return 'failure';
|
|
||||||
} on SocketException {
|
final sc = response.statusCode;
|
||||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
final body = response.body;
|
||||||
return 'no_internet';
|
Log.print('request: ${response.request}');
|
||||||
} on TimeoutException {
|
Log.print('body: $body');
|
||||||
return 'failure';
|
Log.print('payload: $payload');
|
||||||
} catch (e, st) {
|
|
||||||
addError('HTTP Request Exception: $e', 'Stack: $st',
|
if (sc >= 200 && sc < 300) {
|
||||||
'CRUD._makeRequest $link');
|
try {
|
||||||
|
return jsonDecode(body);
|
||||||
|
} catch (e, st) {
|
||||||
|
addError('JSON Decode Error', 'Body: $body\n$st',
|
||||||
|
'CRUD._makeRequest $link');
|
||||||
|
return 'failure';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc == 401) {
|
||||||
|
final isNonCritical = link.contains('errorApp.php');
|
||||||
|
if (!_isRefreshingJWT && !isNonCritical) {
|
||||||
|
_isRefreshingJWT = true;
|
||||||
|
try {
|
||||||
|
await Get.put(LoginController()).getJWT();
|
||||||
|
} finally {
|
||||||
|
_isRefreshingJWT = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'token_expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc >= 500) {
|
||||||
|
addError(
|
||||||
|
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
||||||
return 'failure';
|
return 'failure';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 'failure';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// post — طلب POST عادي للراكب/السائق
|
|
||||||
// ───────────────────────────────────────────────────────────────
|
|
||||||
// التغيير: إضافة X-Device-FP header
|
|
||||||
// القيمة: fp_encrypted من GetStorage
|
|
||||||
// السيرفر يتحقق: sha256(fp_encrypted + FP_PEPPER) == JWT.fingerPrint
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
Future<dynamic> post({
|
Future<dynamic> post({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
final token = await _getJwt();
|
String token = await _getJwt();
|
||||||
|
|
||||||
final headers = {
|
final headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $token',
|
'Authorization': 'Bearer $token',
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': _getFpHeader(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return await _makeRequest(
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
link: link,
|
|
||||||
payload: payload,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// get — طلب GET للراكب/السائق (يستخدم POST method)
|
|
||||||
// ───────────────────────────────────────────────────────────────
|
|
||||||
// التغيير: إضافة X-Device-FP header
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
Future<dynamic> get({
|
Future<dynamic> get({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
final token = await _getJwt();
|
String token = await _getJwt();
|
||||||
var url = Uri.parse(link);
|
|
||||||
var response = await _client.post(
|
|
||||||
url,
|
|
||||||
body: payload,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Authorization': 'Bearer $token',
|
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Log.print('request: ${response.request}');
|
final headers = {
|
||||||
Log.print('body: ${response.body}');
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
Log.print('payload: $payload');
|
'Authorization': 'Bearer $token',
|
||||||
|
'X-Device-FP': _getFpHeader(),
|
||||||
|
};
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
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 _client.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}'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
@@ -290,65 +249,30 @@ class CRUD {
|
|||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization': 'Bearer $jwt',
|
'Authorization': 'Bearer $jwt',
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': _getFpHeader(),
|
||||||
};
|
};
|
||||||
// add print debug
|
|
||||||
Log.print('headers: $headers');
|
Log.print('headers: $headers');
|
||||||
Log.print('payload: $payload');
|
Log.print('payload: $payload');
|
||||||
Log.print('link: $link');
|
Log.print('link: $link');
|
||||||
|
|
||||||
return await _makeRequest(
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
link: link,
|
|
||||||
payload: payload,
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
// getWallet — طلب GET لسيرفر المدفوعات (يستخدم POST method)
|
|
||||||
// ───────────────────────────────────────────────────────────────
|
|
||||||
// التغيير: إضافة X-Device-FP header
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
|
||||||
Future<dynamic> getWallet({
|
Future<dynamic> getWallet({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
var s = await LoginController().getJwtWallet();
|
var s = await LoginController().getJwtWallet();
|
||||||
final hmac = box.read(BoxName.hmac);
|
final hmac = box.read(BoxName.hmac);
|
||||||
var url = Uri.parse(link);
|
|
||||||
|
|
||||||
var response = await _client.post(
|
final headers = {
|
||||||
url,
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
body: payload,
|
'Authorization': 'Bearer $s',
|
||||||
headers: {
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'X-Device-FP': _getFpHeader(),
|
||||||
'Authorization': 'Bearer $s',
|
};
|
||||||
'X-HMAC-Auth': hmac.toString(),
|
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================================================================
|
// =======================================================================
|
||||||
@@ -361,65 +285,20 @@ class CRUD {
|
|||||||
{required String link, Map<String, dynamic>? payload}) async {
|
{required String link, Map<String, dynamic>? payload}) async {
|
||||||
final s = await LoginController().getJwtWallet();
|
final s = await LoginController().getJwtWallet();
|
||||||
final hmac = box.read(BoxName.hmac);
|
final hmac = box.read(BoxName.hmac);
|
||||||
final url = Uri.parse(link);
|
|
||||||
|
|
||||||
try {
|
final headers = {
|
||||||
final response = await _client.post(
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
url,
|
'Authorization': 'Bearer $s',
|
||||||
body: payload,
|
'X-HMAC-Auth': hmac.toString(),
|
||||||
headers: {
|
'X-Device-FP': _getFpHeader(),
|
||||||
'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}) {
|
final result = await _makeRequest(link: link, payload: payload, headers: headers);
|
||||||
return {
|
if (result is Map || result is List) return result;
|
||||||
'status': status,
|
if (result == 'no_internet') {
|
||||||
'message': message,
|
return {'status': 'failure', 'message': 'no_internet', 'code': -1};
|
||||||
'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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future sendWhatsAppAuth(String to, String token) async {
|
Future sendWhatsAppAuth(String to, String token) async {
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ class SecurityHelper {
|
|||||||
/// Deletes all app data
|
/// Deletes all app data
|
||||||
static void _showSecurityWarning() {
|
static void _showSecurityWarning() {
|
||||||
// Use an RxInt to track the remaining seconds. This is the KEY!
|
// Use an RxInt to track the remaining seconds. This is the KEY!
|
||||||
RxInt secondsRemaining = 10.obs;
|
final secondsRemaining = RxInt(10);
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
CupertinoAlertDialog(
|
CupertinoAlertDialog(
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ class SecurityChecks {
|
|||||||
return result;
|
return result;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
Log.print("Failed to check security status: ${e.message}");
|
Log.print("Failed to check security status: ${e.message}");
|
||||||
return true; // Treat platform errors as a compromised device (for safety)
|
return false; // Platform not supported → treat as secure (not compromised)
|
||||||
|
} catch (e) {
|
||||||
|
Log.print("Security check error: $e");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
// Function to check for common root binaries
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
bool isRooted()
|
bool isRooted()
|
||||||
{
|
{
|
||||||
std::string paths[] = {
|
std::string paths[] = {
|
||||||
@@ -29,7 +29,13 @@ bool isRooted()
|
|||||||
"/system/bin/su",
|
"/system/bin/su",
|
||||||
"/system/bin/magisk",
|
"/system/bin/magisk",
|
||||||
"/system/xbin/magisk",
|
"/system/xbin/magisk",
|
||||||
"/sbin/magisk"};
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
for (const auto &path : paths)
|
for (const auto &path : paths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart' as http_io;
|
||||||
|
|
||||||
class SslPinning {
|
class SslPinning {
|
||||||
SslPinning._();
|
SslPinning._();
|
||||||
@@ -29,13 +30,13 @@ class SslPinning {
|
|||||||
(X509Certificate cert, String host, int port) {
|
(X509Certificate cert, String host, int port) {
|
||||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||||
for (final entry in _pins.entries) {
|
for (final entry in _pins.entries) {
|
||||||
if (host.endsWith(entry.key)) {
|
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||||
if (entry.value.contains(derHash)) return true;
|
if (entry.value.contains(derHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_globalPins.contains(derHash)) return true;
|
if (_globalPins.contains(derHash)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
return http.IOClient(httpClient);
|
return http_io.IOClient(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
jni
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import firebase_messaging
|
|||||||
import flutter_image_compress_macos
|
import flutter_image_compress_macos
|
||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
|
import path_provider_foundation
|
||||||
import share_plus
|
import share_plus
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
@@ -26,6 +27,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "93.0.0"
|
version: "88.0.0"
|
||||||
_flutterfire_internals:
|
_flutterfire_internals:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -21,10 +21,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.1"
|
version: "8.1.1"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -53,10 +53,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.1"
|
version: "2.13.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -117,10 +117,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -145,14 +145,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
code_assets:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: code_assets
|
|
||||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.1"
|
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -197,18 +189,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.9"
|
version: "1.0.8"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.7"
|
version: "3.1.2"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -245,18 +237,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: envied
|
name: envied
|
||||||
sha256: "42132a746494b0a7bc19062cdddd3a01694f696caca684456ff01526c833decc"
|
sha256: cd95ddf0982e53f0b6664e889d4a9ce678b3907a59a5047923404375ef6dcacc
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.3.1"
|
||||||
envied_generator:
|
envied_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: envied_generator
|
name: envied_generator
|
||||||
sha256: e1e66498080f531e89d9ea7971f96b287dffdd05df16efdd31f9f74faa77e005
|
sha256: "81ad332912f1b31afbd2b913aff9ec7b032e97f4ba7e419f52d02bb90637e77c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.3.1"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -301,10 +293,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_macos
|
name: file_selector_macos
|
||||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.5"
|
version: "0.9.4+4"
|
||||||
file_selector_platform_interface:
|
file_selector_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -506,10 +498,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.35"
|
version: "2.0.31"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -596,10 +588,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: google_fonts
|
name: google_fonts
|
||||||
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.3"
|
version: "6.3.2"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -608,14 +600,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
hooks:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: hooks
|
|
||||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.2"
|
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -676,18 +660,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac"
|
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "1.2.1"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+17"
|
version: "0.8.13+1"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -700,10 +684,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+6"
|
version: "0.8.13"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -716,10 +700,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_macos
|
name: image_picker_macos
|
||||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.2+1"
|
version: "0.2.2"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -752,22 +736,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
jni:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: jni
|
|
||||||
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
jni_flutter:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: jni_flutter
|
|
||||||
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -780,10 +748,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.12.0"
|
version: "4.9.0"
|
||||||
jwt_decoder:
|
jwt_decoder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -796,26 +764,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.2"
|
version: "10.0.9"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.10"
|
version: "3.0.9"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.1"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -836,26 +804,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.18"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.16.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -864,14 +832,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.6"
|
||||||
objective_c:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: objective_c
|
|
||||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "9.4.1"
|
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -900,18 +860,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
|
sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.2.19"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.0"
|
version: "2.4.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1056,14 +1016,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
record_use:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_use
|
|
||||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.0"
|
|
||||||
secure_string_operations:
|
secure_string_operations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1112,42 +1064,42 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
|
sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.3"
|
version: "4.2.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.2"
|
version: "1.10.1"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
|
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2+1"
|
version: "2.4.2"
|
||||||
sqflite_android:
|
sqflite_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_android
|
name: sqflite_android
|
||||||
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
|
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2+3"
|
version: "2.4.1"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.8"
|
version: "2.5.6"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1200,10 +1152,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0+1"
|
version: "3.4.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1216,10 +1168,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
version: "0.7.4"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1248,18 +1200,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.30"
|
version: "6.3.20"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.4.1"
|
version: "6.3.4"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1272,10 +1224,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.5"
|
version: "3.2.3"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1288,10 +1240,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.1"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1312,10 +1264,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.1.4"
|
||||||
vibration:
|
vibration:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1336,10 +1288,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.0"
|
version: "15.0.0"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1413,5 +1365,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.11.0 <4.0.0"
|
dart: ">=3.8.0 <4.0.0"
|
||||||
flutter: ">=3.38.4"
|
flutter: ">=3.32.0"
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
flutter_local_notifications_windows
|
flutter_local_notifications_windows
|
||||||
jni
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||