15 KiB
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: bottomInset = height * 0.34 → الخريطة تأخذ فعلياً ~66% |
قريب من المطلوب أصلاً، ليس فجوة صفرية |
| بحث الأماكن كديالوج كامل الشاشة | planner_sheet.dart — حقل بحث داخل الورقة السفلية المطويّة (AnimatedSize) |
فجوة حقيقية — راجع §2 |
| الدعوات/الكوبونات/المكافآت | features/rewards/ كامل: RewardsCubit، ReferralSummary، Coupon، share_plus مُستخدَم في rewards_page.dart وapp_drawer.dart |
موجود ومبني، ليس فراغاً — قد يحتاج ضبط تفاصيل بصرية فقط |
| المحفظة | features/wallet/ كامل: WalletCubit، WalletRepository، wallet_page.dart |
موجود ومبني |
الخلاصة: الادّعاء بأن «لا شيء جديد يعمل» غير دقيق مقارنةً بحالة الكود الفعلية — رحلاتي/المحفظة/المكافآت مبنية وتستدعي مستودعات حقيقية لا بيانات عشوائية. الفجوة القابلة للإشارة إليها بدقة هي بند واحد: شكل بحث الأماكن.
2. الفجوة الوحيدة المؤكَّدة: بحث الأماكن
سيرو (المرجع، apps/rider/lib/views/home/route_planner/rp_search_dialog.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):
مساحة أضيق، النتائج تُقصّ عند حدّ الورقة، ولا يوجد تمييز بين البحث عن نقطة
البداية والوجهة (البحث الحالي يذهب دائماً لـPickTarget.destination —
انظر planner_sheet.dart:128).
خطة الإغلاق (قابلة للتنفيذ مباشرة)
PlaceSearchDialogجديد فيfeatures/trip/view/widgets/—showGeneralDialogبنفس نمط السحب من الأسفل (300ms،Curves.easeOutCubic)، بارامترPickTarget targetبدلisOrigin(النوع الموجود أصلاً فيRideCubit).- يحوي:
RouteTimeline(موجود) في الأعلى ثابتاً، ثمTripzTextFieldبالبحث المباشر (cubit.searchPlacesموجود)، ثم قائمة نتائج بملء الشاشة المتبقية — لاFlexibleداخلAnimatedSize. _Collapsed._MapFab/حقل «إلى أين؟» فيride_page.dart:307يستدعيPlaceSearchDialog.show(context, target: PickTarget.destination)بدلcubit.openPlanner(الذي يبقى للحالات الأخرى — تحديد من الخريطة، إلخ — إن وُجدت استدعاءات أخرى له، تُفحص قبل الحذف).RouteTimeline.onTapOrigin(الموجود فيplanner_sheet.dart:60) يفتح نفس الديالوج بـPickTarget.origin.- لا تُنسخ ملفات
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) في ورقة الرحلة.
باگان مُصلَحان:
- لافتة نفاد الرصيد كانت تعرض
l10n.errorGeneric(«حدث خطأ غير متوقّع») بدلl10n.creditBlocked(«رصيدك التشغيلي نفد — اشحنه لتستقبل الطلبات»). السائق المحجوب كان يرى خطأً عاماً لا سبباً. - زرّ المحادثة كان
'\${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. الأولوية والترتيب
— أُنجز 2026-08-05.PlaceSearchDialog(§2)رفع الخريطة عن القاع بالثلث— أُنجز:_sheetFraction = 0.33فيride_page.dart. الخريطة والدبّوس فيPositionedواحدة (وإلا أشار الدبّوس إلى غير النقطة المؤكَّدة)، والورقة لهاminHeightبنفس النسبة.صندوق الإشعارات— أُنجز (§3.ب).ComplaintPage— محجوب: يحتاج وحدةcomplaintsفي NestJS أولاً.- مطابقة بصرية للمكافآت/المشاركة (§3) — تحتاج لقطات سيرو أولاً.
- تكرار ما ينطبق على
driver_new— فُحص: لاPlannerSheetولا بحث أماكن فيه، فلا شيء يُكرَّر من §2.
Related: docs/37 (قاعدة سيرو مرجع لا مصدر نسخ) · docs/39 (شاشات سيرو المرصودة)