refactor: Clean up code by removing unnecessary whitespace and improving readability

This commit is contained in:
Van Leemput Dayron
2025-11-05 07:55:05 +01:00
parent 9cb21c3470
commit 30dca05e15
8 changed files with 605 additions and 446 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -11,10 +11,7 @@ import '../../services/error_service.dart';
class AddActivityBottomSheet extends StatefulWidget { class AddActivityBottomSheet extends StatefulWidget {
final Trip trip; final Trip trip;
const AddActivityBottomSheet({ const AddActivityBottomSheet({super.key, required this.trip});
Key? key,
required this.trip,
}) : super(key: key);
@override @override
State<AddActivityBottomSheet> createState() => _AddActivityBottomSheetState(); State<AddActivityBottomSheet> createState() => _AddActivityBottomSheetState();
@@ -26,7 +23,7 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
final _descriptionController = TextEditingController(); final _descriptionController = TextEditingController();
final _addressController = TextEditingController(); final _addressController = TextEditingController();
final ErrorService _errorService = ErrorService(); final ErrorService _errorService = ErrorService();
ActivityCategory _selectedCategory = ActivityCategory.attraction; ActivityCategory _selectedCategory = ActivityCategory.attraction;
bool _isLoading = false; bool _isLoading = false;
@@ -43,16 +40,11 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
final theme = Theme.of(context); final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final keyboardHeight = mediaQuery.viewInsets.bottom; final keyboardHeight = mediaQuery.viewInsets.bottom;
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
height: mediaQuery.size.height * 0.85, height: mediaQuery.size.height * 0.85,
margin: EdgeInsets.only( margin: EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 16),
left: 16,
right: 16,
top: 16,
bottom: 16,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.cardColor, color: theme.cardColor,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
@@ -167,7 +159,9 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: theme.colorScheme.outline), side: BorderSide(
color: theme.colorScheme.outline,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@@ -193,7 +187,9 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white), valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
), ),
) )
: const Text('Ajouter'), : const Text('Ajouter'),
@@ -214,9 +210,9 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
Widget _buildSectionTitle(String title) { Widget _buildSectionTitle(String title) {
return Text( return Text(
title, title,
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(
fontWeight: FontWeight.w600, context,
), ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
); );
} }
@@ -240,17 +236,17 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide( borderSide: BorderSide(
color: isDarkMode color: isDarkMode
? Colors.white.withOpacity(0.2) ? Colors.white.withOpacity(0.2)
: Colors.black.withOpacity(0.2), : Colors.black.withOpacity(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.withOpacity(0.2)
: Colors.black.withOpacity(0.2), : Colors.black.withOpacity(0.2),
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
@@ -265,15 +261,13 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
Widget _buildCategorySelector() { Widget _buildCategorySelector() {
final theme = Theme.of(context); final theme = Theme.of(context);
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.colorScheme.surface, color: theme.colorScheme.surface,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(color: theme.colorScheme.outline.withOpacity(0.5)),
color: theme.colorScheme.outline.withOpacity(0.5),
),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -290,7 +284,7 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
runSpacing: 8, runSpacing: 8,
children: ActivityCategory.values.map((category) { children: ActivityCategory.values.map((category) {
final isSelected = _selectedCategory == category; final isSelected = _selectedCategory == category;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
@@ -298,12 +292,17 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
}); });
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.transparent, color: isSelected ? Colors.blue : Colors.transparent,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: isSelected ? Colors.blue : theme.colorScheme.outline, color: isSelected
? Colors.blue
: theme.colorScheme.outline,
), ),
), ),
child: Row( child: Row(
@@ -312,17 +311,17 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
Icon( Icon(
_getCategoryIcon(category), _getCategoryIcon(category),
size: 16, size: 16,
color: isSelected color: isSelected
? Colors.white ? Colors.white
: theme.colorScheme.onSurface, : theme.colorScheme.onSurface,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
category.displayName, category.displayName,
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: isSelected color: isSelected
? Colors.white ? Colors.white
: theme.colorScheme.onSurface, : theme.colorScheme.onSurface,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -361,19 +360,18 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
name: _nameController.text.trim(), name: _nameController.text.trim(),
description: _descriptionController.text.trim(), description: _descriptionController.text.trim(),
category: _selectedCategory.displayName, category: _selectedCategory.displayName,
address: _addressController.text.trim().isNotEmpty address: _addressController.text.trim().isNotEmpty
? _addressController.text.trim() ? _addressController.text.trim()
: null, : null,
votes: {}, votes: {},
createdAt: DateTime.now(), createdAt: DateTime.now(),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
); );
context.read<ActivityBloc>().add(AddActivity(activity)); context.read<ActivityBloc>().add(AddActivity(activity));
// Fermer le bottom sheet // Fermer le bottom sheet
Navigator.pop(context); Navigator.pop(context);
} catch (e) { } catch (e) {
_errorService.showSnackbar( _errorService.showSnackbar(
message: 'Erreur lors de l\'ajout de l\'activité', message: 'Erreur lors de l\'ajout de l\'activité',
@@ -412,4 +410,4 @@ class _AddActivityBottomSheetState extends State<AddActivityBottomSheet> {
return Icons.spa; return Icons.spa;
} }
} }
} }

