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. اقرأ قبل أول سطر كود
docs/23-flutter-conventions.md— قانون الكود، و**docs/26-flutter-design-system.md** — قانون الشكل. كلاهما مُلزم حرفياً.docs/28-flutter-build-prompt.md— القيود الصلبة والممنوعات (§2 و§7 تنطبق هنا بحذافيرها).- الموجود فعلاً في
apps/rider/lib/core/— tokens · typography · TripzColors · buildTheme · TripzScaffold · عدةcore/ui/· ARB ·ApiClient·BuildConfig. تبني فوقه ولا تعيد اختراعه؛ إن وجدت نقصاً فيه تكمله في مكانه. - حزمة الهاتف الدولية في سيرو:
~/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 أدناه، وكلها مقروءة من الكود لا من الذاكرة.
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). بلا شبكة تُنتظر لأجل الشكل.
- وظيفتها توجيه لا استعراض — تقرّر الوجهة بهذا الترتيب:
- لا توكن → أونبوردنج (أول تشغيل فقط) ثم الهاتف.
- توكن +
GET /trips/rating/pendingفيه رحلة → شاشة التقييم الإجبارية. - توكن +
GET /trips/mineفيه رحلة نشطة → التتبّع. - غير ذلك → الهوم.
- نداءات 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→…→الوجهة) عبر
GET /maps/routeبالتوازي (Future.wait) — كل ضلع يستفيد من كاش Redis المستقل لزوج نقاطه. - فك الـ polyline في
compute(): الاستجابة تحملgeometryمشفّراً (§5-ج) — يُفكّ في isolate إلىList<LatLng>جاهزة، ولا يلمس الـ main isolate إلا القائمة النهائية. - الرسم:
Polylineواحد متصل من أضلاع متسلسلة بلون primary وسماكة من tokens، وماركرات: نقطة بداية، مربعات توقف مرقّمة، دبوس وجهة. - الكاميرا على الـ bounds: تُؤخذ من استجابة الـ API إن وُجدت، وإلا تُحسب من نقاط الـ polyline المفكوكة — ثم
CameraUpdateبحواف آمنة (padding يراعي الـ sheet السفلي كي لا يغطي المسار) وحركةeaseOutCubicمن tokens. - الرجوع الآمن: إن رجع السيرفر
provider: 'straight-line'(انطلق ساقط) — خط متقطّع مستقيم بين النقاط + المسافة/الزمن التقديريان، والتجربة تكمل ولا تنكسر. - من هنا: الفئات والسعر (
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