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
+2
View File
@@ -28,6 +28,8 @@ class ApiClient {
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();
if (device != null) options.headers['x-device-id'] = device;
+22 -22
View File
@@ -180,10 +180,10 @@ packages:
dependency: transitive
description:
name: dbus
sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91"
sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
url: "https://pub.dev"
source: hosted
version: "0.7.13"
version: "0.7.14"
device_info_plus:
dependency: "direct main"
description:
@@ -438,10 +438,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_native_splash
sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff"
sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002"
url: "https://pub.dev"
source: hosted
version: "2.4.8"
version: "2.4.7"
flutter_overlay_window:
dependency: "direct main"
description:
@@ -640,10 +640,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
url: "https://pub.dev"
source: hosted
version: "4.9.1"
version: "4.8.0"
image_picker:
dependency: "direct main"
description:
@@ -656,10 +656,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a"
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
url: "https://pub.dev"
source: hosted
version: "0.8.13+19"
version: "0.8.13+17"
image_picker_for_web:
dependency: transitive
description:
@@ -872,10 +872,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
version: "0.12.19"
version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
@@ -888,10 +888,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.17.0"
mime:
dependency: transitive
description:
@@ -1127,10 +1127,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7"
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
url: "https://pub.dev"
source: hosted
version: "2.4.27"
version: "2.4.23"
shared_preferences_foundation:
dependency: transitive
description:
@@ -1236,10 +1236,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
version: "0.7.9"
timezone:
dependency: transitive
description:
@@ -1291,10 +1291,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
url: "https://pub.dev"
source: hosted
version: "6.3.32"
version: "6.3.30"
url_launcher_ios:
dependency: transitive
description:
@@ -1443,10 +1443,10 @@ packages:
dependency: transitive
description:
name: xml
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
version: "6.6.1"
yaml:
dependency: transitive
description:
@@ -1456,5 +1456,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.12.0 <4.0.0"
flutter: ">=3.44.0"
dart: ">=3.11.0 <4.0.0"
flutter: ">=3.41.0"
@@ -9,9 +9,4 @@ class MethodChannelTripOverlayPlugin extends TripOverlayPluginPlatform {
@visibleForTesting
final methodChannel = const MethodChannel('trip_overlay_plugin');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}
@@ -23,7 +23,4 @@ abstract class TripOverlayPluginPlatform extends PlatformInterface {
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}
@@ -1,27 +1,5 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:trip_overlay_plugin/trip_overlay_plugin_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
MethodChannelTripOverlayPlugin platform = MethodChannelTripOverlayPlugin();
const MethodChannel channel = MethodChannel('trip_overlay_plugin');
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) async {
return '42';
},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('getPlatformVersion', () async {
expect(await platform.getPlatformVersion(), '42');
});
}
@@ -7,9 +7,6 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockTripOverlayPluginPlatform
with MockPlatformInterfaceMixin
implements TripOverlayPluginPlatform {
@override
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
@@ -19,11 +16,5 @@ void main() {
expect(initialPlatform, isInstanceOf<MethodChannelTripOverlayPlugin>());
});
test('getPlatformVersion', () async {
TripOverlayPlugin tripOverlayPlugin = TripOverlayPlugin();
MockTripOverlayPluginPlatform fakePlatform = MockTripOverlayPluginPlatform();
TripOverlayPluginPlatform.instance = fakePlatform;
expect(await tripOverlayPlugin.getPlatformVersion(), '42');
});
}
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';
}

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