Update codebase

This commit is contained in:
Hamza-Ayed
2026-08-05 14:12:41 +03:00
parent 58928cd258
commit 95e2e4f35d
51 changed files with 3141 additions and 390 deletions
@@ -70,7 +70,14 @@ class Trip extends Equatable {
this.driverPhone,
this.vehiclePlate,
this.vehicleModel,
this.vehicleColor,
this.vehicleColorHex,
this.driverRating,
this.requestedAt,
this.distanceKm,
this.durationMin,
this.paymentMethod,
this.serviceClass,
});
final String id;
@@ -90,8 +97,34 @@ class Trip extends Equatable {
final String? driverPhone;
final String? vehiclePlate;
final String? vehicleModel;
/// لون المركبة كاسم (`White`, `أبيض`) — **ما يبحث عنه الراكب في الشارع
/// قبل اللوحة**. الخادم يرسله في `vehicle.color`
/// (`trips.service.ts:427`).
final String? vehicleColor;
/// اللون كـhex إن وُجد (`vehicles` عندها `color_hex`). يُقدَّم على الاسم
/// عند الرسم لأنه دقيق، والاسم يبقى للعرض النصّي.
final String? vehicleColorHex;
final String? driverRating;
// ── حقول السجلّ ────────────────────────────────────────────────────────
// كلها موجودة في `trip.entity.ts` وتصل مع `GET /trips/mine`، وكانت
// مُهمَلة في القراءة — فبدت بطاقة «رحلاتي» فارغة إلا من سعر وحالة.
/// وقت الطلب (`requested_at`, `CreateDateColumn`).
final DateTime? requestedAt;
final double? distanceKm;
final double? durationMin;
/// `cash` افتراضاً في الخادم — يُعرض كي يعرف الراكب كيف دفع.
final String? paymentMethod;
/// نوع الخدمة (`economy` افتراضاً).
final String? serviceClass;
factory Trip.fromJson(Map<String, dynamic> json) {
GeoPoint? point(String key) {
final v = json[key];
@@ -99,6 +132,11 @@ class Trip extends Equatable {
}
final driver = json['driver'] as Map<String, dynamic>?;
// المركبة تصل بشكلين: مسطّحة (`vehicle_plate`) في ردّ الرحلة، ومتداخلة
// (`vehicle: {plate, color}`) في حدث `assigned` (`trips.service.ts:423`).
// القراءة تقبل الاثنين بدل أن تُفرغ البطاقة عند أحدهما.
final vehicle = (json['vehicle'] ?? driver?['vehicle'])
as Map<String, dynamic>?;
return Trip(
id: json['id'] as String,
status: TripStatus.parse(json['status'] as String?),
@@ -112,11 +150,26 @@ class Trip extends Equatable {
driverName: driver?['name'] as String? ?? json['driver_name'] as String?,
driverPhone:
driver?['phone'] as String? ?? json['driver_phone'] as String?,
vehiclePlate:
driver?['vehicle_plate'] as String? ?? json['vehicle_plate'] as String?,
vehicleModel:
driver?['vehicle_model'] as String? ?? json['vehicle_model'] as String?,
vehiclePlate: vehicle?['plate'] as String? ??
driver?['vehicle_plate'] as String? ??
json['vehicle_plate'] as String?,
vehicleModel: vehicle?['model'] as String? ??
driver?['vehicle_model'] as String? ??
json['vehicle_model'] as String?,
vehicleColor: vehicle?['color'] as String? ??
driver?['vehicle_color'] as String? ??
json['vehicle_color'] as String?,
vehicleColorHex: vehicle?['color_hex'] as String? ??
driver?['vehicle_color_hex'] as String? ??
json['vehicle_color_hex'] as String?,
driverRating: driver?['rating']?.toString(),
requestedAt: DateTime.tryParse(json['requested_at']?.toString() ?? ''),
// المسافة الفعلية أدقّ من المقدّرة حين تتوفّر بعد انتهاء الرحلة.
distanceKm: (json['actual_distance_km'] as num?)?.toDouble() ??
(json['distance_km'] as num?)?.toDouble(),
durationMin: (json['duration_min'] as num?)?.toDouble(),
paymentMethod: json['payment_method'] as String?,
serviceClass: json['service_class'] as String?,
);
}
@@ -0,0 +1,74 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'models/geo_point.dart';
import 'models/place.dart';
/// الوجهات الأخيرة — **محلّية**، كما في سيرو
/// (`rp_favorites.dart: RpRecentsRow` + جدول `recentLocations`).
///
/// أكثر رحلات الراكب تتكرّر: البيت، العمل، بيت الأهل. وبلا هذا يعيد كتابة
/// الاسم نفسه كل مرة — وهي أكبر خطوة ضائعة في الشاشة الرئيسية.
///
/// محلّي لا خادمي عن قصد: `GET /trips/mine` يرجّع رحلات لا **أماكن**
/// (بلا اسم الوجهة)، فبناء «الأخيرة» عليه يعطي أسطراً بلا عناوين.
class RecentPlacesStore {
static const _key = 'recent_places';
/// خمسة تكفي صفّاً أفقياً واحداً؛ أكثر منها يصير قائمةً تُتصفَّح لا تُلمح.
static const _max = 5;
List<Place> _items = const [];
List<Place> get items => _items;
Future<void> load() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_key);
if (raw == null || raw.isEmpty) return;
try {
final list = jsonDecode(raw) as List<dynamic>;
_items = list
.whereType<Map<String, dynamic>>()
.map(_decode)
.toList(growable: false);
} catch (_) {
_items = const [];
}
}
/// يضيف المكان في الصدارة، ويزيح تكراره إن سبق — لا يتكدّس نفس المكان.
Future<void> add(Place place) async {
if (place.label.isEmpty) return;
_items = [
place,
..._items.where((p) => p.id != place.id || p.label != place.label),
].take(_max).toList(growable: false);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
_key,
jsonEncode(_items.map(_encode).toList(growable: false)),
);
}
static Map<String, dynamic> _encode(Place p) => {
'id': p.id,
'name': p.name,
'address': p.address,
'category': p.category,
'lat': p.point.lat,
'lng': p.point.lng,
};
static Place _decode(Map<String, dynamic> json) => Place(
id: json['id']?.toString() ?? '',
name: json['name'] as String? ?? '',
address: json['address'] as String? ?? '',
category: json['category'] as String? ?? '',
point: GeoPoint(
(json['lat'] as num?)?.toDouble() ?? 0,
(json['lng'] as num?)?.toDouble() ?? 0,
),
);
}