View File

@@ -8,11 +8,7 @@ class TripCard extends StatefulWidget {
final Trip trip; final Trip trip;
final VoidCallback? onTap; final VoidCallback? onTap;
const TripCard({ const TripCard({super.key, required this.trip, this.onTap});
super.key,
required this.trip,
this.onTap,
});
@override @override
State<TripCard> createState() => _TripCardState(); State<TripCard> createState() => _TripCardState();
@@ -29,7 +25,7 @@ class _TripCardState extends State<TripCard> {
void initState() { void initState() {
super.initState(); super.initState();
_currentImageUrl = widget.trip.imageUrl; _currentImageUrl = widget.trip.imageUrl;
// Si aucune image n'est disponible, essayer de la charger // Si aucune image n'est disponible, essayer de la charger
if (_currentImageUrl == null || _currentImageUrl!.isEmpty) { if (_currentImageUrl == null || _currentImageUrl!.isEmpty) {
_loadImageForTrip(); _loadImageForTrip();
@@ -38,28 +34,29 @@ class _TripCardState extends State<TripCard> {
Future<void> _loadImageForTrip() async { Future<void> _loadImageForTrip() async {
if (_hasTriedLoading || _isLoadingImage) return; if (_hasTriedLoading || _isLoadingImage) return;
setState(() { setState(() {
_isLoadingImage = true; _isLoadingImage = true;
_hasTriedLoading = true; _hasTriedLoading = true;
}); });
try { try {
// 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(widget.trip.location); String? imageUrl = await _placeImageService.getExistingImageUrl(
widget.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(
imageUrl = await _placeImageService.getPlaceImageUrl(widget.trip.location); widget.trip.location,
} );
if (mounted && imageUrl != null) { if (mounted && imageUrl != null) {
setState(() { setState(() {
_currentImageUrl = imageUrl; _currentImageUrl = imageUrl;
_isLoadingImage = false; _isLoadingImage = false;
}); });
// Mettre à jour le voyage dans la base de données avec l'imageUrl // Mettre à jour le voyage dans la base de données avec l'imageUrl
_updateTripWithImage(imageUrl); _updateTripWithImage(imageUrl);
} else { } else {
@@ -84,7 +81,7 @@ class _TripCardState extends State<TripCard> {
imageUrl: imageUrl, imageUrl: imageUrl,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
); );
// Mettre à jour dans la base de données // Mettre à jour dans la base de données
await _tripRepository.updateTrip(widget.trip.id!, updatedTrip); await _tripRepository.updateTrip(widget.trip.id!, updatedTrip);
} }
@@ -95,7 +92,7 @@ class _TripCardState extends State<TripCard> {
Widget _buildImageWidget() { Widget _buildImageWidget() {
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
if (_isLoadingImage) { if (_isLoadingImage) {
return Container( return Container(
color: isDarkMode ? Colors.grey[700] : Colors.grey[200], color: isDarkMode ? Colors.grey[700] : Colors.grey[200],
@@ -111,21 +108,20 @@ class _TripCardState extends State<TripCard> {
), ),
); );
} }
if (_currentImageUrl != null && _currentImageUrl!.isNotEmpty) { if (_currentImageUrl != null && _currentImageUrl!.isNotEmpty) {
return CachedNetworkImage( return CachedNetworkImage(
imageUrl: _currentImageUrl!, imageUrl: _currentImageUrl!,
fit: BoxFit.cover, fit: BoxFit.cover,
placeholder: (context, url) => Container( placeholder: (context, url) => Container(
color: Colors.grey[200], color: Colors.grey[200],
child: const Center( child: const Center(child: CircularProgressIndicator()),
child: CircularProgressIndicator(),
),
), ),
errorWidget: (context, url, error) => _buildPlaceholderImage(isDarkMode), errorWidget: (context, url, error) =>
_buildPlaceholderImage(isDarkMode),
); );
} }
return _buildPlaceholderImage(isDarkMode); return _buildPlaceholderImage(isDarkMode);
} }
@@ -140,9 +136,7 @@ class _TripCardState extends State<TripCard> {
elevation: 4, elevation: 4,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: cardColor, color: cardColor,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
borderRadius: BorderRadius.circular(12),
),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@@ -153,7 +147,9 @@ class _TripCardState extends State<TripCard> {
children: [ children: [
// Image du voyage // Image du voyage
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), borderRadius: const BorderRadius.vertical(
top: Radius.circular(12),
),
child: SizedBox( child: SizedBox(
height: 200, height: 200,
width: double.infinity, width: double.infinity,
@@ -178,7 +174,7 @@ class _TripCardState extends State<TripCard> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// Dates, participants et bouton voir // Dates, participants et bouton voir
Row( Row(
children: [ children: [
@@ -207,14 +203,17 @@ class _TripCardState extends State<TripCard> {
], ],
), ),
), ),
// Bouton Voir // Bouton Voir
ElevatedButton( ElevatedButton(
onPressed: widget.onTap, onPressed: widget.onTap,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 8,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
@@ -229,8 +228,8 @@ class _TripCardState extends State<TripCard> {
], ],
), ),
), ),
], ],
), ),
), ),
), ),
); );
@@ -261,4 +260,4 @@ class _TripCardState extends State<TripCard> {
), ),
); );
} }
} }

