feat: implement core architecture, design system, localization, and trip features for both driver and rider apps.

This commit is contained in:
Hamza-Ayed
2026-07-19 16:21:58 +03:00
parent 959e2fa758
commit c4dfeb4c6f
207 changed files with 14017 additions and 5567 deletions
+88
View File
@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'tripz_colors.dart';
import 'typography.dart';
/// بناء الثيم — المدخلان الوحيدان: السطوع واللغة.
/// docs/26 §2, §4
ThemeData buildTheme({required Brightness brightness, required Locale locale}) {
final bool isDark = brightness == Brightness.dark;
final seed = const Color(0xFF0E7C66); // أخضر تركوازي — بذرة المستأجر
final colorScheme = ColorScheme.fromSeed(
seedColor: seed,
brightness: brightness,
);
final textTheme = buildTextTheme(locale);
return ThemeData(
useMaterial3: true,
brightness: brightness,
colorScheme: colorScheme,
textTheme: textTheme,
extensions: [
isDark ? TripzColors.dark : TripzColors.light,
],
appBarTheme: AppBarTheme(
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
titleTextStyle: textTheme.titleLarge?.copyWith(
color: colorScheme.onSurface,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: colorScheme.surfaceContainerHighest,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: colorScheme.outline),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: colorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: colorScheme.error),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
labelStyle: textTheme.bodyLarge,
hintStyle: textTheme.bodyLarge?.copyWith(color: colorScheme.onSurfaceVariant),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(52),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
textStyle: textTheme.labelLarge,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
minimumSize: const Size.fromHeight(52),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
textStyle: textTheme.labelLarge,
),
),
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: colorScheme.outlineVariant),
),
margin: EdgeInsets.zero,
),
bottomSheetTheme: const BottomSheetThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
showDragHandle: true,
),
);
}
+58
View File
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
/// الرموز الثابتة — كل قيمة هنا `const` ولا تُخترَع في الشاشات.
/// docs/26 §3
abstract final class T {
// ── المسافات (مضاعفات 4) ──
static const double s4 = 4;
static const double s8 = 8;
static const double s12 = 12;
static const double s16 = 16;
static const double s24 = 24;
static const double s32 = 32;
static const double s48 = 48;
/// حشوة الصفحة الافتراضية
static const double pagePadding = s16;
// ── الزوايا ──
static const double r8 = 8; // حقول / شارات
static const double r12 = 12; // أزرار / بطاقات
static const double r16 = 16; // محتوى مدمّق
static const double r24 = 24; // bottom sheets
static const double rCircle = 999; // avatars
// ── الحركة (أزمنة) ──
static const Duration fast = Duration(milliseconds: 120); // لمسة
static const Duration medium = Duration(milliseconds: 240); // انتقال ضمن الشاشة
static const Duration slow = Duration(milliseconds: 400); // sheet / صفحة
// ── المسافات الإضافية ──
static const double s20 = 20;
// ── الظلال ──
static const BoxShadow shadowSm = BoxShadow(
color: Color(0x0F000000),
blurRadius: 4,
offset: Offset(0, 1),
);
static const BoxShadow shadowLg = BoxShadow(
color: Color(0x1A000000),
blurRadius: 12,
offset: Offset(0, 4),
);
// ── الأيقونات ──
static const double iconSm = 20;
static const double iconMd = 24;
static const double iconLg = 32;
// ── الارتفاعات ──
static const double buttonHeight = 52;
static const double inputHeight = 52;
static const double touchTarget = 48;
// ── عناصر الخريطة ──
static const double markerSize = 40;
static const double polylineWidth = 6;
}
@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
/// الأدوار الدلالية غير الموجودة في ColorScheme الأساسي.
/// الشاشة تستدعيها عبر `context.tripzColors` — لا تقرّر درجة اللون بنفسها.
/// docs/26 §2
@immutable
class TripzColors extends ThemeExtension<TripzColors> {
const TripzColors({
required this.success,
required this.warning,
required this.danger,
required this.info,
required this.surfaceRaised,
required this.mapOverlayDark,
required this.mapOverlayLight,
});
final Color success;
final Color warning;
final Color danger;
final Color info;
final Color surfaceRaised;
final Color mapOverlayDark;
final Color mapOverlayLight;
@override
TripzColors copyWith({
Color? success,
Color? warning,
Color? danger,
Color? info,
Color? surfaceRaised,
Color? mapOverlayDark,
Color? mapOverlayLight,
}) {
return TripzColors(
success: success ?? this.success,
warning: warning ?? this.warning,
danger: danger ?? this.danger,
info: info ?? this.info,
surfaceRaised: surfaceRaised ?? this.surfaceRaised,
mapOverlayDark: mapOverlayDark ?? this.mapOverlayDark,
mapOverlayLight: mapOverlayLight ?? this.mapOverlayLight,
);
}
@override
TripzColors lerp(TripzColors? other, double t) {
if (other is! TripzColors) return this;
return TripzColors(
success: Color.lerp(success, other.success, t)!,
warning: Color.lerp(warning, other.warning, t)!,
danger: Color.lerp(danger, other.danger, t)!,
info: Color.lerp(info, other.info, t)!,
surfaceRaised: Color.lerp(surfaceRaised, other.surfaceRaised, t)!,
mapOverlayDark: Color.lerp(mapOverlayDark, other.mapOverlayDark, t)!,
mapOverlayLight: Color.lerp(mapOverlayLight, other.mapOverlayLight, t)!,
);
}
// ── الوضع النهاري ──
static const light = TripzColors(
success: Color(0xFF2E7D32),
warning: Color(0xFFF9A825),
danger: Color(0xFFD32F2F),
info: Color(0xFF1565C0),
surfaceRaised: Color(0xFFF5F5F5),
mapOverlayDark: Color(0xCC1A1A2E),
mapOverlayLight: Color(0xCCFFFFFF),
);
// ── الوضع الليلي ──
static const dark = TripzColors(
success: Color(0xFF66BB6A),
warning: Color(0xFFFFD54F),
danger: Color(0xFFEF5350),
info: Color(0xFF42A5F5),
surfaceRaised: Color(0xFF2C2C3A),
mapOverlayDark: Color(0xCC0D0D1A),
mapOverlayLight: Color(0xCC1A1A2E),
);
}
/// امتداد مريح للوصول السريع من السياق
extension TripzColorsExtension on BuildContext {
TripzColors get tripzColors =>
Theme.of(this).extension<TripzColors>() ?? TripzColors.light;
}
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
/// الخطوط المضمّنة محلياً — Inter + IBM Plex Sans Arabic.
/// لا حزمة google_fonts — الاتصال لا يُراهَن عليه.
/// docs/26 §1
///
/// الأوزان المضمّنة: Regular(400) · Medium(500) · SemiBold(600) · Bold(700)
/// لكل خط (٨ ملفات في `assets/fonts/`).
TextTheme buildTextTheme(Locale locale) {
final bool isArabic = locale.languageCode == 'ar';
// الخط الرئيسي: عربي ⇒ Plex Arabic، إنجليزي ⇒ Inter
final String primary = isArabic ? 'IBMPlexSansArabic' : 'Inter';
// الاحتياطي: العكس
final String fallback = isArabic ? 'Inter' : 'IBMPlexSansArabic';
return TextTheme(
displayLarge: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w700, fontSize: 57, height: 1.12),
displayMedium: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w700, fontSize: 45, height: 1.16),
displaySmall: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w700, fontSize: 36, height: 1.22),
headlineLarge: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w700, fontSize: 32, height: 1.25),
headlineMedium: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w600, fontSize: 28, height: 1.29),
headlineSmall: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w600, fontSize: 24, height: 1.33),
titleLarge: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w600, fontSize: 22, height: 1.27),
titleMedium: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w500, fontSize: 16, height: 1.50, letterSpacing: 0.15),
titleSmall: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w500, fontSize: 14, height: 1.43, letterSpacing: 0.1),
bodyLarge: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w400, fontSize: 16, height: 1.50, letterSpacing: 0.5),
bodyMedium: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w400, fontSize: 14, height: 1.43, letterSpacing: 0.25),
bodySmall: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w400, fontSize: 12, height: 1.33, letterSpacing: 0.4),
labelLarge: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w500, fontSize: 14, height: 1.43, letterSpacing: 0.1),
labelMedium: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w500, fontSize: 12, height: 1.33, letterSpacing: 0.5),
labelSmall: TextStyle(fontFamily: primary, fontFamilyFallback: [fallback], fontWeight: FontWeight.w500, fontSize: 11, height: 1.45, letterSpacing: 0.5),
);
}