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

@@ -0,0 +1,62 @@
import 'dart:io';
import '../lib/services/trip_image_service.dart';
/// Script utilitaire pour nettoyer les images inutilisées
/// À exécuter manuellement si nécessaire
void main() async {
print('🧹 Script de nettoyage des images inutilisées');
print('=====================================');
try {
final tripImageService = TripImageService();
// Remplacez par votre ID utilisateur
// Vous pouvez le récupérer depuis Firebase Auth dans votre app
const userId = 'YOUR_USER_ID_HERE';
if (userId == 'YOUR_USER_ID_HERE') {
print('❌ Veuillez configurer votre userId dans le script');
print(' Récupérez votre ID depuis Firebase Auth dans l\'app');
return;
}
print('📊 Récupération des statistiques...');
final stats = await tripImageService.getImageStatistics(userId);
print('Statistiques actuelles:');
print('- Voyages totaux: ${stats['totalTrips']}');
print('- Voyages avec image: ${stats['tripsWithImages']}');
print('- Voyages sans image: ${stats['tripsWithoutImages']}');
if (stats['tripsWithImages'] > 0) {
print('\n🧹 Nettoyage des images inutilisées...');
await tripImageService.cleanupUnusedImages(userId);
print('\n📊 Nouvelles statistiques...');
final newStats = await tripImageService.getImageStatistics(userId);
print('- Voyages totaux: ${newStats['totalTrips']}');
print('- Voyages avec image: ${newStats['tripsWithImages']}');
print('- Voyages sans image: ${newStats['tripsWithoutImages']}');
} else {
print('✅ Aucune image à nettoyer');
}
print('\n✅ Script terminé avec succès');
} catch (e) {
print('❌ Erreur lors du nettoyage: $e');
exit(1);
}
}
/*
Instructions d'utilisation:
1. Ouvrez votre application Flutter
2. Connectez-vous à votre compte
3. Dans le code de votre app, ajoutez temporairement:
print('User ID: ${FirebaseAuth.instance.currentUser?.uid}');
4. Récupérez votre User ID affiché dans la console
5. Remplacez 'YOUR_USER_ID_HERE' par votre ID dans ce script
6. Exécutez: dart run scripts/cleanup_images.dart
*/

View File

@@ -0,0 +1,55 @@
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import '../lib/services/trip_image_service.dart';
import '../lib/firebase_options.dart';
/// Script pour nettoyer les doublons d'images de Londres
void main() async {
print('🧹 Nettoyage spécifique des doublons d\'images de Londres');
print('========================================================');
try {
// Initialiser Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
print('✅ Firebase initialisé');
final tripImageService = TripImageService();
print('🔍 Analyse et nettoyage des doublons...');
await tripImageService.cleanupDuplicateImages();
print('✅ Nettoyage terminé !');
print('');
print('🎯 Les doublons pour Londres (et autres destinations) ont été supprimés');
print(' Seule l\'image la plus récente pour chaque destination a été conservée');
} catch (e) {
print('❌ Erreur lors du nettoyage: $e');
exit(1);
}
}
/*
Instructions d'utilisation:
1. Assurez-vous que Firebase est configuré dans votre projet
2. Exécutez: dart run scripts/cleanup_london_duplicates.dart
3. Le script analysera automatiquement tous les doublons et les supprimera
4. Vérifiez Firebase Storage après l'exécution
Le script:
- Groupe toutes les images par destination (normalisée)
- Identifie les doublons pour la même destination
- Garde l'image la plus récente (basé sur le timestamp)
- Supprime les anciennes versions
Pour Londres spécifiquement, si vous avez:
- Londres_Royaume_Uni_1762175016594.jpg
- Londres_Royaume_Uni_1762175016603.jpg
Le script gardera la version _1762175016603.jpg (plus récente)
et supprimera _1762175016594.jpg (plus ancienne)
*/

View File

