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()) {
|
super(const ActivityInitial()) {
|
||||||
|
|
||||||
on<LoadActivities>(_onLoadActivities);
|
on<LoadActivities>(_onLoadActivities);
|
||||||
|
on<LoadTripActivitiesPreservingSearch>(_onLoadTripActivitiesPreservingSearch);
|
||||||
on<SearchActivities>(_onSearchActivities);
|
on<SearchActivities>(_onSearchActivities);
|
||||||
on<SearchActivitiesWithCoordinates>(_onSearchActivitiesWithCoordinates);
|
on<SearchActivitiesWithCoordinates>(_onSearchActivitiesWithCoordinates);
|
||||||
on<SearchActivitiesByText>(_onSearchActivitiesByText);
|
on<SearchActivitiesByText>(_onSearchActivitiesByText);
|
||||||
@@ -36,6 +37,8 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
|||||||
on<UpdateActivity>(_onUpdateActivity);
|
on<UpdateActivity>(_onUpdateActivity);
|
||||||
on<ToggleActivityFavorite>(_onToggleActivityFavorite);
|
on<ToggleActivityFavorite>(_onToggleActivityFavorite);
|
||||||
on<RestoreCachedSearchResults>(_onRestoreCachedSearchResults);
|
on<RestoreCachedSearchResults>(_onRestoreCachedSearchResults);
|
||||||
|
on<RemoveFromSearchResults>(_onRemoveFromSearchResults);
|
||||||
|
on<AddActivityAndRemoveFromSearch>(_onAddActivityAndRemoveFromSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles loading activities for a trip
|
/// 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
|
/// Handles searching activities using Google Places API
|
||||||
Future<void> _onSearchActivities(
|
Future<void> _onSearchActivities(
|
||||||
SearchActivities event,
|
SearchActivities event,
|
||||||
@@ -200,12 +228,74 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
|||||||
final activityId = await _repository.addActivity(event.activity);
|
final activityId = await _repository.addActivity(event.activity);
|
||||||
|
|
||||||
if (activityId != null) {
|
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(
|
emit(ActivityAdded(
|
||||||
activity: event.activity.copyWith(id: activityId),
|
activity: event.activity.copyWith(id: activityId),
|
||||||
message: 'Activité ajoutée avec succès',
|
message: 'Activité ajoutée avec succès',
|
||||||
));
|
));
|
||||||
// Reload activities
|
// Reload activities while preserving search results
|
||||||
add(LoadActivities(event.activity.tripId));
|
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 {
|
} else {
|
||||||
emit(const ActivityError('Impossible d\'ajouter l\'activité'));
|
emit(const ActivityError('Impossible d\'ajouter l\'activité'));
|
||||||
}
|
}
|
||||||
@@ -482,6 +572,29 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
|||||||
return filtered;
|
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
|
/// Restores cached search results
|
||||||
Future<void> _onRestoreCachedSearchResults(
|
Future<void> _onRestoreCachedSearchResults(
|
||||||
RestoreCachedSearchResults event,
|
RestoreCachedSearchResults event,
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ class LoadActivities extends ActivityEvent {
|
|||||||
List<Object> get props => [tripId];
|
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
|
/// Event to search activities using Google Places API
|
||||||
class SearchActivities extends ActivityEvent {
|
class SearchActivities extends ActivityEvent {
|
||||||
final String tripId;
|
final String tripId;
|
||||||
@@ -195,3 +205,27 @@ class RestoreCachedSearchResults extends ActivityEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [searchResults];
|
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 '../../models/activity.dart';
|
||||||
import '../../services/activity_cache_service.dart';
|
import '../../services/activity_cache_service.dart';
|
||||||
import '../activities/add_activity_bottom_sheet.dart';
|
import '../activities/add_activity_bottom_sheet.dart';
|
||||||
|
import '../loading/laoding_content.dart';
|
||||||
|
|
||||||
class ActivitiesPage extends StatefulWidget {
|
class ActivitiesPage extends StatefulWidget {
|
||||||
final Trip trip;
|
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) {
|
if (state is ActivityLoaded) {
|
||||||
// Stocker les activités localement
|
// Stocker les activités localement
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@@ -172,11 +204,6 @@ class _ActivitiesPageState extends State<ActivitiesPage>
|
|||||||
// Recharger les activités du voyage pour mettre à jour les votes
|
// Recharger les activités du voyage pour mettre à jour les votes
|
||||||
_loadActivities();
|
_loadActivities();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is ActivityAdded) {
|
|
||||||
// Recharger automatiquement les activités du voyage
|
|
||||||
_loadActivities();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: theme.colorScheme.surface,
|
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(),
|
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
context.read<ActivityBloc>().add(AddActivity(newActivity));
|
// Afficher le LoadingContent avec la tâche d'ajout
|
||||||
|
showDialog(
|
||||||
// Afficher un feedback à l'utilisateur
|
context: context,
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
barrierDismissible: false,
|
||||||
SnackBar(
|
builder: (BuildContext dialogContext) {
|
||||||
content: Text('${activity.name} ajoutée au voyage !'),
|
return LoadingContent(
|
||||||
duration: const Duration(seconds: 2),
|
loadingText: 'Ajout de ${activity.name}...',
|
||||||
backgroundColor: Colors.green,
|
onBackgroundTask: () async {
|
||||||
action: SnackBarAction(
|
// Ajouter l'activité au voyage
|
||||||
label: 'Voir',
|
context.read<ActivityBloc>().add(
|
||||||
textColor: Colors.white,
|
AddActivityAndRemoveFromSearch(
|
||||||
onPressed: () {
|
activity: newActivity,
|
||||||
// Revenir à l'onglet des activités du voyage
|
googleActivityId: activity.id,
|
||||||
_tabController.animateTo(0);
|
),
|
||||||
|
);
|
||||||
|
// 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
|
return; // Utiliser le cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sinon, lancer la recherche
|
// Sinon, lancer la recherche avec le maximum d'activités
|
||||||
context.read<ActivityBloc>().add(
|
context.read<ActivityBloc>().add(
|
||||||
widget.trip.hasCoordinates
|
widget.trip.hasCoordinates
|
||||||
? SearchActivitiesWithCoordinates(
|
? SearchActivitiesWithCoordinates(
|
||||||
@@ -50,14 +50,14 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
latitude: widget.trip.latitude!,
|
latitude: widget.trip.latitude!,
|
||||||
longitude: widget.trip.longitude!,
|
longitude: widget.trip.longitude!,
|
||||||
category: null,
|
category: null,
|
||||||
maxResults: 6,
|
maxResults: 100, // Charger le maximum d'activités possible
|
||||||
reset: true,
|
reset: true,
|
||||||
)
|
)
|
||||||
: SearchActivities(
|
: SearchActivities(
|
||||||
tripId: widget.trip.id!,
|
tripId: widget.trip.id!,
|
||||||
destination: widget.trip.location,
|
destination: widget.trip.location,
|
||||||
category: null,
|
category: null,
|
||||||
maxResults: 6,
|
maxResults: 100, // Charger le maximum d'activités possible
|
||||||
reset: true,
|
reset: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user