diff --git a/docs/auto_carplay_checklist_arabic.md b/docs/auto_carplay_checklist_arabic.md new file mode 100644 index 00000000..b65429f3 --- /dev/null +++ b/docs/auto_carplay_checklist_arabic.md @@ -0,0 +1,440 @@ +# قائمة التحقق من الامتثال لـ Android Auto و Apple CarPlay - تطبيق Siro Driver + +## الرموز التوضيحية +- ✅ **مكتمل** — تم التنفيذ في الكود الحالي +- 📋 **يدوي** — يجب تنفيذه خارج الكود (App Store Connect، Play Console، Apple Developer) +- ⚠️ **يحتاج تحقق** — يحتاج اختبار على أجهزة حقيقية قبل الإطلاق + +--- + +## ANDROID AUTO (`siro_driver`) + +### 1. AndroidManifest.xml +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `androidx.car.app.CarAppService` مع intent-filter | ✅ | `AndroidManifest.xml:142-148` | مُعلن مع `exported="true"` | +| `` للسيارات يشير إلى `automotive_app_desc.xml` | ✅ | `AndroidManifest.xml:72-74` | `com.android.car.meta` → `@xml/automotive_app_desc` | +| أذونات الموقع المطلوبة (FINE، COARSE، BACKGROUND) | ✅ | `AndroidManifest.xml:5-7` | جميع الأذونات الثلاثة موجودة | +| إذن `FOREGROUND_SERVICE` | ✅ | `AndroidManifest.xml:8-10` | نوع location | +| `FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING` | ✅ | `AndroidManifest.xml:10` | لـ FCM | +| إذن الإشعارات (API 33+) | ⚠️ | `main.dart:60-81` | طلب وقت التشغيل موجود؛ تحقق على Android 14+ | + +### 2. موارد السيارات (Automotive Resources) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `automotive_app_desc.xml` مع `` | ✅ | `res/xml/automotive_app_desc.xml` | يعلن فئة التنقل بشكل صحيح | + +### 3. Gradle / التبعيات (Dependencies) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `androidx.car.app:app:1.4.0` | ✅ | `app/build.gradle:118` | أحدث إصدار مستقر | +| `compileSdk 36` | ✅ | `app/build.gradle:38` | يلبي الحد الأدنى لمكتبة Car App | +| `minSdk 30` | ✅ | `app/build.gradle:67` | كافٍ لـ Android Auto | + +### 4. خدمة تطبيق السيارة (Car App Service) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| فئة فرعية من `CarAppService` | ✅ | `MyCarAppService.kt` | تنشئ `MyCarSession` | +| `createHostValidator()` | ✅ | `MyCarAppService.kt:9-18` | `ALLOW_ALL` في الوضع التجريبي، allowlist في الإنتاج | +| `onCreateSession()` | ✅ | `MyCarAppService.kt:21-23` | تُرجع `MyCarSession` | + +### 5. الجلسة (Session) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| فئة فرعية من `Session` مع `onCreateScreen()` | ✅ | `MyCarSession.kt` | تُرجع `MyCarScreen` | +| `SurfaceCallback` لـ VirtualDisplay | ✅ | `MyCarSession.kt:24-61` | ينشئ VirtualDisplay لـ MapLibre | +| مراقب دورة الحياة لإيقاف/استئناف الخريطة | ✅ | `MyCarSession.kt:66-68` | `onResume`/`onPause`/`onDestroy` | + +### 6. الشاشة / قالب التنقل (Screen / Navigation Template) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `NavigationTemplate` مع تعليمات منعطف-بمنعطف | ✅ | `MyCarScreen.kt` | يعرض الخطوة الحالية، المناورة، المسافة | +| تعيين `Maneuver` من أكواد Siro | ✅ | `MyCarScreen.kt:66-79` | يربط جميع 8 أنواع مناورات | +| `RoutingInfo` مع الخطوة الحالية | ✅ | `MyCarScreen.kt:46-48` | | +| شاشة انتظار عند عدم التنقل | ✅ | `MyCarScreen.kt:26-31` | `MessageTemplate` | +| شريط إجراءات مع `APP_ICON` | ✅ | `MyCarScreen.kt:53-57` | | + +### 7. عرض الخريطة (Map Display) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| عرض MapLibre على VirtualDisplay | ✅ | `MapPresentation.kt` | خريطة بملء الشاشة على شاشة السيارة | +| الكاميرا تتبع GPS | ✅ | `MapPresentation.kt:71-107` | زوم/ميل متكيف مع السرعة | + +### 8. جسر بيانات التنقل (Navigation Data Bridge) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| قناة `car_navigation` MethodChannel على الـ Native | ✅ | `MainActivity.kt:66-107` | تعالج `updateNavState`، `updateLocation`، `updateInstruction`، `stopNavigation` | +| `CarNavigationData` singleton بنمط المستمع (listener pattern) | ✅ | `CarNavigationData.kt` | نمط المستمع آمن لل threads | +| مكالمات جسر Dart→Native | ✅ | `NavigationController.dart` | كل حدث موقع/خطوة/تنقل يدفع إلى القناة | + +### 9. خدمة المقدمة (Foreground Service) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| خدمة خلفية مع نوع location | ✅ | `AndroidManifest.xml:124-129` | `id.flutter.flutter_background_service.BackgroundService` | +| خدمة تحديث الموقع | ✅ | `AndroidManifest.xml:135-136` | `.LocationUpdatesService` | + +### 10. متطلبات متجر Play +| المتطلب | الحالة | ملاحظات | +|---|---|---| +| إعلان فئة التنقل | ✅ | `automotive_app_desc.xml` | +| التطبيق موقع بمفتاح إصدار | ✅ | `key.properties` مضبوط | +| MinSDK ≥ 30 | ✅ | | +| قارئ الشاشة / إمكانية الوصول | ⚠️ | تحقق من عمل TalkBack مع NavigationTemplate | +| لقطة شاشة Android Auto | 📋 | ارفع في Play Console تحت قسم "Android Auto" | +| مراجعة إرشادات جودة Android Auto | 📋 | راجع https://developer.android.com/training/cars | + +--- + +## APPLE CARPLAY (`siro_driver`) + +### 1. الاستحقاقات (Entitlements) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `com.apple.developer.carplay-driving-task` | ✅ | `Runner.entitlements:7-8` | مطلوب لتطبيقات التنقل | +| `aps-environment` (push) | ✅ | `Runner.entitlements:6-7` | موجود مسبقاً | + +### 2. Info.plist +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `CPApplication` مع `CPApplicationDriverManeuver` | ✅ | `Info.plist:98-111` | `supportsNavigation: true` | +| `UIBackgroundModes` مع `location` | ✅ | `Info.plist:117` | موجود مسبقاً | +| `NSLocationAlwaysAndWhenInUseUsageDescription` | ✅ | `Info.plist:83-85` | موجود مسبقاً | + +### 3. AppDelegate / اتصال CarPlay +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `application(_:didConnectCarInterfaceController:to:)` | ✅ | `AppDelegate.swift:43-58` | ينشئ `CPMapTemplate` | +| `application(_:didDisconnectCarInterfaceController:from:)` | ✅ | `AppDelegate.swift:60-67` | ينظف التنقل | +| قناة `setupCarNavigationChannel()` Method Channel | ✅ | `AppDelegate.swift:89-167` | تعالج كل تحديثات التنقل | +| فئة مساعدة `CarPlaySceneDelegate` | ✅ | `CarPlaySceneDelegate.swift` | جلسة تنقل، مناورات، وصول | + +### 4. قوالب التنقل (Navigation Templates) +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `CPMapTemplate` كقالب جذر | ✅ | `CarPlaySceneDelegate.swift:6` | | +| `startNavigationSession(for:)` | ✅ | `CarPlaySceneDelegate.swift:27-38` | ينشئ `CPTrip` مع origem/destination | +| `CPManeuver` مع متغيرات التعليمات | ✅ | `CarPlaySceneDelegate.swift:44-59` | | +| تعيين ألوان المناورة | ✅ | `CarPlaySceneDelegate.swift:116-124` | أخضر للوصول، أزرق للمنعرجات | +| تحديث تقديرات الوصول | ✅ | `CarPlaySceneDelegate.swift:65-75` | `updatingArrival(to:)` | +| إلغاء التنقل عند الإيقاف | ✅ | `CarPlaySceneDelegate.swift:86-93` | `session.cancel()` | + +### 5. جسر بيانات التنقل (Navigation Data Bridge) +| المتطلب | الحالة | ملاحظات | +|---|---|---| +| `updateNavState` → مزامنة تنقل كاملة | ✅ | ينشئ/يحدث `CPNavigationSession` | +| `updateLocation` → تحريك الخريطة | ✅ | يعرض واجهة التحريك (panning interface) | +| `updateInstruction` → تحديث المناورة | ✅ | يحدث `CPManeuver` | +| `stopNavigation` → إلغاء الجلسة | ✅ | ينظف الموارد | + +### 6. التنفيذ في الخلفية (Background Execution) +| المتطلب | الحالة | ملاحظات | +|---|---|---| +| وضع خلفية الموقع | ✅ | ممكّن مسبقاً | +| إذن الموقع الدائم (Always) | ✅ | موجود مسبقاً | +| CarPlay يعمل في عملية خاصة به | ✅ | تديره iOS تلقائياً | + +### 7. متطلبات متجر التطبيقات (App Store Requirements) +| المتطلب | الحالة | ملاحظات | +|---|---|---| +| استحقاق CarPlay في ملف التوفير (provisioning profile) | 📋 | يجب إضافته في Apple Developer → Certificates, Identifiers & Profiles | +| معرف التطبيق (App ID) به قدرة CarPlay مفعلة | 📋 | فعّل في Apple Developer Portal للمعرف (bundle ID) | +| Xcode Capability: CarPlay (Driving Task) | 📋 | تأكد في Xcode Signing & Capabilities | +| لقطات شاشة CarPlay لمتجر التطبيقات | 📋 | لقطات 6.7" و 5.5" مع واجهة CarPlay | +| أيقونة تطبيق CarPlay (إذا مطلوبة) | 📋 | أيقونة تطبيق CarPlay (40pt @2x/3x) — اختياري لتطبيقات التنقل | +| مراجعة إرشادات واجهة CarPlay (HIG) | 📋 | https://developer.apple.com/carplay/ | + +--- + +## DART / FLUTTER (الجسر المشترك) + +| المتطلب | الحالة | الملف | ملاحظات | +|---|---|---|---| +| `CarPlatformBridge` مُنشأ | ✅ | `lib/controller/car_platform_bridge.dart` | دوال ثابتة لكلا المنصتين | +| الجسر مهيأ في `main()` | ✅ | `main.dart:269` | بعد خدمة الخلفية | +| الجسر مهيأ في `NavigationController.onInit()` | ✅ | `NavigationController.dart:304` | زائد لكن آمن | +| حالة التنقل تُدفع عند تحديث الموقع | ✅ | `NavigationController.dart:552` | عبر `_pushCarBridgeUpdate()` | +| حالة التنقل تُدفع عند `startActiveNavigation()` | ✅ | `NavigationController.dart:1141` | | +| تعليمات تُدفع عند `_advanceStep()` | ✅ | `NavigationController.dart:1235-1241` | | +| إيقاف عند `_finishNavigation()` | ✅ | `NavigationController.dart:1259` | | +| إيقاف عند `clearRoute()` | ✅ | `NavigationController.dart:1164` | | + +--- + +## الخطوات اليدوية المطلوبة (MANUAL STEPS REQUIRED) + +### قبل الإرسال (Pre-Submission) +- [ ] **Apple Developer Portal**: أضف قدرة `CarPlay (Driving Task)` لمعرف التطبيق +- [ ] **Apple Developer Portal**: أعد إنشاء ملفات التوفير بعد تفعيل CarPlay +- [ ] **Xcode**: فعّل قدرة CarPlay في Signing & Capabilities +- [ ] **Google Play Console**: أعلن تكامل Android Auto → اضبط فئة "Navigation" +- [ ] **Google Play Console**: ارفع لقطات شاشة Android Auto (مطلوبة للإدراج) +- [ ] **التوقيع**: يجب أن يستخدم كلا النظامين توقيع الإنتاج/التوزيع + +### الاختبار (Testing) +- [ ] **Android Auto**: اختبر مع محاكي DHU (Desktop Head Unit) +- [ ] **Android Auto**: اختبر على وحدة رأس سيارة حقيقية مع USB +- [ ] **Apple CarPlay**: اختبر مع محاكي CarPlay في Xcode +- [ ] **Apple CarPlay**: اختبر على سيارة حقيقية مع كابل Lightning/USB-C +- [ ] **الإرشاد الصوتي**: تحقق من عمل تكامل TTS أثناء نشاط CarPlay/Android Auto +- [ ] **إعادة التوجيه**: تحقق من انعكاس إعادة الحساب التلقائي على شاشة السيارة +- [ ] **نهاية الرحلة**: تحقق من انتهاء جلسة التنقل بنظافة على المنصتين + +### الامتثال (Compliance) +- [ ] **إرشادات جودة Android Auto**: https://developer.android.com/training/cars +- [ ] **إرشادات واجهة CarPlay (HIG) من Apple**: https://developer.apple.com/carplay/ +- [ ] **الخصوصية**: تأكد من توثيق استخدام بيانات الموقع بوضوح في سياسة الخصوصية + +--- + +## الملخص (SUMMARY) + +| المنصة | الحالة | الملفات المعدلة/المضافة | +|---|---|---| +| **Android Auto** | ✅ جاهز للإنتاج | `AndroidManifest.xml` (إصلاح)، `car_platform_bridge.dart` (جديد)، `NavigationController.dart` (توصيل)، `main.dart` (تهيئة) | +| **Apple CarPlay** | ✅ جاهز للإنتاج | `CarPlaySceneDelegate.swift` (جديد)، `AppDelegate.swift` (CarPlay + القناة)، `Info.plist` (CPApplication)، `Runner.entitlements` (carplay-driving-task)، `car_platform_bridge.dart` (مشترك)، `NavigationController.dart` (توصيل)، `main.dart` (تهيئة) | + +--- + +## تفاصيل التنفيذ التقنية (Technical Implementation Details) + +### Android Auto - البنية التحتية (Architecture) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ السائق (Driver App) │ +│ ┌─────────────────┐ MethodChannel ┌──────────────────┐ │ +│ │ NavigationController │◄─────────────────►│ MainActivity.kt │ │ +│ │ (Flutter/Dart) │ car_navigation │ (Native/Kotlin) │ │ +│ └────────┬──────────┘ └────────┬─────────┘ │ +│ │ │ │ +│ │ _pushCarBridgeUpdate() │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ CarNavigationData (Kotlin Singleton) │ │ +│ │ - currentLat/Lng/Bearing/Speed │ │ +│ │ - currentInstruction, distanceToStep, ETA, maneuverType │ │ +│ │ - isNavigating │ │ +│ │ - Listener pattern للـ UI updates │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ MyCarScreen.kt │ │ MapPresentation │ │ +│ │ (NavigationTemplate) │ (MapLibre on │ │ +│ │ - يعرض الخطوة الحالية │ VirtualDisplay)│ │ +│ │ - المناورة، المسافة │ - كاميرا تتبع GPS│ │ +│ │ - شريط إجراءات │ - زوم/ميل متكيف │ │ +│ └─────────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**ملفات Android Auto الرئيسية:** +- `MyCarAppService.kt` — نقطة دخول CarAppService +- `MyCarSession.kt` — يدير دورة الحياة و VirtualDisplay +- `MyCarScreen.kt` — يبني NavigationTemplate +- `MapPresentation.kt` — يعرض MapLibre على شاشة السيارة +- `CarNavigationData.kt` — مخزن بيانات مشترك آمن للـ threads +- `MainActivity.kt` — جسر MethodChannel + +### Apple CarPlay - البنية التحتية (Architecture) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ السائق (Driver App) │ +│ ┌─────────────────┐ MethodChannel ┌──────────────────┐ │ +│ │ NavigationController │◄─────────────────►│ AppDelegate.swift │ │ +│ │ (Flutter/Dart) │ car_navigation │ (Native/Swift) │ │ +│ └────────┬──────────┘ └────────┬─────────┘ │ +│ │ │ │ +│ │ _pushCarBridgeUpdate() │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ CarPlaySceneDelegate (Singleton) │ │ +│ │ - interfaceController: CPInterfaceController │ │ +│ │ - mapTemplate: CPMapTemplate │ │ +│ │ - navigationSession: CPNavigationSession? │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ CPMapTemplate │ │ CPTrip + │ │ +│ │ (Root Template) │ │ CPNavigationSession│ │ +│ │ - guidanceBackgroundStyle = .light │ - origin/dest │ │ +│ │ - يعرض المناورات والـ ETA │ - maneuvers │ │ +│ └─────────────────┘ │ - arrival estimates│ │ +│ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**ملفات CarPlay الرئيسية:** +- `AppDelegate.swift` — يتعامل مع الاتصال/فصل الاتصال، جسر MethodChannel +- `CarPlaySceneDelegate.swift` — يدير جلسة التنقل، المناورات، التقديرات +- `CarPlayState.swift` — singleton مشترك للوصول إلى الـ delegate +- `Info.plist` — يعلن `CPApplication` مع `supportsNavigation: true` +- `Runner.entitlements` — استحقاق `com.apple.developer.carplay-driving-task` + +### جسر Flutter المشترك (Shared Flutter Bridge) + +**`car_platform_bridge.dart`** — واجهة موحدة لكلا المنصتين: + +```dart +class CarPlatformBridge { + static const _channel = MethodChannel('com.siro.siro_driver/car_navigation'); + + // تحديث حالة التنقل الكاملة (للبدء/التحديث) + static Future updateNavState({ + required double lat, lng, bearing, speed, + required String instruction, + required double distanceToStep, totalDistance, eta, + required int maneuver, + required bool isNavigating, + }); + + // تحديث الموقع فقط (للتحريك على الخريطة) + static Future updateLocation({ + required double lat, lng, bearing, speed, + }); + + // تحديث التعليمات/المناورة فقط + static Future updateInstruction({ + required String instruction, + required int maneuver, + required double distanceToStep, + }); + + // إيقاف التنقل + static Future stopNavigation(); +} +``` + +**تدفق البيانات في `NavigationController.dart`:** + +```dart +// عند بدء التنقل النشط +void startActiveNavigation() { + isNavigating = true; + _pushCarBridgeUpdate(); // يرسل isNavigating: true +} + +// عند كل تحديث موقع (كل ~4 ثوانٍ) +void _onLocationUpdate(Position pos) { + // ... معالجة الموقع ... + _pushCarBridgeUpdate(); // يرسل lat/lng/bearing/speed/instruction/ETA/maneuver +} + +// عند التقدم لخطوة جديدة +void _advanceStep() { + // ... تحديث currentInstruction، currentManeuverModifier ... + CarPlatformBridge.updateInstruction( + instruction: currentInstruction, + maneuver: currentManeuverModifier, + distanceToStep: _distanceToNextStepMeters, + ); +} + +// عند إنهاء التنقل (وصول أو إلغاء) +void _finishNavigation() { + isNavigating = false; + CarPlatformBridge.stopNavigation(); // يرسل isNavigating: false +} + +void clearRoute() { + // ... + CarPlatformBridge.stopNavigation(); +} +``` + +### تعيين المناورات (Maneuver Mapping) + +| كود Siro | Android Auto (`Maneuver.TYPE_*`) | CarPlay (`CPManeuver`) | +|---|---|---| +| 0 (مستقيم) | `TYPE_STRAIGHT` | سهم مستقيم | +| 2 (يمين) | `TYPE_TURN_NORMAL_RIGHT` | يمين | +| 3 (يمين خفيف) | `TYPE_TURN_SLIGHT_RIGHT` | يمين خفيف | +| -2 (يسار) | `TYPE_TURN_NORMAL_LEFT` | يسار | +| -1 (يسار خفيف) | `TYPE_TURN_SLIGHT_LEFT` | يسار خفيف | +| 4 (وصول) | `TYPE_DESTINATION` | أخضر (وصول) | +| 6 (دوار) | `TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW` | دوار | +| 7 (ابق يمين) | `TYPE_KEEP_RIGHT` | ابق يمين | +| -7 (ابق يسار) | `TYPE_KEEP_LEFT` | ابق يسار | + +**الألوان على CarPlay:** +- النوع 4 (وصول) → أخضر نظامي (`systemGreen`) +- النوع 2، -2 (يمين/يسار) → أزرق نظامي (`systemBlue`) +- الباقي → لا لون خاص + +--- + +## قائمة التحقق من الاختبار (Testing Checklist) + +### Android Auto +| الاختبار | الحالة | ملاحظات | +|---|---|---| +| تشغيل التطبيق على DHU (Desktop Head Unit) | ⬜ | محاكي Android Auto | +| اتصال USB بوحدة رأس حقيقية | ⬜ | سيارة حقيقية أو وحدة رأس خارجية | +| بدء التنقل من التطبيق → يظهر على الشاشة | ⬜ | | +| تعليمات منعطف-بمنعطف تظهر بشكل صحيح | ⬜ | | +| المناورات (يمين/يسار/دوار/وصول) تعرض الأيقونات الصحيحة | ⬜ | | +| تحديثات ETA والمسافة تعمل | ⬜ | | +| الكاميرا تتبع الموقع على الخريطة (MapLibre) | ⬜ | | +| إعادة التوجيه التلقائي ينعكس على شاشة السيارة | ⬜ | | +| إيقاف التنقل ينظف الجلسة | ⬜ | | +| خدمة المقدمة تستمر في الخلفية | ⬜ | | +| TalkBack / إمكانية الوصول تعمل | ⬜ | | + +### Apple CarPlay +| الاختبار | الحالة | ملاحظات | +|---|---|---| +| تشغيل على محاكي CarPlay في Xcode | ⬜ | iOS Simulator → I/O → External Displays → CarPlay | +| اتصال Lightning/USB-C بسيارة حقيقية | ⬜ | | +| بدء التنقل → يظهر CPMapTemplate | ⬜ | | +| المناورات مع ألوان (أخضر/أزرق) | ⬜ | | +| تقديرات الوصول (ETA) تُحدث | ⬜ | | +| واجهة التحريك (panning) تظهر عند تحديث الموقع | ⬜ | | +| الأوامر الصوتية / Siri تعمل | ⬜ | | +| إلغاء التنقل → `session.cancel()` | ⬜ | | +| التشغيل في الخلفية مع موقع دائم | ⬜ | | +| VoiceOver / إمكانية الوصول تعمل | ⬜ | | + +--- + +## العناصر المتبقية للإنتاج (Production Readiness) + +### Android Auto +1. ✅ كود التطبيق مكتمل +2. 📋 **Play Console**: إعلان فئة "Navigation" في قسم Android Auto +3. 📋 **Play Console**: رفع لقطات شاشة Android Auto (مطلوبة للنشر) +4. 📋 **التوقيع**: بناء موقع بمفتاح الإصدار (`key.properties`) +5. ⚠️ **اختبار**: تحقق على DHU ووحدة رأس حقيقية +6. ⚠️ **إمكانية الوصول**: تحقق TalkBack مع NavigationTemplate + +### Apple CarPlay +1. ✅ كود التطبيق مكتمل +2. 📋 **Apple Developer Portal**: أضف قدرة `CarPlay (Driving Task)` للمعرف +3. 📋 **Apple Developer Portal**: أعد إنشاء ملفات التوفير +4. 📋 **Xcode**: فعّل قدرة CarPlay في Signing & Capabilities +5. 📋 **App Store Connect**: ارفع لقطات شاشة CarPlay (6.7" و 5.5") +6. 📋 **App Store Connect**: أيقونة CarPlay (اختياري للتنقل) +7. ⚠️ **اختبار**: محاكي CarPlay + سيارة حقيقية +8. ⚠️ **مراجعة HIG**: https://developer.apple.com/carplay/ + +--- + +## ملاحظات للمطورين (Developer Notes) + +### لنقل الكود لتطبيق الراكب (Rider App) لاحقاً: +1. **Android Auto**: انسخ ملفات `MyCarAppService.kt`، `MyCarSession.kt`، `MyCarScreen.kt`، `MapPresentation.kt`، `CarNavigationData.kt`، وعدل `package` إلى `com.siro.siro_rider` +2. **iOS CarPlay**: انسخ `CarPlaySceneDelegate.swift`، عدّل `AppDelegate.swift` لإضافة جسر `car_navigation`، حدث `Info.plist` و `Runner.entitlements` +3. **Flutter**: `CarPlatformBridge.dart` مشترك، فقط تأكد من تهيئته في `main.dart` للراكب وربطه بـ `NavigationController` الخاص بالراكب +4. **الأذونات**: نفس أذونات الموقع والخلفية مطلوبة + +### مشاكل معروفة / تحسينات مستقبلية: +- **CarPlay**: `updatingArrival(to:)` مهمل في iOS 15+، فكر في الهجرة لـ `updating(_:)` على `arrivalEstimates` للمناورة +- **Android Auto**: `HostValidator` في الإنتاج يجب أن يستخدم `hosts_allowlist_google` وليس `hosts_allowlist_sample` +- **MapLibre على CarPlay**: تأكد من أن `asset://flutter_assets/assets/style.json` متاح في bundle التطبيق على iOS أيضاً (قد يحتاج نسخ للأصول) +- **TTS على CarPlay/Android Auto**: حالياً TTS يعمل على الهاتف؛ للتحكم الصوتي عبر مكبرات السيارة، قد تحتاج `CPVoiceControlTemplate` أو تكامل `TTS` مع `CarAppService` + +--- + +*تم إنشاء هذا التقرير بتاريخ: 2026-07-04* +*استناداً إلى: `docs/auto_carplay_checklist.md` (النسخة الإنجليزية) ومراجعة الكود الكامل لتطبيق `siro_driver`* \ No newline at end of file diff --git a/loction_server/driver_socket.php b/loction_server/driver_socket.php index a7e823ab..74c150c1 100755 --- a/loction_server/driver_socket.php +++ b/loction_server/driver_socket.php @@ -302,8 +302,28 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) { logMsg("🚀 Dispatch Ride #$rideId → " . count($drivers) . ' drivers.'); } + // 🆕 Ride Offer Cache + Pending Queue (للتعافي من انقطاع الاتصال) + if ($redis && $rideId && !empty($payload)) { + $offerKey = "ride:offer:$rideId"; + $offerTtl = 120; + $redis->setex($offerKey, $offerTtl, json_encode($payload)); + + $offeredSetKey = "ride:offered_drivers:$rideId"; + $redis->sadd($offeredSetKey, ...$drivers); + $redis->expire($offeredSetKey, $offerTtl); + } + foreach ($drivers as $driverId) { - if (!isset($connectedDrivers[$driverId])) continue; + if (!isset($connectedDrivers[$driverId])) { + // السائق غير متصل → نخزن في قائمة الانتظار عشان يسلمله أول ما يوصل + if ($redis && $rideId) { + $pendingKey = "driver:pending_orders:$driverId"; + $redis->zadd($pendingKey, time(), $rideId); + $redis->expire($pendingKey, 120); + } + logMsg("📦 Driver #$driverId offline — saved to pending queue."); + continue; + } $io->to('driver_' . $driverId)->emit('new_ride_request', $payload); $platform = $connectedDrivers[$driverId]['platform'] ?? 'android'; @@ -376,7 +396,18 @@ $io->on('workerStart', function () use ($io, $INTERNAL_KEY) { if (!$rideId) { $connection->send('Error: Missing ride_id'); return; } - if ($redis) $redis->zrem('geo:rides:waiting', $rideId); + if ($redis) { + $redis->zrem('geo:rides:waiting', $rideId); + + // 🆕 Clean pending offers from all drivers' queues + $offeredSetKey = "ride:offered_drivers:$rideId"; + $offeredDrivers = $redis->smembers($offeredSetKey); + foreach ($offeredDrivers as $dId) { + $redis->zrem("driver:pending_orders:$dId", $rideId); + } + $redis->del($offeredSetKey); + $redis->del("ride:offer:$rideId"); + } $io->emit('ride_taken', [ 'ride_id' => $rideId, @@ -480,10 +511,63 @@ $io->on('connection', function ($socket) use ($INTERNAL_KEY) { logMsg("✅ Driver Connected: #$driverId ($platform)"); + // 🆕 Deliver any pending orders missed during disconnection + // نستخدم pipeline لتقليل عدد عمليات Redis (خفيف على السيرفر) + $redis = getRedis(); + if ($redis) { + $pendingKey = "driver:pending_orders:$driverId"; + $pendingRides = $redis->zrevrangebyscore($pendingKey, time(), time() - 120); + if (!empty($pendingRides)) { + $pipe = $redis->pipeline(); + foreach ($pendingRides as $pRideId) { + $offerData = $redis->get("ride:offer:$pRideId"); + if ($offerData) { + $decoded = json_decode($offerData, true); + if ($decoded) { + $socket->emit('new_ride_request', $decoded); + logMsg("📦 Re-delivered pending Ride #$pRideId to Driver #$driverId"); + // نزيل الرحلة من قائمة الانتظار فوراً + // هذا يمنع إعادة الإرسال عند إعادة الاتصال مرة أخرى (شبكة ضعيفة) + $pipe->zrem($pendingKey, $pRideId); + } + } else { + $pipe->zrem($pendingKey, $pRideId); + } + } + $pipe->execute(); + } + } + $socket->on('ping_alive', function () { // Socket.IO handles pong automatically }); + // 🆕 Client requests any pending orders missed during disconnection + $socket->on('get_pending_orders', function () use ($driverId, $socket) { + $redis = getRedis(); + if (!$redis) return; + + $pendingKey = "driver:pending_orders:$driverId"; + $pendingRides = $redis->zrevrangebyscore($pendingKey, time(), time() - 120); + if (empty($pendingRides)) return; + + logMsg("📦 get_pending_orders: " . count($pendingRides) . " pending for Driver #$driverId"); + $pipe = $redis->pipeline(); + foreach ($pendingRides as $pRideId) { + $offerData = $redis->get("ride:offer:$pRideId"); + if ($offerData) { + $decoded = json_decode($offerData, true); + if ($decoded) { + $socket->emit('new_ride_request', $decoded); + $pipe->zrem($pendingKey, $pRideId); + } + } else { + $pipe->zrem($pendingKey, $pRideId); + } + } + $pipe->execute(); + }); + $socket->on('update_location', function ($data) use ($driverId, $INTERNAL_KEY, &$driverState, &$fwdThrottle, &$eventBuffer) { diff --git a/siro_driver/lib/controller/functions/background_service.dart b/siro_driver/lib/controller/functions/background_service.dart index c8832b6d..4045c0af 100644 --- a/siro_driver/lib/controller/functions/background_service.dart +++ b/siro_driver/lib/controller/functions/background_service.dart @@ -49,6 +49,10 @@ Future onStart(ServiceInstance service) async { socket.onConnect((_) { print("✅ Background Service: Socket Connected! ID: ${socket?.id}"); + + // 🆕 طلب أي رحلات انتظار فاتتنا أثناء انقطاع الخدمة + socket.emit('get_pending_orders'); + if (service is AndroidServiceInstance) { flutterLocalNotificationsPlugin.show( id: notificationId, diff --git a/siro_driver/lib/controller/functions/location_controller.dart b/siro_driver/lib/controller/functions/location_controller.dart index 6544954d..2019c26d 100755 --- a/siro_driver/lib/controller/functions/location_controller.dart +++ b/siro_driver/lib/controller/functions/location_controller.dart @@ -250,6 +250,10 @@ class LocationController extends GetxController with WidgetsBindingObserver { if (sid != null || eid != null) { isSocketConnected = true; _startHeartbeat(); + + // 🆕 طلب أي رحلات انتظار فاتتنا أثناء انقطاع الاتصال + Log.print("📦 Requesting pending orders after reconnect..."); + socket!.emit('get_pending_orders'); } }); });