feat: implement splash and onboarding flows, update app assets, and add project design documentation
@@ -28,6 +28,8 @@ class ApiClient {
|
|||||||
dio.interceptors.add(InterceptorsWrapper(
|
dio.interceptors.add(InterceptorsWrapper(
|
||||||
onRequest: (options, handler) async {
|
onRequest: (options, handler) async {
|
||||||
options.headers['x-tenant-id'] = AppConfig.tenantSlug;
|
options.headers['x-tenant-id'] = AppConfig.tenantSlug;
|
||||||
|
// راكب أم سائق — هويتان منفصلتان لنفس الرقم (قرار المالك 2026-07-20 — ب).
|
||||||
|
options.headers['x-app-role'] = AppConfig.appRole;
|
||||||
|
|
||||||
final device = await _resolveDeviceId();
|
final device = await _resolveDeviceId();
|
||||||
if (device != null) options.headers['x-device-id'] = device;
|
if (device != null) options.headers['x-device-id'] = device;
|
||||||
|
|||||||
@@ -180,10 +180,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91"
|
sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.13"
|
version: "0.7.14"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -438,10 +438,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_native_splash
|
name: flutter_native_splash
|
||||||
sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff"
|
sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.8"
|
version: "2.4.7"
|
||||||
flutter_overlay_window:
|
flutter_overlay_window:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -640,10 +640,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
|
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.9.1"
|
version: "4.8.0"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -656,10 +656,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a"
|
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+19"
|
version: "0.8.13+17"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -872,10 +872,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.18"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -888,10 +888,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1127,10 +1127,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7"
|
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.27"
|
version: "2.4.23"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1236,10 +1236,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.9"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1291,10 +1291,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
|
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.32"
|
version: "6.3.30"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1443,10 +1443,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "6.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1456,5 +1456,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.12.0 <4.0.0"
|
dart: ">=3.11.0 <4.0.0"
|
||||||
flutter: ">=3.44.0"
|
flutter: ">=3.41.0"
|
||||||
|
|||||||
@@ -9,9 +9,4 @@ class MethodChannelTripOverlayPlugin extends TripOverlayPluginPlatform {
|
|||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
final methodChannel = const MethodChannel('trip_overlay_plugin');
|
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;
|
_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:flutter_test/flutter_test.dart';
|
||||||
import 'package:trip_overlay_plugin/trip_overlay_plugin_method_channel.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
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
|
class MockTripOverlayPluginPlatform
|
||||||
with MockPlatformInterfaceMixin
|
with MockPlatformInterfaceMixin
|
||||||
implements TripOverlayPluginPlatform {
|
implements TripOverlayPluginPlatform {
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String?> getPlatformVersion() => Future.value('42');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -19,11 +16,5 @@ void main() {
|
|||||||
expect(initialPlatform, isInstanceOf<MethodChannelTripOverlayPlugin>());
|
expect(initialPlatform, isInstanceOf<MethodChannelTripOverlayPlugin>());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getPlatformVersion', () async {
|
|
||||||
TripOverlayPlugin tripOverlayPlugin = TripOverlayPlugin();
|
|
||||||
MockTripOverlayPluginPlatform fakePlatform = MockTripOverlayPluginPlatform();
|
|
||||||
TripOverlayPluginPlatform.instance = fakePlatform;
|
|
||||||
|
|
||||||
expect(await tripOverlayPlugin.getPlatformVersion(), '42');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 387 B |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 493 B |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 734 B |
|
Before Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 369 KiB After Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 679 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 78 KiB |
@@ -38,7 +38,7 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="500" height="500"/>
|
<image name="LaunchImage" width="512" height="279"/>
|
||||||
<image name="LaunchBackground" width="1" height="1"/>
|
<image name="LaunchBackground" width="1" height="1"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import '../config.dart';
|
import '../config.dart';
|
||||||
import '../storage/token_store.dart';
|
import '../storage/token_store.dart';
|
||||||
|
import 'logging_interceptor.dart';
|
||||||
|
|
||||||
/// عميل HTTP لكل نداءات الـAPI (docs/23 §4).
|
/// عميل HTTP لكل نداءات الـAPI (docs/23 §4).
|
||||||
///
|
///
|
||||||
@@ -21,15 +22,22 @@ class ApiClient {
|
|||||||
ApiClient(this._store)
|
ApiClient(this._store)
|
||||||
: dio = Dio(BaseOptions(
|
: dio = Dio(BaseOptions(
|
||||||
baseUrl: AppConfig.baseUrl,
|
baseUrl: AppConfig.baseUrl,
|
||||||
connectTimeout: const Duration(seconds: 15),
|
connectTimeout: const Duration(seconds: 10),
|
||||||
receiveTimeout: const Duration(seconds: 20),
|
receiveTimeout: const Duration(seconds: 15),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
)) {
|
)) {
|
||||||
|
// Resolve device ID once in background — don't block requests
|
||||||
|
_resolveDeviceId();
|
||||||
|
|
||||||
|
dio.interceptors.add(AppLoggingInterceptor());
|
||||||
dio.interceptors.add(InterceptorsWrapper(
|
dio.interceptors.add(InterceptorsWrapper(
|
||||||
onRequest: (options, handler) async {
|
onRequest: (options, handler) async {
|
||||||
options.headers['x-tenant-id'] = AppConfig.tenantSlug;
|
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;
|
if (device != null) options.headers['x-device-id'] = device;
|
||||||
|
|
||||||
final token = await _store.access();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
}
|
||||||