Add UserStateWrapper and ProfileImageService for user state management and profile image handling
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
42
lib/components/widgets/user_state_widget.dart
Normal file
42
lib/components/widgets/user_state_widget.dart
Normal 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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,14 +24,16 @@ 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);
|
||||||
|
|
||||||
// 2. Rechercher les activités par catégorie ou toutes les catégories
|
// 2. Rechercher les activités par catégorie ou toutes les catégories
|
||||||
List<Activity> allActivities = [];
|
List<Activity> allActivities = [];
|
||||||
|
|
||||||
if (category != null) {
|
if (category != null) {
|
||||||
final activities = await _searchByCategory(
|
final activities = await _searchByCategory(
|
||||||
coordinates['lat']!,
|
coordinates['lat']!,
|
||||||
@@ -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);
|
|
||||||
print('ActivityPlacesService: Retour de ${paginatedResults.length} activités (offset: $offset, max: $maxResults)');
|
|
||||||
|
|
||||||
return paginatedResults;
|
|
||||||
|
|
||||||
|
final paginatedResults = uniqueActivities.sublist(startIndex, endIndex);
|
||||||
|
print(
|
||||||
|
'ActivityPlacesService: Retour de ${paginatedResults.length} activités (offset: $offset, max: $maxResults)',
|
||||||
|
);
|
||||||
|
|
||||||
|
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,19 +110,20 @@ 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');
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
|
|
||||||
print('ActivityPlacesService: Status code = ${response.statusCode}');
|
print('ActivityPlacesService: Status code = ${response.statusCode}');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = json.decode(response.body);
|
final data = json.decode(response.body);
|
||||||
print('ActivityPlacesService: Réponse géocodage = ${data['status']}');
|
print('ActivityPlacesService: Réponse géocodage = ${data['status']}');
|
||||||
|
|
||||||
if (data['status'] == 'OK' && data['results'].isNotEmpty) {
|
if (data['status'] == 'OK' && data['results'].isNotEmpty) {
|
||||||
final location = data['results'][0]['geometry']['location'];
|
final location = data['results'][0]['geometry']['location'];
|
||||||
final coordinates = {
|
final coordinates = {
|
||||||
@@ -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(
|
||||||
'• Geocoding API\n'
|
'🔑 Clé API non autorisée. Activez les APIs suivantes dans Google Cloud Console:\n'
|
||||||
'• Places API\n'
|
'• Geocoding API\n'
|
||||||
'• Maps JavaScript API\n'
|
'• Places API\n'
|
||||||
'Puis ajoutez des restrictions appropriées.');
|
'• Maps JavaScript API\n'
|
||||||
|
'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,23 +168,28 @@ 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}'
|
||||||
'&key=$_apiKey';
|
'&key=$_apiKey';
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = json.decode(response.body);
|
final data = json.decode(response.body);
|
||||||
|
|
||||||
if (data['status'] == 'OK') {
|
if (data['status'] == 'OK') {
|
||||||
final List<Activity> activities = [];
|
final List<Activity> activities = [];
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -176,11 +197,11 @@ class ActivityPlacesService {
|
|||||||
print('ActivityPlacesService: Erreur conversion place: $e');
|
print('ActivityPlacesService: Erreur conversion place: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return activities;
|
return activities;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('ActivityPlacesService: Erreur recherche par catégorie: $e');
|
print('ActivityPlacesService: Erreur recherche par catégorie: $e');
|
||||||
@@ -200,15 +221,16 @@ class ActivityPlacesService {
|
|||||||
|
|
||||||
// Récupérer les détails supplémentaires
|
// Récupérer les détails supplémentaires
|
||||||
final details = await _getPlaceDetails(placeId);
|
final details = await _getPlaceDetails(placeId);
|
||||||
|
|
||||||
final geometry = place['geometry']?['location'];
|
final geometry = place['geometry']?['location'];
|
||||||
final photos = place['photos'] as List?;
|
final photos = place['photos'] as List?;
|
||||||
|
|
||||||
// Obtenir une image de qualité
|
// Obtenir une image de qualité
|
||||||
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,9 +240,10 @@ 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?['formatted_address'] ??
|
details?['editorial_summary']?['overview'] ??
|
||||||
'Découvrez cette activité incontournable !',
|
details?['formatted_address'] ??
|
||||||
|
'Découvrez cette activité incontournable !',
|
||||||
category: category.displayName,
|
category: category.displayName,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
rating: place['rating']?.toDouble(),
|
rating: place['rating']?.toDouble(),
|
||||||
@@ -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,20 +268,21 @@ 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';
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = json.decode(response.body);
|
final data = json.decode(response.body);
|
||||||
if (data['status'] == 'OK') {
|
if (data['status'] == 'OK') {
|
||||||
return data['result'];
|
return data['result'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('ActivityPlacesService: Erreur récupération détails: $e');
|
print('ActivityPlacesService: Erreur récupération détails: $e');
|
||||||
@@ -277,10 +300,10 @@ class ActivityPlacesService {
|
|||||||
/// Parse les heures d'ouverture
|
/// Parse les heures d'ouverture
|
||||||
List<String> _parseOpeningHours(Map<String, dynamic>? openingHours) {
|
List<String> _parseOpeningHours(Map<String, dynamic>? openingHours) {
|
||||||
if (openingHours == null) return [];
|
if (openingHours == null) return [];
|
||||||
|
|
||||||
final weekdayText = openingHours['weekday_text'] as List?;
|
final weekdayText = openingHours['weekday_text'] as List?;
|
||||||
if (weekdayText == null) return [];
|
if (weekdayText == null) return [];
|
||||||
|
|
||||||
return weekdayText.cast<String>();
|
return weekdayText.cast<String>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,33 +326,40 @@ 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'
|
||||||
'&key=$_apiKey';
|
'&key=$_apiKey';
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = json.decode(response.body);
|
final data = json.decode(response.body);
|
||||||
|
|
||||||
if (data['status'] == 'OK') {
|
if (data['status'] == 'OK') {
|
||||||
final List<Activity> activities = [];
|
final List<Activity> activities = [];
|
||||||
|
|
||||||
for (final place in data['results']) {
|
for (final place in data['results']) {
|
||||||
try {
|
try {
|
||||||
// Déterminer la catégorie basée sur les types du lieu
|
// Déterminer la catégorie basée sur les types du lieu
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -337,11 +367,11 @@ class ActivityPlacesService {
|
|||||||
print('ActivityPlacesService: Erreur conversion place: $e');
|
print('ActivityPlacesService: Erreur conversion place: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return activities;
|
return activities;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('ActivityPlacesService: Erreur recherche textuelle: $e');
|
print('ActivityPlacesService: Erreur recherche textuelle: $e');
|
||||||
@@ -358,7 +388,7 @@ class ActivityPlacesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catégories par défaut basées sur des types communs
|
// Catégories par défaut basées sur des types communs
|
||||||
if (types.contains('restaurant') || types.contains('food')) {
|
if (types.contains('restaurant') || types.contains('food')) {
|
||||||
return ActivityCategory.restaurant;
|
return ActivityCategory.restaurant;
|
||||||
@@ -371,7 +401,7 @@ class ActivityPlacesService {
|
|||||||
} else if (types.contains('night_club') || types.contains('bar')) {
|
} else if (types.contains('night_club') || types.contains('bar')) {
|
||||||
return ActivityCategory.nightlife;
|
return ActivityCategory.nightlife;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ActivityCategory.attraction; // Par défaut
|
return ActivityCategory.attraction; // Par défaut
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,14 +418,18 @@ class ActivityPlacesService {
|
|||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
double lat, lng;
|
double lat, lng;
|
||||||
|
|
||||||
// Utiliser les coordonnées fournies ou géocoder la destination
|
// Utiliser les coordonnées fournies ou géocoder la destination
|
||||||
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}'
|
||||||
@@ -460,20 +497,24 @@ class ActivityPlacesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = json.decode(response.body);
|
final data = json.decode(response.body);
|
||||||
|
|
||||||
if (data['status'] == 'OK') {
|
if (data['status'] == 'OK') {
|
||||||
final List<Activity> activities = [];
|
final List<Activity> activities = [];
|
||||||
final results = data['results'] as List? ?? [];
|
final results = data['results'] as List? ?? [];
|
||||||
|
|
||||||
// Limiter à pageSize résultats
|
// Limiter à pageSize résultats
|
||||||
final limitedResults = results.take(pageSize).toList();
|
final limitedResults = results.take(pageSize).toList();
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -481,7 +522,7 @@ class ActivityPlacesService {
|
|||||||
print('ActivityPlacesService: Erreur conversion place: $e');
|
print('ActivityPlacesService: Erreur conversion place: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'activities': activities,
|
'activities': activities,
|
||||||
'nextPageToken': data['next_page_token'],
|
'nextPageToken': data['next_page_token'],
|
||||||
@@ -489,7 +530,7 @@ class ActivityPlacesService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'activities': <Activity>[],
|
'activities': <Activity>[],
|
||||||
'nextPageToken': null,
|
'nextPageToken': null,
|
||||||
@@ -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'
|
||||||
@@ -527,24 +569,28 @@ class ActivityPlacesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final response = await http.get(Uri.parse(url));
|
final response = await http.get(Uri.parse(url));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = json.decode(response.body);
|
final data = json.decode(response.body);
|
||||||
|
|
||||||
if (data['status'] == 'OK') {
|
if (data['status'] == 'OK') {
|
||||||
final List<Activity> activities = [];
|
final List<Activity> activities = [];
|
||||||
final results = data['results'] as List? ?? [];
|
final results = data['results'] as List? ?? [];
|
||||||
|
|
||||||
// Limiter à pageSize résultats
|
// Limiter à pageSize résultats
|
||||||
final limitedResults = results.take(pageSize).toList();
|
final limitedResults = results.take(pageSize).toList();
|
||||||
|
|
||||||
for (final place in limitedResults) {
|
for (final place in limitedResults) {
|
||||||
try {
|
try {
|
||||||
// Déterminer la catégorie basée sur les types du lieu
|
// Déterminer la catégorie basée sur les types du lieu
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -552,7 +598,7 @@ class ActivityPlacesService {
|
|||||||
print('ActivityPlacesService: Erreur conversion place: $e');
|
print('ActivityPlacesService: Erreur conversion place: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'activities': activities,
|
'activities': activities,
|
||||||
'nextPageToken': data['next_page_token'],
|
'nextPageToken': data['next_page_token'],
|
||||||
@@ -560,14 +606,16 @@ class ActivityPlacesService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'activities': <Activity>[],
|
'activities': <Activity>[],
|
||||||
'nextPageToken': null,
|
'nextPageToken': null,
|
||||||
'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,
|
||||||
@@ -575,4 +623,4 @@ class ActivityPlacesService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
lib/services/profile_image_service.dart
Normal file
54
lib/services/profile_image_service.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,17 +11,17 @@ 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;
|
||||||
}
|
}
|
||||||
for (final trip in tripsWithoutImage) {
|
for (final trip in tripsWithoutImage) {
|
||||||
try {
|
try {
|
||||||
await _loadImageForTrip(trip);
|
await _loadImageForTrip(trip);
|
||||||
|
|
||||||
// Petite pause entre les requêtes pour éviter de surcharger l'API
|
// Petite pause entre les requêtes pour éviter de surcharger l'API
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -35,42 +35,40 @@ 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)
|
||||||
final updatedTrip = trip.copyWith(
|
final updatedTrip = trip.copyWith(
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
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)
|
||||||
Future<String?> reloadImageForTrip(Trip trip) async {
|
Future<String?> reloadImageForTrip(Trip trip) async {
|
||||||
try {
|
try {
|
||||||
final imageUrl = await _placeImageService.getPlaceImageUrl(trip.location);
|
final imageUrl = await _placeImageService.getPlaceImageUrl(trip.location);
|
||||||
|
|
||||||
if (imageUrl != null && trip.id != null) {
|
if (imageUrl != null && trip.id != null) {
|
||||||
final updatedTrip = trip.copyWith(
|
final updatedTrip = trip.copyWith(
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
updatedAt: DateTime.now(),
|
updatedAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
await _tripRepository.updateTrip(trip.id!, updatedTrip);
|
await _tripRepository.updateTrip(trip.id!, updatedTrip);
|
||||||
return imageUrl;
|
return imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_errorService.logError(
|
_errorService.logError(
|
||||||
@@ -87,16 +85,15 @@ class TripImageService {
|
|||||||
// Récupérer tous les voyages de l'utilisateur
|
// Récupérer tous les voyages de l'utilisateur
|
||||||
final tripsStream = _tripRepository.getTripsByUserId(userId);
|
final tripsStream = _tripRepository.getTripsByUserId(userId);
|
||||||
final trips = await tripsStream.first;
|
final trips = await tripsStream.first;
|
||||||
|
|
||||||
// Extraire toutes les URLs d'images utilisées
|
// Extraire toutes les URLs d'images utilisées
|
||||||
final usedImageUrls = trips
|
final usedImageUrls = trips
|
||||||
.where((trip) => trip.imageUrl != null && trip.imageUrl!.isNotEmpty)
|
.where((trip) => trip.imageUrl != null && trip.imageUrl!.isNotEmpty)
|
||||||
.map((trip) => trip.imageUrl!)
|
.map((trip) => trip.imageUrl!)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// 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',
|
||||||
@@ -110,10 +107,12 @@ class TripImageService {
|
|||||||
try {
|
try {
|
||||||
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 {
|
||||||
'totalTrips': trips.length,
|
'totalTrips': trips.length,
|
||||||
'tripsWithImages': tripsWithImages,
|
'tripsWithImages': tripsWithImages,
|
||||||
@@ -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(),
|
||||||
@@ -140,4 +142,4 @@ class TripImageService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user