Update codebase
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
# 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]] (شاشات سيرو المرصودة)
|
||||
Reference in New Issue
Block a user