View File

@@ -70,7 +70,7 @@ class Activity {
/// Vérifie si tous les participants du voyage ont voté positivement pour cette activité /// Vérifie si tous les participants du voyage ont voté positivement pour cette activité
bool isApprovedByAllParticipants(List<String> tripParticipants) { bool isApprovedByAllParticipants(List<String> tripParticipants) {
if (tripParticipants.isEmpty) return false; if (tripParticipants.isEmpty) return false;
// Tous les participants doivent avoir voté // Tous les participants doivent avoir voté
for (String participantId in tripParticipants) { for (String participantId in tripParticipants) {
if (!votes.containsKey(participantId)) { if (!votes.containsKey(participantId)) {
@@ -80,7 +80,7 @@ class Activity {
return false; // Quelqu'un a voté négativement ou neutre return false; // Quelqu'un a voté négativement ou neutre
} }
} }
return true; // Tous ont voté positivement return true; // Tous ont voté positivement
} }
@@ -183,7 +183,7 @@ class Activity {
@override @override
String toString() { String toString() {
return 'Activity(id: $id, name: $name, category: $category, votes: ${totalVotes})'; return 'Activity(id: $id, name: $name, category: $category, votes: $totalVotes)';
} }
@override @override
@@ -245,4 +245,4 @@ enum PriceLevel {
} }
return null; return null;
} }
} }

View File

