feat: implement rewards module with referral/coupon systems, update tariff seeding, and add trip distance service for improved billing accuracy.

This commit is contained in:
Hamza-Ayed
2026-07-19 00:20:37 +03:00
parent 22d18f32fe
commit 92f1eeb48c
39 changed files with 3612 additions and 208 deletions
+61
View File
@@ -277,6 +277,67 @@
---
## المجموعة L — الإحالات والمكافآت والكوبونات — ✅ منفَّذة
> قرار المالك 2026-07-18. **قانونان ثابتان يحكمان المجموعة كلها:**
> 1. **محفظة الراكب لا تُسحب أبداً** — فمكافأة الراكب **كوبون خصم** لا رصيد محفظة. إنزالها في المحفظة كان يخلق التزاماً نقدياً قابلاً للسحب مقابل حملة تسويقية.
> 2. **كل خصم مسقوف بعملته** — 25 جنيهاً في مصر · 25 ليرة سورية · 0.25 دينار (نفس نسبة `credit-policy`). السقف **حدّ أعلى مطلق** لا افتراض.
| # | البند | الحالة |
|---|-------|--------|
| L1 | `referral_codes` (كود شخصي ثابت) + `referrals` بحالات `pending → qualified → paid` | ✅ |
| L2 | نوع قيد `referral_bonus` في `credit_txns` + `grantReferralBonus` بحارسَي J6 (فحص تطبيقي + فهرس فريد جزئي على `ref`) | ✅ |
| L3 | محرّك الكوبونات: `coupons` + `coupon_redemptions`. يخصم من `price_for_passenger` وحده — `price_for_driver` يبقى كاملاً (docs/18 §52) | ✅ |
| L4 | **شرط الاستحقاق**: بعد أول رحلة مكتملة للمُحال لا عند التسجيل. `qualifying_trips` لا ينزل تحت 1 ولو ضبطه الأدمن صفراً | ✅ |
| L5 | `RewardsSweeper` — نمط `ScheduledTripsSweeper` (setInterval + قفل `running`). **شبكة أمان لا مسار أساسي**: الصرف يقع لحظة التأهّل | ✅ |
| L6 | `tenants.settings.rewards` بنفس بنية `credit-policy` — سقف الخصم ومكافآت الطرفين لكل عملة | ✅ |
| L7 | ربط `fraud.service` — كشف حلقات الإحالة والأجهزة المكرّرة | ⏳ |
| L8 | باقات الشحن (J5) فوق نفس المحرّك | ⏳ |
**اتجاهات الإحالة الأربعة ليست أربعة مسارات**: شكل المكافأة يتبع **دور المستفيد** لا اتجاه الإحالة — سائق ← رصيد تشغيلي، راكب ← كوبون. والدور يُحسم **وقت الصرف** من وجود صفّ سائق لا من الدور المخزَّن وقت التسجيل، فمن سجّل بكود دعوة ثم تقدّم سائقاً يستحقّ رصيداً لا كوبوناً.
**حرّاس الاحتيال (على القاعدة لا بالفحص التطبيقي وحده)**:
- فريد على `(tenant_id, referee_user_id)` — الشخص يُحال **مرة واحدة** في عمره. بدونه تُعاد نفس الضحية تحت عشرة داعين.
- `CHECK (referrer <> referee)` — إحالة النفس.
- فريد جزئي على `credit_txns(tenant_id, ref) WHERE type='referral_bonus'` وعلى `coupons(tenant_id, ref)` — الماسح والصرف الفوري قد يلتقيان على إحالة واحدة.
- فريد على `coupon_redemptions(tenant_id, trip_id)` — كوبون واحد لكل رحلة.
**درس مثبَّت باختبار**: المحاولة الأولى حسبت عتبة التأهّل داخل SQL (`CASE WHEN qualifying_trips + 1 >= n`)، فجُمع العمود **نصّاً** (`'0' + 1 = '01'`) ولم تتحقق الشرطية أبداً — كل إحالة تبقى معلّقة بلا مكافأة، وهو فشل صامت لا يُكتشف إلا بشكوى مستخدم. الحساب صار في TypeScript عبر `RETURNING`، والتزامن محفوظ بشرط الحالة في كلتا العبارتين.
**العمولة تُحتسب قبل خصم الكوبون** عمداً: الكوبون تنازل تسويقي من المنصة، فاحتسابها بعده كان يجعل المنصة تدفع الخصم مرتين — مرة للراكب ومرة بعمولة أنقص من السائق.
---
## المجموعة M — أساس التسعير: تعرفة الانطلاق والمسافة الحقيقية — ✅ منفَّذة
> قرار المالك 2026-07-19. **الحدّ الأدنى للرحلة: 1.10 دينار · 20 جنيهاً · 150 ليرة** — وهذه المرساة التي تُشتقّ منها كل الأرقام الأخرى.
| # | البند | الحالة |
|---|-------|--------|
| M1 | `default-tariffs.ts` — مصدر حقيقة واحد يستهلكه `SeedService` ومولّد الـSQL معاً | ✅ |
| M2 | `city = 'default'` بدل اسم البلد؛ البلد من `country_pack` للمستأجر | ✅ |
| M3 | نوافذ التعرفة **بالتوقيت المحلي** (`timezone` في التعريف) عبر `Intl` — لا UTC ولا توقيت الخادم | ✅ |
| M4 | فتحة العدّاد (`flag`) حقيقية في كل نافذة، ورسم حجز (`booking_fee`) | ✅ |
| M5 | `fixed_quote` منفَّذ فعلاً — لا مسافة ولا زمن ولا حدّ أدنى؛ الانتظار وحده يُضاف | ✅ |
| M6 | الفئات مشتقّة بمعاملات: أوفر < اقتصادي < كهربائي < ليدي < مريح < فان < VIP | ✅ |
| M7 | `TripDistanceService` — المسافة من نبضات GPS مع تنقية الضجيج والقفزات | ✅ |
| M8 | إعادة التسعير عند الإنهاء على المسافة المقطوعة فعلاً، بسقف `routed × 1.25` | ✅ |
| M9 | زرع التعرفة **لكل فئة خدمة** لا الاقتصادي وحده | ✅ |
| M10 | سماحية المسافة لكل مستأجر + ربط الانحراف بـ`fraud.service` | ⏳ |
**الأعطال التي أُصلحت (كلها كانت فشلاً صامتاً)**:
- **رحلة مجانية**: السكربت القديم كتب `city='jordan'`، والكود يبحث بـ`'default'` → لا تعرفة → `quotedFare = null` → `settleFare` تأخذ `quoted_fare ?? 0` → **أجرة صفر وعمولة صفر**، بسطر تحذير واحد في اللوج.
- **ذروة لا تُحصَّل أبداً**: `pickWindow` قرأ `getUTCHours()`، فذروة 16:00–20:00 تقع 19:00–23:00 في عمّان.
- **الفان أرخص من الاقتصادي** في مصر وسوريا (22 مقابل 29.5 جنيهاً لنفس الرحلة) — أي أن الخيار العقلاني لكل راكب هو أغلى فئة على المشغّل.
- **حدّ أدنى ثلث الحقيقي**: `perKm × 2` = 0.368 ديناراً بدل 1.10.
- **`fixed_quote` لم يكن ثابتاً**: كان يسقط للفرع المتري فيُحسب بالعدّاد رغم اسمه.
- **السكربت لم يعمل أصلاً**: `require` في ملف ESM؛ ولا وجود لـ`seed-tariffs.sql` إطلاقاً.
- **تشغيلتان = تعرفتان فعّالتان** بنفس النسخة، و`getActive` ترتّب `version DESC` فالاختيار بينهما غير محدَّد.
**سياسة المسافة (M7/M8)**: يُحاسَب **الأطول** من (المقاس، المقدَّر) بسقف `routed × 1.25`. التحويلة الحقيقية يقبضها السائق، والجولة المخترَعة تُقصّ وتُعلَّم للمراجعة لا تُخصم صامتة. والطريق الأقصر يُحاسَب بالأقصر — الراكب لا يدفع تقديراً لم يُقطع. نبضات أقل من 5 = رجوع للسعر المقفول: رقم مخترَع من الضجيج أسوأ من تقدير معقول.
**تصحيح على المجموعة L**: سقوف الكوبونات كانت مبنية على نسبة `credit-policy` القديمة (1:100:100)، وهي لا تطابق مرساة المالك (1.10 = 20 = 150، أي 1 دينار ≈ 18.2 جنيهاً ≈ 136 ليرة). السقف 25 جنيهاً صار يقابل **1.375 ديناراً و190 ليرة** بدل 0.25 و25 — النسخة الأولى كانت تعطي سوريا سُدس العرض.
---
## مؤجَّل عمداً (قرار المالك)
المفاوض الذكي · تدرّج السائق · خصم العمولة — **آخر شيء** (جديدة حتى على سيرو).
**Geofence** — مؤجَّل («لوقتها»)، موجود في سيرو للاستئناس.
+4
View File
@@ -116,6 +116,9 @@ Shorebird يرقّع Dart فقط. بتوحيد Kotlin/الصلاحيات/NDK ت
| N4 | **لوحة أدمن المستأجر (ويب)**: سائقون (اعتماد) · رحلات · مراجعة وثائق · سحوبات (تحويل/فشل). | ✅ `dashboards/admin-web/index.html` + نقاط سرد إدارية (`/drivers/admin/list` · `/trips/admin/list` · `/payouts/admin/list` خلف RolesGuard admin/dispatcher). التعرفة والتقارير تُضاف مع L. |
| N5 | **لوحة خدمة العملاء (ويب)**: بحث مستخدم برقمه (فهرس أعمى) · رحلاته · تفاصيل رحلة بالمعرّف. | ✅ `dashboards/service-web/index.html` + `GET /admin/users/search` · `/trips/admin/list?rider=`. الشكاوى تحتاج وحدة complaints (لا توجد بعد — بند لاحق). |
| N6 | **كتالوج تسعير الميزات** (قرار المالك 2026-07-19): جدول `feature_catalog` — المفتاح · الاسم العربي · الفئة (نواة/باقة/add-on) · **سعر شهري** · رسم إعداد · الوصف — والسوبر-أدمن يحرّر الأسعار من اللوحة. يوسّع K2: الكتالوج نفسه يغذّي الاستحقاقات الافتراضية والتسعير معاً. الإضافات المسعّرة أولاً: استخبار السوق · البوتات/التسويق · التسعير الديناميكي · المواصلات · المفاوض · شرائح السائقين · تأمين السائق. | ⬜ |
| N7 | **مولّد عرض السعر (الفوترة التعاقدية)**: في لوحة السوبر-أدمن — اختيار باقة + إضافات ⇒ عرض سعر مجمّع (إعداد مرة واحدة + شهري) يُطبع/يُرسل للعميل عند التعاقد. عند الاعتماد: تُكتب `tenants.features` آلياً من البنود المختارة (تكامل K3 — لا إدخال يدوي مزدوج ينسى ميزة) + سجل اشتراك شهري يظهر في overview (N1b). | ⬜ |
**مصادقة اللوحتين**: هاتف + OTP → JWT بدور admin/dispatcher (نفس تدفّق الموبايل). كل الحماية على السيرفر (RolesGuard) — الواجهة عرض فقط. الوصول للوحة معيَّن بـ`?tenant=<slug>`.
**اختبار الحراسة**: E2E يثبت أن السائق/الراكب (غير أدمن) يُرفضان من كل النقاط الإدارية (403).
@@ -156,6 +159,7 @@ Shorebird يرقّع Dart فقط. بتوحيد Kotlin/الصلاحيات/NDK ت
| Q3 | **السائق**: overlay العرض · قبول · widgets الرحلة · **متحكّم الموقع** (تسجيل 3ث/رفع دفعات/واعٍ للبطارية — نمط سيرو) · حيوية الوجه · الرصيد/الشحن. |
| Q4 | **الخرائط المتقدّمة**: خريطة حرارية · خريطة تنبّؤية (من O1). |
| Q5 | **البنية التحتية للتطبيق**: طبقة الشبكة (توقيع HMAC · x-device-id) · country pack ديناميكي · flavors · **طبقتا الإعداد وأعلام `const` حسب §1.5** · موديولات feature-first تُسجَّل حسب العلم. |
| Q6 | **نظام التصميم الثابت — [26-flutter-design-system](26-flutter-design-system.md)** (قرار المالك 2026-07-19): tokens · ثيم ليلي/نهاري من بذرة المستأجر · خطوط ثابتة (Inter + IBM Plex Sans Arabic مضمّنة محلياً) · اتجاه وخط يتبعان اللغة · `TripzScaffold` + عدة مكونات `core/ui/` · ARB للترجمة. **يُبنى قبل شاشات Q1–Q3** — ترتيب البناء في 26 §11. |
---
+5 -3
View File
@@ -3,7 +3,7 @@
> **هذا المستند مُلزِم.** كل كود Dart في `apps/rider` و`apps/driver` يتبعه. أي انحراف يُصحَّح لا يُبرَّر.
> اقرأه كاملاً قبل كتابة أول سطر في أي جلسة جديدة — هو العقد الذي يجعل جلستين مختلفتين تُنتجان نفس البنية.
آخر تحديث: 2026-07-18. ذو صلة: [22 §1.5](22-full-product-roadmap.md) (الطبقات) · [19](19-entitlements-licensing.md) (الاستحقاقات) · [[tripz-mobile-store-identity]].
آخر تحديث: 2026-07-19. ذو صلة: [22 §1.5](22-full-product-roadmap.md) (الطبقات) · [19](19-entitlements-licensing.md) (الاستحقاقات) · **[26](26-flutter-design-system.md) (نظام التصميم الثابت — قانون الشكل المكمّل لهذا القانون)** · [[tripz-mobile-store-identity]].
---
@@ -28,7 +28,9 @@ apps/<rider|driver>/lib/
│ ├── config.dart # إعداد وقت التشغيل (--dart-define)
│ ├── di.dart # get_it: تسجيل التبعيات
│ ├── router.dart # go_router: كل المسارات
│ ├── theme.dart # الثيم من ألوان المستأجر
│ ├── design/ # نظام التصميم (docs/26): tokens · typography · tripz_colors · theme
│ ├── ui/ # عدة المكونات المشتركة (docs/26 §7): TripzScaffold · TripzButton …
│ ├── l10n/ # ملفات ARB عربي/إنجليزي — كل نص مرئي يمرّ هنا
│ ├── api/
│ │ ├── api_client.dart # Dio + الاعتراضات (توكن · مستأجر · توقيع · جهاز)
│ │ └── api_exception.dart
@@ -141,7 +143,7 @@ if (Features.chat) routes.add(GoRoute(path: '/chat', builder: ...));
| `firebase.json` · `firebase_options.dart` · `google-services.json` | مشروع FCM القائم |
| `shorebird.yaml` | **app_id منشور** — تغييره يقطع تحديثات OTA عن المستخدمين الحاليين |
| `key/` | **شهادات توقيع** — فقدانها يعني تعذّر تحديث التطبيق المنشور نهائياً |
| الخطوط (mohanad · josefin · digit) | هوية بصرية قائمة |
| ~~الخطوط (mohanad · josefin · digit)~~ | **نُسخ (2026-07-19)**: الخطوط الجديدة الثابتة Inter + IBM Plex Sans Arabic — القرار وأسبابه في [26 §1](26-flutter-design-system.md). الملفات القديمة تبقى حتى يؤكد المالك حذفها |
**معرّفات المتجر** في [[tripz-mobile-store-identity]] — تُنسخ ولا تُكتب من الذاكرة.
⚠️ معرّفا السائق متضاربان بين المنصّتين (`com.sefer_driver` مقابل `com.sefer.driver`) — **يؤكّدهما المالك قبل أي إصدار**.
+146
View File
@@ -0,0 +1,146 @@
# 26 — نظام التصميم الثابت (الهوية البصرية لتطبيقات فلاتر)
> **هذا المستند مُلزِم** ويكمّل [23-flutter-conventions](23-flutter-conventions.md): ذاك قانون *الكود*، وهذا قانون *الشكل*.
> الهدف: كل مستأجر يأخذ تطبيقاً يبدو مصمَّماً له، بينما هو نفس النظام حرفياً — المتغيّر لونه ولوغوه فقط.
آخر تحديث: 2026-07-19. ذو صلة: [22 §1.5](22-full-product-roadmap.md) (الطبقات) · [06](06-tenant-model.md) (المستأجرون).
---
## 0. العقد الحاكم: الثابت مقابل المتغيّر
| **ثابت عبر كل المستأجرين والطبقات** | **متغيّر لكل مستأجر (من BuildConfig — يولّده N3)** |
|---|---|
| الخطوط (عربي + إنجليزي + أرقام) | اللون البذرة (seed) الأساسي والثانوي |
| المسافات والزوايا والظلال والحركة | اللوغو والأيقونة والـsplash |
| بنية الشاشات (TripzScaffold) وعدة المكونات | اسم التطبيق |
| سلوك الليلي/النهاري و RTL/LTR | — |
| فلسفة التصميم وأنماط الحالات | — |
**القاعدة**: أي شيء غير الأعمدة الثلاثة (لون · لوغو · اسم) يتغيّر بين مستأجرين = خطأ يُصحَّح.
السبب تشغيلي قبل أن يكون جمالياً: نظام واحد ثابت = شاشة تُبنى مرة وتخدم كل المستأجرين، وShorebird يرقّعها للجميع دفعة واحدة.
---
## 1. الخطوط — قرار 2026-07-19
المالك طلب «خط الآيفون». خط آبل هو **SF Pro** وهو مملوك لآبل ولا يجوز تضمينه قانونياً في تطبيق أندرويد. البديل المطابق المعتمد عالمياً من Google Fonts:
| الدور | الخط | لماذا |
|---|---|---|
| إنجليزي + أرقام | **Inter** | أقرب مطابق حرّ لـSF Pro (نفس الروح الهندسية)، أوزان كاملة، أرقام tabular للعدّادات والأسعار |
| عربي | **IBM Plex Sans Arabic** | النظير العربي الأقرب لروح SF Arabic: حديث، محايد، أوزان كاملة، ممتاز في الأحجام الصغيرة |
**قواعد لا تُخالَف:**
- الخطوط **تُضمَّن ملفاتٍ محلية** في `assets/fonts/` (نفس الملفات في الراكب والسائق). حزمة `google_fonts` بجلبها الشبكي **ممنوعة** — اتصال سوريا لا يُراهَن عليه، والشكل يجب أن يكون حتمياً من أول إقلاع.
- الأوزان المضمّنة: Regular · Medium · SemiBold · Bold لكل خط (٨ ملفات). لا أوزان أخرى.
- الأسعار والعدّادات والأرقام الحيّة تستعمل **Inter بأرقام tabular** (`FontFeature.tabularFigures`) كي لا «ترقص» الأعمدة عند تغيّر القيمة — هذا يُغني عن خط `digit` القديم.
- خطوط التطبيق القديم (`mohanad` · `josefin` · `digit`) **متقاعدة** من التصميم الجديد. ملفاتها تبقى في المستودع حتى يؤكد المالك حذفها. *(هذا يُحدّث docs/23 §7 الذي كان يعتبرها هوية قائمة.)*
- تبديل الخط مستقبلاً = تغيير سطرين في `typography.dart` — لا قرار مبعثر.
---
## 2. الألوان والثيم
- `ColorScheme.fromSeed` من لون المستأجر (`BuildConfig.primaryColor`) — نسختان light وdark تُشتقّان آلياً من نفس البذرة. **لا لون hex مكتوب في أي شاشة.**
- الأدوار الدلالية غير الموجودة في ColorScheme تعيش في `ThemeExtension` واحد اسمه `TripzColors`: `success` · `warning` · `danger` · `info` · `surfaceRaised` · ألوان طبقات الخريطة. الشاشة تقول `context.tripzColors.success` ولا تقرّر درجة اللون بنفسها.
- **الليلي ليس أسود صرفاً**: سطوح متدرّجة من الرمادي الداكن (elevation بالسطوع لا بالظل). الخريطة تبدّل ستايلها مع الثيم — خريطة نهارية في وضع ليلي تكسر الغرض.
- تباين النص على خلفيته **≥ 4.5:1** دائماً. البذرة مهما كانت فاتحة، `fromSeed` يضمن السطوح — والفحص جزء من قائمة تحقق الشاشة (§9).
## 3. الرموز الثابتة (tokens) — `core/design/tokens.dart`
كلها `const` في ملف واحد. الشاشة **لا تخترع رقماً**:
| المجموعة | السلّم |
|---|---|
| المسافات | مضاعفات 4: `4 · 8 · 12 · 16 · 24 · 32 · 48` — حشوة الصفحة الافتراضية **16** |
| الزوايا | `8` (حقول/شارات) · `12` (أزرار/بطاقات) · `24` (bottom sheets) · دائري كامل (avatars) |
| الحركة | `120ms` (لمسة) · `240ms` (انتقال ضمن الشاشة) · `400ms` (sheet/صفحة) — منحنى `easeOutCubic` موحّد |
| الأيقونات | `20 · 24 · 32` — عائلة واحدة: Material Symbols Rounded |
| الارتفاع | زر CTA `52` · حقل إدخال `52` · هدف لمس أدنى `48` |
## 4. الليلي/النهاري واللغة والاتجاه
- **الثيم**: الافتراضي `ThemeMode.system`، والمستخدم يستطيع تثبيت light/dark من الإعدادات. الاختيار يُحفظ محلياً (SharedPreferences — ليس سراً) عبر `SettingsCubit` في `features/settings/`.
- **اللغة**: الافتراضي عربي، والإنجليزية مدعومة. الاختيار محفوظ بنفس الطريقة.
- **الاتجاه يتبع اللغة آلياً** — MaterialApp يفعلها وحده من الـlocale. ⚠️ الموجود الآن في `app.dart` يفرض `Directionality.rtl` فوق كل شيء — **يُحذف**: مع الإنجليزية يجب أن ينقلب التطبيق LTR كاملاً.
- **الخط يتبع اللغة**: الـTextTheme يُبنى حسب الـlocale — عربي ⇒ IBM Plex Sans Arabic والاحتياط Inter، إنجليزي ⇒ Inter والاحتياط Plex (كي لا تنكسر الكلمة العربية داخل جملة إنجليزية والعكس).
- **الترجمة**: ARB + `gen_l10n` القياسية. لا نص مرئي داخل widget — يمرّ بـ`context.l10n` (يفعّل قاعدة docs/23 §2 التي كانت بلا أداة).
- دالة بناء الثيم النهائية: `buildTheme(brightness, locale)` — المدخلان الوحيدان.
## 5. الأرقام والتاريخ والعملة
- **الأرقام لاتينية (1 2 3) دائماً** في اللغتين — عرف المنطقة في التطبيقات، وتناسق الأسعار والعدادات أهم من الأرقام الهندية.
- العملة ورمزها وموضعها من **country pack** المستأجر عبر `intl` — لا تنسيق يدوي بـ`toString`.
- التاريخ يُعرض بصيغة الـlocale، والزمن النسبي («قبل ٣ د») من طبقة الترجمة.
## 6. مستوى السكافولد — `TripzScaffold` (نقطة التنظيم المركزية)
كل شاشة تُبنى داخل `TripzScaffold` — **ممنوع `Scaffold` خام في `features/`**:
```dart
TripzScaffold(
title: 'رحلاتي', // يمر بالترجمة فعلياً
status: state.status, // enum الـCubit كما هو
onRetry: cubit.load, // زر إعادة المحاولة في حالة الخطأ
empty: EmptyView(...), // حالة اللاشيء
bottomAction: TripzButton(...), // خانة CTA السفلية (52px) — keyboard-safe
child: ..., // المحتوى عند النجاح فقط
)
```
هو ما يجعل النظام **متوافقاً مع Cubit بالبنية لا بالاتفاق**: الـCubit يُصدر `status` من enum (قانون docs/23 §3)، والسكافولد يحوّله بنفسه إلى skeleton (تحميل) / ErrorView بزر إعادة (خطأ) / EmptyView (فراغ) / المحتوى (نجاح). الشاشة لا تكتب `if (loading)` أبداً — فتتوحّد حالات التطبيق كله من نقطة واحدة، وتغيير شكل «التحميل» لاحقاً سطرٌ واحد.
كما يوحّد: SafeArea · حشوة الصفحة (16) · شكل AppBar (مسطّح، بلا ظل، عنوان يسار-حسب-الاتجاه) · سلوك الكيبورد (الـCTA يعلو فوقه).
## 7. عدة المكونات — `core/ui/`
الشاشات **تركّب من هذه العدة ولا تستعمل ويدجت Material خاماً بستايل يدوي**:
| المكوّن | يغطي |
|---|---|
| `TripzButton` | primary (معبّأ 52px) · secondary (محدّد) · text — مع حالة تحميل داخلية |
| `TripzTextField` | إدخال موحّد: هاتف · OTP · بحث — رسالة الخطأ تحته بالعربية |
| `TripzSheet` | كل bottom sheet: مقبض علوي، زوايا 24، ارتفاعات محسوبة — **الحوار الافتراضي في التطبيق sheet لا dialog** |
| `TripzCard` | بطاقة الرحلة/السائق/المحفظة |
| `StatusBanner` | شريط حالة الرحلة الحي |
| `EmptyView` · `ErrorView` · `Skeleton` | الحالات الثلاث — تستهلكها `TripzScaffold` |
ملف واحد = مكوّن واحد (قانون docs/23). مكوّن جديد يُضاف هنا أولاً ثم تستعمله الميزة — لا widget مشترك داخل `features/`.
## 8. فلسفة التصميم (المرجع عند كل خلاف ذوقي)
1. **الشاشة مهمة واحدة.** سؤال واحد للمستخدم، زر أساسي واحد في الأسفل. ما زاد نُقل لشاشة أو sheet.
2. **الوضوح قبل الزينة.** لا تدرّجات ولا ظلال ثقيلة ولا زخرفة. الجمال من الفراغ والمحاذاة والخط — النموذج الذهني: تطبيقات آبل الأصلية.
3. **منطقة الإبهام.** كل فعل أساسي في الثلث السفلي. أعلى الشاشة للمعلومة، أسفلها للفعل.
4. **الحالة معروضة دائماً**: skeleton لا spinner فارغ، خطأ بالعربية مع «أعد المحاولة»، فراغ برسالة تدلّ على الفعل التالي. شاشة بيضاء صامتة = باغ.
5. **الحركة وظيفة لا استعراض**: تشرح من-أين-إلى-أين ضمن أزمنة §3، وتُحترم إعدادات تقليل الحركة في النظام.
## 9. قائمة تحقّق الشاشة (تُضاف لقائمة docs/23 §12)
- [ ] داخل `TripzScaffold` وبحالات skeleton/error/empty تعمل فعلاً
- [ ] لا لون hex ولا رقم مسافة/زاوية خارج tokens — ولا `Scaffold`/Material خام بستايل يدوي
- [ ] تعمل بالعربية RTL وبالإنجليزية LTR (تُفحص باللغتين)
- [ ] تعمل light وdark (تُفحص بالوضعين)
- [ ] النصوص عبر `context.l10n`، الأرقام لاتينية، العملة من country pack
- [ ] أهداف اللمس ≥ 48 وتكبير النص حتى 1.3 لا يكسر التخطيط
## 10. البنية في الكود (تحديث شجرة docs/23 §1)
```
lib/core/
├── design/
│ ├── tokens.dart # المسافات/الزوايا/الحركة — const
│ ├── typography.dart # TextTheme حسب الـlocale (Inter + Plex Arabic)
│ ├── tripz_colors.dart # ThemeExtension للأدوار الدلالية
│ └── theme.dart # buildTheme(brightness, locale) — يستبدل theme.dart الحالي
├── ui/ # عدة المكونات (§7)
└── l10n/ # ARB عربي/إنجليزي
features/settings/ # SettingsCubit: الثيم + اللغة (محفوظان محلياً)
```
## 11. ترتيب البناء (يسبق شاشات المجموعة Q)
1. **Q5-أ**: الخطوط (تنزيل الأوزان الثمانية للتطبيقين) · tokens · typography · TripzColors · buildTheme · حذف فرض RTL من `app.dart` · SettingsCubit.
2. **Q5-ب**: `TripzScaffold` + عدة المكونات + ARB — ثم **تُعاد صياغة** شاشات auth/home القائمة عليها كإثبات.
3. بعدها فقط تُبنى شاشات Q1/Q2/Q3 — كل شاشة جديدة تولد ملتزمة من أول سطر.