Refactor ActivityCard UI and improve voting functionality
- Updated ActivityCard layout for better visual consistency and responsiveness. - Simplified the category badge and adjusted styles for better readability. - Enhanced the voting section with a progress bar and improved button designs. - Added a new method in Activity model to check if all trip participants approved an activity. - Improved error handling and validation in ActivityRepository for voting and fetching activities. - Implemented pagination in ActivityPlacesService for activity searches. - Removed outdated scripts for cleaning up duplicate images.
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../models/activity.dart';
|
||||
import '../../../repositories/activity_repository.dart';
|
||||
import '../../../services/error_service.dart';
|
||||
import 'approved_activity_event.dart';
|
||||
import 'approved_activity_state.dart';
|
||||
|
||||
/// BLoC pour gérer les activités approuvées par tous les participants
|
||||
class ApprovedActivityBloc extends Bloc<ApprovedActivityEvent, ApprovedActivityState> {
|
||||
final ActivityRepository _repository;
|
||||
final ErrorService _errorService;
|
||||
|
||||
ApprovedActivityBloc({
|
||||
required ActivityRepository repository,
|
||||
required ErrorService errorService,
|
||||
}) : _repository = repository,
|
||||
_errorService = errorService,
|
||||
super(const ApprovedActivityInitial()) {
|
||||
|
||||
on<LoadApprovedActivities>(_onLoadApprovedActivities);
|
||||
on<SearchApprovedActivities>(_onSearchApprovedActivities);
|
||||
on<FilterApprovedActivities>(_onFilterApprovedActivities);
|
||||
on<RefreshApprovedActivities>(_onRefreshApprovedActivities);
|
||||
on<ClearApprovedSearchResults>(_onClearApprovedSearchResults);
|
||||
}
|
||||
|
||||
/// Charger les activités approuvées par tous les participants
|
||||
Future<void> _onLoadApprovedActivities(
|
||||
LoadApprovedActivities event,
|
||||
Emitter<ApprovedActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const ApprovedActivityLoading());
|
||||
|
||||
final allActivities = await _repository.getActivitiesByTrip(event.tripId);
|
||||
|
||||
// Filtrer les activités qui ont reçu des votes positifs de TOUS les participants
|
||||
final approvedActivities = allActivities.where((activity) {
|
||||
// Une activité est approuvée si tous les participants ont voté positivement
|
||||
final positiveVoters = activity.votes.entries
|
||||
.where((entry) => entry.value > 0)
|
||||
.map((entry) => entry.key)
|
||||
.toSet();
|
||||
|
||||
// Vérifier que tous les participants ont voté positivement
|
||||
return event.tripParticipants.every((participant) =>
|
||||
positiveVoters.contains(participant));
|
||||
}).toList();
|
||||
|
||||
// Trier par nombre total de votes puis par rating
|
||||
approvedActivities.sort((a, b) {
|
||||
final aVotes = a.totalVotes;
|
||||
final bVotes = b.totalVotes;
|
||||
if (aVotes != bVotes) {
|
||||
return bVotes.compareTo(aVotes);
|
||||
}
|
||||
return (b.rating ?? 0).compareTo(a.rating ?? 0);
|
||||
});
|
||||
|
||||
emit(ApprovedActivityLoaded(
|
||||
approvedActivities: approvedActivities,
|
||||
tripParticipants: event.tripParticipants,
|
||||
));
|
||||
} catch (e) {
|
||||
_errorService.logError('approved_activity_bloc', 'Erreur chargement activités approuvées: $e');
|
||||
emit(const ApprovedActivityError('Impossible de charger les activités approuvées'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Rechercher dans les activités approuvées
|
||||
Future<void> _onSearchApprovedActivities(
|
||||
SearchApprovedActivities event,
|
||||
Emitter<ApprovedActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const ApprovedActivitySearching());
|
||||
|
||||
// Charger d'abord toutes les activités approuvées
|
||||
final allActivities = await _repository.getActivitiesByTrip(event.tripId);
|
||||
|
||||
// Filtrer les approuvées puis rechercher
|
||||
final approvedActivities = allActivities.where((activity) {
|
||||
final positiveVoters = activity.votes.entries
|
||||
.where((entry) => entry.value > 0)
|
||||
.map((entry) => entry.key)
|
||||
.toSet();
|
||||
|
||||
return event.tripParticipants.every((participant) =>
|
||||
positiveVoters.contains(participant));
|
||||
}).toList();
|
||||
|
||||
// Rechercher dans les activités approuvées
|
||||
final results = approvedActivities
|
||||
.where((activity) =>
|
||||
activity.name.toLowerCase().contains(event.query.toLowerCase()) ||
|
||||
activity.description.toLowerCase().contains(event.query.toLowerCase()) ||
|
||||
activity.category.toLowerCase().contains(event.query.toLowerCase()))
|
||||
.toList();
|
||||
|
||||
emit(ApprovedActivitySearchResults(
|
||||
results: results,
|
||||
query: event.query,
|
||||
tripParticipants: event.tripParticipants,
|
||||
));
|
||||
} catch (e) {
|
||||
_errorService.logError('approved_activity_bloc', 'Erreur recherche approuvées: $e');
|
||||
emit(const ApprovedActivityError('Erreur lors de la recherche'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Filtrer les activités approuvées
|
||||
Future<void> _onFilterApprovedActivities(
|
||||
FilterApprovedActivities event,
|
||||
Emitter<ApprovedActivityState> emit,
|
||||
) async {
|
||||
if (state is! ApprovedActivityLoaded) return;
|
||||
|
||||
final currentState = state as ApprovedActivityLoaded;
|
||||
List<Activity> filteredActivities = List.from(currentState.approvedActivities);
|
||||
|
||||
// Filtrer par catégorie
|
||||
if (event.category != null && event.category!.isNotEmpty) {
|
||||
filteredActivities = filteredActivities
|
||||
.where((activity) => activity.category == event.category)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Filtrer par rating minimum
|
||||
if (event.minRating != null) {
|
||||
filteredActivities = filteredActivities
|
||||
.where((activity) => (activity.rating ?? 0) >= event.minRating!)
|
||||
.toList();
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(approvedActivities: filteredActivities));
|
||||
}
|
||||
|
||||
/// Rafraîchir les activités approuvées
|
||||
Future<void> _onRefreshApprovedActivities(
|
||||
RefreshApprovedActivities event,
|
||||
Emitter<ApprovedActivityState> emit,
|
||||
) async {
|
||||
add(LoadApprovedActivities(
|
||||
tripId: event.tripId,
|
||||
tripParticipants: event.tripParticipants,
|
||||
));
|
||||
}
|
||||
|
||||
/// Effacer les résultats de recherche
|
||||
Future<void> _onClearApprovedSearchResults(
|
||||
ClearApprovedSearchResults event,
|
||||
Emitter<ApprovedActivityState> emit,
|
||||
) async {
|
||||
if (state is ApprovedActivitySearchResults) {
|
||||
emit(const ApprovedActivityInitial());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Events pour les activités approuvées par tous les participants
|
||||
abstract class ApprovedActivityEvent extends Equatable {
|
||||
const ApprovedActivityEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// Charger les activités approuvées par tous
|
||||
class LoadApprovedActivities extends ApprovedActivityEvent {
|
||||
final String tripId;
|
||||
final List<String> tripParticipants;
|
||||
|
||||
const LoadApprovedActivities({
|
||||
required this.tripId,
|
||||
required this.tripParticipants,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [tripId, tripParticipants];
|
||||
}
|
||||
|
||||
/// Rechercher dans les activités approuvées
|
||||
class SearchApprovedActivities extends ApprovedActivityEvent {
|
||||
final String tripId;
|
||||
final String query;
|
||||
final List<String> tripParticipants;
|
||||
|
||||
const SearchApprovedActivities({
|
||||
required this.tripId,
|
||||
required this.query,
|
||||
required this.tripParticipants,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [tripId, query, tripParticipants];
|
||||
}
|
||||
|
||||
/// Filtrer les activités approuvées
|
||||
class FilterApprovedActivities extends ApprovedActivityEvent {
|
||||
final String? category;
|
||||
final double? minRating;
|
||||
|
||||
const FilterApprovedActivities({
|
||||
this.category,
|
||||
this.minRating,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [category, minRating];
|
||||
}
|
||||
|
||||
/// Rafraîchir les activités approuvées
|
||||
class RefreshApprovedActivities extends ApprovedActivityEvent {
|
||||
final String tripId;
|
||||
final List<String> tripParticipants;
|
||||
|
||||
const RefreshApprovedActivities({
|
||||
required this.tripId,
|
||||
required this.tripParticipants,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [tripId, tripParticipants];
|
||||
}
|
||||
|
||||
/// Effacer les résultats de recherche approuvées
|
||||
class ClearApprovedSearchResults extends ApprovedActivityEvent {
|
||||
const ClearApprovedSearchResults();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../../models/activity.dart';
|
||||
|
||||
/// States pour les activités approuvées par tous les participants
|
||||
abstract class ApprovedActivityState extends Equatable {
|
||||
const ApprovedActivityState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// État initial
|
||||
class ApprovedActivityInitial extends ApprovedActivityState {
|
||||
const ApprovedActivityInitial();
|
||||
}
|
||||
|
||||
/// État de chargement
|
||||
class ApprovedActivityLoading extends ApprovedActivityState {
|
||||
const ApprovedActivityLoading();
|
||||
}
|
||||
|
||||
/// État de recherche
|
||||
class ApprovedActivitySearching extends ApprovedActivityState {
|
||||
const ApprovedActivitySearching();
|
||||
}
|
||||
|
||||
/// État avec les activités approuvées chargées
|
||||
class ApprovedActivityLoaded extends ApprovedActivityState {
|
||||
final List<Activity> approvedActivities;
|
||||
final List<String> tripParticipants;
|
||||
|
||||
const ApprovedActivityLoaded({
|
||||
required this.approvedActivities,
|
||||
required this.tripParticipants,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [approvedActivities, tripParticipants];
|
||||
|
||||
/// Créer une copie avec des modifications
|
||||
ApprovedActivityLoaded copyWith({
|
||||
List<Activity>? approvedActivities,
|
||||
List<String>? tripParticipants,
|
||||
}) {
|
||||
return ApprovedActivityLoaded(
|
||||
approvedActivities: approvedActivities ?? this.approvedActivities,
|
||||
tripParticipants: tripParticipants ?? this.tripParticipants,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// État avec résultats de recherche approuvées
|
||||
class ApprovedActivitySearchResults extends ApprovedActivityState {
|
||||
final List<Activity> results;
|
||||
final String query;
|
||||
final List<String> tripParticipants;
|
||||
|
||||
const ApprovedActivitySearchResults({
|
||||
required this.results,
|
||||
required this.query,
|
||||
required this.tripParticipants,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [results, query, tripParticipants];
|
||||
}
|
||||
|
||||
/// État d'erreur
|
||||
class ApprovedActivityError extends ApprovedActivityState {
|
||||
final String message;
|
||||
|
||||
const ApprovedActivityError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
Reference in New Issue
Block a user