Files
tripz-llc/docs/29-rider-phase1-prompt.md
T

17 KiB

29 — أمر بناء المرحلة الأولى لتطبيق الراكب (يُسلَّم لنموذج البناء كما هو)

طريقة الاستعمال: افتح جلسة جديدة لنموذج البناء وجذر عمله ~/development/App/Tripz، وألصق كل ما تحت الخط. هذا المستند يفصّل المرحلة 1 من 28-flutter-build-prompt ويضيف إليها سبلاش/أونبوردنج/تسجيل الاسم من Q1 — و28 يبقى الحاكم العام: أي شيء غير مذكور هنا (القيود، الممنوعات، القناة المزدوجة، بقية الشاشات) يُؤخذ من 28 كما هو. كُتب 2026-07-19 بإملاء المالك.


أنت مكلَّف ببناء سلسلة الطلب الكاملة في تطبيق الراكب apps/rider حتى حالة searching:

سبلاش → أونبوردنج → الهاتف → OTP → الاسم → الهوم (الخريطة) → الوجهة والتوقفات → معاينة المسار (polyline) → الفئات والسعر → إنشاء الطلب.

شاشات auth/home/destination/route-preview قائمة اليوم في الكود لكنها تُعاد صياغتها بالمواصفة أدناه — لا تُرقَّع. شاشة الفئات والسعر بمواصفة 28 §4 كما هي ولا تُعاد هنا.

الهوية المطلوبة: تصميمنا نحن — سهل، سلس، نظيف، مقروء بيد واحدة. لا نسخ حرفي من أي تطبيق قائم؛ DiDi يبقى المرجع الذهني للنظافة فقط (28 §4).

1. اقرأ قبل أول سطر كود

  1. docs/23-flutter-conventions.md — قانون الكود، و**docs/26-flutter-design-system.md** — قانون الشكل. كلاهما مُلزم حرفياً.
  2. docs/28-flutter-build-prompt.md — القيود الصلبة والممنوعات (§2 و§7 تنطبق هنا بحذافيرها).
  3. الموجود فعلاً في apps/rider/lib/core/ — tokens · typography · TripzColors · buildTheme · TripzScaffold · عدة core/ui/ · ARB · ApiClient · BuildConfig. تبني فوقه ولا تعيد اختراعه؛ إن وجدت نقصاً فيه تكمله في مكانه.
  4. حزمة الهاتف الدولية في سيرو: ~/development/App/Siro/siro_rider/lib/controller/local/phone_intel/ — خمسة ملفات (countries.dart · intl_phone_field.dart · country_picker_dialog.dart · phone_number.dart · helpers.dart). انظر §3.3.
  5. عقود الباك إند الحقيقية — §5 أدناه، وكلها مقروءة من الكود لا من الذاكرة.

2. قوانين هذه المرحلة (فوق قوانين 28)

  • الألوان: البذرة متغيّرة حسب المستأجر (BuildConfig.primaryColorValue)، لكن سكريبت الألوان ثابت: كل لون يمرّ حصراً عبر ColorScheme.fromSeed أو context.tripzColors أو tokens. لا hex في أي شاشة — ولا في السبلاش والأونبوردنج.
  • بدون توليد صور: لا صور نقطية جديدة ولا أصول مولّدة. السبلاش والأونبوردنج والحالات الفارغة تُبنى من لوغو المستأجر القائم + أيقونات Material Symbols + رسم متجهي CustomPaint + حركة — فتتلوّن آلياً مع بذرة كل مستأجر وثيمه.
  • كل شغل ثقيل خارج الـ main isolate: فك الـ polyline، وأي parsing لقائمة كبيرة — عبر compute(). قطعة الخريطة لا يجوز أن تُسقط إطاراً واحداً أثناء الرسم.
  • استثناء مالك صريح (2026-07-19): نسخ حزمة phone_intel من سيرو مسموح — هي أصلاً حزمة intl_phone_field (MIT) مُضمّنة داخل سيرو وليست كود سيرو نفسه. قاعدة «لا نسخ من سيرو» في 28 §7 تبقى قائمة لكل ما عداها.
  • كل شاشة داخل TripzScaffold، كل نص عبر ARB، كل شيء يعمل عربي/إنجليزي × ليلي/نهاري (قائمتا تحقق 23 §12 و26 §9 شرط تسليم كل شاشة).

3. الشاشات — المواصفة التفصيلية

