/// A BLoC (Business Logic Component) that manages trip-related operations. /// /// This bloc handles all trip operations including creation, updates, deletion, /// and loading trips for users. It provides real-time updates through streams /// and manages the trip lifecycle with proper state transitions. /// /// The bloc processes these main events: /// - [LoadTripsByUserId]: Loads all trips for a specific user with real-time updates /// - [TripCreateRequested]: Creates a new trip and reloads the user's trip list /// - [TripUpdateRequested]: Updates an existing trip and refreshes the list /// - [TripDeleteRequested]: Deletes a trip and refreshes the list /// - [ResetTrips]: Resets the trip state and cancels subscriptions /// /// Dependencies: /// - [TripRepository]: Repository for trip data operations /// /// State Management: /// The bloc maintains the current user ID to enable automatic list refreshing /// after operations like create, update, or delete. This ensures the UI stays /// in sync with the latest data. /// /// Example usage: /// ```dart /// final tripBloc = TripBloc(tripRepository); /// /// // Load trips for a user /// tripBloc.add(LoadTripsByUserId(userId: 'userId123')); /// /// // Create a new trip /// tripBloc.add(TripCreateRequested(trip: newTrip)); /// /// // Update a trip /// tripBloc.add(TripUpdateRequested(trip: updatedTrip)); /// /// // Delete a trip /// tripBloc.add(TripDeleteRequested(tripId: 'tripId456')); /// ``` library; import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/models/trip.dart'; import 'trip_event.dart'; import 'trip_state.dart'; import '../../repositories/trip_repository.dart'; import '../../services/error_service.dart'; /// BLoC that manages trip-related operations and state. class TripBloc extends Bloc { /// Repository for trip data operations final TripRepository _repository; final _errorService = ErrorService(); /// Subscription to trip stream for real-time updates StreamSubscription? _tripsSubscription; /// Current user ID for automatic list refreshing after operations String? _currentUserId; /// Constructor for TripBloc. /// /// Initializes the bloc with the trip repository and sets up event handlers /// for all trip-related operations. /// /// Args: /// [_repository]: Repository for trip data operations TripBloc(this._repository) : super(TripInitial()) { on(_onLoadTripsByUserId); on(_onTripCreateRequested); on(_onTripUpdateRequested); on(_onTripDeleteRequested); on<_TripsUpdated>(_onTripsUpdated); on(_onResetTrips); } /// Handles [LoadTripsByUserId] events. /// /// Loads all trips for a specific user with real-time updates via stream subscription. /// Stores the user ID for future automatic refreshing after operations and cancels /// any existing subscription to prevent memory leaks. /// /// Args: /// [event]: The LoadTripsByUserId event containing the user ID /// [emit]: State emitter function Future _onLoadTripsByUserId( LoadTripsByUserId event, Emitter emit, ) async { emit(TripLoading()); _currentUserId = event.userId; await _tripsSubscription?.cancel(); _tripsSubscription = _repository .getTripsByUserId(event.userId) .listen( (trips) { add(_TripsUpdated(trips)); }, onError: (error, stackTrace) { _errorService.logError( 'TripBloc', 'Error loading trips: $error', stackTrace, ); add( const _TripsUpdated( [], error: 'Impossible de charger les voyages', ), ); }, ); } /// Handles [_TripsUpdated] events. /// /// Processes real-time updates from the trip stream and emits the /// updated trip list to the UI. /// /// Args: /// [event]: The _TripsUpdated event containing the updated trip list /// [emit]: State emitter function void _onTripsUpdated(_TripsUpdated event, Emitter emit) { if (event.error != null) { emit(TripError(event.error!)); } else { emit(TripLoaded(event.trips)); } } /// Handles [TripCreateRequested] events. /// /// Creates a new trip and automatically refreshes the user's trip list /// to show the newly created trip. Includes a delay to allow the creation /// to complete before refreshing. /// /// Args: /// [event]: The TripCreateRequested event containing the trip data /// [emit]: State emitter function Future _onTripCreateRequested( TripCreateRequested event, Emitter emit, ) async { try { emit(TripLoading()); final tripId = await _repository.createTrip(event.trip); emit(TripCreated(tripId: tripId)); await Future.delayed(const Duration(milliseconds: 800)); if (_currentUserId != null) { add(LoadTripsByUserId(userId: _currentUserId!)); } } catch (e, stackTrace) { _errorService.logError('TripBloc', 'Error creating trip: $e', stackTrace); emit(const TripError('Impossible de créer le voyage')); } } /// Handles [TripUpdateRequested] events. /// /// Updates an existing trip and automatically refreshes the user's trip list /// to show the updated information. Includes a delay to allow the update /// to complete before refreshing. /// /// Args: /// [event]: The TripUpdateRequested event containing the updated trip data /// [emit]: State emitter function Future _onTripUpdateRequested( TripUpdateRequested event, Emitter emit, ) async { try { await _repository.updateTrip(event.trip.id!, event.trip); emit(const TripOperationSuccess('Trip updated successfully')); await Future.delayed(const Duration(milliseconds: 500)); if (_currentUserId != null) { add(LoadTripsByUserId(userId: _currentUserId!)); } } catch (e, stackTrace) { _errorService.logError('TripBloc', 'Error updating trip: $e', stackTrace); emit(const TripError('Impossible de mettre à jour le voyage')); } } /// Handles [TripDeleteRequested] events. /// /// Deletes a trip and automatically refreshes the user's trip list /// to remove the deleted trip from the UI. Includes a delay to allow /// the deletion to complete before refreshing. /// /// Args: /// [event]: The TripDeleteRequested event containing the trip ID to delete /// [emit]: State emitter function Future _onTripDeleteRequested( TripDeleteRequested event, Emitter emit, ) async { try { await _repository.deleteTrip(event.tripId); emit(const TripOperationSuccess('Trip deleted successfully')); await Future.delayed(const Duration(milliseconds: 500)); if (_currentUserId != null) { add(LoadTripsByUserId(userId: _currentUserId!)); } } catch (e, stackTrace) { _errorService.logError('TripBloc', 'Error deleting trip: $e', stackTrace); emit(const TripError('Impossible de supprimer le voyage')); } } /// Handles [ResetTrips] events. /// /// Resets the trip state to initial and cleans up resources. /// Cancels the trip stream subscription and clears the current user ID. /// This is useful for user logout or when switching contexts. /// /// Args: /// [event]: The ResetTrips event /// [emit]: State emitter function Future _onResetTrips(ResetTrips event, Emitter emit) async { await _tripsSubscription?.cancel(); _currentUserId = null; emit(TripInitial()); } /// Cleans up resources when the bloc is closed. /// /// Cancels the trip stream subscription to prevent memory leaks /// and ensure proper disposal of resources. @override Future close() { _tripsSubscription?.cancel(); return super.close(); } } /// Private event for handling real-time trip updates from streams. /// /// This internal event is used to process updates from the trip stream /// subscription and emit appropriate states based on the received data. /// internal event class _TripsUpdated extends TripEvent { final List trips; final String? error; const _TripsUpdated(this.trips, {this.error}); @override List get props => [trips, error]; }