feat(apps): سقالة rider_new و driver_new على Cubit — المرحلة 1
بناء من الصفر على باك إند NestJS، بقرار المالك 2026-08-04 الذي يعكس قرارَي نقل GetX (2026-07-21) واعتماد باك إند PHP (2026-07-27). لا يُورَّث سطر دارت من apps/*-archive-cubit، وسيرو مرجع بصري وسلوكي لا مصدر نسخ. الوثائق: - docs/37: الخطة الكاملة بستّ مراحل وبواباتها - docs/38: عقد الـAPI من 37 controller، معظمه متحقَّق حيّاً من السيرفر المنشور - docs/39: جرد 202 شاشة في تطبيقَي سيرو، مصنّفة داخل/خارج النطاق الطبقة الأصلية منقولة من *-archive-cubit وحدها لأنها هوية النشر: - rider_new → com.mobileapp.store.ride · shorebird 496cb3ac - driver_new → com.sefer_driver (أندرويد) · com.sefer.driver (iOS) · 68cc9345 - أُصلح تعارض: هدف RunnerTests في driver_new كان يحمل bundle الراكب الأصول مصدرها *-archive-cubit لا سيرو: أصول الأرشيف مجموعة أشمل (كل صور سيرو + صور تريبز) وخطوطها هي خطوط docs/26. استُكمل السائق بعشرة ملفات ناقصة من سيرو (شعارات مزوّدي الدفع + صوتان). lib/ مكتوب من الصفر (11 ملف لكل تطبيق): - AppConfig بأعلام const — أساس نموذج lite/pro/max - TokenStore على التخزين الآمن، يقرأ exp محليّاً بلا حزمة خارجية - AuthInterceptor بتجديد استباقي وطلقة واحدة — عمر التوكن 15 دقيقة فقط - ApiClient و ApiException يفهم مصفوفة message في NestJS flutter analyze نظيف في التطبيقين. البناء الفعلي لم يُجرَّب بعد: بوابة المرحلة 1 تتطلّب البناء على السيرفر، والوصول إليه غير متاح حالياً. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1243e5a3b2
commit
69da4abc01
@@ -0,0 +1,201 @@
|
||||
# 37 — خطة `rider_new` و `driver_new` (Cubit + باك إند NestJS)
|
||||
|
||||
تاريخ: 2026-08-04 · الحالة: مسودّة للموافقة
|
||||
|
||||
## 1. القرار المطلوب ومعناه
|
||||
|
||||
المطلوب: بناء تطبيقين جديدين (`rider_new`, `driver_new`) بـ Bloc/Cubit،
|
||||
يعملان على **`backend-archive/` (NestJS)**، بأخذ الأصول والحزم من تطبيقات
|
||||
سيرو، مع الحفاظ على `build.gradle` و`Info.plist` كي لا تنكسر هوية النشر.
|
||||
|
||||
هذا **عكس لقرارين مقفلين**:
|
||||
|
||||
| القرار | التاريخ | المرجع |
|
||||
|---|---|---|
|
||||
| الباك إند المعتمد = سيرو PHP، وNestJS مؤرشف | 2026-07-27 | `backend-archive/README-ARCHIVE.md`, `docs/34` |
|
||||
| التطبيقات = نقل سيرو GetX بدل إعادة بناء Cubit | 2026-07-21 | `docs/30` |
|
||||
|
||||
سبب الأرشفة وقتها كان مكتوباً صراحة: «NestJS مكتمل تقنياً لكن **بلا تطبيقات
|
||||
تعمل عليه**، وبناء تطبيقات له من الصفر أطول من نقل تطبيقات سيرو».
|
||||
الخطة الحالية تقول: نعم، سنبني التطبيقات. هذا يحلّ الاعتراض — بشرط قبول أن
|
||||
شغل نقل PHP يتوقّف مؤقتاً.
|
||||
|
||||
## 2. القاعدة الحاكمة — بناء من الصفر، وسيرو مرجع لا مصدر نسخ
|
||||
|
||||
قرار المالك 2026-08-04، وهو يعلو على أي اقتراح في هذه الوثيقة:
|
||||
|
||||
1. **كل كود دارت يُكتب من الصفر.** لا يُورَّث سطر من
|
||||
`apps/rider-archive-cubit` (77 ملف) ولا `apps/driver-archive-cubit`
|
||||
(53 ملف) — السبب: أداؤها وشكلها مرفوضان، ووراثتها تعني وراثة المشكلة.
|
||||
تبقى للقراءة فقط ثم تُحذف بعد المرحلة 3.
|
||||
2. **سيرو مرجع بصري وسلوكي، لا مصدر نسخ.** نقرأ منه: كيف بُنيت الشاشة، ما
|
||||
ترتيب الخطوات، أي حالات حافة عولجت ميدانياً. لا ننسخ منه ملفات دارت.
|
||||
3. **سقف الجودة = شكل تريبز الحالي أو أفضل.** الهدف ليس المعادلة بل التفوّق:
|
||||
أداء (60fps، بلا rebuild زائد) وشكل (نظام تصميم واحد متّسق).
|
||||
|
||||
### أ. الشيء الوحيد الذي يُنقل حرفياً: الطبقة الأصلية
|
||||
|
||||
لأن كل شيء آخر يُكتب من جديد، تبقى الطبقة الأصلية (`android/`, `ios/`,
|
||||
`shorebird.yaml`) هي الوحيدة التي تُنقل كما هي — لأنها هوية النشر ولا يجوز
|
||||
إعادة اختراعها. مصدرها محسوم في §2.ج.
|
||||
|
||||
### ب. `pubspec` — قائمة تُبنى بالطلب، لا تُنسخ
|
||||
|
||||
`siro_rider/pubspec.yaml` فيه ~60 اعتماد، ومنها `get` و`get_storage` كحزمتين
|
||||
**محليّتين معدّلتين** (`./packages/get`) — أي أن نسخه يجرّ GetX كاملاً معه،
|
||||
وكذلك `intaleq_maps` و`secure_string_operations` المربوطة بعقد PHP.
|
||||
|
||||
لذلك `pubspec` الجديد يُكتب من الصفر، وتُضاف الحزمة **فقط عند الحاجة إليها في
|
||||
مرحلتها** — لا قائمة مسبقة من 60 اعتماداً. سيرو يجيب على سؤال «أي حزمة نجحت
|
||||
ميدانياً لهذه المهمة»، لا «ما القائمة».
|
||||
|
||||
- ✅ نأخذ منه: الأصول (`assets/`)، والحزم المحليّة غير المرتبطة بـ GetX
|
||||
(`secure_string_operations`, `trip_overlay_plugin`, `bubble-master`)،
|
||||
والحزم الأصلية (webrtc · geolocator · location · permission_handler ·
|
||||
firebase · local_auth · image_cropper · live_activities · quick_actions …)
|
||||
لأن هذي هي القيمة الحقيقية المجرّبة ميدانياً.
|
||||
- ❌ لا ننسخ: `get`, `get_storage` — تُستبدل بـ `flutter_bloc` +
|
||||
`hydrated_bloc`/`shared_preferences`.
|
||||
- ⚠️ نراجع: `intaleq_maps` (قرار الخرائط المباشرة لـ map-saas بمفتاح
|
||||
`x-api-key` قائم — يبقى)، و`socket_io_client` (NestJS realtime قد يكون
|
||||
عقده مختلفاً — يُتحقّق من `backend-archive/src/realtime`).
|
||||
|
||||
### ج. خطر هوية النشر — يُحسم قبل أي نسخ
|
||||
|
||||
| التطبيق | applicationId | shorebird app_id |
|
||||
|---|---|---|
|
||||
| `Siro/siro_driver` | `com.siro.siro_driver` | `f8e9c087-…0120c` |
|
||||
| `Tripz/apps/driver` | `com.sefer_driver` | `f8e9c087-…0120c` ← **نفس سيرو** |
|
||||
| `apps/driver-archive-cubit` | `com.sefer_driver` | `68cc9345-…5836d` |
|
||||
| `Siro/siro_rider` | `com.siro.rider` | `44245793-…305dd` |
|
||||
| `Tripz/apps/rider` | `com.mobileapp.store.ride` | `44245793-…305dd` ← **نفس سيرو** |
|
||||
| `apps/rider-archive-cubit` | `com.mobileapp.store.ride` | `496cb3ac-…a13ac` |
|
||||
|
||||
قرار المالك: **لكل تطبيق تريبز هويته الخاصة**، منفصلة تماماً عن سيرو. لذلك
|
||||
`apps/driver` و`apps/rider` — اللذان يحملان app_id سيرو نفسه — **أثرُ نسخٍ
|
||||
خاطئ عند نقل GetX، وليسا مرجع هوية**. باتش شوربيرد منهما قد يُدفع لتطبيق
|
||||
سيرو المنشور. المرجع الصحيح هو `*-archive-cubit`:
|
||||
|
||||
| | applicationId | shorebird app_id |
|
||||
|---|---|---|
|
||||
| `driver_new` | `com.sefer_driver` | `68cc9345-…5836d` |
|
||||
| `rider_new` | `com.mobileapp.store.ride` | `496cb3ac-…a13ac` |
|
||||
|
||||
القاعدة: **لا يُنسخ أي `build.gradle` أو `shorebird.yaml` أو `Info.plist` من
|
||||
سيرو.** الطبقة الأصلية تُنقل من `*-archive-cubit` (هي وحدها كودٌ لم يكتبه
|
||||
النموذج السابق — ملفات إعداد مُولّدة من Flutter ومعدّلة يدوياً)، ويُقرأ من
|
||||
سيرو ما يلزم من أذونات وخدمات خلفية و entitlements فيُكتب يدوياً سطراً سطراً.
|
||||
|
||||
## 3. الخطة بالترتيب
|
||||
|
||||
### المرحلة 0 — تثبيت الأساس (قبل أي كود) — **نُفّذت 2026-08-04**
|
||||
1. ✅ عقد NestJS موثّق في **[`docs/38-api-contract.md`](38-api-contract.md)** —
|
||||
37 controller + `e2e-test.mjs`، ومعظم الأشكال **مُتحقَّقة حيّاً** من
|
||||
السيرفر المنشور. المصدر الوحيد للحقيقة لطبقة الشبكة.
|
||||
2. ⚠️ **جزئي**: الباك إند حيّ ويعمل (`/health` → 200)، والدخول والمستخدم
|
||||
والمحفظة وأنواع الرحلات والتعرفة والمسار والتقييم المعلّق كلها تحقّقت
|
||||
حيّاً. لكن **`e2e-test.mjs` الكامل لم يُشغَّل**: يحتاج `OTP_DEV_MODE=true`
|
||||
وهو مطفأ على المنشور، والـSSH من الماك مرفوض
|
||||
(`Permission denied (publickey)`). المتبقّي غير المُتحقَّق مذكور في
|
||||
`docs/38` §13. **بلوكر يحتاج المالك.**
|
||||
3. ✅ المرجع البصري في **[`docs/39-siro-screens.md`](39-siro-screens.md)** —
|
||||
89 شاشة راكب + 113 شاشة سائق، مصنّفة داخل/خارج النطاق، مع ترتيب الخطوات
|
||||
وحالات الحافة وملاحظات الأداء. **بلا نسخ كود.**
|
||||
|
||||
**ما انحسم في م0** (كان مخاطرة، صار حقيقة):
|
||||
- `x-app-role` **منفّذ فعلاً**: نفس الرقم أعطى user id مختلفاً للراكب والسائق.
|
||||
لا عمل باك إند إضافي في م3.
|
||||
- `access_token` = **15 دقيقة فقط** → تجديد استباقي إجباري في طبقة الشبكة.
|
||||
- `GET /tenant/config/:slug` يرجّع **خريطة ميزات كاملة** → هي ربط lite/pro/max
|
||||
بالخادم، لا أعلام مكتوبة في الكود (يعدّل فهم م6.25).
|
||||
- حسابا مراجعة المتاجر `0790000001`/`0790000002` بالرمز `1234` يعملان **بلا**
|
||||
`OTP_DEV_MODE` — مدخلنا للاختبار حتى يتوفّر وصول السيرفر.
|
||||
- ثغرات مؤكَّدة على المنشور: `/maps/geocode` معطوب (قائمة دول فارغة)،
|
||||
و`/tariff/quote` يفشل صامتاً بقيم `null` عند إرسال المعاملات الخطأ.
|
||||
|
||||
### المرحلة 1 — سقالة `rider_new` و `driver_new` من الصفر — **نُفّذت 2026-08-04، متوقّفة عند البوابة**
|
||||
|
||||
الحالة: الخطوات 4–8 ✅ · الخطوة 9 (البوابة) ⚠️ جزئية —
|
||||
`flutter analyze` **نظيف في التطبيقين (0 مشاكل)**، لكن البناء الفعلي لم
|
||||
يُجرَّب: SSH للسيرفر مرفوض (بلوكر م0.2 نفسه).
|
||||
|
||||
قرارات اتُّخذت أثناء التنفيذ ولم تكن في الخطة:
|
||||
- **الأصول مصدرها `*-archive-cubit` لا سيرو.** أصول الأرشيف **مجموعة أشمل**
|
||||
من أصول سيرو (كل صور سيرو + `login_hero`/`name_hero`/`onboarding_1..3`/
|
||||
`splash_logo`) وخطوطها هي خطوط `docs/26` (IBM Plex Sans Arabic + Inter)
|
||||
بينما سيرو عليه `mohanad`/`josefin`. النسخ من سيرو كان سيكون **تراجعاً**.
|
||||
استُكمل السائق بعشرة ملفات ناقصة من سيرو (شعارات مزوّدي الدفع + صوتان).
|
||||
الأصول ليست كوداً كتبه النموذج السابق، فلا تخالف قاعدة «من الصفر».
|
||||
- **أُصلح تعارض هوية في iOS للسائق**: هدف `RunnerTests` في
|
||||
`driver_new` كان يحمل `com.mobileapp.store.ride` (bundle الراكب) —
|
||||
نُقل إلى `com.sefer.driver.RunnerTests`.
|
||||
- `flutter_secure_storage` + `shared_preferences` أُضيفتا لقائمة م1 الدنيا:
|
||||
التوكن لا يُخزَّن إلا في التخزين الآمن، وهذا أساس لا إضافة لاحقة.
|
||||
4. `flutter create` نظيف لكل تطبيق، ثم **استبدال `android/` و`ios/` و
|
||||
`shorebird.yaml` بنسخة من `*-archive-cubit`** (الهوية فقط — §2.ج).
|
||||
5. `lib/` فارغ يُبنى من جديد: `core/` (شبكة · تخزين · أخطاء · توجيه · DI)
|
||||
ثم `features/` — بلا استيراد أي ملف من الأرشيف.
|
||||
6. نقل الأصول من سيرو: `assets/` + الخطوط + الأيقونات + ملفات الترجمة.
|
||||
7. `pubspec` بالحد الأدنى: `flutter_bloc` · `dio` · `go_router` ·
|
||||
`get_it` · `freezed`. الباقي يُضاف في مرحلته.
|
||||
8. مواءمة الأذونات في `AndroidManifest` و`Info.plist` مع ما تحتاجه كل حزمة
|
||||
عند إضافتها — بلا لمس المعرّفات.
|
||||
9. **بوابة**: يبني على السيرفر، يقلع على المحاكي بشاشة splash،
|
||||
و`flutter analyze` نظيف.
|
||||
|
||||
### المرحلة 2 — نظام التصميم (هنا يُكسب الشكل أو يُخسر)
|
||||
10. تطبيق `docs/26-flutter-design-system.md` + `docs/23-flutter-conventions.md`:
|
||||
theme · ألوان · تايبوغرافي · مسافات · حركة · RTL/LTR بأربع لغات.
|
||||
11. حزمة مشتركة `packages/tripz_ui` يستهلكها التطبيقان (تجنّب ازدواج الشاشات).
|
||||
12. **ميزانية أداء مكتوبة** تُفحص في كل بوابة بعدها: بلا `setState` فوق شجرة
|
||||
كبيرة · `BlocSelector`/`buildWhen` افتراضياً · `const` على كل widget ساكن ·
|
||||
قوائم كسولة · بلا عمل ثقيل في `build`. سبب الأداء السيئ سابقاً يُشخّص من
|
||||
`*-archive-cubit` **مرة واحدة** ويُكتب هنا كقائمة ممنوعات.
|
||||
|
||||
### المرحلة 3 — التسجيل والدخول (OTP) كاملاً
|
||||
13. طبقة الشبكة: Dio + interceptors + تجديد التوكن + `x-app-role`.
|
||||
14. شاشات: onboarding → إدخال الهاتف → OTP → إكمال الملف → استعادة الجلسة.
|
||||
15. مراعاة قرار **هوية الراكب/السائق المنفصلة** (قيد `tenant,phone_bidx,role`
|
||||
+ ترويسة `x-app-role`) — يجب أن يكون موجوداً في NestJS، وإن لم يكن فهو
|
||||
عمل باك إند إضافي يُحسب في هذه المرحلة.
|
||||
16. **بوابة**: دخول حقيقي من التطبيقين على NestJS المشتغل + لقطات شاشة تُقارن
|
||||
بمرجع سيرو، ولا تُقبل إن كانت أدنى منه.
|
||||
|
||||
### المرحلة 4 — الخريطة وطلب الرحلة
|
||||
17. الخريطة مباشرة لـ map-saas بمفتاح `x-api-key` مقيّد ببصمة التطبيق
|
||||
(قرار `maps-direct-decision`) — لا تمرير عبر الباك إند.
|
||||
18. الراكب: اختيار المصدر/الوجهة · التسعير المسبق · تأكيد الطلب · انتظار سائق.
|
||||
19. السائق: أونلاين/أوفلاين · استقبال العروض · قبول/رفض.
|
||||
20. الواقع اللحظي (WebSocket) حسب `backend-archive/src/realtime`.
|
||||
21. **بوابة**: رحلة كاملة من الطلب إلى الإنهاء بين جهازين + الخريطة تتحرّك
|
||||
بسلاسة أثناء التتبّع (لا اهتزاز، لا إعادة رسم للشاشة كاملة).
|
||||
|
||||
### المرحلة 5 — مسار الرحلة وما بعدها
|
||||
22. حالات الرحلة · التتبّع الحيّ · الإلغاء · الإنهاء · الأجرة · التقييم.
|
||||
23. المحفظتان (محفظة الراكب = التزام، محفظة السائق = إيراد — لا تُدمجان).
|
||||
|
||||
### المرحلة 6 — الدراور والإضافات
|
||||
24. الدراور · الملف الشخصي · الرحلات السابقة · الإشعارات · الدعم · اللغة.
|
||||
25. الوحدات المدفوعة السبع (`docs/33`) خلف أعلام `const` حسب نموذج
|
||||
lite/pro/max — بناء مولَّد، لا فروع.
|
||||
|
||||
## 4. مصير المجلدات القائمة
|
||||
|
||||
| المجلد | المصير |
|
||||
|---|---|
|
||||
| `apps/rider-archive-cubit`, `driver-archive-cubit` | مصدر الطبقة الأصلية في م1، ومصدر تشخيص الأداء في م2 — ثم **يُحذفان** بعد نجاح بوابة م3 |
|
||||
| `apps/rider`, `apps/driver` (نقل GetX) | مرجع سلوك ميداني فقط → `apps/*-archive-getx` بعد م3. **ليسا مرجع هوية** (§2.ج) |
|
||||
| `Siro/siro_rider`, `siro_driver` | مرجع خارجي دائم — لا يُعدَّل ولا يُنسخ منه دارت |
|
||||
| باك إند PHP (`docs/34`) | يتوقّف العمل عليه — لا يُحذف |
|
||||
|
||||
## 5. المخاطر
|
||||
|
||||
| الخطر | التخفيف |
|
||||
|---|---|
|
||||
| تعارض shorebird app_id بين سيرو وتريبز | الهوية من `*-archive-cubit` حصراً (§2.ج)؛ يُفحص قبل أول باتش |
|
||||
| تكرار الأداء السيئ نفسه | م2.12 ميزانية أداء مكتوبة + بوابة أداء في كل مرحلة |
|
||||
| «من الصفر» تتحول لنسخ صامت من الأرشيف | ممنوع أي `import` من `*-archive-*`؛ يُفحص بـ grep عند كل بوابة |
|
||||
| NestJS «جاهز» لكن غير مختبر منذ 2026-07 | بوابة م0.2 قبل أي شغل تطبيق |
|
||||
| هوية الراكب/السائق المنفصلة غير منفّذة في NestJS | يُتحقّق في م0.1 |
|
||||
| جرّ GetX بالغلط عبر pubspec | ممنوع `get`/`get_storage` في `*_new` |
|
||||
| بناء/اختبار على الماك | كل بناء وهجرة على السيرفر |
|
||||
@@ -0,0 +1,308 @@
|
||||
# 38 — عقد الـ API لتطبيقي `rider_new` و `driver_new`
|
||||
|
||||
المصدر: `backend-archive/` (NestJS) · مُتحقَّق حيّاً على
|
||||
`https://tripz-api.intaleqapp.com/api` بتاريخ **2026-08-04**.
|
||||
هذا الملف **المصدر الوحيد للحقيقة** لطبقة الشبكة في التطبيقين (م0.1 من `docs/37`).
|
||||
|
||||
> ✅ = تُحقّق شكله حيّاً من السيرفر · 📄 = مقروء من الكود فقط
|
||||
|
||||
---
|
||||
|
||||
## 0. حالة السيرفر — تحقّق حيّ
|
||||
|
||||
```
|
||||
GET https://tripz-api.intaleqapp.com/api/health
|
||||
→ 200 {"status":"ok","service":"tripz-api","time":"2026-08-04T17:52:23Z"}
|
||||
```
|
||||
|
||||
الباك إند المؤرشف **ما زال منشوراً ويعمل** رغم قرار الأرشفة 2026-07-27.
|
||||
المستأجر التجريبي `siro` → `efefa0ad-d803-4a81-9627-125945bb079b`.
|
||||
|
||||
---
|
||||
|
||||
## 1. الترويسات — في كل طلب
|
||||
|
||||
| الترويسة | القيمة | إلزامي |
|
||||
|---|---|---|
|
||||
| `x-tenant-id` | **slug** المستأجر (`siro`) لا UUID | نعم — بدونه 401 |
|
||||
| `x-app-role` | `rider` أو `driver` | نعم عملياً (الافتراض `rider`) |
|
||||
| `x-device-id` | بصمة الجهاز | عند `verify-otp` و`refresh` |
|
||||
| `Authorization` | `Bearer <access_token>` | لكل ما عدا `auth/*` و`maps/*` و`tenant/config` |
|
||||
|
||||
**`x-app-role` ليس تجميلاً.** ✅ نفس الرقم `0790000001` أعطى:
|
||||
`rider` → `d3a29299-909b-4267-80f3-887d8e478e04` ·
|
||||
`driver` → `f21875fa-15db-4805-be39-ccefb9f63450`.
|
||||
هويتان منفصلتان بمحفظتين منفصلتين. **التطبيق يجب أن يثبّت الترويسة على
|
||||
قيمة واحدة عند البناء ولا يغيّرها أبداً** — تغييرها = مستخدم آخر.
|
||||
|
||||
---
|
||||
|
||||
## 2. المصادقة — هاتف + OTP، بلا كلمة سر
|
||||
|
||||
### `POST /auth/send-otp` 📄
|
||||
`{ phone }` → `{ success: true, message: 'OTP sent' }`
|
||||
حدّ: **3 طلبات / 5 دقائق**. التطبيق يجب أن يعرض عدّاداً تنازلياً ويمنع الضغط
|
||||
المتكرر، وإلا صار المستخدم محظوراً بعد ثلاث ضغطات.
|
||||
|
||||
### `POST /auth/verify-otp` ✅
|
||||
```jsonc
|
||||
// الطلب
|
||||
{ "phone": "0790000001", "code": "1234", "referral_code": "اختياري" }
|
||||
// الرد 200
|
||||
{
|
||||
"access_token": "eyJ…",
|
||||
"refresh_token": "eyJ…",
|
||||
"signing_key": "598d8044…c2a2", // ← لتوقيع الطلبات (§8)
|
||||
"user": {
|
||||
"id": "uuid", "tenant_id": "uuid",
|
||||
"phone": "962790000001", // مطبّع دولياً — لا كما أُدخل
|
||||
"phone_bidx": "3f14…", // فهرس أعمى — لا يُعرض
|
||||
"name": null, "role": "rider", "status": "active",
|
||||
"language": "ar", "rating": "5.00",
|
||||
"created_at": "…", "updated_at": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
- **التطبيع**: `0790000001` يُخزَّن `962790000001`. التطبيق يعرض ما أدخله
|
||||
المستخدم، ويرسل ما شاء — الخادم يطبّع. لا تعتمد على تطابق نصّي.
|
||||
- **حدّ المحاولات**: 10 محاولات / 5 دقائق شبكياً + عدّاد لكل (مستأجر، رقم)
|
||||
يُبطل الرمز فوراً عند تجاوزه. رسالة الخطأ:
|
||||
`401 {"message":"Too many attempts — request a new code"}`.
|
||||
- الرمز الخاطئ: `401 {"message":"Invalid or expired OTP code"}`.
|
||||
- **حسابا مراجعة المتاجر** (مقيّدان بمستأجر `siro` فقط):
|
||||
`0790000001` و`0790000002` بالرمز الثابت `1234` — يعملان **بلا**
|
||||
`OTP_DEV_MODE`. ✅ هذان مدخلنا للاختبار حتى نحصل على وصول للسيرفر.
|
||||
|
||||
### `POST /auth/refresh` 📄
|
||||
`{ refresh_token }` + ترويسة `x-device-id` → توكن جديد.
|
||||
|
||||
### أعمار التوكن ✅
|
||||
`access_token` صالح **900 ثانية (15 دقيقة)** — من `iat`/`exp` الفعليين.
|
||||
معناه للتطبيق: interceptor يجدّد استباقياً قبل انتهاء الصلاحية، ولا ينتظر
|
||||
401 — 15 دقيقة قصيرة جداً في منتصف رحلة.
|
||||
|
||||
---
|
||||
|
||||
## 3. المستخدم
|
||||
|
||||
| النقطة | الوصف |
|
||||
|---|---|
|
||||
| `GET /users/me` ✅ | نفس شكل `user` أعلاه بالضبط |
|
||||
| `PATCH /users/me` 📄 | `{ name?, language? }` |
|
||||
|
||||
---
|
||||
|
||||
## 4. الراكب — دورة الرحلة
|
||||
|
||||
### `POST /trips` — طلب رحلة 📄
|
||||
```jsonc
|
||||
{
|
||||
"origin": { "lat": 31.9539, "lng": 35.9106 },
|
||||
"destination": { "lat": 31.98, "lng": 35.87 },
|
||||
"service_class": "economy", // من /ride-types
|
||||
"city": "…", // اختياري
|
||||
"payment_method": "wallet",
|
||||
"is_round_trip": false,
|
||||
"stops": [{ "lat": …, "lng": …, "label": "…" }], // محطات وسيطة
|
||||
"scheduled_at": "ISO", // حجز مسبق
|
||||
"coupon_code": "…"
|
||||
}
|
||||
→ { "trip": { "id", "status": "searching", "quoted_fare": …, … },
|
||||
"offeredDrivers": 3 }
|
||||
```
|
||||
|
||||
### باقي النقاط
|
||||
|
||||
| النقطة | من | ملاحظة |
|
||||
|---|---|---|
|
||||
| `GET /trips/mine` ✅ | راكب | مصفوفة (فارغة `[]` للحساب الجديد) |
|
||||
| `GET /trips/:id` 📄 | الطرفان | |
|
||||
| `POST /trips/:id/cancel` 📄 | الطرفان | الفاعل يُستنتج من الدور |
|
||||
| `GET /trips/available` 📄 | سائق | الطلبات القريبة |
|
||||
| `POST /trips/:id/accept` 📄 | سائق | قبول ذرّي — أول واحد يفوز |
|
||||
| `PATCH /trips/:id/status` 📄 | سائق | `{ status }` |
|
||||
|
||||
### آلة الحالات
|
||||
```
|
||||
searching → assigned → driver_arriving → driver_arrived
|
||||
→ in_progress → completed → paid
|
||||
(+ cancelled · expired/no_drivers)
|
||||
```
|
||||
⚠️ **`expired`/`no_drivers` قد لا يُطلقان أبداً** (ثغرة R1 المسجّلة).
|
||||
لا يجوز أن ينتظر التطبيق حدثاً قد لا يصل — **لازم مهلة محليّة في
|
||||
`searching`** تعرض «لا يوجد سائقون» وتتيح الإلغاء.
|
||||
|
||||
### الأجرة
|
||||
عند الإنهاء: `price_for_passenger` و`price_for_driver` **حقلان منفصلان**
|
||||
(الفرق = العمولة). لا تعرض حقلاً واحداً للطرفين.
|
||||
|
||||
---
|
||||
|
||||
## 5. السائق
|
||||
|
||||
| النقطة | الجسم |
|
||||
|---|---|
|
||||
| `POST /drivers/apply` | `{ vehicle_make, service_class }` |
|
||||
| `GET /drivers/me` | ملف السائق |
|
||||
| `PATCH /drivers/profile` | `gender · national_number · name_arabic · birthdate · address · license_type · license_categories · license_issue · license_expiry` |
|
||||
| `PATCH /drivers/status` | `{ online: bool }` |
|
||||
| `POST /drivers/location` | `{ lat, lng, heading?, speed? }` → Redis |
|
||||
| `GET /credit` | الرصيد التشغيلي. للراكب: `403 "Not a driver"` ✅ |
|
||||
| `GET /credit/transactions` | كشف الرصيد |
|
||||
|
||||
**الدور يتغيّر بعد `apply`+`approve`** — التوكن القديم يحمل الدور القديم.
|
||||
لازم إعادة دخول (أو refresh) بعد الاعتماد، وإلا فشلت نقاط السائق بـ403.
|
||||
|
||||
### المركبات
|
||||
`GET /vehicles/mine` · `POST /vehicles` (`make · model · year · color ·
|
||||
color_hex · plate`) · `PATCH /vehicles/:id/default` · `DELETE /vehicles/:id`.
|
||||
أول مركبة تصير الافتراضية تلقائياً.
|
||||
|
||||
### الوثائق
|
||||
`POST /drivers/documents` (رفع) · `GET /drivers/documents/mine` ·
|
||||
`GET /drivers/documents/requirements` (يقود شاشة «الوثائق الناقصة»).
|
||||
|
||||
---
|
||||
|
||||
## 6. المال
|
||||
|
||||
| النقطة | الرد ✅ |
|
||||
|---|---|
|
||||
| `GET /wallet` | `{ id, tenant_id, user_id, balance: "0.000", currency: "JOD", … }` |
|
||||
| `GET /wallet/transactions` | كشف |
|
||||
| `POST /wallet/topup` | `{ amount }` |
|
||||
|
||||
⚠️ **`balance` نصّ لا رقم** (`"0.000"`). لا تمرّره لعملية حسابية بلا تحويل.
|
||||
`currency` من المستأجر (`JOD`/`EGP`/`SYP`) — لا يُثبَّت في التطبيق.
|
||||
|
||||
**السحب — خطوتان**: `POST /payouts/request` (`{ amount, channel }`) يرسل
|
||||
رمزاً **بلا خصم** → `POST /payouts/:id/confirm` (`{ code }`) يخصم ويحجز.
|
||||
`GET /payouts/mine` للسجل. شاشة السحب يجب أن تعكس الخطوتين لا خطوة واحدة.
|
||||
|
||||
`POST /payments/charge` · `GET /payments/mine` لبوابات الدفع.
|
||||
|
||||
---
|
||||
|
||||
## 7. التقييم · التعرفة · الأنواع · الخريطة
|
||||
|
||||
### `GET /trips/rating/pending` ✅
|
||||
`{ "pending": null, "count": 0 }` أو
|
||||
`{ "pending": { "tripId", "role": "rider"|"driver" } }`.
|
||||
**يُنادى عند كل فتح للتطبيق** — إن وُجدت رحلة معلّقة تُفرض شاشة التقييم.
|
||||
`POST /trips/:id/rate` `{ stars, comment? }`. التقييم المزدوج → `400`.
|
||||
|
||||
### `GET /ride-types` ✅
|
||||
```jsonc
|
||||
[{ "id","code":"economy","name_ar":"اقتصادي","name_en":"Economy",
|
||||
"vehicle_kind":"car","women_only":false,"round_trip_supported":true,
|
||||
"icon":null,"sort":1,"active":true }, …]
|
||||
```
|
||||
شاشة اختيار النوع تُبنى من هنا — لا قائمة مكتوبة في التطبيق. انتبه
|
||||
لـ`women_only` و`round_trip_supported` و`sort`.
|
||||
|
||||
### `POST /tariff/quote` ✅
|
||||
```jsonc
|
||||
{ "city": "…", "serviceClass": "economy", "distanceKm": 5.2, "durationMin": 14 }
|
||||
→ { "quote": { "window":"normal_evening","flag","distance","time","waiting",
|
||||
"weight","bookingFee","subtotal","surgeMultiplier","total",
|
||||
"currency":"JOD" }, "tariffId", "version" }
|
||||
```
|
||||
⚠️ **الأسماء camelCase هنا، بخلاف بقية الـAPI (snake_case).** وإرسال
|
||||
`origin/destination` بدل `distanceKm/durationMin` يرجّع 200 بقيم `null`
|
||||
صامتة — لا خطأ. يعني: احسب المسافة من `/maps/route` أولاً ثم اطلب التسعيرة،
|
||||
وتحقّق أن `total != null` قبل عرضها.
|
||||
⚠️ `city` غير معروف → `404 "No active tariff for amman/economy"` ✅ —
|
||||
قيمة `city` تأتي من إعداد المستأجر لا من نصّ حرّ.
|
||||
|
||||
### الخريطة
|
||||
| النقطة | الحالة |
|
||||
|---|---|
|
||||
| `GET /maps/route?fromLat&fromLng&toLat&toLng&country` | ✅ `{"distanceKm":7.209,"durationMin":11.5,"provider":"antlaq"}` |
|
||||
| `GET /maps/reverse?lat&lng&country` | ✅ مصفوفة أماكن (`name_ar`, `category`, `distance`, …) |
|
||||
| `GET /maps/geocode?q&country` | ❌ `400 "country must be one of the following values: "` — **قائمة الدول فارغة على السيرفر المنشور** |
|
||||
| `POST /maps/voice-search` · `POST /maps/places` | 📄 |
|
||||
|
||||
**تعارض قرار يُحسم قبل م4**: قرار `maps-direct-decision` (2026-07-20) يقول كل
|
||||
الخرائط مباشرة لـ map-saas بمفتاح `x-api-key`؛ و`docs/01`/الذاكرة الأقدم
|
||||
تقول geocode/route عبر خادمنا. النقاط أعلاه موجودة وتعمل (عدا geocode).
|
||||
القرار الأحدث يسود — لكن يُثبَّت صراحةً قبل بناء شاشة الخريطة.
|
||||
هذه النقاط **بلا `JwtAuthGuard`** — مفتوحة بلا توكن.
|
||||
|
||||
---
|
||||
|
||||
## 8. الأمان — علمان مُطفآن ينتظران التطبيق
|
||||
|
||||
`signing_key` يعود في رد الدخول، ويوجد `PAYMENTS_REQUIRE_SIGNATURE` و
|
||||
`AUTH_REQUIRE_DEVICE_BINDING`. القرار: **يُفعَّلان بعد أن يوقّع التطبيق
|
||||
طلباته ويرسل `x-device-id`**. لذلك يُبنى التوقيع وبصمة الجهاز في طبقة
|
||||
الشبكة **من م3**، لا يُؤجَّل — تأجيله يعني إعادة كتابة الـinterceptors لاحقاً.
|
||||
|
||||
---
|
||||
|
||||
## 9. الواقع اللحظي (WebSocket)
|
||||
|
||||
Socket.IO على أصل الـAPI بلا `/api`، المصادقة `auth: { token }`،
|
||||
`transports: ['websocket']`.
|
||||
|
||||
**يُرسِله الخادم**: `trip:update` · `driver:location` · `trip:offer` ·
|
||||
`trip:offer_taken` · `bus:arrived`
|
||||
**يستقبله الخادم**: `trip:join` (`{ tripId }`) · `driver:location` ·
|
||||
`call:offer` / `call:answer` / `call:ice` / `call:end` (WebRTC)
|
||||
|
||||
`trip:offer` و`trip:offer_taken` = قلب شاشة السائق: عرض يصل، ثم يختفي إذا
|
||||
سبقه غيره. لازم انضمام (`trip:join`) **قبل** أي انتقال حالة وإلا فاتت الأحداث.
|
||||
|
||||
---
|
||||
|
||||
## 10. إعداد المستأجر — يقود واجهة التطبيق
|
||||
|
||||
### `GET /tenant/config/:slug` ✅ (بلا توكن)
|
||||
```jsonc
|
||||
{ "slug":"siro","name":"Siro Amman","countryPack":"jo","plan":"brand",
|
||||
"branding":{},
|
||||
"features":{ "dispatch":false,"wallet":true,"payments":true,"chat":true,
|
||||
"calls":true,"ride_types":true,"market_intel":false,"bots":false,
|
||||
"ads":false,"transit":false,"api_access":false,"driver_tiers":true,
|
||||
"marketing_engine":false,"dynamic_pricing":false,"geofence":false,
|
||||
"negotiator":false,"driver_assurance":false,"coupons":false },
|
||||
"limits":{ "drivers_max":500,"cities_max":3 } }
|
||||
```
|
||||
**كل ميزة في التطبيق تُخفى أو تُظهر من `features`**، لا من علم مكتوب في
|
||||
الكود. هذا هو ربط lite/pro/max بالخادم. يُجلب مرة عند الإقلاع ويُخزَّن.
|
||||
`GET /tenant/logo/:slug` للشعار.
|
||||
|
||||
---
|
||||
|
||||
## 11. نقاط أخرى تخصّ التطبيقين
|
||||
|
||||
`POST /notifications/token` `{ token, platform }` — FCM ·
|
||||
`POST /trips/:id/messages` + `GET /trips/:id/messages` — الدردشة ·
|
||||
`GET /geofence/nearby` · `GET /rewards/referrals` · `GET /rewards/coupons` ·
|
||||
`GET /rewards/coupons/preview` · `POST /trips/:id/audio` + `GET .../audio`.
|
||||
|
||||
**نقاط الأدمن ليست للتطبيقين** (`/admin/*`, `/superadmin/*`, `/tenant/billing`,
|
||||
`/dispatch/*`): محروسة بـ`RolesGuard` وترجع `403` — لا تُستدعى.
|
||||
|
||||
---
|
||||
|
||||
## 12. مصائد مؤكَّدة — تُقرأ قبل كتابة طبقة الشبكة
|
||||
|
||||
1. `access_token` = 15 دقيقة → تجديد استباقي إجباري.
|
||||
2. `balance` وكل المبالغ **نصوص** — حوّلها.
|
||||
3. `x-app-role` يحدّد الهوية — يُثبَّت عند البناء.
|
||||
4. `/tariff/quote` بـcamelCase وحده، ويفشل صامتاً بقيم `null`.
|
||||
5. `/auth/send-otp` = 3 طلبات / 5 دقائق — عدّاد تنازلي إجباري في الواجهة.
|
||||
6. الدور يتغيّر بعد اعتماد السائق → إعادة دخول إجبارية.
|
||||
7. `expired`/`no_drivers` قد لا يصلان → مهلة محليّة في `searching`.
|
||||
8. `/maps/geocode` معطوب على المنشور (قائمة دول فارغة).
|
||||
9. `x-tenant-id` هو **slug** لا UUID.
|
||||
|
||||
---
|
||||
|
||||
## 13. ما لم يُتحقّق بعد — يحتاج وصول للسيرفر
|
||||
|
||||
`e2e-test.mjs` الكامل يحتاج `OTP_DEV_MODE=true` (أرقام عشوائية)؛ على
|
||||
المنشور هو **مطفأ**، والـSSH من الماك مرفوض (`Permission denied (publickey)`).
|
||||
غير مُتحقَّق حيّاً: دورة الرحلة الكاملة · WebSocket · التسوية والعمولة ·
|
||||
السحب بـOTP · حراسة الأدمن. الشكل موثّق أعلاه من الكود (📄) ويُثبَّت عند
|
||||
توفّر الوصول.
|
||||
@@ -0,0 +1,196 @@
|
||||
# 39 — جرد شاشات سيرو: المرجع البصري والسلوكي
|
||||
|
||||
الغرض: هذا **ما نبني عليه الشكل والترتيب** في `rider_new` و`driver_new`
|
||||
(م0.3 من `docs/37`). المصدر: `~/development/App/Siro/siro_rider` و`siro_driver`.
|
||||
|
||||
> **قاعدة الاستخدام**: هذا الملف يجيب على «ما هي الشاشة، وما ترتيب خطواتها،
|
||||
> وأي حالة حافة عولجت ميدانياً». **لا يُنسخ ملف دارت واحد من سيرو.**
|
||||
> شكل الواجهة يُعاد بناؤه على `docs/26` بجودة أعلى، والعقد من `docs/38`.
|
||||
|
||||
---
|
||||
|
||||
## 1. المخزون — الحجم الحقيقي
|
||||
|
||||
| | شاشات/ويدجت | ملاحظة |
|
||||
|---|---|---|
|
||||
| `siro_rider/lib/views` | 89 ملف | يشمل الطعام والنقل العام |
|
||||
| `siro_driver/lib/views` | 113 ملف | يشمل التوصيل والتلعيب والإحصاءات |
|
||||
|
||||
**ليس كله في النطاق.** خارج نطاق `docs/37` تماماً: `food/`, `food_delivery/`,
|
||||
`transit/`, `gamification/`, `assurance_health_page`, `siro_prime_page`,
|
||||
`android_bot`. هذي تُقرأ لاحقاً عند وحدات `docs/33` المدفوعة، لا الآن.
|
||||
|
||||
**داخل النطاق** (م1–م6): المصادقة · الخريطة وطلب الرحلة · مسار الرحلة ·
|
||||
المحفظة · الدراور والملف · الإشعارات · التقييم.
|
||||
|
||||
---
|
||||
|
||||
## 2. المصادقة — مرجع م3
|
||||
|
||||
### التسلسل في سيرو (متطابق في التطبيقين)
|
||||
|
||||
```
|
||||
موافقة الشروط → إذن الموقع → رقم الهاتف → OTP → [تسجيل] → الرئيسية
|
||||
```
|
||||
|
||||
بوابتان **قبل** أي حقل إدخال، وكل واحدة شاشة كاملة لا حوار:
|
||||
|
||||
1. **صفحة الموافقة** (`_buildAgreementPage`): نصّ الاتفاقية بـHTML من الخادم،
|
||||
مربّع اختيار، وزر متابعة **معطّل حتى يُؤشَّر**. تُحفظ
|
||||
(`BoxName.agreeTerms == 'agreed'`) ولا تتكرر.
|
||||
2. **صفحة إذن الموقع**: تطلب الإذن، ومعها زر `openAppSettings()` للحالة التي
|
||||
يرفض فيها المستخدم نهائياً. تطبيق السائق يراقب دورة حياة التطبيق
|
||||
(`WidgetsBindingObserver` + `didChangeAppLifecycleState`) ليعيد الفحص عند
|
||||
**العودة من إعدادات النظام** — بلا هذا تبقى الشاشة عالقة. حالة حافة حقيقية
|
||||
لازم تُنقل.
|
||||
|
||||
### شاشة الهاتف
|
||||
- `IntlPhoneField` مع مفتاح دولة افتراضي من `BoxName.countryCode`.
|
||||
- تحقّقات مطبّقة: فارغ · **يبدأ بصفر → رفض** («أدخل بلا الصفر») · أقصر من 10.
|
||||
- الرقم يُرسل بلا `+`.
|
||||
|
||||
### شاشة الـOTP
|
||||
- **3 خانات** في سيرو، حقل واحد بتباعد حروف كبير + ثلاث نقاط تقدّم متحرّكة.
|
||||
- ⚠️ **تريبز يستخدم 4 خانات** (`docs/38`). الشكل يُقلَّد، الطول لا.
|
||||
- ⚠️ **لا يوجد عدّاد إعادة إرسال ولا زر «إعادة الإرسال» في سيرو.** هذه
|
||||
**فجوة، لا مرجع**: تريبز يحدّ `send-otp` بـ3 طلبات/5 دقائق، فبلا عدّاد
|
||||
تنازلي يُحظر المستخدم بثلاث ضغطات. **لازم نضيفها.**
|
||||
|
||||
### شاشة التسجيل
|
||||
الاسم الأول · الأخير · البريد. ⚠️ عقد تريبز يقبل `{ name, language }` فقط
|
||||
(`PATCH /users/me`) — حقل اسم واحد. لا تُنقل الحقول كما هي.
|
||||
|
||||
### فروق تطبيق السائق
|
||||
مسار إضافي بعد الدخول: `registration_view` (تسجيل السائق) →
|
||||
`pending_driver_page` (شاشة «قيد المراجعة») → `camera_lisence_page` /
|
||||
`text_scanner` / `passportimage` (تصوير الوثائق) → `cars_inserting_page`.
|
||||
يقابلها في تريبز: `/drivers/apply` → `/drivers/profile` →
|
||||
`/drivers/documents` → `/vehicles`، **ثم إعادة دخول إجبارية** لأن الدور
|
||||
يتغيّر بعد الاعتماد (مصيدة `docs/38` §5).
|
||||
|
||||
### ما لا يُنقل من طبقة مصادقة سيرو
|
||||
`login_controller.dart` فيه JWT مزدوج (توكن عام + `walletJwt` منفصل)، وقفل
|
||||
تزامن ساكن، وعدّاد إخفاقات، وفكّ base64 يدوي للـpayload. **هذا تعقيد مفروض
|
||||
من باك إند PHP**، وتريبز عنده `access_token`/`refresh_token` واحد. لا يُنقل.
|
||||
المُنقَل هو الفكرة الصالحة: **تجديد واحد متزامن لا يتضاعف عند تعدد الطلبات**.
|
||||
|
||||
---
|
||||
|
||||
## 3. الخريطة وطلب الرحلة — مرجع م4
|
||||
|
||||
أنضج جزء في سيرو، وأدقّ ما نأخذه منه. الوحدة: `views/home/route_planner/`
|
||||
(10 ملفات، 1,928 سطراً) + `views/home/map_widget.dart/`.
|
||||
|
||||
### البنية: خريطة ثابتة + ورقة سفلية بثلاث حالات
|
||||
1. **مطوية** (`_Collapsed`) — بطاقة «إلى أين؟» فقط.
|
||||
2. **موسّعة** (`_Expanded`) — المخطّط الكامل.
|
||||
3. **اختيار من الخريطة** (`RpMapPickOverlay`) — الورقة تنزوي، ودبّوس ثابت في
|
||||
المنتصف (`rp_center_pin`) والخريطة تتحرك تحته. النمط الصحيح: **المستخدم
|
||||
يحرّك الخريطة لا الدبّوس.**
|
||||
|
||||
### ترتيب المخطّط الموسّع (من أعلى لأسفل)
|
||||
ترويسة → **مبدّل نوع الطلب** (لنفسي ⇄ لشخص آخر) → **الخط الزمني للمسار**
|
||||
(المصدر · محطات وسيطة · الوجهة) → **وصول سريع** (المنزل/العمل) → **متقدّم**.
|
||||
|
||||
- المصدر افتراضياً = الموقع الحالي، وقابل للتغيير من الخريطة.
|
||||
- عند «لشخص آخر» يتبدّل حقل المصدر فوراً (`rp_sheet.dart:509`).
|
||||
- **المحطات الوسيطة** صفوف قابلة للإضافة/الحذف — يقابلها `stops[]` في
|
||||
`POST /trips` (`docs/38` §4). موجودة في الطرفين.
|
||||
- المفضّلات (`rp_favorites`) والبحث (`rp_search_dialog` + `rp_search_results`).
|
||||
|
||||
### نافذة البحث عن سائق (`searching_captain_window`)
|
||||
- أنيميشن رادار (دورة ثانيتين) + نصّ الحالة من الكنترولر + مؤشّر دوّار.
|
||||
- **لا مقبض سحب** — لا تُغلق بالسحب، **فقط بزر «إلغاء البحث»**. قرار تصميمي
|
||||
صحيح يُنقل: المستخدم لا يزيح شاشة البحث بالخطأ.
|
||||
- الويدجت «غبي» بلا منطق؛ المؤقّت في الكنترولر هو من يقرر متى يعرض حوار
|
||||
زيادة الأجرة. **يقابل مباشرةً** مصيدة `docs/38` §4: `expired`/`no_drivers`
|
||||
قد لا يصلان من الخادم → **المهلة المحليّة إلزامية**. سيرو عالجها؛ نكرّرها.
|
||||
|
||||
### بطاقة الرحلة الجارية (`apply_order_widget`, 871 سطراً)
|
||||
ترويسة مضغوطة · بطاقة معلومات · **شارة السائق** · **لوحة مركبة مرسومة** ·
|
||||
أيقونة سيارة متحرّكة · صف أزرار · حقل رسالة مخصّصة · مؤشّرا
|
||||
«وصل السائق وينتظر ن دقيقة» و«الوقت المتبقّي للوصول».
|
||||
هذي المكوّنات هي ما يجعل الشاشة تبدو احترافية — تُعاد بناؤها في
|
||||
`packages/tripz_ui`، لا تُنسخ.
|
||||
|
||||
### شاشات مساندة
|
||||
`payment_method_page` · `cash_confirm_bottom_page` · `cancel_raide_page`
|
||||
(أسباب الإلغاء) · `ride_begin_passenger` · `car_details_widget_to_go` ·
|
||||
`trip_monitor` + `trip_link_monitor` (مشاركة رابط تتبّع) ·
|
||||
`voice_call_bottom_sheet` (WebRTC — يقابل `call:*` في `docs/38` §9).
|
||||
|
||||
### شاشة السائق (`order_request_page`, 468 سطراً)
|
||||
خريطة → **كبسولة «الوصول للراكب»** → بطاقة سفلية:
|
||||
`الراكب + السعر` → `شريط معلومات (المسافة/المدة)` → `العناوين` → `الأزرار`.
|
||||
- **حالة تحميل صريحة** إن وصل العرض بلا بيانات كاملة — لا شاشة فارغة.
|
||||
- `order_over_lay.dart` (847 سطراً) = العرض المنبثق فوق أي شاشة. يقابل
|
||||
`trip:offer` و`trip:offer_taken` في `docs/38` §9.
|
||||
- ⚠️ شارة `driverEarningsExtra` تعتمد حقولاً من `add_ride.php` (باك إند PHP)
|
||||
**غير موجودة في عقد NestJS** — تُسقط أو يُضاف لها حقل في الخادم. مثال حيّ
|
||||
على أن سيرو ليس مطابقاً للعقد.
|
||||
|
||||
---
|
||||
|
||||
## 4. المحفظة — مرجع م5
|
||||
|
||||
| الراكب | السائق |
|
||||
|---|---|
|
||||
| `passenger_wallet` · `payment_history_passenger_page` · `cliq_payment_sheet` · `payment_screen_cliq/mtn/sham` | `walet_captain` · `pay_out_screen` · `weekly_payment_page` · `transfer_budget_page` · `points_captain` · `ecash` · `bank_account_egypt` |
|
||||
|
||||
محفظة السائق مقسّمة لويدجت جاهزة (`balance_card` ·
|
||||
`financial_summary_card` · `quick_actions` · `transaction_preview_item`) —
|
||||
تفكيك جيد يُحتذى.
|
||||
|
||||
⚠️ **مصيدتان**:
|
||||
1. سيرو يخلط محفظة الراكب والسائق في تصميم واحد. تريبز يفصلهما (التزام مقابل
|
||||
إيراد) — لا تُوحَّد الشاشة.
|
||||
2. `pay_out_screen` في سيرو **خطوة واحدة**؛ تريبز **خطوتان** مع OTP
|
||||
(`docs/38` §6). الشاشة تُعاد تصميمها لا تُقلَّد.
|
||||
|
||||
---
|
||||
|
||||
## 5. الدراور والملف والإشعارات — مرجع م6
|
||||
|
||||
**الراكب**: `drawer_menu_page` · `passenger_profile_page` · `order_history` ·
|
||||
`setting_page` · `languages` · `about_page` · `contact_us` ·
|
||||
`frequentlyQuestionsPage` · `share_app_page` · `complaint_page` ·
|
||||
`promos_passenger_page` · `taarif_page` · `notification_page` ·
|
||||
`rate_captain` + `rating_driver_bottom`.
|
||||
|
||||
**السائق**: `drawer_captain` · `bottom_bar` (تنقّل سفلي — الراكب بلا) ·
|
||||
`profile_captain` · `captains_cars` · `history_captain` +
|
||||
`history_details_page` · `settings_captain` · `behavior_page` ·
|
||||
`feed_back_page` · `help_captain` + `help_details_replay_page` ·
|
||||
`available_rides_page` · `statistics_dashboard` (+6 ويدجت رسوم) ·
|
||||
`rate_passenger` · `ride_calculate_driver` · `sos_connect` (استغاثة).
|
||||
|
||||
**شاشة التقييم مهمة معمارياً**: تُفرض عند فتح التطبيق عبر
|
||||
`GET /trips/rating/pending` (`docs/38` §7) — تُبنى في م5 لا م6.
|
||||
|
||||
---
|
||||
|
||||
## 6. الويدجت المشتركة — مدخل `packages/tripz_ui`
|
||||
|
||||
الملفات نفسها **مكرّرة حرفياً** في التطبيقين:
|
||||
`circle_container` · `elevated_btn` · `error_snakbar` · `icon_widget_menu` ·
|
||||
`my_circular_indicator_timer` · `my_scafold` · `my_textField` · `mycircular` ·
|
||||
`mydialoug` · `voice_call_bottom_sheet`.
|
||||
|
||||
**هذا بالضبط ما تحلّه `packages/tripz_ui`** (م2.11): ازدواج مؤكَّد في سيرو،
|
||||
لا يتكرر عندنا.
|
||||
|
||||
---
|
||||
|
||||
## 7. ملاحظات أداء — مادة قائمة الممنوعات (م2.12)
|
||||
|
||||
من قراءة سيرو، هذي أنماط تُتجنّب في `*_new`:
|
||||
- `Get.put()` داخل `build()` (`login_page.dart:27`) — حقن عند كل إعادة بناء.
|
||||
- ملفات ويدجت تتجاوز 800 سطر (`apply_order_widget` 871 ·
|
||||
`order_over_lay` 847 · `home_captin` 840) — تفكّك.
|
||||
- `GetBuilder` يلفّ الشاشة كاملة → إعادة بناء الشجرة كلها عند أي تحديث.
|
||||
البديل: `BlocSelector`/`buildWhen` على أصغر جزء.
|
||||
- منطق داخل الويدجت. النمط الصحيح موجود في سيرو نفسه
|
||||
(`searching_captain_window` «غبي» والمؤقّت في الكنترولر) — يُعمَّم.
|
||||
|
||||
> قائمة الممنوعات النهائية تُستكمل في م2 بعد تشخيص `*-archive-cubit` نفسه —
|
||||
> فسبب الأداء المرفوض هناك، لا هنا.
|
||||
Reference in New Issue
Block a user