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:
Dayron
2025-11-04 20:21:54 +01:00
parent 8ff9e12fd4
commit f6c8432335
19 changed files with 2902 additions and 961 deletions

View File

@@ -0,0 +1,157 @@
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 'google_activity_event.dart';
import 'google_activity_state.dart';
/// BLoC pour gérer les activités Google Places
class GoogleActivityBloc extends Bloc<GoogleActivityEvent, GoogleActivityState> {
final ActivityPlacesService _placesService;
final ActivityRepository _repository;
final ErrorService _errorService;
GoogleActivityBloc({
required ActivityPlacesService placesService,
required ActivityRepository repository,
required ErrorService errorService,
}) : _placesService = placesService,
_repository = repository,
_errorService = errorService,
super(const GoogleActivityInitial()) {
on<LoadGoogleActivities>(_onLoadGoogleActivities);
on<LoadMoreGoogleActivities>(_onLoadMoreGoogleActivities);
on<UpdateGoogleActivities>(_onUpdateGoogleActivities);
on<AddGoogleActivityToDb>(_onAddGoogleActivityToDb);
on<SearchGoogleActivitiesByCategory>(_onSearchGoogleActivitiesByCategory);
on<ClearGoogleActivities>(_onClearGoogleActivities);
}
/// Charger les activités Google Places avec pagination (6 par page)
Future<void> _onLoadGoogleActivities(
LoadGoogleActivities event,
Emitter<GoogleActivityState> emit,
) async {
try {
emit(const GoogleActivitySearching());
final result = await _placesService.searchActivitiesPaginated(
destination: event.destination,
tripId: event.tripId,
category: event.category,
pageSize: 6,
);
emit(GoogleActivityLoaded(
googleActivities: result['activities'] as List<Activity>,
nextPageToken: result['nextPageToken'] as String?,
hasMoreData: result['hasMoreData'] as bool? ?? false,
query: event.category?.displayName ?? 'Toutes les activités',
));
} catch (e) {
_errorService.logError('google_activity_bloc', 'Erreur chargement activités Google: $e');
emit(const GoogleActivityError('Impossible de charger les activités Google'));
}
}
/// Charger plus d'activités (pagination)
Future<void> _onLoadMoreGoogleActivities(
LoadMoreGoogleActivities event,
Emitter<GoogleActivityState> emit,
) async {
if (state is! GoogleActivityLoaded) return;
final currentState = state as GoogleActivityLoaded;
if (!currentState.hasMoreData || currentState.nextPageToken == null) return;
try {
emit(GoogleActivityLoadingMore(
currentActivities: currentState.googleActivities,
query: currentState.query,
));
final result = await _placesService.searchActivitiesPaginated(
destination: event.destination,
tripId: event.tripId,
category: event.category,
pageSize: 6,
nextPageToken: currentState.nextPageToken,
);
final newActivities = result['activities'] as List<Activity>;
final allActivities = [...currentState.googleActivities, ...newActivities];
emit(GoogleActivityLoaded(
googleActivities: allActivities,
nextPageToken: result['nextPageToken'] as String?,
hasMoreData: result['hasMoreData'] as bool? ?? false,
query: currentState.query,
));
} catch (e) {
_errorService.logError('google_activity_bloc', 'Erreur chargement plus activités: $e');
emit(const GoogleActivityError('Impossible de charger plus d\'activités'));
}
}
/// Mettre à jour les activités Google
Future<void> _onUpdateGoogleActivities(
UpdateGoogleActivities event,
Emitter<GoogleActivityState> emit,
) async {
emit(GoogleActivityLoaded(
googleActivities: event.activities,
nextPageToken: event.nextPageToken,
hasMoreData: event.hasMoreData,
query: event.query,
));
}
/// Ajouter une activité Google à la base de données
Future<void> _onAddGoogleActivityToDb(
AddGoogleActivityToDb event,
Emitter<GoogleActivityState> emit,
) async {
try {
// Vérifier si l'activité existe déjà
if (event.activity.placeId != null) {
final existingActivity = await _repository.findExistingActivity(
event.activity.tripId,
event.activity.placeId!,
);
if (existingActivity != null) {
emit(const GoogleActivityOperationSuccess('Cette activité est déjà dans votre voyage'));
return;
}
}
await _repository.addActivity(event.activity);
emit(const GoogleActivityOperationSuccess('Activité ajoutée au voyage'));
} catch (e) {
_errorService.logError('google_activity_bloc', 'Erreur ajout activité: $e');
emit(const GoogleActivityError('Impossible d\'ajouter l\'activité'));
}
}
/// Rechercher par catégorie
Future<void> _onSearchGoogleActivitiesByCategory(
SearchGoogleActivitiesByCategory event,
Emitter<GoogleActivityState> emit,
) async {
add(LoadGoogleActivities(
tripId: event.tripId,
destination: event.destination,
category: event.category,
));
}
/// Effacer les activités Google
Future<void> _onClearGoogleActivities(
ClearGoogleActivities event,
Emitter<GoogleActivityState> emit,
) async {
emit(const GoogleActivityInitial());
}
}