@@ -1,32 +1,27 @@
import 'dart:io'; import 'dart:io';
import '../lib/services/trip_image_service.dart'; import 'package:travel_mate/services/trip_image_service.dart';
/// Script utilitaire pour nettoyer les images inutilisées /// Script utilitaire pour nettoyer les images inutilisées
/// À exécuter manuellement si nécessaire /// À exécuter manuellement si nécessaire
void main() async { void main() async {
try { try {
final tripImageService = TripImageService(); final tripImageService = TripImageService();
// Remplacez par votre ID utilisateur // Remplacez par votre ID utilisateur
// Vous pouvez le récupérer depuis Firebase Auth dans votre app // Vous pouvez le récupérer depuis Firebase Auth dans votre app
const userId = 'YOUR_USER_ID_HERE'; const userId = 'YOUR_USER_ID_HERE';
if (userId == 'YOUR_USER_ID_HERE') { if (userId == 'YOUR_USER_ID_HERE') {
return; return;
} }
final stats = await tripImageService.getImageStatistics(userId); final stats = await tripImageService.getImageStatistics(userId);
if (stats['tripsWithImages'] > 0) { if (stats['tripsWithImages'] > 0) {
await tripImageService.cleanupUnusedImages(userId); await tripImageService.cleanupUnusedImages(userId);
final newStats = await tripImageService.getImageStatistics(userId); final newStats = await tripImageService.getImageStatistics(userId);
} else { } else {}
}
} catch (e) { } catch (e) {
exit(1); exit(1);
} }
@@ -42,4 +37,4 @@ Instructions d'utilisation:
4. Récupérez votre User ID affiché dans la console 4. Récupérez votre User ID affiché dans la console
5. Remplacez 'YOUR_USER_ID_HERE' par votre ID dans ce script 5. Remplacez 'YOUR_USER_ID_HERE' par votre ID dans ce script
6. Exécutez: dart run scripts/cleanup_images.dart 6. Exécutez: dart run scripts/cleanup_images.dart
*/ */

View File

@@ -1,48 +1,47 @@
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage/firebase_storage.dart';
import '../lib/firebase_options.dart'; import 'package:travel_mate/firebase_options.dart';
/// Script de diagnostic pour analyser les images dans Firebase Storage /// Script de diagnostic pour analyser les images dans Firebase Storage
void main() async { void main() async {
try { try {
// Initialiser Firebase // Initialiser Firebase
await Firebase.initializeApp( await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform, options: DefaultFirebaseOptions.currentPlatform,
); );
final storage = FirebaseStorage.instance; final storage = FirebaseStorage.instance;
final listResult = await storage.ref('trip_images').listAll(); final listResult = await storage.ref('trip_images').listAll();
if (listResult.items.isEmpty) { if (listResult.items.isEmpty) {
return; return;
} }
final Map<String, List<Map<String, dynamic>>> locationGroups = {}; final Map<String, List<Map<String, dynamic>>> locationGroups = {};
for (int i = 0; i < listResult.items.length; i++) { for (int i = 0; i < listResult.items.length; i++) {
final item = listResult.items[i]; final item = listResult.items[i];
final fileName = item.name; final fileName = item.name;
try { try {
// Récupérer les métadonnées // Récupérer les métadonnées
final metadata = await item.getMetadata(); final metadata = await item.getMetadata();
final customMeta = metadata.customMetadata ?? {}; final customMeta = metadata.customMetadata ?? {};
final location = customMeta['location'] ?? 'Inconnue'; final location = customMeta['location'] ?? 'Inconnue';
final normalizedLocation = customMeta['normalizedLocation'] ?? 'Non définie'; final normalizedLocation =
customMeta['normalizedLocation'] ?? 'Non définie';
final source = customMeta['source'] ?? 'Inconnue'; final source = customMeta['source'] ?? 'Inconnue';
final uploadedAt = customMeta['uploadedAt'] ?? 'Inconnue'; final uploadedAt = customMeta['uploadedAt'] ?? 'Inconnue';
// Récupérer l'URL de téléchargement // Récupérer l'URL de téléchargement
final downloadUrl = await item.getDownloadURL(); final downloadUrl = await item.getDownloadURL();
// Grouper par location normalisée // Grouper par location normalisée
final groupKey = normalizedLocation != 'Non définie' ? normalizedLocation : location.toLowerCase(); final groupKey = normalizedLocation != 'Non définie'
? normalizedLocation
: location.toLowerCase();
if (!locationGroups.containsKey(groupKey)) { if (!locationGroups.containsKey(groupKey)) {
locationGroups[groupKey] = []; locationGroups[groupKey] = [];
} }
@@ -53,14 +52,12 @@ void main() async {
'uploadedAt': uploadedAt, 'uploadedAt': uploadedAt,
'downloadUrl': downloadUrl, 'downloadUrl': downloadUrl,
}); });
} catch (e) { } catch (e) {
// Essayer de deviner la location depuis le nom du fichier // Essayer de deviner la location depuis le nom du fichier
final parts = fileName.split('_'); final parts = fileName.split('_');
if (parts.length >= 2) { if (parts.length >= 2) {
final guessedLocation = parts.take(parts.length - 1).join('_'); final guessedLocation = parts.take(parts.length - 1).join('_');
if (!locationGroups.containsKey(guessedLocation)) { if (!locationGroups.containsKey(guessedLocation)) {
locationGroups[guessedLocation] = []; locationGroups[guessedLocation] = [];
} }
@@ -73,30 +70,24 @@ void main() async {
}); });
} }
} }
} }
// Analyser les doublons // Analyser les doublons
int totalDuplicates = 0; int totalDuplicates = 0;
for (final entry in locationGroups.entries) { for (final entry in locationGroups.entries) {
final location = entry.key; final location = entry.key;
final images = entry.value; final images = entry.value;
if (images.length > 1) { if (images.length > 1) {
totalDuplicates += images.length - 1; totalDuplicates += images.length - 1;
for (int i = 0; i < images.length; i++) { for (int i = 0; i < images.length; i++) {
final image = images[i]; final image = images[i];
} }
} else { } else {}
}
} }
if (totalDuplicates > 0) {}
if (totalDuplicates > 0) { } catch (e) {}
} }
} catch (e) {
}
}

