211 lines
15 KiB
Markdown
211 lines
15 KiB
Markdown
# 40 — فجوات واجهة `rider_new` مقابل سيرو، وخطة الإغلاق
|
|
|
|
تاريخ: 2026-08-05 · الحالة: خطة معتمدة للتنفيذ · يمكن تسليمها لنموذج آخر بلا سياق إضافي
|
|
|
|
## 0. لماذا هذه الوثيقة
|
|
|
|
المالك راجع `rider_new` مقابل تطبيقَي سيرو المنشورين (App Store:
|
|
`id6458734951` راكب، `id6502189302` سائق؛ Play: `com.mobileapp.store.ride`)
|
|
وطلب تشخيصاً دقيقاً — لا وعوداً — لثلاث فجوات محدّدة، مع خطة قابلة للتنفيذ
|
|
من أي طرف. هذا فحص كودٍ فعلي على الشجرتين، لا افتراض.
|
|
|
|
## 1. النتيجة أوّلاً: الفجوة الحقيقية أضيق ممّا بدت
|
|
|
|
فحصت `apps/rider_new` مقابل `apps/rider` (سيرو) سطراً بسطر على النقاط
|
|
الثلاث التي ذكرها المالك:
|
|
|
|
| البند | الحالة في `rider_new` | الحكم |
|
|
|---|---|---|
|
|
| ارتفاع الخريطة (70-80%) | [ride_page.dart:87](../apps/rider_new/lib/features/trip/view/ride_page.dart#L87): `bottomInset = height * 0.34` → الخريطة تأخذ فعلياً **~66%** | **قريب من المطلوب أصلاً**، ليس فجوة صفرية |
|
|
| بحث الأماكن كديالوج كامل الشاشة | [planner_sheet.dart](../apps/rider_new/lib/features/trip/view/widgets/planner_sheet.dart) — حقل بحث **داخل** الورقة السفلية المطويّة (`AnimatedSize`) | **فجوة حقيقية** — راجع §2 |
|
|
| الدعوات/الكوبونات/المكافآت | `features/rewards/` كامل: `RewardsCubit`، `ReferralSummary`، `Coupon`، `share_plus` مُستخدَم في [rewards_page.dart](../apps/rider_new/lib/features/rewards/view/rewards_page.dart) و[app_drawer.dart](../apps/rider_new/lib/core/ui/app_drawer.dart) | **موجود ومبني**، ليس فراغاً — قد يحتاج ضبط تفاصيل بصرية فقط |
|
|
| المحفظة | `features/wallet/` كامل: `WalletCubit`، `WalletRepository`، `wallet_page.dart` | **موجود ومبني** |
|
|
|
|
**الخلاصة:** الادّعاء بأن «لا شيء جديد يعمل» غير دقيق مقارنةً بحالة الكود
|
|
الفعلية — رحلاتي/المحفظة/المكافآت مبنية وتستدعي مستودعات حقيقية لا بيانات
|
|
عشوائية. الفجوة القابلة للإشارة إليها بدقة هي بند واحد: **شكل بحث الأماكن**.
|
|
|
|
## 2. الفجوة الوحيدة المؤكَّدة: بحث الأماكن
|
|
|
|
### سيرو (المرجع، `apps/rider/lib/views/home/route_planner/rp_search_dialog.dart`)
|
|
|
|
ديالوج **كامل الشاشة** منفصل تماماً عن الورقة السفلية:
|
|
|
|
```dart
|
|
showGeneralDialog(
|
|
context: context,
|
|
barrierDismissible: true,
|
|
barrierColor: RP.sheet,
|
|
transitionDuration: const Duration(milliseconds: 300),
|
|
pageBuilder: (context, animation, secondaryAnimation) =>
|
|
RpSearchDialog(accent: accent, hintText: hintText, isOrigin: isOrigin),
|
|
transitionBuilder: (context, animation, secondaryAnimation, child) =>
|
|
SlideTransition(
|
|
position: Tween<Offset>(begin: const Offset(0, 1), end: Offset.zero)
|
|
.animate(CurvedAnimation(parent: animation, curve: Curves.easeOutCubic)),
|
|
child: child,
|
|
),
|
|
);
|
|
```
|
|
|
|
يُستدعى بنسختين — `isOrigin: true/false` — بدل حقل واحد يبحث عن الوجهة فقط.
|
|
|
|
### `rider_new` الحالي
|
|
|
|
`planner_sheet.dart` يعرض حقل البحث والنتائج **داخل** `_Sheet` المطوية على
|
|
الخريطة (`AnimatedSize` في [ride_page.dart:206](../apps/rider_new/lib/features/trip/view/ride_page.dart#L206)):
|
|
مساحة أضيق، النتائج تُقصّ عند حدّ الورقة، ولا يوجد تمييز بين البحث عن نقطة
|
|
البداية والوجهة (البحث الحالي يذهب دائماً لـ`PickTarget.destination` —
|
|
انظر `planner_sheet.dart:128`).
|
|
|
|
### خطة الإغلاق (قابلة للتنفيذ مباشرة)
|
|
|
|
1. **`PlaceSearchDialog` جديد** في `features/trip/view/widgets/` — `showGeneralDialog`
|
|
بنفس نمط السحب من الأسفل (300ms، `Curves.easeOutCubic`)، بارامتر
|
|
`PickTarget target` بدل `isOrigin` (النوع الموجود أصلاً في `RideCubit`).
|
|
2. يحوي: `RouteTimeline` (موجود) في الأعلى ثابتاً، ثم `TripzTextField`
|
|
بالبحث المباشر (`cubit.searchPlaces` موجود)، ثم قائمة نتائج بملء الشاشة
|
|
المتبقية — لا `Flexible` داخل `AnimatedSize`.
|
|
3. `_Collapsed._MapFab`/حقل «إلى أين؟» في `ride_page.dart:307` يستدعي
|
|
`PlaceSearchDialog.show(context, target: PickTarget.destination)` بدل
|
|
`cubit.openPlanner` (الذي يبقى للحالات الأخرى — تحديد من الخريطة، إلخ —
|
|
إن وُجدت استدعاءات أخرى له، تُفحص قبل الحذف).
|
|
4. `RouteTimeline.onTapOrigin` (الموجود في `planner_sheet.dart:60`) يفتح نفس
|
|
الديالوج بـ`PickTarget.origin`.
|
|
5. **لا تُنسخ** ملفات `rp_search_dialog.dart` وأخواتها حرفياً (docs/37 §2:
|
|
سيرو مرجع لا مصدر نسخ) — يُعاد بناؤها بـCubit/`TripzTextField`/نظام
|
|
التصميم الحالي، بنفس السلوك المرصود فقط.
|
|
|
|
## 3. ما لم يُثبَت بعد — يحتاج فحصاً بصرياً لا كودياً
|
|
|
|
الفحص أعلاه كودي (قراءة ملفات). البنود التالية **مبنية في الكود** لكن
|
|
مطابقتها البصرية لسيرو لم تُتحقّق على جهاز حقيقي:
|
|
|
|
- شكل بطاقة المكافآت/الكوبون في `rewards_page.dart` — الحقول موجودة
|
|
(`referrals`, `coupons`)، لكن التخطيط والألوان لم تُقارَن سطراً بسطر مع
|
|
سيرو.
|
|
- نص/زر مشاركة الدعوة تحديداً (رابط + كود) — `share_plus` مستورد، لكن نص
|
|
الرسالة المُشارَكة نفسه يحتاج تحقّقاً من محتواه مقابل ما يشاركه سيرو.
|
|
|
|
**لتنفيذ هذين البندين بدقة:** التقط لقطات شاشة من تطبيقَي سيرو المنشورين
|
|
(الروابط أعلاه) وضعها بجانب `rewards_page.dart`/`app_drawer.dart` الحاليين
|
|
— هذا الجزء يحتاج عيناً بشرية أو أداة مقارنة بصرية، لا قراءة كود وحدها.
|
|
|
|
## 3.ب — جرد الدراور مقابل سيرو (فحص كودي، 2026-08-05)
|
|
|
|
مرجع سيرو الحقيقي **ليس** `drawer_menu_page.dart` (ذاك ملف ميّت 25 سطراً
|
|
بلا مدخل واحد)، بل
|
|
`apps/rider/lib/views/home/map_widget.dart/map_menu_widget.dart`.
|
|
|
|
| مدخل سيرو | صفحته في سيرو | الحالة في `rider_new` |
|
|
|---|---|---|
|
|
| Mawasalati | `TransitHomePage` | ❌ منتج منفصل (`backend-transit/`) — خارج نطاق تطبيق الراكب |
|
|
| My Balance | `PassengerWallet` | ✅ `Routes.wallet` → `/wallet`, `/wallet/transactions` موجودتان |
|
|
| Order History | `OrderHistory` | ✅ `Routes.history` → `GET /trips/mine` |
|
|
| Promos | `PromosPassengerPage` | ✅ `Routes.rewards` → `/rewards/referrals`, `/rewards/coupons` |
|
|
| Contact Us | `ContactUsPage` | ✅ `Routes.support` |
|
|
| Complaint | `ComplaintPage` | ❌ **محجوب على الخادم** — لا وحدة `complaints` في `backend-archive/src/modules` |
|
|
| Driver | رابط خارجي | ✅ `BuildConfig.driverAppUrl` |
|
|
| Share App | `ShareAppPage` | ⚠️ عندنا مشاركة مباشرة لا صفحة — فرق شكل لا وظيفة |
|
|
| Privacy Policy | رابط خارجي | ✅ |
|
|
| (صفّ الأيقونات) Profile | `PassengerProfilePage` | ✅ `Routes.profileEdit` |
|
|
| (صفّ الأيقونات) Notification | `NotificationPage` | ✅ **أُنجز 2026-08-05** — انظر أدناه |
|
|
| (صفّ الأيقونات) Setting | `SettingPage` | ✅ `Routes.settings` |
|
|
|
|
**تصحيح لتشخيص أسبق:** لا شيء في الدراور «بيانات ديمو». كل صفحة تستدعي
|
|
نقطة حقيقية موجودة في `backend-archive` (تحقّقت من `wallet.controller.ts`,
|
|
`rewards.controller.ts`, `trips.controller.ts`, `payments/*`). ما يبدو
|
|
فارغاً هو خادمٌ لم تُبذر بياناته، لا واجهةٌ وهمية.
|
|
|
|
### صندوق الإشعارات — أُنجز، بحدٍّ معروف
|
|
|
|
`features/notifications/` + `core/notifications/notification_store.dart`.
|
|
|
|
الخادم يوفّر `POST /notifications/token` فقط — **لا نقطة سرد**
|
|
(`backend-archive/src/modules/notifications/notifications.controller.ts`
|
|
فيه `@Post('token')` وحده). فالصندوق **محلّي**: `PushService` يلتقط
|
|
`onMessage` و`onMessageOpenedApp` و`getInitialMessage` ويحفظها في
|
|
`SharedPreferences` (سقف 50)، والدراور يعرض عدّاد غير المقروء.
|
|
|
|
**الحدّ:** ما يصل والتطبيق مغلق تماماً لا يُلتقط (لا `background handler`
|
|
مسجَّل). حين تُضاف نقطة سرد على الخادم يُستبدل المصدر وحده ويبقى شكل
|
|
الشاشة.
|
|
|
|
### شاشات الدراور الثلاث — أُنجزت 2026-08-05
|
|
|
|
**رحلاتي.** البطاقة كانت ثلاثة عناصر (نقطة · حالة · سعر). والسبب لم يكن
|
|
نقصاً في الخادم بل **قراءةً ناقصة**: `trip.entity.ts` فيه `requested_at`
|
|
(`CreateDateColumn`) و`distance_km` و`duration_min` و`payment_method`
|
|
و`service_class`، وكلها تصل مع `GET /trips/mine` وكان `Trip.fromJson`
|
|
يتجاهلها. صارت تُقرأ وتُعرض: التاريخ والوقت، المسافة والمدة، وشارة طريقة
|
|
الدفع.
|
|
- **العناوين النصّية ما زالت مستحيلة**: الكيان يخزّن `origin_lat/lng` بلا
|
|
`origin_address`. سيرو يعرض عناوين لأن PHP يخزّنها. عرضُ إحداثيات خام
|
|
أسوأ من لا شيء — يحتاج حقلاً على الخادم.
|
|
|
|
**الإعدادات.** كانت قائمة مسطّحة (مظهر + لغة). صارت بأقسام كسيرو
|
|
(`setting_page.dart`: General · Support & Info)، وأُضيف: مدخل المساعدة،
|
|
مشاركة التطبيق، سياسة الخصوصية، **ورقم الإصدار** (مفتاح `aboutVersion` كان
|
|
موجوداً بلا شاشة تستعمله).
|
|
- **باگ مُصلَح:** تبديل اللغة كان **محلياً فقط**. الخادم يصوغ الإشعارات
|
|
بلغة المستخدم المخزّنة عنده و`PATCH /users/me` يقبل `language`
|
|
(docs/38 §3) — فكانت الواجهة عربية والإشعارات إنجليزية. صار التبديل
|
|
يُزامَن، وفشلُ الشبكة لا يمنع التبديل المحلي.
|
|
|
|
**حسابي.** أُضيفت ترويسة (حرف الاسم · الاسم · الرقم · **التقييم**).
|
|
`AppUser.rating` كان يصل من `GET /users/me` ولا تعرضه أي شاشة، بينما الراكب
|
|
يُقيَّم من السائق ويحقّ له رؤية نتيجته.
|
|
- **لا صورة شخصية**: الخادم لا يوفّر رفع صور (سيرو يفعل عبر PHP). وحقول
|
|
سيرو الأخرى (الجنس، التعليم، الحالة الاجتماعية، هاتف الطوارئ) غير ممكنة:
|
|
`PATCH /users/me` يقبل `{name, language}` حصراً.
|
|
|
|
الثلاث مُطبَّقة على `driver_new` أيضاً (كانت متطابقة بايتاً ببايت مع
|
|
`rider_new` قبل التعديل، فالنسخ آمن).
|
|
|
|
## 3.ج — تطبيق السائق: ما أُصلح قبل التجربة الحقيقية (2026-08-05)
|
|
|
|
**السائق كان أعمى أثناء الرحلة.** `duty_page.dart` كان يمرّر
|
|
`RideMap(origin: me, cameraTarget: me)` فقط — بلا نقطة الراكب، بلا الوجهة،
|
|
بلا خطّ يقوده. تجربةٌ حقيقية بهذه الشاشة مستحيلة.
|
|
- `DutyState` أخذ `navRoute` و`navTarget`؛ والهدف تشتقّه **آلة الحالات**:
|
|
نقطة الراكب في `assigned/driver_arriving/driver_arrived`، ووجهته في
|
|
`in_progress`.
|
|
- `DutyCubit` صار يحسب المسار من موقع السائق إلى الهدف، بنفس حدّ الـ50 متراً
|
|
المستعمل في تطبيق الراكب (يلتقط الانعطاف، يتجاهل رجفة GPS)، ويعيد الحساب
|
|
فوراً عند كل انتقال حالة بدل انتظار نبضة الموقع.
|
|
- الخريطة صارت تعرض النقطتين والمسار، ومرفوعة بالثلث كخريطة الراكب.
|
|
- شريط الوقت والمسافة (`NavEtaStrip`) في ورقة الرحلة.
|
|
|
|
**باگان مُصلَحان:**
|
|
1. لافتة نفاد الرصيد كانت تعرض `l10n.errorGeneric` («حدث خطأ غير متوقّع»)
|
|
بدل `l10n.creditBlocked` («رصيدك التشغيلي نفد — اشحنه لتستقبل الطلبات»).
|
|
السائق المحجوب كان يرى خطأً عاماً لا سبباً.
|
|
2. زرّ المحادثة كان `'\${Routes.chat}?trip=\\\${trip.id}'` — الهروب الزائد
|
|
يجعل الناتج نصّاً حرفياً `?trip=\${trip.id}`، فالمحادثة تُفتح بمعرّف رحلة
|
|
غير موجود ولا تحمّل رسالة واحدة أبداً. (نفس الخطأ كان في تطبيق الراكب
|
|
وزال حين استُبدل الزرّ بـ`TripContactRow`.)
|
|
|
|
**محجوب على الخادم:** لا اسم الراكب ولا رقمه يصلان السائق — لا
|
|
`rider_name`/`rider_phone` في أي وحدة من `backend-archive/src/modules`.
|
|
فزرّ «اتصل بالراكب» غير ممكن؛ المحادثة داخل التطبيق هي القناة الوحيدة.
|
|
|
|
### Complaint — محجوب، ويحتاج قراراً
|
|
|
|
بناؤه يتطلّب وحدة `complaints` في NestJS (كيان + `POST /complaints` +
|
|
`GET /complaints/mine`). هذا شغل باك إند يُبنى ويُختبر على الخادم
|
|
(`docs/15`), لا على الماك. لم أبنِ واجهةً لنقطة غير موجودة.
|
|
|
|
## 4. الأولوية والترتيب
|
|
|
|
1. ~~`PlaceSearchDialog` (§2)~~ — **أُنجز 2026-08-05**.
|
|
2. ~~رفع الخريطة عن القاع بالثلث~~ — **أُنجز**: `_sheetFraction = 0.33` في
|
|
`ride_page.dart`. الخريطة والدبّوس في `Positioned` واحدة (وإلا أشار
|
|
الدبّوس إلى غير النقطة المؤكَّدة)، والورقة لها `minHeight` بنفس النسبة.
|
|
3. ~~صندوق الإشعارات~~ — **أُنجز** (§3.ب).
|
|
4. `ComplaintPage` — **محجوب**: يحتاج وحدة `complaints` في NestJS أولاً.
|
|
5. مطابقة بصرية للمكافآت/المشاركة (§3) — تحتاج لقطات سيرو أولاً.
|
|
6. تكرار ما ينطبق على `driver_new` — فُحص: لا `PlannerSheet` ولا بحث أماكن
|
|
فيه، فلا شيء يُكرَّر من §2.
|
|
|
|
Related: [[docs/37]] (قاعدة سيرو مرجع لا مصدر نسخ) · [[docs/39]] (شاشات سيرو المرصودة)
|