Add new activity events and enhance ActivityBloc for better state management
- Introduced LoadTripActivitiesPreservingSearch event to load trip activities while preserving search results. - Added RemoveFromSearchResults and AddActivityAndRemoveFromSearch events for improved activity handling. - Updated ActivitiesPage to show loading dialog during activity addition and provide user feedback. - Increased maxResults for activity search to load more activities.
This commit is contained in:
@@ -23,6 +23,7 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
||||
super(const ActivityInitial()) {
|
||||
|
||||
on<LoadActivities>(_onLoadActivities);
|
||||
on<LoadTripActivitiesPreservingSearch>(_onLoadTripActivitiesPreservingSearch);
|
||||
on<SearchActivities>(_onSearchActivities);
|
||||
on<SearchActivitiesWithCoordinates>(_onSearchActivitiesWithCoordinates);
|
||||
on<SearchActivitiesByText>(_onSearchActivitiesByText);
|
||||
@@ -36,6 +37,8 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
||||
on<UpdateActivity>(_onUpdateActivity);
|
||||
on<ToggleActivityFavorite>(_onToggleActivityFavorite);
|
||||
on<RestoreCachedSearchResults>(_onRestoreCachedSearchResults);
|
||||
on<RemoveFromSearchResults>(_onRemoveFromSearchResults);
|
||||
on<AddActivityAndRemoveFromSearch>(_onAddActivityAndRemoveFromSearch);
|
||||
}
|
||||
|
||||
/// Handles loading activities for a trip
|
||||
@@ -58,6 +61,31 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles loading trip activities while preserving search results state
|
||||
Future<void> _onLoadTripActivitiesPreservingSearch(
|
||||
LoadTripActivitiesPreservingSearch event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
final activities = await _repository.getActivitiesByTrip(event.tripId);
|
||||
|
||||
// Si on a un état de recherche actif, on le préserve
|
||||
if (state is ActivitySearchResults) {
|
||||
// On garde l'état de recherche inchangé, pas besoin d'émettre
|
||||
return;
|
||||
}
|
||||
|
||||
// Sinon, on charge normalement
|
||||
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<void> _onSearchActivities(
|
||||
SearchActivities event,
|
||||
@@ -200,12 +228,74 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
||||
final activityId = await _repository.addActivity(event.activity);
|
||||
|
||||
if (activityId != null) {
|
||||
// Si on est en état de recherche (suggestions Google), préserver cet état
|
||||
if (state is ActivitySearchResults) {
|
||||
// On ne change rien à l'état de recherche, on le garde tel quel
|
||||
// La suppression de l'activité des résultats se fait dans _onRemoveFromSearchResults
|
||||
return;
|
||||
}
|
||||
|
||||
// Sinon, émettre l'état d'ajout réussi
|
||||
emit(ActivityAdded(
|
||||
activity: event.activity.copyWith(id: activityId),
|
||||
message: 'Activité ajoutée avec succès',
|
||||
));
|
||||
// Reload activities
|
||||
add(LoadActivities(event.activity.tripId));
|
||||
// Reload activities while preserving search results
|
||||
add(LoadTripActivitiesPreservingSearch(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 an activity and removing it from search results in one action
|
||||
Future<void> _onAddActivityAndRemoveFromSearch(
|
||||
AddActivityAndRemoveFromSearch event,
|
||||
Emitter<ActivityState> 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) {
|
||||
// Si on est en état de recherche (suggestions Google), préserver cet état
|
||||
// en supprimant l'activité des résultats
|
||||
if (state is ActivitySearchResults) {
|
||||
final currentState = state as ActivitySearchResults;
|
||||
final updatedResults = currentState.searchResults
|
||||
.where((activity) => activity.id != event.googleActivityId)
|
||||
.toList();
|
||||
|
||||
emit(ActivitySearchResults(
|
||||
searchResults: updatedResults,
|
||||
query: currentState.query,
|
||||
isLoading: false,
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Sinon, émettre l'état d'ajout réussi
|
||||
emit(ActivityAdded(
|
||||
activity: event.activity.copyWith(id: activityId),
|
||||
message: 'Activité ajoutée avec succès',
|
||||
));
|
||||
// Reload activities while preserving search results
|
||||
add(LoadTripActivitiesPreservingSearch(event.activity.tripId));
|
||||
} else {
|
||||
emit(const ActivityError('Impossible d\'ajouter l\'activité'));
|
||||
}
|
||||
@@ -482,6 +572,29 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/// Removes an activity from search results
|
||||
Future<void> _onRemoveFromSearchResults(
|
||||
RemoveFromSearchResults event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
// Si on est actuellement dans un état de résultats de recherche
|
||||
if (state is ActivitySearchResults) {
|
||||
final currentState = state as ActivitySearchResults;
|
||||
|
||||
// Filtrer l'activité à retirer
|
||||
final updatedResults = currentState.searchResults
|
||||
.where((activity) => activity.id != event.activityId)
|
||||
.toList();
|
||||
|
||||
// Émettre le nouvel état avec l'activité retirée
|
||||
emit(ActivitySearchResults(
|
||||
searchResults: updatedResults,
|
||||
query: currentState.query,
|
||||
isLoading: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Restores cached search results
|
||||
Future<void> _onRestoreCachedSearchResults(
|
||||
RestoreCachedSearchResults event,
|
||||
|
||||
@@ -19,6 +19,16 @@ class LoadActivities extends ActivityEvent {
|
||||
List<Object> get props => [tripId];
|
||||
}
|
||||
|
||||
/// Event to load trip activities without resetting search results
|
||||
class LoadTripActivitiesPreservingSearch extends ActivityEvent {
|
||||
final String tripId;
|
||||
|
||||
const LoadTripActivitiesPreservingSearch(this.tripId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [tripId];
|
||||
}
|
||||
|
||||
/// Event to search activities using Google Places API
|
||||
class SearchActivities extends ActivityEvent {
|
||||
final String tripId;
|
||||
@@ -194,4 +204,28 @@ class RestoreCachedSearchResults extends ActivityEvent {
|
||||
|
||||
@override
|
||||
List<Object?> get props => [searchResults];
|
||||
}
|
||||
|
||||
/// Event to remove an activity from search results
|
||||
class RemoveFromSearchResults extends ActivityEvent {
|
||||
final String activityId;
|
||||
|
||||
const RemoveFromSearchResults({required this.activityId});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId];
|
||||
}
|
||||
|
||||
/// Event to add an activity and remove it from search results in one action
|
||||
class AddActivityAndRemoveFromSearch extends ActivityEvent {
|
||||
final Activity activity;
|
||||
final String googleActivityId;
|
||||
|
||||
const AddActivityAndRemoveFromSearch({
|
||||
required this.activity,
|
||||
required this.googleActivityId,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activity, googleActivityId];
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import '../../models/trip.dart';
|
||||
import '../../models/activity.dart';
|
||||
import '../../services/activity_cache_service.dart';
|
||||
import '../activities/add_activity_bottom_sheet.dart';
|
||||
import '../loading/laoding_content.dart';
|
||||
|
||||
class ActivitiesPage extends StatefulWidget {
|
||||
final Trip trip;
|
||||
@@ -138,6 +139,37 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
);
|
||||
}
|
||||
|
||||
if (state is ActivityAdded) {
|
||||
// Fermer le dialog de loading
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
// Ajouter l'activité à la liste locale des activités du voyage
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
_tripActivities.add(state.activity);
|
||||
});
|
||||
|
||||
// Afficher un feedback de succès
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${state.activity.name} ajoutée au voyage !'),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: Colors.green,
|
||||
action: SnackBarAction(
|
||||
label: 'Voir',
|
||||
textColor: Colors.white,
|
||||
onPressed: () {
|
||||
// Revenir à l'onglet des activités du voyage
|
||||
_tabController.animateTo(0);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (state is ActivityLoaded) {
|
||||
// Stocker les activités localement
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -172,11 +204,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
// Recharger les activités du voyage pour mettre à jour les votes
|
||||
_loadActivities();
|
||||
}
|
||||
|
||||
if (state is ActivityAdded) {
|
||||
// Recharger automatiquement les activités du voyage
|
||||
_loadActivities();
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
@@ -754,21 +781,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
// Bouton de debug temporaire
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
_checkAndLoadMoreActivitiesIfNeeded();
|
||||
},
|
||||
icon: const Icon(Icons.bug_report, size: 16),
|
||||
label: const Text('🧪 Test Auto-Reload'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -1264,23 +1276,32 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
);
|
||||
|
||||
context.read<ActivityBloc>().add(AddActivity(newActivity));
|
||||
|
||||
// Afficher un feedback à l'utilisateur
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${activity.name} ajoutée au voyage !'),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: Colors.green,
|
||||
action: SnackBarAction(
|
||||
label: 'Voir',
|
||||
textColor: Colors.white,
|
||||
onPressed: () {
|
||||
// Revenir à l'onglet des activités du voyage
|
||||
_tabController.animateTo(0);
|
||||
// Afficher le LoadingContent avec la tâche d'ajout
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return LoadingContent(
|
||||
loadingText: 'Ajout de ${activity.name}...',
|
||||
onBackgroundTask: () async {
|
||||
// Ajouter l'activité au voyage
|
||||
context.read<ActivityBloc>().add(
|
||||
AddActivityAndRemoveFromSearch(
|
||||
activity: newActivity,
|
||||
googleActivityId: activity.id,
|
||||
),
|
||||
);
|
||||
// Attendre que l'ajout soit complété
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
},
|
||||
),
|
||||
),
|
||||
onComplete: () {
|
||||
// Fermer le dialog quand l'ajout est complété
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
||||
return; // Utiliser le cache
|
||||
}
|
||||
|
||||
// Sinon, lancer la recherche
|
||||
// Sinon, lancer la recherche avec le maximum d'activités
|
||||
context.read<ActivityBloc>().add(
|
||||
widget.trip.hasCoordinates
|
||||
? SearchActivitiesWithCoordinates(
|
||||
@@ -50,14 +50,14 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
||||
latitude: widget.trip.latitude!,
|
||||
longitude: widget.trip.longitude!,
|
||||
category: null,
|
||||
maxResults: 6,
|
||||
maxResults: 100, // Charger le maximum d'activités possible
|
||||
reset: true,
|
||||
)
|
||||
: SearchActivities(
|
||||
tripId: widget.trip.id!,
|
||||
destination: widget.trip.location,
|
||||
category: null,
|
||||
maxResults: 6,
|
||||
maxResults: 100, // Charger le maximum d'activités possible
|
||||
reset: true,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user