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
@@ -0,0 +1,77 @@
/// استثناءات الشبكة الموحّدة — كل خطأ يتحول إلى صنف هنا.
/// docs/23 §4
class ApiException implements Exception {
ApiException(this.message, {this.statusCode, this.type});
final String message;
final int? statusCode;
final ApiExceptionType? type;
@override
String toString() => 'ApiException($statusCode): $message';
}
enum ApiExceptionType {
network,
timeout,
unauthorized,
forbidden,
notFound,
server,
unknown,
}
/// تحليل DioException إلى ApiException
ApiException fromDioError(Object error) {
final dio = error;
final type = switch (dio.runtimeType.toString()) {
'DioException' => _mapDioType(dio),
_ => ApiExceptionType.unknown,
};
final statusCode = _extractStatusCode(dio);
final message = _extractMessage(dio, statusCode);
return ApiException(message, statusCode: statusCode, type: type);
}
int? _extractStatusCode(Object error) {
try {
return (error as dynamic).response?.statusCode as int?;
} catch (_) {
return null;
}
}
String _extractMessage(Object error, int? statusCode) {
try {
final data = (error as dynamic).response?.data;
if (data is Map) {
return (data['message'] ?? data['error'] ?? 'خطأ غير معروف').toString();
}
} catch (_) {}
return switch (statusCode) {
401 => 'انتهت الجلسة',
403 => 'غير مصرح',
404 => 'غير موجود',
500 => 'خطأ في السيرفر',
_ => 'خطأ غير معروف',
};
}
ApiExceptionType _mapDioType(Object error) {
try {
final dioType = (error as dynamic).type;
return switch (dioType.toString()) {
'DioExceptionType.connectionTimeout' => ApiExceptionType.timeout,
'DioExceptionType.sendTimeout' => ApiExceptionType.timeout,
'DioExceptionType.receiveTimeout' => ApiExceptionType.timeout,
'DioExceptionType.connectionError' => ApiExceptionType.network,
'DioExceptionType.badResponse' => ApiExceptionType.server,
'DioExceptionType.cancel' => ApiExceptionType.unknown,
_ => ApiExceptionType.unknown,
};
} catch (_) {
return ApiExceptionType.unknown;
}
}
+60
View File
@@ -0,0 +1,60 @@
/// مُولَّد من المانيفست عبر سكربت N3 — لا يُحرَّر يدوياً.
/// يحتوي على أعلام الميزات `const` + إعداد المستأجر الثابت.
/// docs/22 §1.5
class BuildConfig {
static const String tenantSlug = 'siro';
static const String baseUrl = 'https://tripz-api.intaleqapp.com/api';
static const String appName = 'Tripz Driver';
static const String appRole = 'driver';
// ألوان المستأجر (البذرة)
static const int primaryColorValue = 0xFF0E7C66;
static const int secondaryColorValue = 0xFF0E7C66;
// أعلام الميزات — const: tree-shaking يحذف الكود المطفأ في release
// docs/22 §1.5, docs/23 §5
static const bool rideClassesSet = true;
static const bool tariffEngineFull = true;
static const bool liveTrackingPro = true;
static const bool coupons = true;
static const bool walletPayment = true;
static const bool chat = true;
static const bool aiDocumentProcessing = false;
static const bool heatmap = false;
static const bool predictiveDemand = false;
static const bool geofence = false;
static const bool targetedPromo = false;
static const bool whatsappCampaigns = false;
static const bool marketingEngine = false;
static const bool dynamicPricing = false;
static const bool transit = false;
static const bool paymentGateways = false;
static const bool smsSettlement = false;
static const bool negotiator = false;
static const bool driverTiers = false;
static const bool driverAssurance = false;
}
/// اختصار وصول للاستعمال في شروط Features.xxx
abstract final class Features {
static const bool rideClassesSet = BuildConfig.rideClassesSet;
static const bool tariffEngineFull = BuildConfig.tariffEngineFull;
static const bool liveTrackingPro = BuildConfig.liveTrackingPro;
static const bool coupons = BuildConfig.coupons;
static const bool walletPayment = BuildConfig.walletPayment;
static const bool chat = BuildConfig.chat;
static const bool aiDocumentProcessing = BuildConfig.aiDocumentProcessing;
static const bool heatmap = BuildConfig.heatmap;
static const bool predictiveDemand = BuildConfig.predictiveDemand;
static const bool geofence = BuildConfig.geofence;
static const bool targetedPromo = BuildConfig.targetedPromo;
static const bool whatsappCampaigns = BuildConfig.whatsappCampaigns;
static const bool marketingEngine = BuildConfig.marketingEngine;
static const bool dynamicPricing = BuildConfig.dynamicPricing;
static const bool transit = BuildConfig.transit;
static const bool paymentGateways = BuildConfig.paymentGateways;
static const bool smsSettlement = BuildConfig.smsSettlement;
static const bool negotiator = BuildConfig.negotiator;
static const bool driverTiers = BuildConfig.driverTiers;
static const bool driverAssurance = BuildConfig.driverAssurance;
}
+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,
),
);
}
+41
View File
@@ -0,0 +1,41 @@
/// الرموز الثابتة — كل قيمة هنا `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 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),
);
}
+48 -7
View File
@@ -2,6 +2,15 @@ import 'package:get_it/get_it.dart';
import 'api/api_client.dart';
import 'storage/token_store.dart';
import '../features/auth/data/auth_repository.dart';
import '../features/home/data/driver_repository.dart';
import '../features/home/cubit/driver_cubit.dart';
import '../features/offers/data/offers_repository.dart';
import '../features/offers/bloc/offers_bloc.dart';
import '../features/trip/data/driver_trip_repository.dart';
import '../features/trip/data/driver_socket_service.dart';
import '../features/trip/data/trip_event_merger.dart';
import '../features/trip/data/fcm_service.dart';
import '../features/trip/bloc/driver_trip_bloc.dart';
final GetIt getIt = GetIt.instance;
@@ -10,17 +19,49 @@ final GetIt getIt = GetIt.instance;
/// **هذه نقطة إقفال الميزات المدفوعة**: الموديول المحروس يُسجَّل داخل
/// `if (Features.x)` فلا يوجد أصلاً حين لا تكون الميزة مشتراة — بدل فحص
/// متناثر في كل شاشة يُنسى أحدها (docs/22 §1.5).
///
/// مثال عند وصول الدردشة:
/// ```dart
/// if (Features.chat) {
/// getIt.registerLazySingleton<ChatRepository>(() => ChatRepository(getIt()));
/// }
/// ```
Future<void> setupDi() async {
getIt.registerLazySingleton<TokenStore>(() => TokenStore());
getIt.registerLazySingleton<ApiClient>(() => ApiClient(getIt<TokenStore>()));
getIt.registerLazySingleton<AuthRepository>(
() => AuthRepository(getIt<ApiClient>(), getIt<TokenStore>()),
);
// ── Driver Phase 3: Data Layer ──
getIt.registerLazySingleton<DriverRepository>(
() => DriverRepository(getIt<ApiClient>()),
);
getIt.registerLazySingleton<OffersRepository>(
() => OffersRepository(getIt<ApiClient>()),
);
getIt.registerLazySingleton<DriverTripRepository>(
() => DriverTripRepository(getIt<ApiClient>()),
);
getIt.registerLazySingleton<DriverTripEventMerger>(
() => DriverTripEventMerger(),
);
getIt.registerLazySingleton<DriverSocketService>(
() => DriverSocketService(getIt<TokenStore>(), getIt<DriverTripEventMerger>()),
);
getIt.registerLazySingleton<DriverFcmService>(
() => DriverFcmService(getIt<DriverTripEventMerger>()),
);
// ── Driver Phase 3: Bloc/Cubit (lazy singletons — يُدمَّرون يدوياً عند logout) ──
getIt.registerLazySingleton<DriverCubit>(
() => DriverCubit(getIt<DriverRepository>()),
);
getIt.registerLazySingleton<OffersBloc>(
() => OffersBloc(
offersRepo: getIt<OffersRepository>(),
socket: getIt<DriverSocketService>(),
merger: getIt<DriverTripEventMerger>(),
),
);
getIt.registerLazySingleton<DriverTripBloc>(
() => DriverTripBloc(
tripRepo: getIt<DriverTripRepository>(),
socket: getIt<DriverSocketService>(),
merger: getIt<DriverTripEventMerger>(),
),
);
}
+121
View File
@@ -0,0 +1,121 @@
{
"@@locale": "ar",
"appName": "Tripz",
"appTagline": "اسحب، وصل، وصل",
"phoneTitle": "أدخل رقم هاتفك للمتابعة",
"phoneLabel": "رقم الهاتف",
"phoneHint": "07XXXXXXXX",
"sendCode": "إرسال الرمز",
"otpTitle": "أدخل رمز التحقق",
"otpSentTo": "أُرسل إلى {phone}",
"@otpSentTo": {
"placeholders": {
"phone": {"type": "String"}
}
},
"otpLabel": "الرمز",
"confirm": "تأكيد",
"welcome": "مرحباً {name}",
"@welcome": {
"placeholders": {
"name": {"type": "String"}
}
},
"welcomeBack": "مرحباً بعودتك",
"whereTo": "إلى أين؟",
"myTrips": "رحلاتي",
"wallet": "المحفظة",
"coupons": "الكوبونات والدعوات",
"settings": "الإعدادات",
"support": "الدعم",
"logout": "تسجيل الخروج",
"language": "اللغة",
"arabic": "العربية",
"english": "English",
"theme": "المظهر",
"lightMode": "الوضع النهاري",
"darkMode": "الوضع الليلي",
"systemMode": "تلقائي",
"errorGeneric": "حدث خطأ ما",
"retry": "أعد المحاولة",
"emptyGeneric": "لا توجد عناصر بعد",
"loading": "جارٍ التحميل...",
"cancel": "إلغاء",
"save": "حفظ",
"close": "إغلاق",
"next": "التالي",
"back": "رجوع",
"search": "بحث",
"noResults": "لا توجد نتائج",
"connectionError": "تحقق من اتصالك بالإنترنت",
"authError": "تعذّر إرسال الرمز",
"invalidOtp": "رمز غير صحيح",
"phoneEmpty": "أدخل رقم الهاتف",
"selectDestination": "اختر وجهتك",
"pickupLocation": "نقطة الانطلاق",
"dropoffLocation": "الوجهة",
"chooseOnMap": "اختر من الخريطة",
"favorites": "المفضلة",
"home": "البيت",
"work": "العمل",
"searchPlaces": "ابحث عن مكان...",
"rideClasses": "فئة الرحلة",
"cash": "نقدي",
"walletPayment": "المحفظة",
"couponCode": "كود الخصم",
"scheduleRide": "جدولة رحلة",
"requestRide": "اطلب رحلة",
"searchingDriver": "جارٍ البحث عن سائق...",
"cancelSearch": "إلغاء البحث",
"driverAccepted": "قبل سائقك الرحلة",
"driverArriving": "السائق في طريقه إليك",
"driverArrived": "وصل سائقك",
"rideInProgress": "الرحلة جارية",
"rideCompleted": "انتهت الرحلة",
"rateDriver": "قيّم سائقك",
"tripFare": "أجرة الرحلة",
"waitingFee": "رسوم الانتظار",
"discount": "الخصم",
"paymentMethod": "طريقة الدفع",
"total": "المجموع",
"submitRating": "إرسال التقييم",
"skipRating": "تخطي",
"freeWaitMinutes": "دقائق انتظار مجانية",
"minutes": "دقائق",
"callDriver": "اتصل بالسائق",
"chatWithDriver": "دردشة مع السائق",
"shareTrip": "مشاركة الرحلة",
"emergencySOS": "طارئ SOS",
"plateNumber": "رقم اللوحة",
"vehicleInfo": "معلومات المركبة",
"driverRating": "تقييم السائق",
"eta": "الوصول المتوقع",
"km": "كم",
"min": "د",
"searchTimeout": "لم نجد سائقاً متاحاً حالياً. حاول مرة أخرى",
"goOnline": "الاتصال",
"goOffline": "الانقطاع",
"online": "مُتصل",
"offline": "مُنقطع",
"accept": "قبول",
"reject": "رفض",
"offersAvailable": "عروض متاحة",
"tripAssigned": "رحلة جديدة مُعيّنة",
"onTheWay": "في الطريق",
"arrivedAtPickup": "وصلت",
"startTrip": "بدء الرحلة",
"completeTrip": "إتمام الرحلة",
"tripInProgress": "الرحلة جارية",
"tripCompleted": "أُتمّت الرحلة",
"cancelTrip": "إلغاء الرحلة",
"cancelTripConfirm": "هل أنت متأكد من إلغاء هذه الرحلة؟",
"yes": "نعم",
"no": "لا",
"callClient": "اتصال بالعميل",
"chat": "محادثة",
"trip": "الرحلة",
"anonymous": "السائق",
"noMessagesYet": "لا توجد رسائل بعد",
"typeMessage": "اكتب رسالة..."
}
+121
View File
@@ -0,0 +1,121 @@
{
"@@locale": "en",
"appName": "Tripz",
"appTagline": "Tap, ride, arrive",
"phoneTitle": "Enter your phone number to continue",
"phoneLabel": "Phone number",
"phoneHint": "07XXXXXXXX",
"sendCode": "Send code",
"otpTitle": "Enter verification code",
"otpSentTo": "Sent to {phone}",
"@otpSentTo": {
"placeholders": {
"phone": {"type": "String"}
}
},
"otpLabel": "Code",
"confirm": "Confirm",
"welcome": "Welcome {name}",
"@welcome": {
"placeholders": {
"name": {"type": "String"}
}
},
"welcomeBack": "Welcome back",
"whereTo": "Where to?",
"myTrips": "My trips",
"wallet": "Wallet",
"coupons": "Coupons & invites",
"settings": "Settings",
"support": "Support",
"logout": "Logout",
"language": "Language",
"arabic": "العربية",
"english": "English",
"theme": "Theme",
"lightMode": "Light",
"darkMode": "Dark",
"systemMode": "System",
"errorGeneric": "Something went wrong",
"retry": "Try again",
"emptyGeneric": "Nothing here yet",
"loading": "Loading...",
"cancel": "Cancel",
"save": "Save",
"close": "Close",
"next": "Next",
"back": "Back",
"search": "Search",
"noResults": "No results found",
"connectionError": "Check your internet connection",
"authError": "Failed to send code",
"invalidOtp": "Invalid code",
"phoneEmpty": "Enter your phone number",
"selectDestination": "Choose your destination",
"pickupLocation": "Pickup location",
"dropoffLocation": "Destination",
"chooseOnMap": "Choose on map",
"favorites": "Favorites",
"home": "Home",
"work": "Work",
"searchPlaces": "Search for a place...",
"rideClasses": "Ride class",
"cash": "Cash",
"walletPayment": "Wallet",
"couponCode": "Coupon code",
"scheduleRide": "Schedule a ride",
"requestRide": "Request a ride",
"searchingDriver": "Looking for a driver...",
"cancelSearch": "Cancel search",
"driverAccepted": "Your driver accepted",
"driverArriving": "Driver is on the way",
"driverArrived": "Your driver has arrived",
"rideInProgress": "Ride in progress",
"rideCompleted": "Ride completed",
"rateDriver": "Rate your driver",
"tripFare": "Trip fare",
"waitingFee": "Waiting fee",
"discount": "Discount",
"paymentMethod": "Payment method",
"total": "Total",
"submitRating": "Submit rating",
"skipRating": "Skip",
"freeWaitMinutes": "Free wait minutes",
"minutes": "minutes",
"callDriver": "Call driver",
"chatWithDriver": "Chat with driver",
"shareTrip": "Share trip",
"emergencySOS": "Emergency SOS",
"plateNumber": "Plate number",
"vehicleInfo": "Vehicle info",
"driverRating": "Driver rating",
"eta": "Estimated arrival",
"km": "km",
"min": "min",
"searchTimeout": "No drivers available right now. Please try again",
"goOnline": "Go online",
"goOffline": "Go offline",
"online": "Online",
"offline": "Offline",
"accept": "Accept",
"reject": "Reject",
"offersAvailable": "offers available",
"tripAssigned": "New trip assigned",
"onTheWay": "On my way",
"arrivedAtPickup": "I've arrived",
"startTrip": "Start trip",
"completeTrip": "Complete trip",
"tripInProgress": "Trip in progress",
"tripCompleted": "Trip completed",
"cancelTrip": "Cancel trip",
"cancelTripConfirm": "Are you sure you want to cancel this trip?",
"yes": "Yes",
"no": "No",
"callClient": "Call client",
"chat": "Chat",
"trip": "Trip",
"anonymous": "Driver",
"noMessagesYet": "No messages yet",
"typeMessage": "Type a message..."
}
@@ -0,0 +1,775 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_ar.dart';
import 'app_localizations_en.dart';
// ignore_for_file: type=lint
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```dart
/// import 'l10n/app_localizations.dart';
///
/// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
/// supportedLocales: AppLocalizations.supportedLocales,
/// home: MyApplicationHome(),
/// );
/// ```
///
/// ## Update pubspec.yaml
///
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications
///
/// iOS applications define key application metadata, including supported
/// locales, in an Info.plist file that is built into the application bundle.
/// To configure the locales supported by your app, you’ll need to edit this
/// file.
///
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// project’s Runner folder.
///
/// Next, select the Information Property List item, select Add Item from the
/// Editor menu, then select Localizations from the pop-up menu.
///
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
///
/// Returns a list of localizations delegates containing this delegate along with
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
/// and GlobalWidgetsLocalizations.delegate.
///
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('ar'),
Locale('en')
];
/// No description provided for @appName.
///
/// In ar, this message translates to:
/// **'Tripz'**
String get appName;
/// No description provided for @appTagline.
///
/// In ar, this message translates to:
/// **'اسحب، وصل، وصل'**
String get appTagline;
/// No description provided for @phoneTitle.
///
/// In ar, this message translates to:
/// **'أدخل رقم هاتفك للمتابعة'**
String get phoneTitle;
/// No description provided for @phoneLabel.
///
/// In ar, this message translates to:
/// **'رقم الهاتف'**
String get phoneLabel;
/// No description provided for @phoneHint.
///
/// In ar, this message translates to:
/// **'07XXXXXXXX'**
String get phoneHint;
/// No description provided for @sendCode.
///
/// In ar, this message translates to:
/// **'إرسال الرمز'**
String get sendCode;
/// No description provided for @otpTitle.
///
/// In ar, this message translates to:
/// **'أدخل رمز التحقق'**
String get otpTitle;
/// No description provided for @otpSentTo.
///
/// In ar, this message translates to:
/// **'أُرسل إلى {phone}'**
String otpSentTo(String phone);
/// No description provided for @otpLabel.
///
/// In ar, this message translates to:
/// **'الرمز'**
String get otpLabel;
/// No description provided for @confirm.
///
/// In ar, this message translates to:
/// **'تأكيد'**
String get confirm;
/// No description provided for @welcome.
///
/// In ar, this message translates to:
/// **'مرحباً {name}'**
String welcome(String name);
/// No description provided for @welcomeBack.
///
/// In ar, this message translates to:
/// **'مرحباً بعودتك'**
String get welcomeBack;
/// No description provided for @whereTo.
///
/// In ar, this message translates to:
/// **'إلى أين؟'**
String get whereTo;
/// No description provided for @myTrips.
///
/// In ar, this message translates to:
/// **'رحلاتي'**
String get myTrips;
/// No description provided for @wallet.
///
/// In ar, this message translates to:
/// **'المحفظة'**
String get wallet;
/// No description provided for @coupons.
///
/// In ar, this message translates to:
/// **'الكوبونات والدعوات'**
String get coupons;
/// No description provided for @settings.
///
/// In ar, this message translates to:
/// **'الإعدادات'**
String get settings;
/// No description provided for @support.
///
/// In ar, this message translates to:
/// **'الدعم'**
String get support;
/// No description provided for @logout.
///
/// In ar, this message translates to:
/// **'تسجيل الخروج'**
String get logout;
/// No description provided for @language.
///
/// In ar, this message translates to:
/// **'اللغة'**
String get language;
/// No description provided for @arabic.
///
/// In ar, this message translates to:
/// **'العربية'**
String get arabic;
/// No description provided for @english.
///
/// In ar, this message translates to:
/// **'English'**
String get english;
/// No description provided for @theme.
///
/// In ar, this message translates to:
/// **'المظهر'**
String get theme;
/// No description provided for @lightMode.
///
/// In ar, this message translates to:
/// **'الوضع النهاري'**
String get lightMode;
/// No description provided for @darkMode.
///
/// In ar, this message translates to:
/// **'الوضع الليلي'**
String get darkMode;
/// No description provided for @systemMode.
///
/// In ar, this message translates to:
/// **'تلقائي'**
String get systemMode;
/// No description provided for @errorGeneric.
///
/// In ar, this message translates to:
/// **'حدث خطأ ما'**
String get errorGeneric;
/// No description provided for @retry.
///
/// In ar, this message translates to:
/// **'أعد المحاولة'**
String get retry;
/// No description provided for @emptyGeneric.
///
/// In ar, this message translates to:
/// **'لا توجد عناصر بعد'**
String get emptyGeneric;
/// No description provided for @loading.
///
/// In ar, this message translates to:
/// **'جارٍ التحميل...'**
String get loading;
/// No description provided for @cancel.
///
/// In ar, this message translates to:
/// **'إلغاء'**
String get cancel;
/// No description provided for @save.
///
/// In ar, this message translates to:
/// **'حفظ'**
String get save;
/// No description provided for @close.
///
/// In ar, this message translates to:
/// **'إغلاق'**
String get close;
/// No description provided for @next.
///
/// In ar, this message translates to:
/// **'التالي'**
String get next;
/// No description provided for @back.
///
/// In ar, this message translates to:
/// **'رجوع'**
String get back;
/// No description provided for @search.
///
/// In ar, this message translates to:
/// **'بحث'**
String get search;
/// No description provided for @noResults.
///
/// In ar, this message translates to:
/// **'لا توجد نتائج'**
String get noResults;
/// No description provided for @connectionError.
///
/// In ar, this message translates to:
/// **'تحقق من اتصالك بالإنترنت'**
String get connectionError;
/// No description provided for @authError.
///
/// In ar, this message translates to:
/// **'تعذّر إرسال الرمز'**
String get authError;
/// No description provided for @invalidOtp.
///
/// In ar, this message translates to:
/// **'رمز غير صحيح'**
String get invalidOtp;
/// No description provided for @phoneEmpty.
///
/// In ar, this message translates to:
/// **'أدخل رقم الهاتف'**
String get phoneEmpty;
/// No description provided for @selectDestination.
///
/// In ar, this message translates to:
/// **'اختر وجهتك'**
String get selectDestination;
/// No description provided for @pickupLocation.
///
/// In ar, this message translates to:
/// **'نقطة الانطلاق'**
String get pickupLocation;
/// No description provided for @dropoffLocation.
///
/// In ar, this message translates to:
/// **'الوجهة'**
String get dropoffLocation;
/// No description provided for @chooseOnMap.
///
/// In ar, this message translates to:
/// **'اختر من الخريطة'**
String get chooseOnMap;
/// No description provided for @favorites.
///
/// In ar, this message translates to:
/// **'المفضلة'**
String get favorites;
/// No description provided for @home.
///
/// In ar, this message translates to:
/// **'البيت'**
String get home;
/// No description provided for @work.
///
/// In ar, this message translates to:
/// **'العمل'**
String get work;
/// No description provided for @searchPlaces.
///
/// In ar, this message translates to:
/// **'ابحث عن مكان...'**
String get searchPlaces;
/// No description provided for @rideClasses.
///
/// In ar, this message translates to:
/// **'فئة الرحلة'**
String get rideClasses;
/// No description provided for @cash.
///
/// In ar, this message translates to:
/// **'نقدي'**
String get cash;
/// No description provided for @walletPayment.
///
/// In ar, this message translates to:
/// **'المحفظة'**
String get walletPayment;
/// No description provided for @couponCode.
///
/// In ar, this message translates to:
/// **'كود الخصم'**
String get couponCode;
/// No description provided for @scheduleRide.
///
/// In ar, this message translates to:
/// **'جدولة رحلة'**
String get scheduleRide;
/// No description provided for @requestRide.
///
/// In ar, this message translates to:
/// **'اطلب رحلة'**
String get requestRide;
/// No description provided for @searchingDriver.
///
/// In ar, this message translates to:
/// **'جارٍ البحث عن سائق...'**
String get searchingDriver;
/// No description provided for @cancelSearch.
///
/// In ar, this message translates to:
/// **'إلغاء البحث'**
String get cancelSearch;
/// No description provided for @driverAccepted.
///
/// In ar, this message translates to:
/// **'قبل سائقك الرحلة'**
String get driverAccepted;
/// No description provided for @driverArriving.
///
/// In ar, this message translates to:
/// **'السائق في طريقه إليك'**
String get driverArriving;
/// No description provided for @driverArrived.
///
/// In ar, this message translates to:
/// **'وصل سائقك'**
String get driverArrived;
/// No description provided for @rideInProgress.
///
/// In ar, this message translates to:
/// **'الرحلة جارية'**
String get rideInProgress;
/// No description provided for @rideCompleted.
///
/// In ar, this message translates to:
/// **'انتهت الرحلة'**
String get rideCompleted;
/// No description provided for @rateDriver.
///
/// In ar, this message translates to:
/// **'قيّم سائقك'**
String get rateDriver;
/// No description provided for @tripFare.
///
/// In ar, this message translates to:
/// **'أجرة الرحلة'**
String get tripFare;
/// No description provided for @waitingFee.
///
/// In ar, this message translates to:
/// **'رسوم الانتظار'**
String get waitingFee;
/// No description provided for @discount.
///
/// In ar, this message translates to:
/// **'الخصم'**
String get discount;
/// No description provided for @paymentMethod.
///
/// In ar, this message translates to:
/// **'طريقة الدفع'**
String get paymentMethod;
/// No description provided for @total.
///
/// In ar, this message translates to:
/// **'المجموع'**
String get total;
/// No description provided for @submitRating.
///
/// In ar, this message translates to:
/// **'إرسال التقييم'**
String get submitRating;
/// No description provided for @skipRating.
///
/// In ar, this message translates to:
/// **'تخطي'**
String get skipRating;
/// No description provided for @freeWaitMinutes.
///
/// In ar, this message translates to:
/// **'دقائق انتظار مجانية'**
String get freeWaitMinutes;
/// No description provided for @minutes.
///
/// In ar, this message translates to:
/// **'دقائق'**
String get minutes;
/// No description provided for @callDriver.
///
/// In ar, this message translates to:
/// **'اتصل بالسائق'**
String get callDriver;
/// No description provided for @chatWithDriver.
///
/// In ar, this message translates to:
/// **'دردشة مع السائق'**
String get chatWithDriver;
/// No description provided for @shareTrip.
///
/// In ar, this message translates to:
/// **'مشاركة الرحلة'**
String get shareTrip;
/// No description provided for @emergencySOS.
///
/// In ar, this message translates to:
/// **'طارئ SOS'**
String get emergencySOS;
/// No description provided for @plateNumber.
///
/// In ar, this message translates to:
/// **'رقم اللوحة'**
String get plateNumber;
/// No description provided for @vehicleInfo.
///
/// In ar, this message translates to:
/// **'معلومات المركبة'**
String get vehicleInfo;
/// No description provided for @driverRating.
///
/// In ar, this message translates to:
/// **'تقييم السائق'**
String get driverRating;
/// No description provided for @eta.
///
/// In ar, this message translates to:
/// **'الوصول المتوقع'**
String get eta;
/// No description provided for @km.
///
/// In ar, this message translates to:
/// **'كم'**
String get km;
/// No description provided for @min.
///
/// In ar, this message translates to:
/// **'د'**
String get min;
/// No description provided for @searchTimeout.
///
/// In ar, this message translates to:
/// **'لم نجد سائقاً متاحاً حالياً. حاول مرة أخرى'**
String get searchTimeout;
/// No description provided for @goOnline.
///
/// In ar, this message translates to:
/// **'الاتصال'**
String get goOnline;
/// No description provided for @goOffline.
///
/// In ar, this message translates to:
/// **'الانقطاع'**
String get goOffline;
/// No description provided for @online.
///
/// In ar, this message translates to:
/// **'مُتصل'**
String get online;
/// No description provided for @offline.
///
/// In ar, this message translates to:
/// **'مُنقطع'**
String get offline;
/// No description provided for @accept.
///
/// In ar, this message translates to:
/// **'قبول'**
String get accept;
/// No description provided for @reject.
///
/// In ar, this message translates to:
/// **'رفض'**
String get reject;
/// No description provided for @offersAvailable.
///
/// In ar, this message translates to:
/// **'عروض متاحة'**
String get offersAvailable;
/// No description provided for @tripAssigned.
///
/// In ar, this message translates to:
/// **'رحلة جديدة مُعيّنة'**
String get tripAssigned;
/// No description provided for @onTheWay.
///
/// In ar, this message translates to:
/// **'في الطريق'**
String get onTheWay;
/// No description provided for @arrivedAtPickup.
///
/// In ar, this message translates to:
/// **'وصلت'**
String get arrivedAtPickup;
/// No description provided for @startTrip.
///
/// In ar, this message translates to:
/// **'بدء الرحلة'**
String get startTrip;
/// No description provided for @completeTrip.
///
/// In ar, this message translates to:
/// **'إتمام الرحلة'**
String get completeTrip;
/// No description provided for @tripInProgress.
///
/// In ar, this message translates to:
/// **'الرحلة جارية'**
String get tripInProgress;
/// No description provided for @tripCompleted.
///
/// In ar, this message translates to:
/// **'أُتمّت الرحلة'**
String get tripCompleted;
/// No description provided for @cancelTrip.
///
/// In ar, this message translates to:
/// **'إلغاء الرحلة'**
String get cancelTrip;
/// No description provided for @cancelTripConfirm.
///
/// In ar, this message translates to:
/// **'هل أنت متأكد من إلغاء هذه الرحلة؟'**
String get cancelTripConfirm;
/// No description provided for @yes.
///
/// In ar, this message translates to:
/// **'نعم'**
String get yes;
/// No description provided for @no.
///
/// In ar, this message translates to:
/// **'لا'**
String get no;
/// No description provided for @callClient.
///
/// In ar, this message translates to:
/// **'اتصال بالعميل'**
String get callClient;
/// No description provided for @chat.
///
/// In ar, this message translates to:
/// **'محادثة'**
String get chat;
/// No description provided for @trip.
///
/// In ar, this message translates to:
/// **'الرحلة'**
String get trip;
/// No description provided for @anonymous.
///
/// In ar, this message translates to:
/// **'السائق'**
String get anonymous;
/// No description provided for @noMessagesYet.
///
/// In ar, this message translates to:
/// **'لا توجد رسائل بعد'**
String get noMessagesYet;
/// No description provided for @typeMessage.
///
/// In ar, this message translates to:
/// **'اكتب رسالة...'**
String get typeMessage;
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) =>
<String>['ar', 'en'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'ar':
return AppLocalizationsAr();
case 'en':
return AppLocalizationsEn();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.');
}
@@ -0,0 +1,335 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Arabic (`ar`).
class AppLocalizationsAr extends AppLocalizations {
AppLocalizationsAr([String locale = 'ar']) : super(locale);
@override
String get appName => 'Tripz';
@override
String get appTagline => 'اسحب، وصل، وصل';
@override
String get phoneTitle => 'أدخل رقم هاتفك للمتابعة';
@override
String get phoneLabel => 'رقم الهاتف';
@override
String get phoneHint => '07XXXXXXXX';
@override
String get sendCode => 'إرسال الرمز';
@override
String get otpTitle => 'أدخل رمز التحقق';
@override
String otpSentTo(String phone) {
return 'أُرسل إلى $phone';
}
@override
String get otpLabel => 'الرمز';
@override
String get confirm => 'تأكيد';
@override
String welcome(String name) {
return 'مرحباً $name';
}
@override
String get welcomeBack => 'مرحباً بعودتك';
@override
String get whereTo => 'إلى أين؟';
@override
String get myTrips => 'رحلاتي';
@override
String get wallet => 'المحفظة';
@override
String get coupons => 'الكوبونات والدعوات';
@override
String get settings => 'الإعدادات';
@override
String get support => 'الدعم';
@override
String get logout => 'تسجيل الخروج';
@override
String get language => 'اللغة';
@override
String get arabic => 'العربية';
@override
String get english => 'English';
@override
String get theme => 'المظهر';
@override
String get lightMode => 'الوضع النهاري';
@override
String get darkMode => 'الوضع الليلي';
@override
String get systemMode => 'تلقائي';
@override
String get errorGeneric => 'حدث خطأ ما';
@override
String get retry => 'أعد المحاولة';
@override
String get emptyGeneric => 'لا توجد عناصر بعد';
@override
String get loading => 'جارٍ التحميل...';
@override
String get cancel => 'إلغاء';
@override
String get save => 'حفظ';
@override
String get close => 'إغلاق';
@override
String get next => 'التالي';
@override
String get back => 'رجوع';
@override
String get search => 'بحث';
@override
String get noResults => 'لا توجد نتائج';
@override
String get connectionError => 'تحقق من اتصالك بالإنترنت';
@override
String get authError => 'تعذّر إرسال الرمز';
@override
String get invalidOtp => 'رمز غير صحيح';
@override
String get phoneEmpty => 'أدخل رقم الهاتف';
@override
String get selectDestination => 'اختر وجهتك';
@override
String get pickupLocation => 'نقطة الانطلاق';
@override
String get dropoffLocation => 'الوجهة';
@override
String get chooseOnMap => 'اختر من الخريطة';
@override
String get favorites => 'المفضلة';
@override
String get home => 'البيت';
@override
String get work => 'العمل';
@override
String get searchPlaces => 'ابحث عن مكان...';
@override
String get rideClasses => 'فئة الرحلة';
@override
String get cash => 'نقدي';
@override
String get walletPayment => 'المحفظة';
@override
String get couponCode => 'كود الخصم';
@override
String get scheduleRide => 'جدولة رحلة';
@override
String get requestRide => 'اطلب رحلة';
@override
String get searchingDriver => 'جارٍ البحث عن سائق...';
@override
String get cancelSearch => 'إلغاء البحث';
@override
String get driverAccepted => 'قبل سائقك الرحلة';
@override
String get driverArriving => 'السائق في طريقه إليك';
@override
String get driverArrived => 'وصل سائقك';
@override
String get rideInProgress => 'الرحلة جارية';
@override
String get rideCompleted => 'انتهت الرحلة';
@override
String get rateDriver => 'قيّم سائقك';
@override
String get tripFare => 'أجرة الرحلة';
@override
String get waitingFee => 'رسوم الانتظار';
@override
String get discount => 'الخصم';
@override
String get paymentMethod => 'طريقة الدفع';
@override
String get total => 'المجموع';
@override
String get submitRating => 'إرسال التقييم';
@override
String get skipRating => 'تخطي';
@override
String get freeWaitMinutes => 'دقائق انتظار مجانية';
@override
String get minutes => 'دقائق';
@override
String get callDriver => 'اتصل بالسائق';
@override
String get chatWithDriver => 'دردشة مع السائق';
@override
String get shareTrip => 'مشاركة الرحلة';
@override
String get emergencySOS => 'طارئ SOS';
@override
String get plateNumber => 'رقم اللوحة';
@override
String get vehicleInfo => 'معلومات المركبة';
@override
String get driverRating => 'تقييم السائق';
@override
String get eta => 'الوصول المتوقع';
@override
String get km => 'كم';
@override
String get min => 'د';
@override
String get searchTimeout => 'لم نجد سائقاً متاحاً حالياً. حاول مرة أخرى';
@override
String get goOnline => 'الاتصال';
@override
String get goOffline => 'الانقطاع';
@override
String get online => 'مُتصل';
@override
String get offline => 'مُنقطع';
@override
String get accept => 'قبول';
@override
String get reject => 'رفض';
@override
String get offersAvailable => 'عروض متاحة';
@override
String get tripAssigned => 'رحلة جديدة مُعيّنة';
@override
String get onTheWay => 'في الطريق';
@override
String get arrivedAtPickup => 'وصلت';
@override
String get startTrip => 'بدء الرحلة';
@override
String get completeTrip => 'إتمام الرحلة';
@override
String get tripInProgress => 'الرحلة جارية';
@override
String get tripCompleted => 'أُتمّت الرحلة';
@override
String get cancelTrip => 'إلغاء الرحلة';
@override
String get cancelTripConfirm => 'هل أنت متأكد من إلغاء هذه الرحلة؟';
@override
String get yes => 'نعم';
@override
String get no => 'لا';
@override
String get callClient => 'اتصال بالعميل';
@override
String get chat => 'محادثة';
@override
String get trip => 'الرحلة';
@override
String get anonymous => 'السائق';
@override
String get noMessagesYet => 'لا توجد رسائل بعد';
@override
String get typeMessage => 'اكتب رسالة...';
}
@@ -0,0 +1,336 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appName => 'Tripz';
@override
String get appTagline => 'Tap, ride, arrive';
@override
String get phoneTitle => 'Enter your phone number to continue';
@override
String get phoneLabel => 'Phone number';
@override
String get phoneHint => '07XXXXXXXX';
@override
String get sendCode => 'Send code';
@override
String get otpTitle => 'Enter verification code';
@override
String otpSentTo(String phone) {
return 'Sent to $phone';
}
@override
String get otpLabel => 'Code';
@override
String get confirm => 'Confirm';
@override
String welcome(String name) {
return 'Welcome $name';
}
@override
String get welcomeBack => 'Welcome back';
@override
String get whereTo => 'Where to?';
@override
String get myTrips => 'My trips';
@override
String get wallet => 'Wallet';
@override
String get coupons => 'Coupons & invites';
@override
String get settings => 'Settings';
@override
String get support => 'Support';
@override
String get logout => 'Logout';
@override
String get language => 'Language';
@override
String get arabic => 'العربية';
@override
String get english => 'English';
@override
String get theme => 'Theme';
@override
String get lightMode => 'Light';
@override
String get darkMode => 'Dark';
@override
String get systemMode => 'System';
@override
String get errorGeneric => 'Something went wrong';
@override
String get retry => 'Try again';
@override
String get emptyGeneric => 'Nothing here yet';
@override
String get loading => 'Loading...';
@override
String get cancel => 'Cancel';
@override
String get save => 'Save';
@override
String get close => 'Close';
@override
String get next => 'Next';
@override
String get back => 'Back';
@override
String get search => 'Search';
@override
String get noResults => 'No results found';
@override
String get connectionError => 'Check your internet connection';
@override
String get authError => 'Failed to send code';
@override
String get invalidOtp => 'Invalid code';
@override
String get phoneEmpty => 'Enter your phone number';
@override
String get selectDestination => 'Choose your destination';
@override
String get pickupLocation => 'Pickup location';
@override
String get dropoffLocation => 'Destination';
@override
String get chooseOnMap => 'Choose on map';
@override
String get favorites => 'Favorites';
@override
String get home => 'Home';
@override
String get work => 'Work';
@override
String get searchPlaces => 'Search for a place...';
@override
String get rideClasses => 'Ride class';
@override
String get cash => 'Cash';
@override
String get walletPayment => 'Wallet';
@override
String get couponCode => 'Coupon code';
@override
String get scheduleRide => 'Schedule a ride';
@override
String get requestRide => 'Request a ride';
@override
String get searchingDriver => 'Looking for a driver...';
@override
String get cancelSearch => 'Cancel search';
@override
String get driverAccepted => 'Your driver accepted';
@override
String get driverArriving => 'Driver is on the way';
@override
String get driverArrived => 'Your driver has arrived';
@override
String get rideInProgress => 'Ride in progress';
@override
String get rideCompleted => 'Ride completed';
@override
String get rateDriver => 'Rate your driver';
@override
String get tripFare => 'Trip fare';
@override
String get waitingFee => 'Waiting fee';
@override
String get discount => 'Discount';
@override
String get paymentMethod => 'Payment method';
@override
String get total => 'Total';
@override
String get submitRating => 'Submit rating';
@override
String get skipRating => 'Skip';
@override
String get freeWaitMinutes => 'Free wait minutes';
@override
String get minutes => 'minutes';
@override
String get callDriver => 'Call driver';
@override
String get chatWithDriver => 'Chat with driver';
@override
String get shareTrip => 'Share trip';
@override
String get emergencySOS => 'Emergency SOS';
@override
String get plateNumber => 'Plate number';
@override
String get vehicleInfo => 'Vehicle info';
@override
String get driverRating => 'Driver rating';
@override
String get eta => 'Estimated arrival';
@override
String get km => 'km';
@override
String get min => 'min';
@override
String get searchTimeout =>
'No drivers available right now. Please try again';
@override
String get goOnline => 'Go online';
@override
String get goOffline => 'Go offline';
@override
String get online => 'Online';
@override
String get offline => 'Offline';
@override
String get accept => 'Accept';
@override
String get reject => 'Reject';
@override
String get offersAvailable => 'offers available';
@override
String get tripAssigned => 'New trip assigned';
@override
String get onTheWay => 'On my way';
@override
String get arrivedAtPickup => 'I\'ve arrived';
@override
String get startTrip => 'Start trip';
@override
String get completeTrip => 'Complete trip';
@override
String get tripInProgress => 'Trip in progress';
@override
String get tripCompleted => 'Trip completed';
@override
String get cancelTrip => 'Cancel trip';
@override
String get cancelTripConfirm => 'Are you sure you want to cancel this trip?';
@override
String get yes => 'Yes';
@override
String get no => 'No';
@override
String get callClient => 'Call client';
@override
String get chat => 'Chat';
@override
String get trip => 'Trip';
@override
String get anonymous => 'Driver';
@override
String get noMessagesYet => 'No messages yet';
@override
String get typeMessage => 'Type a message...';
}
+19
View File
@@ -1,11 +1,16 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../features/auth/cubit/auth_cubit.dart';
import '../features/auth/cubit/auth_state.dart';
import '../features/auth/view/phone_screen.dart';
import '../features/auth/view/otp_screen.dart';
import '../features/home/view/home_screen.dart';
import '../features/trip/bloc/driver_trip_bloc.dart';
import '../features/trip/view/driver_trip_screen.dart';
import '../features/trip/view/driver_chat_screen.dart';
import '../core/di.dart';
GoRouter buildRouter(AuthCubit auth) {
return GoRouter(
@@ -27,6 +32,20 @@ GoRouter buildRouter(AuthCubit auth) {
GoRoute(path: '/phone', builder: (_, __) => const PhoneScreen()),
GoRoute(path: '/otp', builder: (_, __) => const OtpScreen()),
GoRoute(path: '/home', builder: (_, __) => const HomeScreen()),
GoRoute(
path: '/trip',
builder: (_, __) => BlocProvider.value(
value: getIt<DriverTripBloc>(),
child: const DriverTripScreen(),
),
),
GoRoute(
path: '/chat/:tripId',
builder: (context, state) {
final tripId = state.pathParameters['tripId']!;
return DriverChatScreen(tripId: tripId);
},
),
],
);
}
-21
View File
@@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
/// ثيم Tripz — أساس بسيط، خط عربي (mohanad) وألوان.
ThemeData buildTheme() {
const seed = Color(0xFF0E7C66); // أخضر تركوازي
return ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: seed),
fontFamily: 'mohanad',
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(52),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
);
}
+39
View File
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// حالة اللاشيء — رسالة + أيقونة.
/// docs/26 §7
class EmptyView extends StatelessWidget {
const EmptyView({
super.key,
this.icon = Icons.inbox_rounded,
this.message,
});
final IconData icon;
final String? message;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Center(
child: Padding(
padding: const EdgeInsets.all(T.s32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 64, color: cs.onSurfaceVariant.withValues(alpha: 0.4)),
const SizedBox(height: T.s16),
Text(
message ?? 'لا توجد عناصر بعد',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: cs.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
+48
View File
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// حالة الخطأ — رسالة بالعربية + زر إعادة المحاولة.
/// docs/26 §7
class ErrorView extends StatelessWidget {
const ErrorView({
super.key,
this.message,
this.onRetry,
});
final String? message;
final VoidCallback? onRetry;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Center(
child: Padding(
padding: const EdgeInsets.all(T.s32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline_rounded,
size: 64, color: cs.error.withValues(alpha: 0.6)),
const SizedBox(height: T.s16),
Text(
message ?? 'حدث خطأ ما',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: cs.onSurface,
),
textAlign: TextAlign.center,
),
if (onRetry != null) ...[
const SizedBox(height: T.s16),
FilledButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh_rounded, size: 20),
label: const Text('أعد المحاولة'),
),
],
],
),
),
);
}
}
+104
View File
@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// هيكل التحميل — تأثير نبض على عناصر مستوحاة من الشاشة الفعلية.
/// docs/26 §7
class Skeleton extends StatelessWidget {
const Skeleton({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final baseColor = cs.surfaceContainerHighest;
final highlightColor = cs.surfaceContainerHigh;
return Padding(
padding: const EdgeInsets.all(T.pagePadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_PulseBox(width: double.infinity, height: 200, base: baseColor, highlight: highlightColor),
const SizedBox(height: T.s16),
_PulseBox(width: 200, height: 20, base: baseColor, highlight: highlightColor),
const SizedBox(height: T.s12),
_PulseBox(width: 140, height: 16, base: baseColor, highlight: highlightColor),
const SizedBox(height: T.s24),
Row(
children: [
_PulseBox(width: 48, height: 48, base: baseColor, highlight: highlightColor, radius: T.rCircle),
const SizedBox(width: T.s12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_PulseBox(width: double.infinity, height: 16, base: baseColor, highlight: highlightColor),
const SizedBox(height: T.s8),
_PulseBox(width: 120, height: 14, base: baseColor, highlight: highlightColor),
],
),
),
],
),
],
),
);
}
}
class _PulseBox extends StatefulWidget {
const _PulseBox({
required this.width,
required this.height,
required this.base,
required this.highlight,
this.radius = 8,
});
final double width;
final double height;
final Color base;
final Color highlight;
final double radius;
@override
State<_PulseBox> createState() => _PulseBoxState();
}
class _PulseBoxState extends State<_PulseBox>
with SingleTickerProviderStateMixin {
late final AnimationController _ctrl;
late final Animation<double> _anim;
@override
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))
..repeat();
_anim = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _anim,
builder: (_, __) {
return Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
color: Color.lerp(widget.base, widget.highlight, _anim.value),
borderRadius: BorderRadius.circular(widget.radius),
),
);
},
);
}
}
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// شريط حالة الرحلة الحي.
/// docs/26 §7
class StatusBanner extends StatelessWidget {
const StatusBanner({
super.key,
required this.label,
this.color,
this.icon,
});
final String label;
final Color? color;
final IconData? icon;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final effectiveColor = color ?? cs.primary;
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: T.s16,
vertical: T.s12,
),
decoration: BoxDecoration(
color: effectiveColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(T.r8),
),
child: Row(
children: [
if (icon != null) ...[
Icon(icon, color: effectiveColor, size: T.iconSm),
const SizedBox(width: T.s8),
],
Expanded(
child: Text(
label,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: effectiveColor,
),
),
),
],
),
);
}
}
+83
View File
@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// زر موحّد: primary (معبّأ) · secondary (محدّد) · text — مع حالة تحميل داخلية.
/// docs/26 §7
enum TripzButtonStyle { primary, secondary, text }
class TripzButton extends StatelessWidget {
const TripzButton({
super.key,
required this.label,
this.onPressed,
this.style = TripzButtonStyle.primary,
this.loading = false,
this.icon,
this.fullWidth = true,
});
final String label;
final VoidCallback? onPressed;
final TripzButtonStyle style;
final bool loading;
final IconData? icon;
final bool fullWidth;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
final labelStyle = theme.textTheme.labelLarge;
final effectiveOnPressed = loading ? null : onPressed;
switch (style) {
case TripzButtonStyle.primary:
return SizedBox(
width: fullWidth ? double.infinity : null,
height: T.buttonHeight,
child: FilledButton(
onPressed: effectiveOnPressed,
child: _buildChild(cs.primary, labelStyle),
),
);
case TripzButtonStyle.secondary:
return SizedBox(
width: fullWidth ? double.infinity : null,
height: T.buttonHeight,
child: OutlinedButton(
onPressed: effectiveOnPressed,
child: _buildChild(cs.primary, labelStyle),
),
);
case TripzButtonStyle.text:
return TextButton(
onPressed: effectiveOnPressed,
child: _buildChild(cs.primary, labelStyle),
);
}
}
Widget _buildChild(Color color, TextStyle? style) {
if (loading) {
return const SizedBox(
height: 22,
width: 22,
child: CircularProgressIndicator(strokeWidth: 2),
);
}
if (icon != null) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 20),
const SizedBox(width: T.s8),
Text(label, style: style),
],
);
}
return Text(label, style: style);
}
}
+48
View File
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// بطاقة موحّدة: رحلة / سائق / محفظة — زوايا 12، حشوة 16.
/// docs/26 §7
class TripzCard extends StatelessWidget {
const TripzCard({
super.key,
required this.child,
this.padding,
this.onTap,
this.color,
this.borderColor,
});
final Widget child;
final EdgeInsetsGeometry? padding;
final VoidCallback? onTap;
final Color? color;
final Color? borderColor;
@override
Widget build(BuildContext context) {
final card = Card(
color: color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(T.r12),
side: borderColor != null
? BorderSide(color: borderColor!)
: BorderSide.none,
),
child: Padding(
padding: padding ?? const EdgeInsets.all(T.pagePadding),
child: child,
),
);
if (onTap != null) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(T.r12),
child: card,
);
}
return card;
}
}
+103
View File
@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
import 'empty_view.dart';
import 'error_view.dart';
import 'skeleton.dart';
/// السكافولد الموحّد — كل شاشة في التطبيق تُبنى داخله.
/// يحوّل `status` من Cubit/Bloc إلى: skeleton / error / empty / محتوى.
/// docs/26 §6
class TripzScaffold extends StatelessWidget {
const TripzScaffold({
super.key,
this.title,
this.status,
this.onRetry,
this.empty,
this.bottomAction,
this.actions,
this.showBackButton = false,
this.onBack,
this.safeAreaTop = true,
this.safeAreaBottom = true,
this.padding = true,
this.backgroundColor,
required this.child,
});
final String? title;
final Enum? status;
final VoidCallback? onRetry;
final Widget? empty;
final Widget? bottomAction;
final List<Widget>? actions;
final bool showBackButton;
final VoidCallback? onBack;
final bool safeAreaTop;
final bool safeAreaBottom;
final bool padding;
final Color? backgroundColor;
final Widget child;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
return Scaffold(
backgroundColor: backgroundColor ?? cs.surface,
appBar: title != null
? AppBar(
title: Text(title!),
leading: showBackButton
? IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded,
size: 20),
onPressed: onBack ?? () => Navigator.maybePop(context),
)
: null,
actions: actions,
)
: null,
body: SafeArea(
top: safeAreaTop,
bottom: safeAreaBottom,
child: _buildBody(context),
),
bottomNavigationBar: bottomAction != null
? Padding(
padding: EdgeInsets.fromLTRB(
T.pagePadding,
0,
T.pagePadding,
MediaQuery.of(context).viewInsets.bottom > 0
? MediaQuery.of(context).viewInsets.bottom + 8
: T.pagePadding,
),
child: bottomAction,
)
: null,
);
}
Widget _buildBody(BuildContext context) {
if (status != null) {
if (_isError) return ErrorView(onRetry: onRetry);
if (_isLoading) return const Skeleton();
if (_isEmpty) return empty ?? const EmptyView();
}
return padding
? Padding(
padding: const EdgeInsets.all(T.pagePadding),
child: child,
)
: child;
}
bool get _isLoading => status != null && status!.name == 'loading';
bool get _isError => status != null && status!.name == 'error';
bool get _isEmpty => status != null && status!.name == 'empty';
}
+74
View File
@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// كل bottom sheet في التطبيق يمرّ من هنا: مقبض علوي، زوايا 24.
/// docs/26 §7
class TripzSheet extends StatelessWidget {
const TripzSheet({
super.key,
this.title,
this.child,
this.bottomAction,
this.isScrollControlled = true,
});
final String? title;
final Widget? child;
final Widget? bottomAction;
final bool isScrollControlled;
/// مساعد للاستدعاء بأمر واحد
static Future<T?> show<T>(
BuildContext context, {
String? title,
Widget? child,
Widget? bottomAction,
bool isScrollControlled = true,
}) {
return showModalBottomSheet<T>(
context: context,
isScrollControlled: isScrollControlled,
useSafeArea: true,
builder: (_) => TripzSheet(
title: title,
child: child,
bottomAction: bottomAction,
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final bottomPadding = MediaQuery.of(context).viewInsets.bottom;
return Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, bottomPadding),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: T.pagePadding),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (title != null) ...[
Text(
title!,
style: theme.textTheme.titleMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: T.s16),
],
if (child != null) child!,
if (bottomAction != null) ...[
const SizedBox(height: T.s16),
bottomAction!,
const SizedBox(height: T.s8),
],
],
),
),
),
);
}
}
@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import '../design/tokens.dart';
/// حقل إدخال موحّد: هاتف · OTP · بحث — رسالة الخطأ تحته بالعربية.
/// docs/26 §7
class TripzTextField extends StatelessWidget {
const TripzTextField({
super.key,
this.controller,
this.label,
this.hint,
this.prefix,
this.suffix,
this.keyboardType,
this.obscureText = false,
this.errorText,
this.enabled = true,
this.onChanged,
this.onSubmitted,
this.autofocus = false,
this.textAlign,
this.maxLength,
this.contentPadding,
});
final TextEditingController? controller;
final String? label;
final String? hint;
final Widget? prefix;
final Widget? suffix;
final TextInputType? keyboardType;
final bool obscureText;
final String? errorText;
final bool enabled;
final ValueChanged<String>? onChanged;
final ValueChanged<String>? onSubmitted;
final bool autofocus;
final TextAlign? textAlign;
final int? maxLength;
final EdgeInsetsGeometry? contentPadding;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: T.inputHeight,
child: TextField(
controller: controller,
keyboardType: keyboardType,
obscureText: obscureText,
enabled: enabled,
onChanged: onChanged,
onSubmitted: onSubmitted,
autofocus: autofocus,
textAlign: textAlign ?? TextAlign.start,
maxLength: maxLength,
style: Theme.of(context).textTheme.bodyLarge,
decoration: InputDecoration(
labelText: label,
hintText: hint,
prefixIcon: prefix,
suffixIcon: suffix,
counterText: '',
contentPadding: contentPadding ??
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
),
),
if (errorText != null) ...[
const SizedBox(height: 4),
Text(
errorText!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: cs.error,
),
),
],
],
);
}
}