View File

@@ -4,43 +4,44 @@ void main() {
group('Image Loading Logic Tests', () { group('Image Loading Logic Tests', () {
test('should demonstrate the new flow without duplicate downloads', () { test('should demonstrate the new flow without duplicate downloads', () {
// Simulation du nouveau flux de chargement d'images // Simulation du nouveau flux de chargement d'images
// Scénario 1: Premier chargement (aucune image existante) // Scénario 1: Premier chargement (aucune image existante)
print('=== Scénario 1: Premier chargement ==='); print('=== Scénario 1: Premier chargement ===');
String? existingImage = null; // Aucune image dans le Storage String? existingImage; // Aucune image dans le Storage
if (existingImage == null) { if (existingImage == null) {
print('✓ Aucune image existante trouvée'); print('✓ Aucune image existante trouvée');
print('✓ Téléchargement d\'une nouvelle image depuis Google Places'); print('✓ Téléchargement d\'une nouvelle image depuis Google Places');
existingImage = 'https://storage.googleapis.com/image1.jpg'; existingImage = 'https://storage.googleapis.com/image1.jpg';
print('✓ Image sauvée: $existingImage'); print('✓ Image sauvée: $existingImage');
} }
expect(existingImage, isNotNull); expect(existingImage, isNotNull);
// Scénario 2: Rechargement (image existante) // Scénario 2: Rechargement (image existante)
print('\n=== Scénario 2: Rechargement avec image existante ==='); print('\n=== Scénario 2: Rechargement avec image existante ===');
String? cachedImage = existingImage; // Image déjà dans le Storage String? cachedImage = existingImage; // Image déjà dans le Storage
print('✓ Image existante trouvée: $cachedImage'); print('✓ Image existante trouvée: $cachedImage');
print('✓ PAS de nouveau téléchargement'); print('✓ PAS de nouveau téléchargement');
print('✓ Réutilisation de l\'image existante'); print('✓ Réutilisation de l\'image existante');
expect(cachedImage, equals(existingImage)); expect(cachedImage, equals(existingImage));
// Scénario 3: Différente destination // Scénario 3: Différente destination
print('\n=== Scénario 3: Destination différente ==='); print('\n=== Scénario 3: Destination différente ===');
String? differentLocationImage = null; // Pas d'image pour cette nouvelle destination String?
differentLocationImage; // Pas d'image pour cette nouvelle destination
if (differentLocationImage == null) { if (differentLocationImage == null) {
print('✓ Nouvelle destination, aucune image existante'); print('✓ Nouvelle destination, aucune image existante');
print('✓ Téléchargement autorisé pour cette nouvelle destination'); print('✓ Téléchargement autorisé pour cette nouvelle destination');
differentLocationImage = 'https://storage.googleapis.com/image2.jpg'; differentLocationImage = 'https://storage.googleapis.com/image2.jpg';
} }
expect(differentLocationImage, isNotNull); expect(differentLocationImage, isNotNull);
expect(differentLocationImage, isNot(equals(existingImage))); expect(differentLocationImage, isNot(equals(existingImage)));
print('\n=== Résumé ==='); print('\n=== Résumé ===');
print('• Image pour destination 1: $existingImage'); print('• Image pour destination 1: $existingImage');
print('• Image pour destination 2: $differentLocationImage'); print('• Image pour destination 2: $differentLocationImage');
@@ -52,9 +53,15 @@ void main() {
final testCases = [ final testCases = [
{'input': 'Paris, France', 'expected': 'paris_france'}, {'input': 'Paris, France', 'expected': 'paris_france'},
{'input': 'New York City', 'expected': 'new_york_city'}, {'input': 'New York City', 'expected': 'new_york_city'},
{'input': 'São Paulo', 'expected': 's_o_paulo'}, // Caractères spéciaux remplacés {
'input': 'São Paulo',
'expected': 's_o_paulo',
}, // Caractères spéciaux remplacés
{'input': 'Londres, Royaume-Uni', 'expected': 'londres_royaume_uni'}, {'input': 'Londres, Royaume-Uni', 'expected': 'londres_royaume_uni'},
{'input': 'Tokyo (東京)', 'expected': 'tokyo'}, // Caractères non-latins supprimés {
'input': 'Tokyo (東京)',
'expected': 'tokyo',
}, // Caractères non-latins supprimés
]; ];
for (final testCase in testCases) { for (final testCase in testCases) {
@@ -66,28 +73,28 @@ void main() {
test('should demonstrate memory and performance benefits', () { test('should demonstrate memory and performance benefits', () {
// Simulation des bénéfices de performance // Simulation des bénéfices de performance
final oldSystem = { final oldSystem = {
'apiCalls': 4, // 4 appels à chaque chargement 'apiCalls': 4, // 4 appels à chaque chargement
'storageWrites': 4, // 4 écritures dans le Storage 'storageWrites': 4, // 4 écritures dans le Storage
'storageReads': 0, // Pas de vérification existante 'storageReads': 0, // Pas de vérification existante
'dataUsage': '4.8 MB', // 4 images × 1.2 MB chacune 'dataUsage': '4.8 MB', // 4 images × 1.2 MB chacune
}; };
final newSystem = { final newSystem = {
'apiCalls': 2, // Seulement pour les nouvelles destinations 'apiCalls': 2, // Seulement pour les nouvelles destinations
'storageWrites': 2, // Seulement pour les nouvelles images 'storageWrites': 2, // Seulement pour les nouvelles images
'storageReads': 2, // Vérifications d'existence 'storageReads': 2, // Vérifications d'existence
'dataUsage': '2.4 MB', // Seulement 2 images nécessaires 'dataUsage': '2.4 MB', // Seulement 2 images nécessaires
}; };
print('=== Comparaison de performance ==='); print('=== Comparaison de performance ===');
print('Ancien système:'); print('Ancien système:');
oldSystem.forEach((key, value) => print(' $key: $value')); oldSystem.forEach((key, value) => print(' $key: $value'));
print('\nNouveau système:'); print('\nNouveau système:');
newSystem.forEach((key, value) => print(' $key: $value')); newSystem.forEach((key, value) => print(' $key: $value'));
print('\nAméliorations:'); print('\nAméliorations:');
print(' • API calls: -50%'); print(' • API calls: -50%');
print(' • Storage writes: -50%'); print(' • Storage writes: -50%');
@@ -113,4 +120,4 @@ String _normalizeLocationName(String location) {
.replaceAll(RegExp(r'[^a-z0-9]'), '_') .replaceAll(RegExp(r'[^a-z0-9]'), '_')
.replaceAll(RegExp(r'_+'), '_') .replaceAll(RegExp(r'_+'), '_')
.replaceAll(RegExp(r'^_|_$'), ''); .replaceAll(RegExp(r'^_|_$'), '');
} }

View File

@@ -11,42 +11,47 @@ void main() {
{'width': 1200, 'height': 800, 'photo_reference': 'horizontal_hd'}, {'width': 1200, 'height': 800, 'photo_reference': 'horizontal_hd'},
{'width': 600, 'height': 400, 'photo_reference': 'horizontal2'}, {'width': 600, 'height': 400, 'photo_reference': 'horizontal2'},
]; ];
// Appliquer l'algorithme de tri de qualité // Appliquer l'algorithme de tri de qualité
photos.sort((a, b) => _sortPhotosByQuality(a, b)); photos.sort((a, b) => _sortPhotosByQuality(a, b));
// Vérifications // Vérifications
expect(photos.first['photo_reference'], 'horizontal_hd'); expect(photos.first['photo_reference'], 'horizontal_hd');
expect(photos.first['width'], 1200); expect(photos.first['width'], 1200);
expect(photos.first['height'], 800); expect(photos.first['height'], 800);
// La photo verticale devrait être parmi les dernières // La photo verticale devrait être parmi les dernières
final lastPhotos = photos.sublist(photos.length - 2); final lastPhotos = photos.sublist(photos.length - 2);
expect(lastPhotos.any((p) => p['photo_reference'] == 'vertical1'), true); expect(lastPhotos.any((p) => p['photo_reference'] == 'vertical1'), true);
print('Photos triées par qualité (meilleure en premier):'); print('Photos triées par qualité (meilleure en premier):');
for (var photo in photos) { for (var photo in photos) {
final width = photo['width'] as int; final width = photo['width'] as int;
final height = photo['height'] as int; final height = photo['height'] as int;
final ratio = width / height; final ratio = width / height;
final resolution = width * height; final resolution = width * height;
print('${photo['photo_reference']}: ${width}x${height} ' print(
'(ratio: ${ratio.toStringAsFixed(2)}, résolution: $resolution)'); '${photo['photo_reference']}: ${width}x$height '
'(ratio: ${ratio.toStringAsFixed(2)}, résolution: $resolution)',
);
} }
}); });
test('should generate correct search terms for Paris', () { test('should generate correct search terms for Paris', () {
final searchTerms = _generateSearchTerms('Paris'); final searchTerms = _generateSearchTerms('Paris');
// Vérifier que les termes spécifiques à Paris sont présents // Vérifier que les termes spécifiques à Paris sont présents
expect(searchTerms.any((term) => term.contains('Tour Eiffel')), true); expect(searchTerms.any((term) => term.contains('Tour Eiffel')), true);
expect(searchTerms.any((term) => term.contains('Arc de Triomphe')), true); expect(searchTerms.any((term) => term.contains('Arc de Triomphe')), true);
expect(searchTerms.any((term) => term.contains('Notre-Dame')), true); expect(searchTerms.any((term) => term.contains('Notre-Dame')), true);
// Vérifier que les termes génériques sont aussi présents // Vérifier que les termes génériques sont aussi présents
expect(searchTerms.any((term) => term.contains('attractions touristiques')), true); expect(
searchTerms.any((term) => term.contains('attractions touristiques')),
true,
);
expect(searchTerms.any((term) => term.contains('landmarks')), true); expect(searchTerms.any((term) => term.contains('landmarks')), true);
print('Termes de recherche pour Paris:'); print('Termes de recherche pour Paris:');
for (var term in searchTerms) { for (var term in searchTerms) {
print('- $term'); print('- $term');
@@ -55,12 +60,12 @@ void main() {
test('should generate correct search terms for London', () { test('should generate correct search terms for London', () {
final searchTerms = _generateSearchTerms('London'); final searchTerms = _generateSearchTerms('London');
// Vérifier que les termes spécifiques à Londres sont présents // Vérifier que les termes spécifiques à Londres sont présents
expect(searchTerms.any((term) => term.contains('Big Ben')), true); expect(searchTerms.any((term) => term.contains('Big Ben')), true);
expect(searchTerms.any((term) => term.contains('Tower Bridge')), true); expect(searchTerms.any((term) => term.contains('Tower Bridge')), true);
expect(searchTerms.any((term) => term.contains('London Eye')), true); expect(searchTerms.any((term) => term.contains('London Eye')), true);
print('Termes de recherche pour Londres:'); print('Termes de recherche pour Londres:');
for (var term in searchTerms) { for (var term in searchTerms) {
print('- $term'); print('- $term');
@@ -69,15 +74,18 @@ void main() {
test('should handle unknown cities gracefully', () { test('should handle unknown cities gracefully', () {
final searchTerms = _generateSearchTerms('Petite Ville Inconnue'); final searchTerms = _generateSearchTerms('Petite Ville Inconnue');
// Devrait au moins avoir des termes génériques // Devrait au moins avoir des termes génériques
expect(searchTerms.isNotEmpty, true); expect(searchTerms.isNotEmpty, true);
expect(searchTerms.any((term) => term.contains('attractions touristiques')), true); expect(
searchTerms.any((term) => term.contains('attractions touristiques')),
true,
);
expect(searchTerms.any((term) => term.contains('landmarks')), true); expect(searchTerms.any((term) => term.contains('landmarks')), true);
// Le terme original devrait être en dernier // Le terme original devrait être en dernier
expect(searchTerms.last, 'Petite Ville Inconnue'); expect(searchTerms.last, 'Petite Ville Inconnue');
print('Termes de recherche pour ville inconnue:'); print('Termes de recherche pour ville inconnue:');
for (var term in searchTerms) { for (var term in searchTerms) {
print('- $term'); print('- $term');
@@ -92,39 +100,39 @@ int _sortPhotosByQuality(Map<String, dynamic> a, Map<String, dynamic> b) {
final aHeight = a['height'] as int; final aHeight = a['height'] as int;
final bWidth = b['width'] as int; final bWidth = b['width'] as int;
final bHeight = b['height'] as int; final bHeight = b['height'] as int;
final aRatio = aWidth / aHeight; final aRatio = aWidth / aHeight;
final bRatio = bWidth / bHeight; final bRatio = bWidth / bHeight;
// 1. Privilégier les photos horizontales (ratio > 1) // 1. Privilégier les photos horizontales (ratio > 1)
if (aRatio > 1 && bRatio <= 1) return -1; if (aRatio > 1 && bRatio <= 1) return -1;
if (bRatio > 1 && aRatio <= 1) return 1; if (bRatio > 1 && aRatio <= 1) return 1;
// 2. Si les deux sont horizontales ou les deux ne le sont pas, // 2. Si les deux sont horizontales ou les deux ne le sont pas,
// privilégier la résolution plus élevée // privilégier la résolution plus élevée
final aResolution = aWidth * aHeight; final aResolution = aWidth * aHeight;
final bResolution = bWidth * bHeight; final bResolution = bWidth * bHeight;
if (aResolution != bResolution) { if (aResolution != bResolution) {
return bResolution.compareTo(aResolution); return bResolution.compareTo(aResolution);
} }
// 3. En cas d'égalité de résolution, privilégier le meilleur ratio (plus proche de 1.5) // 3. En cas d'égalité de résolution, privilégier le meilleur ratio (plus proche de 1.5)
final idealRatio = 1.5; final idealRatio = 1.5;
final aDiff = (aRatio - idealRatio).abs(); final aDiff = (aRatio - idealRatio).abs();
final bDiff = (bRatio - idealRatio).abs(); final bDiff = (bRatio - idealRatio).abs();
return aDiff.compareTo(bDiff); return aDiff.compareTo(bDiff);
} }
/// Reproduit l'algorithme de génération de termes de recherche /// Reproduit l'algorithme de génération de termes de recherche
List<String> _generateSearchTerms(String location) { List<String> _generateSearchTerms(String location) {
final terms = <String>[]; final terms = <String>[];
// Ajouter des termes spécifiques pour les villes connues // Ajouter des termes spécifiques pour les villes connues
final citySpecificTerms = _getCitySpecificTerms(location.toLowerCase()); final citySpecificTerms = _getCitySpecificTerms(location.toLowerCase());
terms.addAll(citySpecificTerms); terms.addAll(citySpecificTerms);
// Termes génériques avec attractions // Termes génériques avec attractions
terms.addAll([ terms.addAll([
'$location attractions touristiques monuments', '$location attractions touristiques monuments',
@@ -136,14 +144,14 @@ List<String> _generateSearchTerms(String location) {
'$location skyline', '$location skyline',
location, // Terme original en dernier location, // Terme original en dernier
]); ]);
return terms; return terms;
} }
/// Reproduit les termes spécifiques pour des villes connues /// Reproduit les termes spécifiques pour des villes connues
List<String> _getCitySpecificTerms(String location) { List<String> _getCitySpecificTerms(String location) {
final specific = <String>[]; final specific = <String>[];
if (location.contains('paris')) { if (location.contains('paris')) {
specific.addAll([ specific.addAll([
'Tour Eiffel Paris', 'Tour Eiffel Paris',
@@ -182,6 +190,6 @@ List<String> _getCitySpecificTerms(String location) {
'Tokyo Skytree', 'Tokyo Skytree',
]); ]);
} }
return specific; return specific;
} }