feat: Add TripImageService for automatic trip image management

- Implemented TripImageService to load missing images for trips, reload images, and clean up unused images.
- Added functionality to get image statistics and clean up duplicate images.
- Created utility scripts for manual image cleanup and diagnostics in Firebase Storage.
- Introduced tests for image loading optimization and photo quality algorithms.
- Updated dependencies in pubspec.yaml and pubspec.lock for image handling.
This commit is contained in:
Dayron
2025-11-03 14:33:58 +01:00
parent 83aed85fea
commit e3dad39c4f
16 changed files with 2415 additions and 190 deletions

View File

@@ -19,6 +19,7 @@ import '../../services/user_service.dart';
import '../../repositories/group_repository.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../services/place_image_service.dart';
/// Create trip content widget for trip creation and editing functionality.
///
@@ -69,6 +70,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
/// Services for user and group operations
final _userService = UserService();
final _groupRepository = GroupRepository();
final _placeImageService = PlaceImageService();
/// Trip date variables
DateTime? _startDate;
@@ -77,6 +79,8 @@ class _CreateTripContentState extends State<CreateTripContent> {
/// Loading and state management variables
bool _isLoading = false;
String? _createdTripId;
String? _selectedImageUrl;
bool _isLoadingImage = false;
/// Google Maps API key for location services
static final String _apiKey = dotenv.env['GOOGLE_MAPS_API_KEY'] ?? '';
@@ -111,6 +115,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
_budgetController.text = trip.budget?.toString() ?? '';
_startDate = trip.startDate;
_endDate = trip.endDate;
_selectedImageUrl = trip.imageUrl; // Charger l'image existante
});
await _loadParticipantEmails(trip.participants);
@@ -250,6 +255,40 @@ class _CreateTripContentState extends State<CreateTripContent> {
setState(() {
_placeSuggestions = [];
});
// Charger l'image du lieu sélectionné
_loadPlaceImage(suggestion.description);
}
/// Charge l'image du lieu depuis Google Places API
Future<void> _loadPlaceImage(String location) async {
print('CreateTripContent: Chargement de l\'image pour: $location');
setState(() {
_isLoadingImage = true;
});
try {
final imageUrl = await _placeImageService.getPlaceImageUrl(location);
print('CreateTripContent: Image URL reçue: $imageUrl');
if (mounted) {
setState(() {
_selectedImageUrl = imageUrl;
_isLoadingImage = false;
});
print('CreateTripContent: État mis à jour avec imageUrl: $_selectedImageUrl');
}
} catch (e) {
print('CreateTripContent: Erreur lors du chargement de l\'image: $e');
if (mounted) {
setState(() {
_isLoadingImage = false;
});
_errorService.logError(
'create_trip_content.dart',
'Erreur lors du chargement de l\'image: $e',
);
}
}
}
Future<void> _loadParticipantEmails(List<String> participantIds) async {
@@ -420,6 +459,64 @@ class _CreateTripContentState extends State<CreateTripContent> {
),
),
const SizedBox(height: 16),
// Aperçu de l'image du lieu
if (_isLoadingImage || _selectedImageUrl != null) ...[
_buildSectionTitle('Aperçu de la destination'),
const SizedBox(height: 8),
Container(
width: double.infinity,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: _isLoadingImage
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 8),
Text('Chargement de l\'image...'),
],
),
)
: _selectedImageUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
_selectedImageUrl!,
width: double.infinity,
height: 200,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: double.infinity,
height: 200,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, color: Colors.grey),
Text('Erreur de chargement'),
],
),
),
);
},
),
)
: const SizedBox(),
),
const SizedBox(height: 16),
],
const SizedBox(height: 24),
_buildSectionTitle('Dates du voyage'),
@@ -891,6 +988,7 @@ class _CreateTripContentState extends State<CreateTripContent> {
participants: participantIds,
createdAt: isEditing ? widget.tripToEdit!.createdAt : DateTime.now(),
updatedAt: DateTime.now(),
imageUrl: _selectedImageUrl, // Ajouter l'URL de l'image
);
if (isEditing) {