3.1 السبلاش (Splash)

  • لوغو المستأجر في المنتصف على خلفية من الثيم (لا لون مثبّت)، حركة دخول واحدة هادئة (fade + scale ضمن أزمنة tokens). بلا شبكة تُنتظر لأجل الشكل.
  • وظيفتها توجيه لا استعراض — تقرّر الوجهة بهذا الترتيب:
    1. لا توكن → أونبوردنج (أول تشغيل فقط) ثم الهاتف.
    2. توكن + GET /trips/rating/pending فيه رحلة → شاشة التقييم الإجبارية.
    3. توكن + GET /trips/mine فيه رحلة نشطة → التتبّع.
    4. غير ذلك → الهوم.
  • نداءات 2 و3 لا تحجب أكثر من مهلة قصيرة (~2ث) — عند الفشل يمضي للهوم ويصالح لاحقاً (منطق 28 §3).

3.2 الأونبوردنج (Onboarding)

  • 3 شرائح كحد أقصى، تُعرض مرة واحدة في العمر (علم في SharedPreferences)، وزر «تخطّي» ظاهر دائماً.
  • كل شريحة: رسم متجهي متحرك بسيط (CustomPaint/أيقونات تتلوّن من الثيم) + عنوان + سطر واحد. المقترح: (1) اطلب بلمسة (2) تتبّع سائقك حياً (3) ادفع كما يناسبك — والنص النهائي عبر ARB باللغتين.
  • مؤشر صفحات + زر أساسي واحد أسفل («التالي» / «ابدأ»). سحب أفقي يعمل بالاتجاهين حسب RTL/LTR.

3.3 الهاتف (تسجيل الدخول)

  • انسخ حزمة phone_intel الخمسة ملفات إلى apps/rider/lib/core/ui/phone_intel/ مع تنظيفها:
    • احذف import 'package:siro_rider/print.dart' وأي أثر له.
    • أعد ربط الستايل بالكامل إلى tokens/الثيم (الحزمة الأصلية فيها ستايل يدوي).
    • countries.dart يبقى كما هو — كل الدول برموزها وأطوال أرقامها وأعلامها.
  • الدولة الافتراضية من BuildConfig.defaultCountry (انظر §5-ب)، والبحث في قائمة الدول يعمل بالعربية والإنجليزية.
  • الشاشة: عنوان ودود واحد + حقل الهاتف (بارتفاع 52 من tokens) + زر «أرسل الرمز» — لا شيء آخر. التحقق من طول الرقم محلياً من countries.dart قبل تفعيل الزر.
  • POST /auth/send-otp ثم الانتقال لشاشة OTP مع تمرير الرقم كاملاً بصيغة دولية.

3.4 رمز التحقق (OTP)

  • 4 خانات منفصلة كبيرة (أرقام Inter tabular)، لصق تلقائي من الرسائل حين يتيحه النظام، إرسال تلقائي عند اكتمال الخانات.
  • عدّاد إعادة إرسال (60ث) بزر يتفعّل بعد انتهائه، ورقم الهاتف معروضاً مع زر «تعديل» يرجع خطوة.
  • POST /auth/verify-otp → التوكنات إلى flutter_secure_storage عبر الطبقة القائمة.

3.5 الاسم (أول دخول فقط)

  • إن كان المستخدم الراجع من verify-otp بلا اسم → شاشة واحدة: «ما اسمك؟» + حقل واحد + زر واحد. PATCH /users/me {name}. من له اسم لا يراها أبداً.

3.6 الهوم

  • الخريطة تملأ الشاشة حتى الحافة العلوية (edge-to-edge خلف شريط الحالة)، وكل العناصر العائمة داخل SafeArea. دبوس/نقطة نابضة على موقعي الحالي، وزر إعادة توسيط عائم.
  • ستايل الخريطة يتبدّل مع الثيم (ليلي/نهاري) — بلاطات انطلق مباشرة عبر intaleq_maps حصراً.
  • الـ sheet السفلي (من TripzSheet) في وضعه المضغوط:
    • حقل «إلى أين؟» كبير واضح — لمسه يفتح شاشة الوجهة.
    • تحته الأماكن المقترحة: صف chips/قائمة قصيرة من مخزن الأماكن المحلي (§4) — الأكثر ذهاباً أولاً ثم الأحدث. لمسة واحدة على مكان مقترح = وجهة محدّدة مباشرة (يقفز لمعاينة المسار).
    • المفضلة المثبّتة (البيت 🏠 / العمل 💼) تظهر أولاً دائماً إن وُجدت.
  • تحية باسم المستخدم حسب وقت اليوم (صباح الخير يا فلان) — سطر واحد فوق الحقل، عبر ARB.

