Update codebase
This commit is contained in:
@@ -12,6 +12,7 @@ import '../data/models/geo_point.dart';
|
||||
import '../data/models/place.dart';
|
||||
import '../data/models/ride_type.dart';
|
||||
import '../data/models/trip.dart';
|
||||
import '../data/recent_places_store.dart';
|
||||
import '../data/trip_repository.dart';
|
||||
import 'ride_state.dart';
|
||||
|
||||
@@ -25,16 +26,19 @@ class RideCubit extends Cubit<RideState> {
|
||||
required MapsRepository maps,
|
||||
required LocationService location,
|
||||
required RealtimeService realtime,
|
||||
required RecentPlacesStore recents,
|
||||
}) : _trips = trips,
|
||||
_maps = maps,
|
||||
_location = location,
|
||||
_realtime = realtime,
|
||||
_recents = recents,
|
||||
super(const RideState());
|
||||
|
||||
final TripRepository _trips;
|
||||
final MapsRepository _maps;
|
||||
final LocationService _location;
|
||||
final RealtimeService _realtime;
|
||||
final RecentPlacesStore _recents;
|
||||
|
||||
StreamSubscription<RealtimeEvent>? _events;
|
||||
Timer? _searchTimeout;
|
||||
@@ -116,8 +120,6 @@ class RideCubit extends Cubit<RideState> {
|
||||
|
||||
void couponChanged(String code) => emit(state.copyWith(couponCode: code));
|
||||
|
||||
void openPlanner() => emit(state.copyWith(phase: RidePhase.planning));
|
||||
|
||||
void collapsePlanner() => emit(state.copyWith(phase: RidePhase.idle));
|
||||
|
||||
/// تبديل النوع **لا يُطلق نداءً**: كل الأنواع مسعَّرة مسبقاً في [_priceAll].
|
||||
@@ -150,7 +152,12 @@ class RideCubit extends Cubit<RideState> {
|
||||
});
|
||||
}
|
||||
|
||||
/// الأماكن الأخيرة — تُقرأ في الشاشة، وتُحدَّث عند كل اختيار وجهة.
|
||||
List<Place> get recentPlaces => _recents.items;
|
||||
|
||||
Future<void> pickPlace(Place place, PickTarget target) async {
|
||||
// الوجهة وحدها تُحفظ: «الأخيرة» تعني أين ذهبتُ لا من أين انطلقت.
|
||||
if (target == PickTarget.destination) unawaited(_recents.add(place));
|
||||
emit(state.copyWith(
|
||||
origin: target == PickTarget.origin ? place.point : null,
|
||||
originLabel: target == PickTarget.origin ? place.label : null,
|
||||
@@ -416,6 +423,29 @@ class RideCubit extends Cubit<RideState> {
|
||||
));
|
||||
}
|
||||
|
||||
// ── المحاكاة ───────────────────────────────────────────────────────────
|
||||
|
||||
/// عَلَم بناء: `--dart-define=SIMULATE_TRIP=true`.
|
||||
///
|
||||
/// **`const` عمداً** — في بناء الإنتاج يصير الشرط ثابتاً زمن الترجمة
|
||||
/// فيُحذف كل نداء محاكاة من الـbinary (نفس منطق `Features` في docs/23 §5).
|
||||
static const bool simulationEnabled =
|
||||
bool.fromEnvironment('SIMULATE_TRIP');
|
||||
|
||||
/// مدخل المحاكي — يمرّ بـ[_attachTrip] نفسه الذي يمرّ به `trip:update`
|
||||
/// الحقيقي، فالشاشات لا تعرف الفرق ولا يُختبر مسارٌ ثانٍ.
|
||||
void injectSimulatedTrip(Trip trip) {
|
||||
if (!simulationEnabled || isClosed) return;
|
||||
_stopSearchGuards();
|
||||
_attachTrip(trip);
|
||||
}
|
||||
|
||||
void injectSimulatedDriverLocation(GeoPoint point) {
|
||||
if (!simulationEnabled || isClosed) return;
|
||||
// نفس مسار الحدث الحقيقي — فمسار الاقتراب يُحسب في المحاكاة أيضاً.
|
||||
_onDriverMoved(point);
|
||||
}
|
||||
|
||||
void _onRealtime(RealtimeEvent event) {
|
||||
switch (event.name) {
|
||||
case RealtimeEvent.tripUpdate:
|
||||
@@ -427,10 +457,62 @@ class RideCubit extends Cubit<RideState> {
|
||||
final lat = event.data['lat'];
|
||||
final lng = event.data['lng'];
|
||||
if (lat is num && lng is num) {
|
||||
emit(state.copyWith(
|
||||
driverLocation: GeoPoint(lat.toDouble(), lng.toDouble()),
|
||||
));
|
||||
_onDriverMoved(GeoPoint(lat.toDouble(), lng.toDouble()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── مسار الاقتراب ──────────────────────────────────────────────────────
|
||||
|
||||
/// أقلّ إزاحة تستدعي إعادة رسم المسار، بالدرجات (~50 متراً).
|
||||
///
|
||||
/// السائق يبثّ موقعه كل بضع ثوانٍ؛ نداءُ مسارٍ عند كل بثّة يعني عشرات
|
||||
/// النداءات في الدقيقة بلا فرق مرئي. وهذا الحدّ يلتقط **تغيّر الاتجاه**
|
||||
/// (انعطاف فعلي) ويتجاهل رجفة GPS في المكان.
|
||||
static const double _redrawThreshold = 0.0005;
|
||||
|
||||
GeoPoint? _lastPickupFrom;
|
||||
bool _pickupRouting = false;
|
||||
|
||||
void _onDriverMoved(GeoPoint point) {
|
||||
emit(state.copyWith(driverLocation: point));
|
||||
|
||||
// المسار يُرسم أثناء الاقتراب فقط — بعد بدء الرحلة صار مسار الرحلة هو
|
||||
// المعروض، ورسم خطٍّ للسائق إلى راكبٍ في سيارته بلا معنى.
|
||||
final approaching = const {
|
||||
TripStatus.assigned,
|
||||
TripStatus.driverArriving,
|
||||
}.contains(state.trip?.status);
|
||||
if (!approaching) {
|
||||
if (state.pickupRoute != null) {
|
||||
emit(state.copyWith(clearPickupRoute: true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final rider = state.origin;
|
||||
if (rider == null || _pickupRouting) return;
|
||||
|
||||
final last = _lastPickupFrom;
|
||||
final moved = last == null ||
|
||||
(point.lat - last.lat).abs() > _redrawThreshold ||
|
||||
(point.lng - last.lng).abs() > _redrawThreshold;
|
||||
if (!moved) return;
|
||||
|
||||
_lastPickupFrom = point;
|
||||
unawaited(_routePickup(point, rider));
|
||||
}
|
||||
|
||||
Future<void> _routePickup(GeoPoint from, GeoPoint to) async {
|
||||
_pickupRouting = true;
|
||||
try {
|
||||
final route = await _maps.route(from, to);
|
||||
if (!isClosed) emit(state.copyWith(pickupRoute: route));
|
||||
} on ApiException {
|
||||
// تعذّر المسار لا يُخفي السائق ولا يُعرض خطأً — البطاقة تبقى بلا
|
||||
// وقتٍ ومسافة، والمحاولة تتكرّر مع الحركة التالية.
|
||||
} finally {
|
||||
_pickupRouting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user