import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../data/driver_trip_repository.dart'; import '../data/driver_socket_service.dart'; import '../data/trip_event_merger.dart'; import 'package:driver/features/trip/data/models/driver_trip_model.dart'; // ─── أحداث ─── sealed class DriverTripEvent extends Equatable { const DriverTripEvent(); @override List get props => []; } /// فحص رحلة نشطة عند بدء التشغيل. class DriverTripCheckActive extends DriverTripEvent { const DriverTripCheckActive(); } /// تم قبول رحلة (من OffersBloc). class DriverTripAccepted extends DriverTripEvent { final DriverTripModel trip; const DriverTripAccepted(this.trip); @override List get props => [trip.id]; } /// تحديث حالة من WebSocket أو FCM. class DriverTripUpdateReceived extends DriverTripEvent { final String tripId; final String status; final Map extra; const DriverTripUpdateReceived({ required this.tripId, required this.status, this.extra = const {}, }); @override List get props => [tripId, status]; } /// تغيير الحالة يدوياً (من زر السائق). class DriverTripStatusChanged extends DriverTripEvent { final String status; const DriverTripStatusChanged(this.status); @override List get props => [status]; } /// إلغاء الرحلة من السائق. class DriverTripCancelRequested extends DriverTripEvent { const DriverTripCancelRequested(); } /// مسح الرحلة من الذاكرة. class DriverTripClear extends DriverTripEvent { const DriverTripClear(); } // ─── حالة ─── class DriverTripState extends Equatable { final DriverTripModel? trip; final bool loading; final String? error; final bool cancelled; const DriverTripState({ this.trip, this.loading = false, this.error, this.cancelled = false, }); DriverTripState copyWith({ DriverTripModel? trip, bool? loading, String? error, bool? cancelled, bool clearError = false, }) { return DriverTripState( trip: trip ?? this.trip, loading: loading ?? this.loading, error: clearError ? null : (error ?? this.error), cancelled: cancelled ?? this.cancelled, ); } /// هل هناك رحلة نشطة؟ bool get hasActiveTrip { if (trip == null) return false; return !['completed', 'paid', 'cancelled', 'expired'] .contains(trip!.status); } /// الحالة التالية المتوقعة للسائق. String? get nextStatus { if (trip == null) return null; switch (trip!.status) { case 'assigned': return 'driver_arriving'; case 'driver_arriving': return 'driver_arrived'; case 'driver_arrived': return 'in_progress'; case 'in_progress': return 'completed'; default: return null; } } /// هل يمكن للسائق إلغاء الرحلة حالياً؟ bool get canCancel { if (trip == null) return false; return ['assigned', 'driver_arriving'].contains(trip!.status); } @override List get props => [trip, loading, error, cancelled]; } // ─── Bloc ─── /// DriverTripBloc — يدير دورة حياة الرحلة من وجهة نظر السائق. /// /// الحالات: assigned → driver_arriving → driver_arrived → in_progress → completed class DriverTripBloc extends Bloc { final DriverTripRepository _tripRepo; final DriverSocketService _socket; final DriverTripEventMerger _merger; Timer? _reconciliationTimer; DriverTripBloc({ required DriverTripRepository tripRepo, required DriverSocketService socket, DriverTripEventMerger? merger, }) : _tripRepo = tripRepo, _socket = socket, _merger = merger ?? DriverTripEventMerger(), super(const DriverTripState()) { on(_onCheckActive); on(_onAccepted); on(_onUpdateReceived); on(_onStatusChanged); on(_onCancel); on(_onClear); // استمع لتحديثات Merger. _merger.updateStream.listen((update) { add(DriverTripUpdateReceived( tripId: update.tripId, status: update.status, extra: update.extra, )); }); } Future _onCheckActive( DriverTripCheckActive event, Emitter emit) async { emit(state.copyWith(loading: true)); try { final trip = await _tripRepo.getTrip('me'); // backend يُعيد الرحلة النشطة للسائق if (trip != null && trip.status != 'completed' && trip.status != 'paid') { emit(state.copyWith(trip: trip, loading: false)); _joinTripRoom(trip.id); _startReconciliation(trip.id); } else { emit(state.copyWith(loading: false)); } } catch (_) { emit(state.copyWith(loading: false)); } } void _onAccepted(DriverTripAccepted event, Emitter emit) { emit(state.copyWith(trip: event.trip)); _joinTripRoom(event.trip.id); _startReconciliation(event.trip.id); } void _onUpdateReceived( DriverTripUpdateReceived event, Emitter emit) { final currentTrip = state.trip; if (currentTrip == null || currentTrip.id != event.tripId) return; // تصفية صامتة: تجاهل الحالات القديمة. if (isTerminalStatus(event.status)) { emit(state.copyWith( trip: currentTrip.copyWith(status: event.status), )); _stopReconciliation(); _socket.leaveTrip(event.tripId); return; } emit(state.copyWith( trip: currentTrip.copyWith(status: event.status), clearError: true, )); } Future _onStatusChanged( DriverTripStatusChanged event, Emitter emit) async { if (state.trip == null) return; emit(state.copyWith(loading: true, clearError: true)); final trip = await _tripRepo.updateStatus(state.trip!.id, event.status); if (trip == null) { emit(state.copyWith(loading: false, error: 'Failed to update status')); return; } emit(state.copyWith(trip: trip, loading: false)); if (isTerminalStatus(trip.status)) { _stopReconciliation(); _socket.leaveTrip(trip.id); } } Future _onCancel( DriverTripCancelRequested event, Emitter emit) async { if (state.trip == null) return; emit(state.copyWith(loading: true, clearError: true)); final trip = await _tripRepo.cancelTrip(state.trip!.id); if (trip == null) { emit(state.copyWith(loading: false, error: 'Failed to cancel trip')); return; } emit(state.copyWith(trip: trip, cancelled: true, loading: false)); _stopReconciliation(); _socket.leaveTrip(trip.id); } void _onClear(DriverTripClear event, Emitter emit) { _stopReconciliation(); emit(const DriverTripState()); } // ─── مساعدات ─── void _joinTripRoom(String tripId) { _socket.joinTrip(tripId); } void _startReconciliation(String tripId) { _stopReconciliation(); _reconciliationTimer = Timer.periodic(const Duration(seconds: 25), (_) async { try { final trip = await _tripRepo.getTrip(tripId); if (trip != null) { add(DriverTripUpdateReceived( tripId: trip.id, status: trip.status, )); } } catch (_) {} }); } void _stopReconciliation() { _reconciliationTimer?.cancel(); _reconciliationTimer = null; } @override Future close() { _stopReconciliation(); _merger.dispose(); return super.close(); } } /// هل هذه الحالة نهائية؟ bool isTerminalStatus(String status) { return status == 'cancelled' || status == 'expired' || status == 'paid' || status == 'completed'; }