3.7 اختيار الوجهة والتوقفات — الـ sheet القابل للسحب

هذه الشاشة قلب التجربة، تُبنى بعناية فائقة:

  • DraggableScrollableSheet فوق الخريطة بثلاث درجات snap:
    • مضغوط (سفلي): بيانات مكثّفة — سطر البداية وسطر الوجهة فقط.
    • متوسط: تظهر الأماكن المحفوظة والمقترحة.
    • ممتد (كامل): يتوسّع البحث — حقل نشط + كيبورد + نتائج الأوتوكومبليت تملأ المساحة. البيانات تتوسّع وتتعبّأ كلما ارتفع الـ sheet وتنضغط كلما نزل.
  • حقول النقاط: البداية (معبّأة من موقعي مع reverse geocode) ثم التوقفات ثم الوجهة، بخط عمودي يربطها بصرياً (نقطة خضراء → مربعات توقف → دبوس وجهة بألوان tripzColors).
  • التوقفات (stops): زر «+» يضيف توقفاً (حد أقصى منطقي، مثلاً 3)، كل توقف عليه نفس البحث الكامل، حذف بالسحب أو ×، وإعادة ترتيب بالسحب الطويل.
  • البحث (لكل نقطة — بداية وتوقف ووجهة):
    • أوتوكومبليت عبر GET /maps/geocode بـ debounce ~300ms وحد أدنى حرفين، مع تحيّز الموقع (lat/lng الحاليان) وcountry من BuildConfig.defaultCountry — لا 'JO' مثبّتة (الموجودة اليوم في maps_repository.dart خطأ يُصحَّح).
    • إلغاء النداء السابق عند كل حرف (CancelToken) — لا سباق نتائج.
    • النتائج المحلية من مخزن §4 تظهر فوراً فوق نتائج الشبكة وهي قادمة (بحث محلي في الاسم/العنوان).
  • الاختيار من الخريطة: خيار «اختر من الخريطة» لأي نقطة — الـ sheet ينزل للوضع المضغوط، دبوس ثابت في مركز الشاشة والخريطة تتحرك تحته، reverse geocode عند سكون الكاميرا (~500ms) يعبّئ العنوان، وزر تأكيد سفلي.
  • كل مكان يُختار (من البحث أو الخريطة) يُحفظ آلياً في مخزن §4 — «أي مكان بحثنا عنه لا يُبحث عنه مرة ثانية».

3.8 معاينة المسار — polyline والكاميرا

عند اكتمال النقاط:

  1. طلب المسار لكل ضلع (البداية→توقف1→…→الوجهة) عبر GET /maps/route بالتوازي (Future.wait) — كل ضلع يستفيد من كاش Redis المستقل لزوج نقاطه.
  2. فك الـ polyline في compute(): الاستجابة تحمل geometry مشفّراً (§5-ج) — يُفكّ في isolate إلى List<LatLng> جاهزة، ولا يلمس الـ main isolate إلا القائمة النهائية.
  3. الرسم: Polyline واحد متصل من أضلاع متسلسلة بلون primary وسماكة من tokens، وماركرات: نقطة بداية، مربعات توقف مرقّمة، دبوس وجهة.
  4. الكاميرا على الـ bounds: تُؤخذ من استجابة الـ API إن وُجدت، وإلا تُحسب من نقاط الـ polyline المفكوكة — ثم CameraUpdate بحواف آمنة (padding يراعي الـ sheet السفلي كي لا يغطي المسار) وحركة easeOutCubic من tokens.
  5. الرجوع الآمن: إن رجع السيرفر provider: 'straight-line' (انطلق ساقط) — خط متقطّع مستقيم بين النقاط + المسافة/الزمن التقديريان، والتجربة تكمل ولا تنكسر.
  6. من هنا: الفئات والسعر (POST /tariff/quote) بمواصفة 28 §4، وإنشاء الرحلة بالتوقفات stops: [{lat,lng,label}] (يدعمها السيرفر — trips.service.ts) حتى searching.

4. مخزن الأماكن المحلي — SavedPlacesStore (عقد كامل)

