13 KiB
26 — نظام التصميم الثابت (الهوية البصرية لتطبيقات فلاتر)
هذا المستند مُلزِم ويكمّل 23-flutter-conventions: ذاك قانون الكود، وهذا قانون الشكل. الهدف: كل مستأجر يأخذ تطبيقاً يبدو مصمَّماً له، بينما هو نفس النظام حرفياً — المتغيّر لونه ولوغوه فقط.
آخر تحديث: 2026-07-19. ذو صلة: 22 §1.5 (الطبقات) · 06 (المستأجرون).
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/:
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. فلسفة التصميم (المرجع عند كل خلاف ذوقي)
- الشاشة مهمة واحدة. سؤال واحد للمستخدم، زر أساسي واحد في الأسفل. ما زاد نُقل لشاشة أو sheet.
- الوضوح قبل الزينة. لا تدرّجات ولا ظلال ثقيلة ولا زخرفة. الجمال من الفراغ والمحاذاة والخط — النموذج الذهني: تطبيقات آبل الأصلية.
- منطقة الإبهام. كل فعل أساسي في الثلث السفلي. أعلى الشاشة للمعلومة، أسفلها للفعل.
- الحالة معروضة دائماً: skeleton لا spinner فارغ، خطأ بالعربية مع «أعد المحاولة»، فراغ برسالة تدلّ على الفعل التالي. شاشة بيضاء صامتة = باغ.
- الحركة وظيفة لا استعراض: تشرح من-أين-إلى-أين ضمن أزمنة §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)
- Q5-أ: الخطوط (تنزيل الأوزان الثمانية للتطبيقين) · tokens · typography · TripzColors · buildTheme · حذف فرض RTL من
app.dart· SettingsCubit. - Q5-ب:
TripzScaffold+ عدة المكونات + ARB — ثم تُعاد صياغة شاشات auth/home القائمة عليها كإثبات. - بعدها فقط تُبنى شاشات Q1/Q2/Q3 — كل شاشة جديدة تولد ملتزمة من أول سطر.