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:
@@ -19,15 +19,14 @@ class ActivityPlacesService {
|
||||
required String tripId,
|
||||
ActivityCategory? category,
|
||||
int radius = 5000,
|
||||
int maxResults = 20,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
try {
|
||||
print('ActivityPlacesService: Recherche d\'activités pour: $destination');
|
||||
print('ActivityPlacesService: Recherche d\'activités pour: $destination (max: $maxResults, offset: $offset)');
|
||||
|
||||
// 1. Géocoder la destination
|
||||
final coordinates = await _geocodeDestination(destination);
|
||||
if (coordinates == null) {
|
||||
throw Exception('Impossible de localiser la destination: $destination');
|
||||
}
|
||||
|
||||
// 2. Rechercher les activités par catégorie ou toutes les catégories
|
||||
List<Activity> allActivities = [];
|
||||
@@ -67,8 +66,21 @@ class ActivityPlacesService {
|
||||
final uniqueActivities = _removeDuplicates(allActivities);
|
||||
uniqueActivities.sort((a, b) => (b.rating ?? 0).compareTo(a.rating ?? 0));
|
||||
|
||||
print('ActivityPlacesService: ${uniqueActivities.length} activités trouvées');
|
||||
return uniqueActivities.take(50).toList(); // Limiter à 50 résultats
|
||||
print('ActivityPlacesService: ${uniqueActivities.length} activités trouvées au total');
|
||||
|
||||
// 4. Appliquer la pagination
|
||||
final startIndex = offset;
|
||||
final endIndex = (startIndex + maxResults).clamp(0, uniqueActivities.length);
|
||||
|
||||
if (startIndex >= uniqueActivities.length) {
|
||||
print('ActivityPlacesService: Offset $startIndex dépasse le nombre total (${uniqueActivities.length})');
|
||||
return [];
|
||||
}
|
||||
|
||||
final paginatedResults = uniqueActivities.sublist(startIndex, endIndex);
|
||||
print('ActivityPlacesService: Retour de ${paginatedResults.length} activités (offset: $offset, max: $maxResults)');
|
||||
|
||||
return paginatedResults;
|
||||
|
||||
} catch (e) {
|
||||
print('ActivityPlacesService: Erreur lors de la recherche: $e');
|
||||
@@ -78,29 +90,56 @@ class ActivityPlacesService {
|
||||
}
|
||||
|
||||
/// Géocode une destination pour obtenir les coordonnées
|
||||
Future<Map<String, double>?> _geocodeDestination(String destination) async {
|
||||
Future<Map<String, dynamic>> _geocodeDestination(String destination) async {
|
||||
try {
|
||||
// Vérifier que la clé API est configurée
|
||||
if (_apiKey.isEmpty) {
|
||||
print('ActivityPlacesService: Clé API Google Maps manquante');
|
||||
throw Exception('Clé API Google Maps non configurée');
|
||||
}
|
||||
|
||||
final encodedDestination = Uri.encodeComponent(destination);
|
||||
final url = 'https://maps.googleapis.com/maps/api/geocode/json?address=$encodedDestination&key=$_apiKey';
|
||||
|
||||
print('ActivityPlacesService: Géocodage de "$destination"');
|
||||
print('ActivityPlacesService: URL = $url');
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
print('ActivityPlacesService: Status code = ${response.statusCode}');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
print('ActivityPlacesService: Réponse géocodage = ${data['status']}');
|
||||
|
||||
if (data['status'] == 'OK' && data['results'].isNotEmpty) {
|
||||
final location = data['results'][0]['geometry']['location'];
|
||||
return {
|
||||
final coordinates = {
|
||||
'lat': location['lat'].toDouble(),
|
||||
'lng': location['lng'].toDouble(),
|
||||
};
|
||||
print('ActivityPlacesService: Coordonnées trouvées = $coordinates');
|
||||
return coordinates;
|
||||
} else {
|
||||
print('ActivityPlacesService: Erreur API = ${data['error_message'] ?? data['status']}');
|
||||
if (data['status'] == 'REQUEST_DENIED') {
|
||||
throw Exception('🔑 Clé API non autorisée. Activez les APIs suivantes dans Google Cloud Console:\n'
|
||||
'• Geocoding API\n'
|
||||
'• Places API\n'
|
||||
'• Maps JavaScript API\n'
|
||||
'Puis ajoutez des restrictions appropriées.');
|
||||
} else if (data['status'] == 'ZERO_RESULTS') {
|
||||
throw Exception('Aucun résultat trouvé pour cette destination');
|
||||
} else {
|
||||
throw Exception('Erreur API: ${data['status']}');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw Exception('Erreur HTTP ${response.statusCode}');
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
print('ActivityPlacesService: Erreur géocodage: $e');
|
||||
return null;
|
||||
throw e; // Rethrow pour permettre la gestion d'erreur en amont
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,9 +307,6 @@ class ActivityPlacesService {
|
||||
|
||||
// Géocoder la destination
|
||||
final coordinates = await _geocodeDestination(destination);
|
||||
if (coordinates == null) {
|
||||
throw Exception('Impossible de localiser la destination');
|
||||
}
|
||||
|
||||
final encodedQuery = Uri.encodeComponent(query);
|
||||
final url = 'https://maps.googleapis.com/maps/api/place/textsearch/json'
|
||||
@@ -338,4 +374,190 @@ class ActivityPlacesService {
|
||||
|
||||
return ActivityCategory.attraction; // Par défaut
|
||||
}
|
||||
|
||||
/// Recherche d'activités avec pagination (6 par page)
|
||||
Future<Map<String, dynamic>> searchActivitiesPaginated({
|
||||
required String destination,
|
||||
required String tripId,
|
||||
ActivityCategory? category,
|
||||
int pageSize = 6,
|
||||
String? nextPageToken,
|
||||
int radius = 5000,
|
||||
}) async {
|
||||
try {
|
||||
print('ActivityPlacesService: Recherche paginée pour: $destination (page: ${nextPageToken ?? "première"})');
|
||||
|
||||
// 1. Géocoder la destination
|
||||
final coordinates = await _geocodeDestination(destination);
|
||||
|
||||
// 2. Rechercher les activités par catégorie avec pagination
|
||||
if (category != null) {
|
||||
return await _searchByCategoryPaginated(
|
||||
coordinates['lat']!,
|
||||
coordinates['lng']!,
|
||||
category,
|
||||
tripId,
|
||||
radius,
|
||||
pageSize,
|
||||
nextPageToken,
|
||||
);
|
||||
} else {
|
||||
// Pour toutes les catégories, faire une recherche générale paginée
|
||||
return await _searchAllCategoriesPaginated(
|
||||
coordinates['lat']!,
|
||||
coordinates['lng']!,
|
||||
tripId,
|
||||
radius,
|
||||
pageSize,
|
||||
nextPageToken,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('ActivityPlacesService: Erreur recherche paginée: $e');
|
||||
_errorService.logError('activity_places_service', e);
|
||||
return {
|
||||
'activities': <Activity>[],
|
||||
'nextPageToken': null,
|
||||
'hasMoreData': false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Recherche paginée par catégorie spécifique
|
||||
Future<Map<String, dynamic>> _searchByCategoryPaginated(
|
||||
double lat,
|
||||
double lng,
|
||||
ActivityCategory category,
|
||||
String tripId,
|
||||
int radius,
|
||||
int pageSize,
|
||||
String? nextPageToken,
|
||||
) async {
|
||||
try {
|
||||
String url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json'
|
||||
'?location=$lat,$lng'
|
||||
'&radius=$radius'
|
||||
'&type=${category.googlePlaceType}'
|
||||
'&key=$_apiKey';
|
||||
|
||||
if (nextPageToken != null) {
|
||||
url += '&pagetoken=$nextPageToken';
|
||||
}
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
|
||||
if (data['status'] == 'OK') {
|
||||
final List<Activity> activities = [];
|
||||
final results = data['results'] as List? ?? [];
|
||||
|
||||
// Limiter à pageSize résultats
|
||||
final limitedResults = results.take(pageSize).toList();
|
||||
|
||||
for (final place in limitedResults) {
|
||||
try {
|
||||
final activity = await _convertPlaceToActivity(place, tripId, category);
|
||||
if (activity != null) {
|
||||
activities.add(activity);
|
||||
}
|
||||
} catch (e) {
|
||||
print('ActivityPlacesService: Erreur conversion place: $e');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'activities': activities,
|
||||
'nextPageToken': data['next_page_token'],
|
||||
'hasMoreData': data['next_page_token'] != null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'activities': <Activity>[],
|
||||
'nextPageToken': null,
|
||||
'hasMoreData': false,
|
||||
};
|
||||
} catch (e) {
|
||||
print('ActivityPlacesService: Erreur recherche catégorie paginée: $e');
|
||||
return {
|
||||
'activities': <Activity>[],
|
||||
'nextPageToken': null,
|
||||
'hasMoreData': false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Recherche paginée pour toutes les catégories
|
||||
Future<Map<String, dynamic>> _searchAllCategoriesPaginated(
|
||||
double lat,
|
||||
double lng,
|
||||
String tripId,
|
||||
int radius,
|
||||
int pageSize,
|
||||
String? nextPageToken,
|
||||
) async {
|
||||
try {
|
||||
// Pour toutes les catégories, on utilise une recherche plus générale
|
||||
String url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json'
|
||||
'?location=$lat,$lng'
|
||||
'&radius=$radius'
|
||||
'&type=tourist_attraction'
|
||||
'&key=$_apiKey';
|
||||
|
||||
if (nextPageToken != null) {
|
||||
url += '&pagetoken=$nextPageToken';
|
||||
}
|
||||
|
||||
final response = await http.get(Uri.parse(url));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
|
||||
if (data['status'] == 'OK') {
|
||||
final List<Activity> activities = [];
|
||||
final results = data['results'] as List? ?? [];
|
||||
|
||||
// Limiter à pageSize résultats
|
||||
final limitedResults = results.take(pageSize).toList();
|
||||
|
||||
for (final place in limitedResults) {
|
||||
try {
|
||||
// Déterminer la catégorie basée sur les types du lieu
|
||||
final types = List<String>.from(place['types'] ?? []);
|
||||
final category = _determineCategoryFromTypes(types);
|
||||
|
||||
final activity = await _convertPlaceToActivity(place, tripId, category);
|
||||
if (activity != null) {
|
||||
activities.add(activity);
|
||||
}
|
||||
} catch (e) {
|
||||
print('ActivityPlacesService: Erreur conversion place: $e');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'activities': activities,
|
||||
'nextPageToken': data['next_page_token'],
|
||||
'hasMoreData': data['next_page_token'] != null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'activities': <Activity>[],
|
||||
'nextPageToken': null,
|
||||
'hasMoreData': false,
|
||||
};
|
||||
} catch (e) {
|
||||
print('ActivityPlacesService: Erreur recherche toutes catégories paginée: $e');
|
||||
return {
|
||||
'activities': <Activity>[],
|
||||
'nextPageToken': null,
|
||||
'hasMoreData': false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user