feat: implement core architecture, design system, localization, and trip features for both driver and rider apps.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,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),
|
||||
);
|
||||
}
|
||||
@@ -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>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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": "اكتب رسالة..."
|
||||
}
|
||||
@@ -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...';
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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('أعد المحاولة'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user