feat: implement core architecture, design system, localization, and trip features for both driver and rider apps.

This commit is contained in:
Hamza-Ayed
2026-07-19 16:21:58 +03:00
parent 959e2fa758
commit c4dfeb4c6f
207 changed files with 14017 additions and 5567 deletions
@@ -0,0 +1,45 @@
import 'package:equatable/equatable.dart';
/// نموذج مكان من نتائج البحث (autocomplete / geocode).
class PlaceModel extends Equatable {
final String? placeId;
final String name;
final String? address;
final double lat;
final double lng;
const PlaceModel({
this.placeId,
required this.name,
this.address,
required this.lat,
required this.lng,
});
factory PlaceModel.fromJson(Map<String, dynamic> j) => PlaceModel(
placeId: j['placeId'] as String?,
name: (j['description'] ?? j['name'] ?? '') as String,
address: j['address'] as String?,
lat: (j['lat'] as num).toDouble(),
lng: (j['lng'] as num).toDouble(),
);
factory PlaceModel.fromGeocode(Map<String, dynamic> j) => PlaceModel(
placeId: j['placeId'] as String?,
name: (j['name'] ?? j['formattedAddress'] ?? '') as String,
address: j['formattedAddress'] as String?,
lat: (j['lat'] as num).toDouble(),
lng: (j['lng'] as num).toDouble(),
);
Map<String, dynamic> toJson() => {
if (placeId != null) 'placeId': placeId,
'name': name,
if (address != null) 'address': address,
'lat': lat,
'lng': lng,
};
@override
List<Object?> get props => [placeId, name, address, lat, lng];
}
@@ -0,0 +1,85 @@
import 'package:equatable/equatable.dart';
/// فئة رحلة واحدة من الاستجابة (tariff/quote).
class QuoteClassModel extends Equatable {
final String vehicleClass;
final String name;
final int price;
final String currency;
final int capacity;
final String? description;
final String? imageUrl;
final int estimatedTime; // ثوانٍ تقريبية
const QuoteClassModel({
required this.vehicleClass,
required this.name,
required this.price,
required this.currency,
required this.capacity,
this.description,
this.imageUrl,
required this.estimatedTime,
});
factory QuoteClassModel.fromJson(Map<String, dynamic> j) => QuoteClassModel(
vehicleClass: j['vehicleClass'] as String,
name: (j['name'] ?? j['vehicleClass']) as String,
price: j['price'] as int,
currency: (j['currency'] ?? 'JOD') as String,
capacity: j['capacity'] as int? ?? 4,
description: j['description'] as String?,
imageUrl: j['imageUrl'] as String?,
estimatedTime: j['estimatedTime'] as int? ?? 0,
);
String get estimatedTimeText {
final min = estimatedTime ~/ 60;
return '$min min';
}
@override
List<Object?> get props => [vehicleClass, name, price, currency, capacity];
}
/// استجابة tariff/quote الكاملة.
class QuoteModel extends Equatable {
final String? pickupAddress;
final String? dropoffAddress;
final int distanceMeters;
final int durationSeconds;
final String? distanceText;
final String? durationText;
final String currency;
final List<QuoteClassModel> classes;
const QuoteModel({
this.pickupAddress,
this.dropoffAddress,
required this.distanceMeters,
required this.durationSeconds,
this.distanceText,
this.durationText,
required this.currency,
required this.classes,
});
factory QuoteModel.fromJson(Map<String, dynamic> j) {
final data = j['data'] ?? j;
return QuoteModel(
pickupAddress: data['pickupAddress'] as String?,
dropoffAddress: data['dropoffAddress'] as String?,
distanceMeters: (data['distanceMeters'] as num?)?.toInt() ?? 0,
durationSeconds: (data['durationSeconds'] as num?)?.toInt() ?? 0,
distanceText: data['distanceText'] as String?,
durationText: data['durationText'] as String?,
currency: (data['currency'] ?? 'JOD') as String,
classes: (data['classes'] as List? ?? [])
.map((c) => QuoteClassModel.fromJson(c as Map<String, dynamic>))
.toList(),
);
}
@override
List<Object?> get props => [distanceMeters, durationSeconds, currency, classes];
}
@@ -0,0 +1,35 @@
import 'package:equatable/equatable.dart';
/// فئة رحلة من ride-types endpoint (تُستخدم كقائمة المرجع).
class RideTypeModel extends Equatable {
final String id;
final String name;
final String? vehicleClass;
final int capacity;
final String? description;
final String? imageUrl;
final bool active;
const RideTypeModel({
required this.id,
required this.name,
this.vehicleClass,
required this.capacity,
this.description,
this.imageUrl,
required this.active,
});
factory RideTypeModel.fromJson(Map<String, dynamic> j) => RideTypeModel(
id: j['id'] as String,
name: (j['name'] ?? '') as String,
vehicleClass: j['vehicleClass'] as String?,
capacity: j['capacity'] as int? ?? 4,
description: j['description'] as String?,
imageUrl: j['imageUrl'] as String?,
active: j['active'] as bool? ?? true,
);
@override
List<Object?> get props => [id, name, vehicleClass, capacity, active];
}
@@ -0,0 +1,74 @@
import 'package:equatable/equatable.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
/// خطاب polyline مشفر ومسافة/وقت تقريبيان.
class RouteModel extends Equatable {
final int distanceMeters;
final int durationSeconds;
final String? polyline;
const RouteModel({
required this.distanceMeters,
required this.durationSeconds,
this.polyline,
});
factory RouteModel.fromJson(Map<String, dynamic> j) {
final dist = j['distanceMeters'] ?? j['distance'];
final dur = j['durationSeconds'] ?? j['duration'];
return RouteModel(
distanceMeters: dist is int ? dist : int.tryParse('$dist') ?? 0,
durationSeconds: dur is int ? dur : int.tryParse('$dur') ?? 0,
polyline: j['polyline'] as String?,
);
}
String get distanceText {
if (distanceMeters < 1000) return '$distanceMeters m';
return '${(distanceMeters / 1000).toStringAsFixed(1)} km';
}
String get durationText {
final min = durationSeconds ~/ 60;
if (min < 60) return '$min min';
final h = min ~/ 60;
final rem = min % 60;
return '$h h ${rem > 0 ? '$rem min' : ''}';
}
List<LatLng> decodePolyline() {
if (polyline == null || polyline!.isEmpty) return [];
return _decodeEncodedPolyline(polyline!);
}
/// فك تشفير polyline وفقاً لخوارزمية Google.
static List<LatLng> _decodeEncodedPolyline(String encoded) {
final List<LatLng> poly = [];
int index = 0, lat = 0, lng = 0;
while (index < encoded.length) {
int b, shift = 0, result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
lat += ((result & 1) != 0) ? ~(result >> 1) : (result >> 1);
shift = 0;
result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
lng += ((result & 1) != 0) ? ~(result >> 1) : (result >> 1);
poly.add(LatLng(lat / 1e5, lng / 1e5));
}
return poly;
}
@override
List<Object?> get props => [distanceMeters, durationSeconds, polyline];
}
@@ -0,0 +1,33 @@
import 'package:equatable/equatable.dart';
/// نقطة توقف أثناء الرحلة.
class StopModel extends Equatable {
final double lat;
final double lng;
final String? label;
final String? address;
const StopModel({
required this.lat,
required this.lng,
this.label,
this.address,
});
factory StopModel.fromJson(Map<String, dynamic> j) => StopModel(
lat: (j['lat'] as num).toDouble(),
lng: (j['lng'] as num).toDouble(),
label: j['label'] as String?,
address: j['address'] as String?,
);
Map<String, dynamic> toJson() => {
'lat': lat,
'lng': lng,
if (label != null) 'label': label,
if (address != null) 'address': address,
};
@override
List<Object?> get props => [lat, lng, label, address];
}