import 'package:flutter_bloc/flutter_bloc.dart'; import '../../models/activity.dart'; import '../../repositories/activity_repository.dart'; import '../../services/activity_places_service.dart'; import '../../services/error_service.dart'; import 'activity_event.dart'; import 'activity_state.dart'; /// BLoC for managing activity-related state and operations class ActivityBloc extends Bloc { final ActivityRepository _repository; final ActivityPlacesService _placesService; final ErrorService _errorService; ActivityBloc({ required ActivityRepository repository, required ActivityPlacesService placesService, required ErrorService errorService, }) : _repository = repository, _placesService = placesService, _errorService = errorService, super(const ActivityInitial()) { on(_onLoadActivities); on(_onSearchActivities); on(_onSearchActivitiesByText); on(_onAddActivity); on(_onAddActivitiesBatch); on(_onVoteForActivity); on(_onDeleteActivity); on(_onFilterActivities); on(_onRefreshActivities); on(_onClearSearchResults); on(_onUpdateActivity); on(_onToggleActivityFavorite); } /// Handles loading activities for a trip Future _onLoadActivities( LoadActivities event, Emitter emit, ) async { try { emit(const ActivityLoading()); final activities = await _repository.getActivitiesByTrip(event.tripId); emit(ActivityLoaded( activities: activities, filteredActivities: activities, )); } catch (e) { _errorService.logError('activity_bloc', 'Erreur chargement activités: $e'); emit(const ActivityError('Impossible de charger les activités')); } } /// Handles searching activities using Google Places API Future _onSearchActivities( SearchActivities event, Emitter emit, ) async { try { // Si c'est un append (charger plus), on garde l'état actuel et on met isLoading à true if (event.appendToExisting && state is ActivitySearchResults) { final currentState = state as ActivitySearchResults; emit(currentState.copyWith(isLoading: true)); } else { emit(const ActivitySearching()); } final searchResults = await _placesService.searchActivities( destination: event.destination, tripId: event.tripId, category: event.category, maxResults: event.maxResults ?? 20, // Par défaut 20, ou utiliser la valeur spécifiée offset: event.offset ?? 0, // Par défaut 0 ); List finalResults; // Si on doit ajouter aux résultats existants if (event.appendToExisting && state is ActivitySearchResults) { final currentState = state as ActivitySearchResults; finalResults = [...currentState.searchResults, ...searchResults]; } else { finalResults = searchResults; } emit(ActivitySearchResults( searchResults: finalResults, query: event.category?.displayName ?? 'Toutes les activités', isLoading: false, )); } catch (e) { _errorService.logError('activity_bloc', 'Erreur recherche activités: $e'); emit(const ActivityError('Impossible de rechercher les activités')); } } /// Handles text-based activity search Future _onSearchActivitiesByText( SearchActivitiesByText event, Emitter emit, ) async { try { emit(const ActivitySearching()); final searchResults = await _placesService.searchActivitiesByText( query: event.query, destination: event.destination, tripId: event.tripId, ); emit(ActivitySearchResults( searchResults: searchResults, query: event.query, )); } catch (e) { _errorService.logError('activity_bloc', 'Erreur recherche textuelle: $e'); emit(const ActivityError('Impossible de rechercher les activités')); } } /// Handles adding a single activity Future _onAddActivity( AddActivity event, Emitter emit, ) async { try { // Check if activity already exists if (event.activity.placeId != null) { final existing = await _repository.findExistingActivity( event.activity.tripId, event.activity.placeId!, ); if (existing != null) { emit(const ActivityError('Cette activité a déjà été ajoutée')); return; } } final activityId = await _repository.addActivity(event.activity); if (activityId != null) { emit(ActivityAdded( activity: event.activity.copyWith(id: activityId), message: 'Activité ajoutée avec succès', )); // Reload activities add(LoadActivities(event.activity.tripId)); } else { emit(const ActivityError('Impossible d\'ajouter l\'activité')); } } catch (e) { _errorService.logError('activity_bloc', 'Erreur ajout activité: $e'); emit(const ActivityError('Impossible d\'ajouter l\'activité')); } } /// Handles adding multiple activities in batch Future _onAddActivitiesBatch( AddActivitiesBatch event, Emitter emit, ) async { try { // Filter out existing activities final filteredActivities = []; emit(ActivityBatchAdding( activitiesToAdd: event.activities, progress: 0, total: event.activities.length, )); for (int i = 0; i < event.activities.length; i++) { final activity = event.activities[i]; if (activity.placeId != null) { final existing = await _repository.findExistingActivity( activity.tripId, activity.placeId!, ); if (existing == null) { filteredActivities.add(activity); } } else { filteredActivities.add(activity); } // Update progress emit(ActivityBatchAdding( activitiesToAdd: event.activities, progress: i + 1, total: event.activities.length, )); } if (filteredActivities.isEmpty) { emit(const ActivityError('Toutes les activités ont déjà été ajoutées')); return; } final addedIds = await _repository.addActivitiesBatch(filteredActivities); if (addedIds.isNotEmpty) { emit(ActivityOperationSuccess( '${addedIds.length} activité(s) ajoutée(s) avec succès', operationType: 'batch_add', )); // Reload activities add(LoadActivities(event.activities.first.tripId)); } else { emit(const ActivityError('Impossible d\'ajouter les activités')); } } catch (e) { _errorService.logError('activity_bloc', 'Erreur ajout en lot: $e'); emit(const ActivityError('Impossible d\'ajouter les activités')); } } /// Handles voting for an activity Future _onVoteForActivity( VoteForActivity event, Emitter emit, ) async { try { // Show voting state if (state is ActivityLoaded) { final currentState = state as ActivityLoaded; emit(ActivityVoting( activityId: event.activityId, activities: currentState.activities, )); } final success = await _repository.voteForActivity( event.activityId, event.userId, event.vote, ); if (success) { emit(ActivityVoteRecorded( activityId: event.activityId, vote: event.vote, userId: event.userId, )); // Reload activities to reflect the new vote if (state is ActivityLoaded) { final currentState = state as ActivityLoaded; final activities = await _repository.getActivitiesByTrip( currentState.activities.first.tripId, ); emit(currentState.copyWith( activities: activities, filteredActivities: _applyFilters( activities, currentState.activeFilter, currentState.minRating, currentState.showVotedOnly, event.userId, ), )); } } else { emit(const ActivityError('Impossible d\'enregistrer le vote')); } } catch (e) { _errorService.logError('activity_bloc', 'Erreur vote: $e'); emit(const ActivityError('Impossible d\'enregistrer le vote')); } } /// Handles deleting an activity Future _onDeleteActivity( DeleteActivity event, Emitter emit, ) async { try { final success = await _repository.deleteActivity(event.activityId); if (success) { emit(ActivityDeleted( activityId: event.activityId, message: 'Activité supprimée avec succès', )); // Reload if we're on the activity list if (state is ActivityLoaded) { final currentState = state as ActivityLoaded; if (currentState.activities.isNotEmpty) { add(LoadActivities(currentState.activities.first.tripId)); } } } else { emit(const ActivityError('Impossible de supprimer l\'activité')); } } catch (e) { _errorService.logError('activity_bloc', 'Erreur suppression: $e'); emit(const ActivityError('Impossible de supprimer l\'activité')); } } /// Handles filtering activities Future _onFilterActivities( FilterActivities event, Emitter emit, ) async { if (state is ActivityLoaded) { final currentState = state as ActivityLoaded; final filteredActivities = _applyFilters( currentState.activities, event.category, event.minRating, event.showVotedOnly ?? false, '', // UserId would be needed for showVotedOnly filter ); emit(currentState.copyWith( filteredActivities: filteredActivities, activeFilter: event.category, minRating: event.minRating, showVotedOnly: event.showVotedOnly ?? false, )); } } /// Handles refreshing activities Future _onRefreshActivities( RefreshActivities event, Emitter emit, ) async { add(LoadActivities(event.tripId)); } /// Handles clearing search results Future _onClearSearchResults( ClearSearchResults event, Emitter emit, ) async { if (state is ActivitySearchResults) { emit(const ActivityInitial()); } } /// Handles updating an activity Future _onUpdateActivity( UpdateActivity event, Emitter emit, ) async { try { if (state is ActivityLoaded) { final currentState = state as ActivityLoaded; emit(ActivityUpdating( activityId: event.activity.id, activities: currentState.activities, )); } final success = await _repository.updateActivity(event.activity); if (success) { emit(const ActivityOperationSuccess( 'Activité mise à jour avec succès', operationType: 'update', )); // Reload activities add(LoadActivities(event.activity.tripId)); } else { emit(const ActivityError('Impossible de mettre à jour l\'activité')); } } catch (e) { _errorService.logError('activity_bloc', 'Erreur mise à jour: $e'); emit(const ActivityError('Impossible de mettre à jour l\'activité')); } } /// Handles toggling activity favorite status Future _onToggleActivityFavorite( ToggleActivityFavorite event, Emitter emit, ) async { try { // This would require extending the Activity model to include favorites // For now, we'll use the voting system as a favorite system add(VoteForActivity( activityId: event.activityId, userId: event.userId, vote: 1, )); } catch (e) { _errorService.logError('activity_bloc', 'Erreur favori: $e'); emit(const ActivityError('Impossible de modifier les favoris')); } } /// Applies filters to the activities list List _applyFilters( List activities, String? category, double? minRating, bool showVotedOnly, String userId, ) { var filtered = activities; if (category != null) { filtered = filtered.where((a) => a.category == category).toList(); } if (minRating != null) { filtered = filtered.where((a) => (a.rating ?? 0) >= minRating).toList(); } if (showVotedOnly && userId.isNotEmpty) { filtered = filtered.where((a) => a.hasUserVoted(userId)).toList(); } return filtered; } }