Files
tripz-llc/docs/26-flutter-design-system.md
T

147 lines
13 KiB
Markdown

# 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 — كل شاشة جديدة تولد ملتزمة من أول سطر.