Add UserStateWrapper and ProfileImageService for user state management and profile image handling

This commit is contained in:
Van Leemput Dayron
2025-11-05 09:31:58 +01:00
parent 30dca05e15
commit 75c12e35a5
6 changed files with 275 additions and 131 deletions

View File

@@ -237,16 +237,16 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: isDarkMode color: isDarkMode
? Colors.white.withOpacity(0.2) ? Colors.white.withValues(alpha: 0.2)
: Colors.black.withOpacity(0.2), : Colors.black.withValues(alpha: 0.2),
), ),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: isDarkMode color: isDarkMode
? Colors.white.withOpacity(0.2) ? Colors.white.withValues(alpha: 0.2)
: Colors.black.withOpacity(0.2), : Colors.black.withValues(alpha: 0.2),
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
@@ -267,7 +267,9 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.colorScheme.surface, color: theme.colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: theme.colorScheme.outline.withOpacity(0.5)), border: Border.all(
color: theme.colorScheme.outline.withValues(alpha: 0.5),
),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -275,7 +277,7 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
Text( Text(
'Sélectionnez une catégorie', 'Sélectionnez une catégorie',
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7), color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:travel_mate/components/widgets/user_state_widget.dart';
import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_bloc.dart';
import '../../blocs/user/user_state.dart' as user_state; import '../../blocs/user/user_state.dart' as user_state;
import '../../blocs/user/user_event.dart' as user_event; import '../../blocs/user/user_event.dart' as user_event;
@@ -10,22 +11,8 @@ class ProfileContent extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<UserBloc, user_state.UserState>( return UserStateWrapper(
builder: (context, state) { builder: (context, user) {
if (state is user_state.UserLoading) {
return Center(child: CircularProgressIndicator());
}
if (state is user_state.UserError) {
return Center(child: Text('Erreur: ${state.message}'));
}
if (state is! user_state.UserLoaded) {
return Center(child: Text('Aucun utilisateur connecté'));
}
final user = state.user;
return Column( return Column(
children: [ children: [
// Section titre // Section titre
@@ -181,7 +168,10 @@ class ProfileContent extends StatelessWidget {
); );
} }
void _showChangePasswordDialog(BuildContext context, user_state.UserModel user) { void _showChangePasswordDialog(
BuildContext context,
user_state.UserModel user,
) {
final currentPasswordController = TextEditingController(); final currentPasswordController = TextEditingController();
final newPasswordController = TextEditingController(); final newPasswordController = TextEditingController();
final confirmPasswordController = TextEditingController(); final confirmPasswordController = TextEditingController();
@@ -210,7 +200,9 @@ class ProfileContent extends StatelessWidget {
TextField( TextField(
controller: confirmPasswordController, controller: confirmPasswordController,
obscureText: true, obscureText: true,
decoration: InputDecoration(labelText: 'Confirmer le mot de passe'), decoration: InputDecoration(
labelText: 'Confirmer le mot de passe',
),
), ),
], ],
), ),
@@ -233,7 +225,8 @@ class ProfileContent extends StatelessWidget {
return; return;
} }
if (newPasswordController.text != confirmPasswordController.text) { if (newPasswordController.text !=
confirmPasswordController.text) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Les mots de passe ne correspondent pas'), content: Text('Les mots de passe ne correspondent pas'),
@@ -274,7 +267,10 @@ class ProfileContent extends StatelessWidget {
); );
} }
void _showDeleteAccountDialog(BuildContext context, user_state.UserModel user) { void _showDeleteAccountDialog(
BuildContext context,
user_state.UserModel user,
) {
final passwordController = TextEditingController(); final passwordController = TextEditingController();
final authService = AuthService(); final authService = AuthService();

View File

@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../blocs/user/user_bloc.dart';
import '../../blocs/user/user_state.dart' as user_state;
class UserStateWrapper extends StatelessWidget {
final Widget Function(BuildContext context, dynamic user) builder;
final Widget? loadingWidget;
final Widget? errorWidget;
final Widget? noUserWidget;
const UserStateWrapper({
super.key,
required this.builder,
this.loadingWidget,
this.errorWidget,
this.noUserWidget,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, user_state.UserState>(
builder: (context, state) {
if (state is user_state.UserLoading) {
return loadingWidget ??
const Center(child: CircularProgressIndicator());
}
if (state is user_state.UserError) {
return errorWidget ?? Center(child: Text('Erreur: ${state.message}'));
}
if (state is! user_state.UserLoaded) {
return noUserWidget ??
const Center(child: Text('Aucun utilisateur connecté.'));
}
return builder(context, state.user);
},
);
}
}

View File

@@ -6,7 +6,8 @@ import '../services/error_service.dart';
/// Service pour rechercher des activités touristiques via Google Places API /// Service pour rechercher des activités touristiques via Google Places API
class ActivityPlacesService { class ActivityPlacesService {
static final ActivityPlacesService _instance = ActivityPlacesService._internal(); static final ActivityPlacesService _instance =
ActivityPlacesService._internal();
factory ActivityPlacesService() => _instance; factory ActivityPlacesService() => _instance;
ActivityPlacesService._internal(); ActivityPlacesService._internal();
@@ -23,7 +24,9 @@ class ActivityPlacesService {
int offset = 0, int offset = 0,
}) async { }) async {
try { try {
print('ActivityPlacesService: Recherche d\'activités pour: $destination (max: $maxResults, offset: $offset)'); print(
'ActivityPlacesService: Recherche d\'activités pour: $destination (max: $maxResults, offset: $offset)',
);
// 1. Géocoder la destination // 1. Géocoder la destination
final coordinates = await _geocodeDestination(destination); final coordinates = await _geocodeDestination(destination);
@@ -66,22 +69,30 @@ class ActivityPlacesService {
final uniqueActivities = _removeDuplicates(allActivities); final uniqueActivities = _removeDuplicates(allActivities);
uniqueActivities.sort((a, b) => (b.rating ?? 0).compareTo(a.rating ?? 0)); uniqueActivities.sort((a, b) => (b.rating ?? 0).compareTo(a.rating ?? 0));
print('ActivityPlacesService: ${uniqueActivities.length} activités trouvées au total'); print(
'ActivityPlacesService: ${uniqueActivities.length} activités trouvées au total',
);
// 4. Appliquer la pagination // 4. Appliquer la pagination
final startIndex = offset; final startIndex = offset;
final endIndex = (startIndex + maxResults).clamp(0, uniqueActivities.length); final endIndex = (startIndex + maxResults).clamp(
0,
uniqueActivities.length,
);
if (startIndex >= uniqueActivities.length) { if (startIndex >= uniqueActivities.length) {
print('ActivityPlacesService: Offset $startIndex dépasse le nombre total (${uniqueActivities.length})'); print(
'ActivityPlacesService: Offset $startIndex dépasse le nombre total (${uniqueActivities.length})',
);
return []; return [];
} }
final paginatedResults = uniqueActivities.sublist(startIndex, endIndex); final paginatedResults = uniqueActivities.sublist(startIndex, endIndex);
print('ActivityPlacesService: Retour de ${paginatedResults.length} activités (offset: $offset, max: $maxResults)'); print(
'ActivityPlacesService: Retour de ${paginatedResults.length} activités (offset: $offset, max: $maxResults)',
);
return paginatedResults; return paginatedResults;
} catch (e) { } catch (e) {
print('ActivityPlacesService: Erreur lors de la recherche: $e'); print('ActivityPlacesService: Erreur lors de la recherche: $e');
_errorService.logError('activity_places_service', e); _errorService.logError('activity_places_service', e);
@@ -99,7 +110,8 @@ class ActivityPlacesService {
} }
final encodedDestination = Uri.encodeComponent(destination); final encodedDestination = Uri.encodeComponent(destination);
final url = 'https://maps.googleapis.com/maps/api/geocode/json?address=$encodedDestination&key=$_apiKey'; final url =
'https://maps.googleapis.com/maps/api/geocode/json?address=$encodedDestination&key=$_apiKey';
print('ActivityPlacesService: Géocodage de "$destination"'); print('ActivityPlacesService: Géocodage de "$destination"');
print('ActivityPlacesService: URL = $url'); print('ActivityPlacesService: URL = $url');
@@ -121,13 +133,17 @@ class ActivityPlacesService {
print('ActivityPlacesService: Coordonnées trouvées = $coordinates'); print('ActivityPlacesService: Coordonnées trouvées = $coordinates');
return coordinates; return coordinates;
} else { } else {
print('ActivityPlacesService: Erreur API = ${data['error_message'] ?? data['status']}'); print(
'ActivityPlacesService: Erreur API = ${data['error_message'] ?? data['status']}',
);
if (data['status'] == 'REQUEST_DENIED') { if (data['status'] == 'REQUEST_DENIED') {
throw Exception('🔑 Clé API non autorisée. Activez les APIs suivantes dans Google Cloud Console:\n' throw Exception(
'🔑 Clé API non autorisée. Activez les APIs suivantes dans Google Cloud Console:\n'
'• Geocoding API\n' '• Geocoding API\n'
'• Places API\n' '• Places API\n'
'• Maps JavaScript API\n' '• Maps JavaScript API\n'
'Puis ajoutez des restrictions appropriées.'); 'Puis ajoutez des restrictions appropriées.',
);
} else if (data['status'] == 'ZERO_RESULTS') { } else if (data['status'] == 'ZERO_RESULTS') {
throw Exception('Aucun résultat trouvé pour cette destination'); throw Exception('Aucun résultat trouvé pour cette destination');
} else { } else {
@@ -139,7 +155,7 @@ class ActivityPlacesService {
} }
} catch (e) { } catch (e) {
print('ActivityPlacesService: Erreur géocodage: $e'); print('ActivityPlacesService: Erreur géocodage: $e');
throw e; // Rethrow pour permettre la gestion d'erreur en amont rethrow; // Rethrow pour permettre la gestion d'erreur en amont
} }
} }
@@ -152,7 +168,8 @@ class ActivityPlacesService {
int radius, int radius,
) async { ) async {
try { try {
final url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json' final url =
'https://maps.googleapis.com/maps/api/place/nearbysearch/json'
'?location=$lat,$lng' '?location=$lat,$lng'
'&radius=$radius' '&radius=$radius'
'&type=${category.googlePlaceType}' '&type=${category.googlePlaceType}'
@@ -168,7 +185,11 @@ class ActivityPlacesService {
for (final place in data['results']) { for (final place in data['results']) {
try { try {
final activity = await _convertPlaceToActivity(place, tripId, category); final activity = await _convertPlaceToActivity(
place,
tripId,
category,
);
if (activity != null) { if (activity != null) {
activities.add(activity); activities.add(activity);
} }
@@ -208,7 +229,8 @@ class ActivityPlacesService {
String? imageUrl; String? imageUrl;
if (photos != null && photos.isNotEmpty) { if (photos != null && photos.isNotEmpty) {
final photoReference = photos.first['photo_reference']; final photoReference = photos.first['photo_reference'];
imageUrl = 'https://maps.googleapis.com/maps/api/place/photo' imageUrl =
'https://maps.googleapis.com/maps/api/place/photo'
'?maxwidth=800' '?maxwidth=800'
'&photoreference=$photoReference' '&photoreference=$photoReference'
'&key=$_apiKey'; '&key=$_apiKey';
@@ -218,7 +240,8 @@ class ActivityPlacesService {
id: '', // Sera généré lors de la sauvegarde id: '', // Sera généré lors de la sauvegarde
tripId: tripId, tripId: tripId,
name: place['name'] ?? 'Activité inconnue', name: place['name'] ?? 'Activité inconnue',
description: details?['editorial_summary']?['overview'] ?? description:
details?['editorial_summary']?['overview'] ??
details?['formatted_address'] ?? details?['formatted_address'] ??
'Découvrez cette activité incontournable !', 'Découvrez cette activité incontournable !',
category: category.displayName, category: category.displayName,
@@ -236,7 +259,6 @@ class ActivityPlacesService {
createdAt: DateTime.now(), createdAt: DateTime.now(),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
); );
} catch (e) { } catch (e) {
print('ActivityPlacesService: Erreur conversion place: $e'); print('ActivityPlacesService: Erreur conversion place: $e');
return null; return null;
@@ -246,7 +268,8 @@ class ActivityPlacesService {
/// Récupère les détails d'un lieu /// Récupère les détails d'un lieu
Future<Map<String, dynamic>?> _getPlaceDetails(String placeId) async { Future<Map<String, dynamic>?> _getPlaceDetails(String placeId) async {
try { try {
final url = 'https://maps.googleapis.com/maps/api/place/details/json' final url =
'https://maps.googleapis.com/maps/api/place/details/json'
'?place_id=$placeId' '?place_id=$placeId'
'&fields=formatted_address,formatted_phone_number,website,opening_hours,editorial_summary' '&fields=formatted_address,formatted_phone_number,website,opening_hours,editorial_summary'
'&key=$_apiKey'; '&key=$_apiKey';
@@ -303,13 +326,16 @@ class ActivityPlacesService {
int radius = 5000, int radius = 5000,
}) async { }) async {
try { try {
print('ActivityPlacesService: Recherche textuelle: $query à $destination'); print(
'ActivityPlacesService: Recherche textuelle: $query à $destination',
);
// Géocoder la destination // Géocoder la destination
final coordinates = await _geocodeDestination(destination); final coordinates = await _geocodeDestination(destination);
final encodedQuery = Uri.encodeComponent(query); final encodedQuery = Uri.encodeComponent(query);
final url = 'https://maps.googleapis.com/maps/api/place/textsearch/json' final url =
'https://maps.googleapis.com/maps/api/place/textsearch/json'
'?query=$encodedQuery in $destination' '?query=$encodedQuery in $destination'
'&location=${coordinates['lat']},${coordinates['lng']}' '&location=${coordinates['lat']},${coordinates['lng']}'
'&radius=$radius' '&radius=$radius'
@@ -329,7 +355,11 @@ class ActivityPlacesService {
final types = List<String>.from(place['types'] ?? []); final types = List<String>.from(place['types'] ?? []);
final category = _determineCategoryFromTypes(types); final category = _determineCategoryFromTypes(types);
final activity = await _convertPlaceToActivity(place, tripId, category); final activity = await _convertPlaceToActivity(
place,
tripId,
category,
);
if (activity != null) { if (activity != null) {
activities.add(activity); activities.add(activity);
} }
@@ -393,9 +423,13 @@ class ActivityPlacesService {
if (latitude != null && longitude != null) { if (latitude != null && longitude != null) {
lat = latitude; lat = latitude;
lng = longitude; lng = longitude;
print('ActivityPlacesService: Utilisation des coordonnées pré-géolocalisées: $lat, $lng'); print(
'ActivityPlacesService: Utilisation des coordonnées pré-géolocalisées: $lat, $lng',
);
} else if (destination != null) { } else if (destination != null) {
print('ActivityPlacesService: Géolocalisation de la destination: $destination'); print(
'ActivityPlacesService: Géolocalisation de la destination: $destination',
);
final coordinates = await _geocodeDestination(destination); final coordinates = await _geocodeDestination(destination);
lat = coordinates['lat']!; lat = coordinates['lat']!;
lng = coordinates['lng']!; lng = coordinates['lng']!;
@@ -403,7 +437,9 @@ class ActivityPlacesService {
throw Exception('Destination ou coordonnées requises'); throw Exception('Destination ou coordonnées requises');
} }
print('ActivityPlacesService: Recherche paginée aux coordonnées: $lat, $lng (page: ${nextPageToken ?? "première"})'); print(
'ActivityPlacesService: Recherche paginée aux coordonnées: $lat, $lng (page: ${nextPageToken ?? "première"})',
);
// 2. Rechercher les activités par catégorie avec pagination // 2. Rechercher les activités par catégorie avec pagination
if (category != null) { if (category != null) {
@@ -449,7 +485,8 @@ class ActivityPlacesService {
String? nextPageToken, String? nextPageToken,
) async { ) async {
try { try {
String url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json' String url =
'https://maps.googleapis.com/maps/api/place/nearbysearch/json'
'?location=$lat,$lng' '?location=$lat,$lng'
'&radius=$radius' '&radius=$radius'
'&type=${category.googlePlaceType}' '&type=${category.googlePlaceType}'
@@ -473,7 +510,11 @@ class ActivityPlacesService {
for (final place in limitedResults) { for (final place in limitedResults) {
try { try {
final activity = await _convertPlaceToActivity(place, tripId, category); final activity = await _convertPlaceToActivity(
place,
tripId,
category,
);
if (activity != null) { if (activity != null) {
activities.add(activity); activities.add(activity);
} }
@@ -516,7 +557,8 @@ class ActivityPlacesService {
) async { ) async {
try { try {
// Pour toutes les catégories, on utilise une recherche plus générale // Pour toutes les catégories, on utilise une recherche plus générale
String url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json' String url =
'https://maps.googleapis.com/maps/api/place/nearbysearch/json'
'?location=$lat,$lng' '?location=$lat,$lng'
'&radius=$radius' '&radius=$radius'
'&type=tourist_attraction' '&type=tourist_attraction'
@@ -544,7 +586,11 @@ class ActivityPlacesService {
final types = List<String>.from(place['types'] ?? []); final types = List<String>.from(place['types'] ?? []);
final category = _determineCategoryFromTypes(types); final category = _determineCategoryFromTypes(types);
final activity = await _convertPlaceToActivity(place, tripId, category); final activity = await _convertPlaceToActivity(
place,
tripId,
category,
);
if (activity != null) { if (activity != null) {
activities.add(activity); activities.add(activity);
} }
@@ -567,7 +613,9 @@ class ActivityPlacesService {
'hasMoreData': false, 'hasMoreData': false,
}; };
} catch (e) { } catch (e) {
print('ActivityPlacesService: Erreur recherche toutes catégories paginée: $e'); print(
'ActivityPlacesService: Erreur recherche toutes catégories paginée: $e',
);
return { return {
'activities': <Activity>[], 'activities': <Activity>[],
'nextPageToken': null, 'nextPageToken': null,

View File

@@ -0,0 +1,54 @@
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'error_service.dart';
class ProfileImageService {
static final FirebaseStorage _storage = FirebaseStorage.instance;
final ImagePicker _picker = ImagePicker();
final ErrorService _errorService = ErrorService();
Future<String?> uploadCustomProfileImage(
String userId,
XFile imageFile,
) async {
try {
final ref = _storage.ref().child('profile_images').child('$userId.jpg');
final uploadTask = ref.putFile(File(imageFile.path));
final snapshot = await uploadTask;
return await snapshot.ref.getDownloadURL();
} catch (e) {
_errorService.logError(
'ProfileImageService',
'Erreur lors du téléchargement de l\'image de profil pour $userId: $e',
);
return null;
}
}
Future<XFile?> pickProfileImageFromGallery() async {
try {
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
return image;
} catch (e) {
_errorService.logError(
'ProfileImageService',
'Erreur lors de la sélection de l\'image depuis la galerie: $e',
);
return null;
}
}
Future<XFile?> takePhoto() async {
try {
final XFile? photo = await _picker.pickImage(source: ImageSource.camera);
return photo;
} catch (e) {
_errorService.logError(
'ProfileImageService',
'Erreur lors de la prise de photo: $e',
);
return null;
}
}
}

View File

@@ -11,9 +11,9 @@ class TripImageService {
/// Charge les images manquantes pour une liste de voyages /// Charge les images manquantes pour une liste de voyages
Future<void> loadMissingImages(List<Trip> trips) async { Future<void> loadMissingImages(List<Trip> trips) async {
final tripsWithoutImage = trips.where( final tripsWithoutImage = trips
(trip) => trip.imageUrl == null || trip.imageUrl!.isEmpty .where((trip) => trip.imageUrl == null || trip.imageUrl!.isEmpty)
).toList(); .toList();
if (tripsWithoutImage.isEmpty) { if (tripsWithoutImage.isEmpty) {
return; return;
@@ -35,14 +35,13 @@ class TripImageService {
/// Charge l'image pour un voyage spécifique /// Charge l'image pour un voyage spécifique
Future<void> _loadImageForTrip(Trip trip) async { Future<void> _loadImageForTrip(Trip trip) async {
// D'abord vérifier si une image existe déjà dans le Storage // D'abord vérifier si une image existe déjà dans le Storage
String? imageUrl = await _placeImageService.getExistingImageUrl(trip.location); String? imageUrl = await _placeImageService.getExistingImageUrl(
trip.location,
);
// Si aucune image n'existe, en télécharger une nouvelle // Si aucune image n'existe, en télécharger une nouvelle
if (imageUrl == null) { imageUrl ??= await _placeImageService.getPlaceImageUrl(trip.location);
imageUrl = await _placeImageService.getPlaceImageUrl(trip.location);
}
if (imageUrl != null && trip.id != null) { if (imageUrl != null && trip.id != null) {
// Mettre à jour le voyage avec l'image (existante ou nouvelle) // Mettre à jour le voyage avec l'image (existante ou nouvelle)
@@ -52,8 +51,7 @@ class TripImageService {
); );
await _tripRepository.updateTrip(trip.id!, updatedTrip); await _tripRepository.updateTrip(trip.id!, updatedTrip);
} else { } else {}
}
} }
/// Recharge l'image d'un voyage spécifique (force le rechargement) /// Recharge l'image d'un voyage spécifique (force le rechargement)
@@ -96,7 +94,6 @@ class TripImageService {
// Nettoyer les images inutilisées // Nettoyer les images inutilisées
await _placeImageService.cleanupUnusedImages(usedImageUrls); await _placeImageService.cleanupUnusedImages(usedImageUrls);
} catch (e) { } catch (e) {
_errorService.logError( _errorService.logError(
'TripImageService', 'TripImageService',
@@ -111,7 +108,9 @@ class TripImageService {
final tripsStream = _tripRepository.getTripsByUserId(userId); final tripsStream = _tripRepository.getTripsByUserId(userId);
final trips = await tripsStream.first; final trips = await tripsStream.first;
final tripsWithImages = trips.where((trip) => trip.imageUrl != null && trip.imageUrl!.isNotEmpty).length; final tripsWithImages = trips
.where((trip) => trip.imageUrl != null && trip.imageUrl!.isNotEmpty)
.length;
final tripsWithoutImages = trips.length - tripsWithImages; final tripsWithoutImages = trips.length - tripsWithImages;
return { return {
@@ -121,7 +120,10 @@ class TripImageService {
'timestamp': DateTime.now().toIso8601String(), 'timestamp': DateTime.now().toIso8601String(),
}; };
} catch (e) { } catch (e) {
_errorService.logError('TripImageService', 'Erreur lors de l\'obtention des statistiques: $e'); _errorService.logError(
'TripImageService',
'Erreur lors de l\'obtention des statistiques: $e',
);
return { return {
'error': e.toString(), 'error': e.toString(),
'timestamp': DateTime.now().toIso8601String(), 'timestamp': DateTime.now().toIso8601String(),