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

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. فلسفة التصميم (المرجع عند كل خلاف ذوقي)

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