feat: implement core architecture, design system, localization, and trip features for both driver and rider apps.
This commit is contained in:
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user