@@ -0,0 +1,131 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';
import '../lib/firebase_options.dart';
/// Script de diagnostic pour analyser les images dans Firebase Storage
void main() async {
print('🔍 Diagnostic des images Firebase Storage');
print('=========================================');
try {
// Initialiser Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
final storage = FirebaseStorage.instance;
print('📂 Analyse du dossier trip_images...');
final listResult = await storage.ref('trip_images').listAll();
if (listResult.items.isEmpty) {
print('❌ Aucune image trouvée dans trip_images/');
return;
}
print('📊 ${listResult.items.length} image(s) trouvée(s):');
print('');
final Map<String, List<Map<String, dynamic>>> locationGroups = {};
for (int i = 0; i < listResult.items.length; i++) {
final item = listResult.items[i];
final fileName = item.name;
print('${i + 1}. Fichier: $fileName');
try {
// Récupérer les métadonnées
final metadata = await item.getMetadata();
final customMeta = metadata.customMetadata ?? {};
final location = customMeta['location'] ?? 'Inconnue';
final normalizedLocation = customMeta['normalizedLocation'] ?? 'Non définie';
final source = customMeta['source'] ?? 'Inconnue';
final uploadedAt = customMeta['uploadedAt'] ?? 'Inconnue';
print(' 📍 Location: $location');
print(' 🏷️ Normalized: $normalizedLocation');
print(' 📤 Source: $source');
print(' 📅 Upload: $uploadedAt');
// Récupérer l'URL de téléchargement
final downloadUrl = await item.getDownloadURL();
print(' 🔗 URL: $downloadUrl');
// Grouper par location normalisée
final groupKey = normalizedLocation != 'Non définie' ? normalizedLocation : location.toLowerCase();
if (!locationGroups.containsKey(groupKey)) {
locationGroups[groupKey] = [];
}
locationGroups[groupKey]!.add({
'fileName': fileName,
'location': location,
'normalizedLocation': normalizedLocation,
'uploadedAt': uploadedAt,
'downloadUrl': downloadUrl,
});
} catch (e) {
print(' ❌ Erreur lecture métadonnées: $e');
// Essayer de deviner la location depuis le nom du fichier
final parts = fileName.split('_');
if (parts.length >= 2) {
final guessedLocation = parts.take(parts.length - 1).join('_');
print(' 🤔 Location devinée: $guessedLocation');
if (!locationGroups.containsKey(guessedLocation)) {
locationGroups[guessedLocation] = [];
}
locationGroups[guessedLocation]!.add({
'fileName': fileName,
'location': 'Devinée: $guessedLocation',
'normalizedLocation': 'Non définie',
'uploadedAt': 'Inconnue',
'downloadUrl': 'Non récupérée',
});
}
}
print('');
}
// Analyser les doublons
print('🔍 Analyse des doublons par location:');
print('====================================');
int totalDuplicates = 0;
for (final entry in locationGroups.entries) {
final location = entry.key;
final images = entry.value;
if (images.length > 1) {
print('⚠️ DOUBLONS détectés pour "$location": ${images.length} images');
totalDuplicates += images.length - 1;
for (int i = 0; i < images.length; i++) {
final image = images[i];
print(' ${i + 1}. ${image['fileName']} (${image['uploadedAt']})');
}
print('');
} else {
print('✅ "$location": 1 image (OK)');
}
}
print('📈 Résumé:');
print('- Total images: ${listResult.items.length}');
print('- Locations uniques: ${locationGroups.length}');
print('- Images en doublon: $totalDuplicates');
print('- Économie possible: ${totalDuplicates} images peuvent être supprimées');
if (totalDuplicates > 0) {
print('');
print('💡 Suggestion: Utilisez la fonctionnalité de nettoyage pour supprimer les doublons');
}
} catch (e) {
print('❌ Erreur lors du diagnostic: $e');
}
}

View File

@@ -0,0 +1,98 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';
void main() async {
print("🧹 Début du nettoyage des doublons Londres...");
try {
await Firebase.initializeApp();
print("✅ Firebase initialisé");
final storage = FirebaseStorage.instance;
final ref = storage.ref().child('trip_images');
print("📋 Récupération de la liste des images...");
final result = await ref.listAll();
print("📊 Nombre total d'images: ${result.items.length}");
// Grouper les images par ville
Map<String, List<Reference>> imagesByCity = {};
for (var item in result.items) {
final name = item.name;
print("🖼️ Image trouvée: $name");
// Extraire la ville du nom de fichier
String city = 'unknown';
if (name.contains('_')) {
// Format: londres_timestamp.jpg ou london_timestamp.jpg
city = name.split('_')[0].toLowerCase();
}
if (!imagesByCity.containsKey(city)) {
imagesByCity[city] = [];
}
imagesByCity[city]!.add(item);
}
print("\n📍 Images par ville:");
for (var entry in imagesByCity.entries) {
print(" ${entry.key}: ${entry.value.length} image(s)");
}
// Focus sur Londres/London
final londonImages = <Reference>[];
londonImages.addAll(imagesByCity['londres'] ?? []);
londonImages.addAll(imagesByCity['london'] ?? []);
print("\n🏴󠁧󠁢󠁥󠁮󠁧󠁿 Images de Londres trouvées: ${londonImages.length}");
if (londonImages.length > 1) {
print("🔄 Suppression des doublons...");
// Trier par timestamp (garder la plus récente)
londonImages.sort((a, b) {
final timestampA = _extractTimestamp(a.name);
final timestampB = _extractTimestamp(b.name);
return timestampB.compareTo(timestampA); // Plus récent en premier
});
print("📅 Images triées par timestamp:");
for (var image in londonImages) {
final timestamp = _extractTimestamp(image.name);
print(" ${image.name} - $timestamp");
}
// Supprimer toutes sauf la première (plus récente)
for (int i = 1; i < londonImages.length; i++) {
print("🗑️ Suppression: ${londonImages[i].name}");
await londonImages[i].delete();
}
print("✅ Suppression terminée. Image conservée: ${londonImages[0].name}");
} else {
print(" Aucun doublon trouvé pour Londres");
}
print("\n🎉 Nettoyage terminé !");
} catch (e) {
print("❌ Erreur: $e");
}
}
int _extractTimestamp(String filename) {
try {
// Extraire le timestamp du nom de fichier
// Format: ville_timestamp.jpg
final parts = filename.split('_');
if (parts.length >= 2) {
final timestampPart = parts[1].split('.')[0]; // Enlever l'extension
return int.parse(timestampPart);
}
} catch (e) {
print("⚠️ Impossible d'extraire le timestamp de $filename");
}
return 0; // Timestamp par défaut
}