feat: implement splash and onboarding flows, update app assets, and add project design documentation

This commit is contained in:
Hamza-Ayed
2026-07-24 04:15:07 +03:00
parent 9f7bc31408
commit 213b459912
208 changed files with 23801 additions and 1066 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 828 B

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 78 KiB

@@ -38,7 +38,7 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="500" height="500"/>
<image name="LaunchImage" width="512" height="279"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>
+65
View File
@@ -0,0 +1,65 @@
import 'package:dio/dio.dart';
import '../config.dart';
/// واجهة مباشرة مع انطلق (map-saas) — لا مرور عبر الباك إند.
///
/// كل نداء يمر عبر هذه النافذة: البحث، التفكيك العكسي، المسار.
/// الباك إند يبقى مسؤولاً عن المصادقة والرحلات والمدفوعات فقط.
class AntlaqApi {
final String apiKey;
final Dio _dio;
AntlaqApi({String? apiKey})
: apiKey = apiKey ?? AppConfig.mapApiKey,
_dio = Dio(BaseOptions(
baseUrl: 'https://map-saas.intaleqapp.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 15),
// المفتاح يمر كترويسة `x-api-key` — map-saas يرفض أي مفتاح في
// query (تحقّق صارم: `property api_key should not exist`).
// المفتاح مقيّد ببصمة التطبيق (bundle id) على map-saas نفسه.
headers: {'x-api-key': apiKey ?? AppConfig.mapApiKey},
));
/// بحث أماكن — GET /api/geocoding/search.
Future<List<dynamic>> search(String query) async {
final r = await _dio.get('/api/geocoding/search', queryParameters: {
'q': query,
});
return (r.data['results'] ?? r.data) as List;
}
/// تفكيك عنوان عكسي — GET /api/geocoding/reverse.
/// يرجّع map-saas **مصفوفة** مرتّبة بالأقرب؛ نأخذ العنصر الأول.
Future<Map<String, dynamic>> reverse(double lat, double lng) async {
final r = await _dio.get('/api/geocoding/reverse', queryParameters: {
'lat': lat,
'lng': lng,
});
final data = r.data;
if (data is List) {
if (data.isEmpty) return {'latitude': lat, 'longitude': lng};
return Map<String, dynamic>.from(data.first as Map);
}
return Map<String, dynamic>.from(data as Map);
}
/// حساب مسار بين نقطتين — GET /api/maps/route.
Future<Map<String, dynamic>> route({
required double fromLat,
required double fromLng,
required double toLat,
required double toLng,
}) async {
final r = await _dio.get('/api/maps/route', queryParameters: {
'fromLat': fromLat,
'fromLng': fromLng,
'toLat': toLat,
'toLng': toLng,
'alternatives': true,
'steps': true,
'locale': 'ar',
});
return r.data as Map<String, dynamic>;
}
}
+11 -3
View File
@@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
import 'package:device_info_plus/device_info_plus.dart';
import '../config.dart';
import '../storage/token_store.dart';
import 'logging_interceptor.dart';
/// عميل HTTP لكل نداءات الـAPI (docs/23 §4).
///
@@ -21,15 +22,22 @@ class ApiClient {
ApiClient(this._store)
: dio = Dio(BaseOptions(
baseUrl: AppConfig.baseUrl,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 20),
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 15),
headers: {'Content-Type': 'application/json'},
)) {
// Resolve device ID once in background — don't block requests
_resolveDeviceId();
dio.interceptors.add(AppLoggingInterceptor());
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) async {
options.headers['x-tenant-id'] = AppConfig.tenantSlug;
// راكب أم سائق — هويتان منفصلتان لنفس الرقم (قرار المالك 2026-07-20 — ب).
options.headers['x-app-role'] = AppConfig.appRole;
final device = await _resolveDeviceId();
// Device ID is cached after first call — non-blocking
final device = _deviceId;
if (device != null) options.headers['x-device-id'] = device;
final token = await _store.access();
@@ -0,0 +1,66 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
/// مساطر طباعة كل طلب HTTP — يظهر فقط في وضع التطوير.
class AppLoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (kDebugMode) {
final method = options.method;
final uri = options.uri;
final payload = options.data;
debugPrint('┌─────── REQUEST ───────');
debugPrint('│ $method $uri');
if (payload != null) {
debugPrint('└────── Payload ───────');
try {
final json = jsonDecode(payload.toString());
debugPrint('${const JsonEncoder.withIndent(' ').convert(json)}');
} catch (_) {
debugPrint('$payload');
}
}
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (kDebugMode) {
final status = response.statusCode;
final uri = response.requestOptions.uri;
final body = response.data;
debugPrint('┌─────── RESPONSE [$status] ───────');
debugPrint('│ $uri');
if (body != null) {
try {
final json = jsonDecode(body.toString());
debugPrint('${const JsonEncoder.withIndent(' ').convert(json)}');
} catch (_) {
debugPrint('$body');
}
}
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (kDebugMode) {
final status = err.response?.statusCode;
final uri = err.requestOptions.uri;
final message = err.message;
debugPrint('┌─────── ERROR [$status] ───────');
debugPrint('│ $uri');
debugPrint('│ $message');
if (err.response?.data != null) {
debugPrint('│ ${err.response?.data}');
}
}
handler.next(err);
}
}
+9
View File
@@ -0,0 +1,9 @@
/// مرجع معلومات التطبيق ورقم الإصدار الموحد.
abstract final class AppInfo {
static const String name = 'Tripz';
static const String version = '1.0.0';
static const int buildNumber = 1;
/// الصيغة المنسقة لرقم الإصدار (مثل v1.0.0).
static String get formattedVersion => 'v$version';
}
+1
View File
@@ -10,6 +10,7 @@ class BuildConfig {
// ألوان المستأجر (البذرة)
static const int primaryColorValue = 0xFF0E7C66;
static const int secondaryColorValue = 0xFF0E7C66;
static const String defaultCountry = 'JO';
// أعلام الميزات — const: tree-shaking يحذف الكود المطفأ في release
// docs/22 §1.5, docs/23 §5
+5 -2
View File
@@ -22,9 +22,12 @@ class AppConfig {
/// دور هذا التطبيق — يميّز تطبيق الراكب عن السائق عند التسجيل.
static const String appRole = 'rider';
/// مفتاح الخريطة — يُمرَّر من البيئة. لا يُكتب في الكود (docs/23 §6).
/// مفتاح الخريطة — يُضمَّن في التطبيق ويُقيَّد ببصمة التطبيق (bundle id
/// للأندرويد/iOS) على map-saas نفسه، فلا يعمل خارج هذا التطبيق (قرار المالك
/// 2026-07-20: خرائط مباشرة إلى map-saas بلا مرور عبر الباك إند). يمكن
/// تجاوزه ببيئة البناء `--dart-define=MAP_API_KEY=...` لكل مستأجر.
static const String mapApiKey = String.fromEnvironment(
'MAP_API_KEY',
defaultValue: '',
defaultValue: 'in_9478b32836d19cff73db3063',
);
}
+2 -1
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../build_config.dart';
import 'tripz_colors.dart';
import 'typography.dart';
@@ -6,7 +7,7 @@ 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 seed = Color(BuildConfig.primaryColorValue); // أخضر تركوازي — بذرة المستأجر
final colorScheme = ColorScheme.fromSeed(
seedColor: seed,
+1
View File
@@ -11,6 +11,7 @@ abstract final class T {
static const double s24 = 24;
static const double s32 = 32;
static const double s48 = 48;
static const double s64 = 64;
/// حشوة الصفحة الافتراضية
static const double pagePadding = s16;
+6 -1
View File
@@ -1,6 +1,8 @@
import 'package:get_it/get_it.dart';
import 'api/api_client.dart';
import 'api/antlaq_api.dart';
import 'storage/token_store.dart';
import 'storage/saved_places_store.dart';
import '../features/auth/data/auth_repository.dart';
import '../features/home/data/maps_repository.dart';
import '../features/home/data/tariff_repository.dart';
@@ -21,8 +23,9 @@ Future<void> setupDi() async {
);
// ميزات Phase 1: الخرائط + الأسعار
getIt.registerLazySingleton<AntlaqApi>(() => AntlaqApi());
getIt.registerLazySingleton<MapsRepository>(
() => MapsRepository(getIt<ApiClient>()),
() => MapsRepository(getIt<AntlaqApi>(), getIt<ApiClient>()),
);
getIt.registerLazySingleton<TariffRepository>(
() => TariffRepository(getIt<ApiClient>()),
@@ -44,4 +47,6 @@ Future<void> setupDi() async {
getIt.registerLazySingleton<FcmService>(
() => FcmService(getIt<TripEventMerger>()),
);
getIt.registerLazySingleton<SavedPlacesStore>(() => SavedPlacesStore());
}
+53 -1
View File
@@ -172,6 +172,58 @@
"distance": {"type": "String"}
}
},
"edit": "تعديل",
"resendIn": "إعادة إرسال خلال {seconds}ث",
"@resendIn": {
"placeholders": {
"seconds": {"type": "int"}
}
},
"resendCode": "إعادة إرسال الرمز",
"nameTitle": "ما اسمك؟",
"nameSubtitle": "ساعدنا في تخصيص تجربتك",
"nameHint": "اسمك",
"firstNameHint": "الاسم الأول",
"lastNameHint": "اسم العائلة",
"nameButton": "متابعة",
"reconnect": "جارٍ إعادة الاتصال...",
"tripExpired": "انتهت صلاحية الرحلة"
"tripExpired": "انتهت صلاحية الرحلة",
"loginTitle": "مرحباً بك في Tripz",
"phoneLoginSubtitle": "أدخل رقم هاتفك لتسجيل الدخول",
"sendOtpButton": "إرسال رمز التحقق",
"nameEntryTitle": "ما اسمك؟",
"homeTitle": "الرئيسية",
"destinationTitle": "الوجهة",
"routeTitle": "المسار",
"whereToHint": "إلى أين تريد الذهاب؟",
"currentLocation": "الموقع الحالي",
"savedPlaces": "الأماكن المحفوظة",
"home": "المنزل",
"work": "العمل",
"pickFromMap": "اختيار من الخريطة",
"searchHint": "ابحث عن مكان...",
"noResultsFound": "لا توجد نتائج",
"connecting": "جارٍ الاتصال...",
"tripSummary": "ملخص الرحلة",
"totalDistance": "المسافة الإجمالية",
"estimatedTime": "الوقت المقدر",
"totalFare": "الأجرة الإجمالية",
"chooseRide": "اختر نوع الرحلة",
"promoCode": "كود الترويج",
"errorTitle": "حدث خطأ",
"errorSubtitle": "يرجى المحاولة مرة أخرى",
"emptyTitle": "لا توجد بيانات",
"emptySubtitle": "لم يتم العثور على محتوى حالياً",
"signIn": "تسجيل الدخول",
"confirmPhoneNumber": "تأكيد رقم الهاتف",
"driverRatingValue": "التقييم",
"missingDataError": "بيانات مفقودة",
"onboardingTitle1": "اطلب بلمسة واحدة",
"onboardingSubtitle1": "أدخل وجهتك واحصل على سائق في ثوانٍ",
"onboardingTitle2": "تتبع رحلتك مباشرة",
"onboardingSubtitle2": "شاهد سائقك وهو يقترب في الوقت الفعلي",
"onboardingTitle3": "ادفع كما يناسبك",
"onboardingSubtitle3": "اختر الدفع نقدًا أو بالبطاقة أو عبر المحفظة",
"getStarted": "ابدأ الآن",
"skip": "تخطي"
}

Some files were not shown because too many files have changed in this diff Show More