core/storage/saved_places_store.dart فوق SharedPreferences (ليست سراً — لا تذهب لـ secure storage):

  • البنية: قائمة JSON واحدة، كل عنصر: {name, address, lat, lng, useCount, lastUsedAt, favoriteType?} حيث favoriteType ∈ {home, work, null}.
  • الإضافة: كل مكان اختير فعلاً (وجهة أو توقفاً أو بداية يدوية). إزالة التكرار بتقريب الإحداثيات إلى 4 منازل — التكرار يزيد useCount ويحدّث lastUsedAt بدل إنشاء سجل.
  • الترتيب عند العرض: المفضلة (home ثم work) أولاً دائماً → ثم البقية بالنقاط: useCount أعلى أولاً، والتعادل يُحسم بـ lastUsedAt الأحدث.
  • السقف: 50 عنصراً — عند التجاوز يُحذف الأدنى نقاطاً غير المفضل.
  • تعيين المفضلة: ضغطة طويلة على أي مكان محفوظ → «تعيين كالبيت/العمل»، وقابلة للتغيير من الإعدادات لاحقاً.
  • Cubit خاص بها (SavedPlacesCubit) تستهلكه الهوم وشاشة الوجهة — لا قراءة SharedPreferences من داخل widget.

5. عقود الباك إند + متطلبان مسبقان

(أ) القائم اليوم — استعمله كما هو:

النداء ملاحظة
POST /auth/send-otp · POST /auth/verify-otp · POST /auth/refresh OTP التطوير 1234
PATCH /users/me {name, language} · GET /users/me تسجيل الاسم
GET /maps/geocode?q&country&lat&lng&radius التحيّز اختياري؛ يُرسل دائماً من موقعي
GET /maps/reverse?lat&lng للاختيار من الخريطة
GET /maps/route?fromLat&fromLng&toLat&toLng مكيّش Redis لكل زوج نقاط
GET /trips/rating/pending · GET /trips/mine قرار السبلاش
POST /tariff/quote ثم إنشاء الرحلة مع stops مواصفة 28

(ب) متطلب مسبق 1 — BuildConfig.defaultCountry: لا حقل دولة في BuildConfig اليوم، وهو سبب تثبيت 'JO' الخاطئ في maps_repository.dart. يُضاف static const String defaultCountry = 'JO'; (مع تحديث قالب N3 المولِّد لاحقاً) ويُستعمل في كل نداءات الخرائط والدولة الافتراضية لحقل الهاتف.

(ج) متطلب مسبق 2 — geometry وbounds في /maps/route: الخدمة اليوم (backend/src/modules/maps/maps.service.ts — parseRoute) تستخرج المسافة/الزمن فقط وترمي الـ geometry. يجب تمرير geometry (الـ polyline المشفّر) وbounds من استجابة انطلق عند وجودهما، وتخزينهما في نفس كاش Redis. رجوع straight-line يبقى بلا geometry — والتطبيق يتصرف (§3.8-5). هذا التعديل الوحيد المسموح في الباك إند ضمن هذه المرحلة، والمالك ينشره بنفسه (لا بناء ولا اختبار على الجهاز).

6. مساحة الإبداع — مطلوبة لا مسموحة فقط

ضمن قوانين 26 حصراً، يُنتظر منك مستوى ملموس في: حركة الـ sheet بين درجاته الثلاث، خروج ودخول الماركرات، نبض نقطة موقعي، انسياب الكاميرا بين الاختيار والمعاينة، ولحظة اكتمال رسم المسار (تُحَسّ لا تُقرأ). وإن رأيت فكرة تجربة أبسط وأسلس من المواصفة أعلاه في موضع ما — اعرضها على المالك قبل تنفيذها، فالمواصفة سقف وضوح لا سقف إبداع.

7. التسليم

  • توقّف عند اكتمال السلسلة وسلّم تقريراً: الشاشات، الملفات، القرارات، وما يحتاج قرار مالك.
  • شرط القبول: السلسلة كاملة من سبلاش إلى searching بلا سائق، باللغتين وبالثيمين، وقائمتا 23 §12 و26 §9 لكل شاشة — والمالك يبني release ويجرّب على جهاز حقيقي؛ رتّب كودك ليعمل من أول بناء.

← ذو صلة: 28-flutter-build-prompt · 26-flutter-design-system · 23-flutter-conventions