View File

@@ -0,0 +1,93 @@
import 'package:equatable/equatable.dart';
import '../../../models/activity.dart';
/// Events pour les activités Google Places
abstract class GoogleActivityEvent extends Equatable {
const GoogleActivityEvent();
@override
List<Object?> get props => [];
}
/// Charger les activités Google Places
class LoadGoogleActivities extends GoogleActivityEvent {
final String tripId;
final String destination;
final ActivityCategory? category;
const LoadGoogleActivities({
required this.tripId,
required this.destination,
this.category,
});
@override
List<Object?> get props => [tripId, destination, category];
}
/// Charger plus d'activités Google (pagination)
class LoadMoreGoogleActivities extends GoogleActivityEvent {
final String tripId;
final String destination;
final ActivityCategory? category;
final String? nextPageToken;
const LoadMoreGoogleActivities({
required this.tripId,
required this.destination,
this.category,
this.nextPageToken,
});
@override
List<Object?> get props => [tripId, destination, category, nextPageToken];
}
/// Mettre à jour les activités Google
class UpdateGoogleActivities extends GoogleActivityEvent {
final List<Activity> activities;
final String? nextPageToken;
final bool hasMoreData;
final String query;
const UpdateGoogleActivities({
required this.activities,
this.nextPageToken,
required this.hasMoreData,
required this.query,
});
@override
List<Object?> get props => [activities, nextPageToken, hasMoreData, query];
}
/// Ajouter une activité Google à la DB
class AddGoogleActivityToDb extends GoogleActivityEvent {
final Activity activity;
const AddGoogleActivityToDb({required this.activity});
@override
List<Object?> get props => [activity];
}
/// Rechercher des activités Google par catégorie
class SearchGoogleActivitiesByCategory extends GoogleActivityEvent {
final String tripId;
final String destination;
final ActivityCategory category;
const SearchGoogleActivitiesByCategory({
required this.tripId,
required this.destination,
required this.category,
});
@override
List<Object?> get props => [tripId, destination, category];
}
/// Effacer les résultats Google
class ClearGoogleActivities extends GoogleActivityEvent {
const ClearGoogleActivities();
}

View File

@@ -0,0 +1,87 @@
import 'package:equatable/equatable.dart';
import '../../../models/activity.dart';
/// States pour les activités Google Places
abstract class GoogleActivityState extends Equatable {
const GoogleActivityState();
@override
List<Object?> get props => [];
}
/// État initial
class GoogleActivityInitial extends GoogleActivityState {
const GoogleActivityInitial();
}
/// État de recherche
class GoogleActivitySearching extends GoogleActivityState {
const GoogleActivitySearching();
}
/// État avec les activités Google chargées
class GoogleActivityLoaded extends GoogleActivityState {
final List<Activity> googleActivities;
final String? nextPageToken;
final bool hasMoreData;
final String query;
const GoogleActivityLoaded({
required this.googleActivities,
this.nextPageToken,
required this.hasMoreData,
required this.query,
});
@override
List<Object?> get props => [googleActivities, nextPageToken, hasMoreData, query];
/// Créer une copie avec des modifications
GoogleActivityLoaded copyWith({
List<Activity>? googleActivities,
String? nextPageToken,
bool? hasMoreData,
String? query,
}) {
return GoogleActivityLoaded(
googleActivities: googleActivities ?? this.googleActivities,
nextPageToken: nextPageToken ?? this.nextPageToken,
hasMoreData: hasMoreData ?? this.hasMoreData,
query: query ?? this.query,
);
}
}
/// État de chargement de plus d'activités (pagination)
class GoogleActivityLoadingMore extends GoogleActivityState {
final List<Activity> currentActivities;
final String query;
const GoogleActivityLoadingMore({
required this.currentActivities,
required this.query,
});
@override
List<Object?> get props => [currentActivities, query];
}
/// État de succès d'opération
class GoogleActivityOperationSuccess extends GoogleActivityState {
final String message;
const GoogleActivityOperationSuccess(this.message);
@override
List<Object?> get props => [message];
}
/// État d'erreur
class GoogleActivityError extends GoogleActivityState {
final String message;
const GoogleActivityError(this.message);
@override
List<Object?> get props => [message];
}