Refactor settings components to organize theme content and update import paths

This commit is contained in:
Van Leemput Dayron
2025-11-07 08:39:06 +01:00
parent 75f51f8cf5
commit 8b09a2e580
3 changed files with 5 additions and 405 deletions

View File

@@ -1,398 +0,0 @@
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../../services/trip_image_service.dart';
/// Page d'administration pour gérer les images des voyages
class ImageManagementPage extends StatefulWidget {
const ImageManagementPage({super.key});
@override
State<ImageManagementPage> createState() => _ImageManagementPageState();
}
class _ImageManagementPageState extends State<ImageManagementPage> {
final TripImageService _tripImageService = TripImageService();
Map<String, dynamic>? _statistics;
bool _isLoading = false;
bool _isCleaningUp = false;
bool _isCleaningDuplicates = false;
String? _cleanupResult;
String? _duplicateCleanupResult;
@override
void initState() {
super.initState();
_loadStatistics();
}
Future<void> _loadStatistics() async {
setState(() {
_isLoading = true;
});
try {
final userId = FirebaseAuth.instance.currentUser?.uid;
if (userId != null) {
final stats = await _tripImageService.getImageStatistics(userId);
setState(() {
_statistics = stats;
});
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur lors du chargement des statistiques: $e')),
);
}
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _cleanupUnusedImages() async {
// Demander confirmation
final shouldCleanup = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Nettoyer les images inutilisées'),
content: const Text(
'Cette action supprimera définitivement toutes les images qui ne sont plus utilisées par vos voyages. '
'Voulez-vous continuer ?'
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Nettoyer'),
),
],
),
);
if (shouldCleanup != true) return;
setState(() {
_isCleaningUp = true;
_cleanupResult = null;
});
try {
final userId = FirebaseAuth.instance.currentUser?.uid;
if (userId != null) {
await _tripImageService.cleanupUnusedImages(userId);
setState(() {
_cleanupResult = 'Nettoyage terminé avec succès !';
});
// Recharger les statistiques
await _loadStatistics();
}
} catch (e) {
setState(() {
_cleanupResult = 'Erreur lors du nettoyage: $e';
});
} finally {
setState(() {
_isCleaningUp = false;
});
}
}
Future<void> _cleanupDuplicateImages() async {
// Demander confirmation
final shouldCleanup = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Nettoyer les doublons d\'images'),
content: const Text(
'Cette action analysera vos images et supprimera automatiquement les doublons pour la même destination. '
'Seule l\'image la plus récente sera conservée. Voulez-vous continuer ?'
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Nettoyer les doublons'),
),
],
),
);
if (shouldCleanup != true) return;
setState(() {
_isCleaningDuplicates = true;
_duplicateCleanupResult = null;
});
try {
await _tripImageService.cleanupDuplicateImages();
setState(() {
_duplicateCleanupResult = 'Doublons supprimés avec succès !';
});
// Recharger les statistiques
await _loadStatistics();
} catch (e) {
setState(() {
_duplicateCleanupResult = 'Erreur lors du nettoyage des doublons: $e';
});
} finally {
setState(() {
_isCleaningDuplicates = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Gestion des Images'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Statistiques
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Statistiques',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
if (_isLoading)
const Center(child: CircularProgressIndicator())
else if (_statistics != null) ...[
_buildStatItem(
'Voyages totaux',
_statistics!['totalTrips']?.toString() ?? '0'
),
_buildStatItem(
'Voyages avec image',
_statistics!['tripsWithImages']?.toString() ?? '0'
),
_buildStatItem(
'Voyages sans image',
_statistics!['tripsWithoutImages']?.toString() ?? '0'
),
const SizedBox(height: 8),
Text(
'Dernière mise à jour: ${_formatTimestamp(_statistics!['timestamp'])}',
style: Theme.of(context).textTheme.bodySmall,
),
] else
const Text('Impossible de charger les statistiques'),
],
),
),
),
const SizedBox(height: 24),
// Actions
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
// Bouton recharger statistiques
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _loadStatistics,
icon: const Icon(Icons.refresh),
label: const Text('Recharger les statistiques'),
),
),
const SizedBox(height: 12),
// Bouton nettoyer les images inutilisées
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isCleaningUp ? null : _cleanupUnusedImages,
icon: _isCleaningUp
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.cleaning_services),
label: Text(_isCleaningUp ? 'Nettoyage en cours...' : 'Nettoyer les images inutilisées'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
),
),
),
const SizedBox(height: 12),
// Bouton nettoyer les doublons
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isCleaningDuplicates ? null : _cleanupDuplicateImages,
icon: _isCleaningDuplicates
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.content_copy),
label: Text(_isCleaningDuplicates ? 'Suppression doublons...' : 'Supprimer les doublons d\'images'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
),
if (_cleanupResult != null) ...[
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _cleanupResult!.contains('Erreur')
? Colors.red.withValues(alpha: 0.1)
: Colors.green.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _cleanupResult!.contains('Erreur')
? Colors.red
: Colors.green,
width: 1,
),
),
child: Text(
_cleanupResult!,
style: TextStyle(
color: _cleanupResult!.contains('Erreur')
? Colors.red
: Colors.green,
fontWeight: FontWeight.bold,
),
),
),
],
if (_duplicateCleanupResult != null) ...[
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _duplicateCleanupResult!.contains('Erreur')
? Colors.red.withValues(alpha: 0.1)
: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _duplicateCleanupResult!.contains('Erreur')
? Colors.red
: Colors.blue,
width: 1,
),
),
child: Text(
_duplicateCleanupResult!,
style: TextStyle(
color: _duplicateCleanupResult!.contains('Erreur')
? Colors.red
: Colors.blue,
fontWeight: FontWeight.bold,
),
),
),
],
],
),
),
),
const SizedBox(height: 24),
// Informations
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Explications',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 12),
const Text(
'• Chaque voyage peut avoir une image automatiquement téléchargée depuis Google Places\n'
'• Les images sont stockées dans Firebase Storage\n'
'• Il peut y avoir des images inutilisées si des voyages ont été supprimés ou modifiés\n'
'• Le nettoyage supprime uniquement les images qui ne sont plus référencées par vos voyages',
style: TextStyle(height: 1.5),
),
],
),
),
),
],
),
),
);
}
Widget _buildStatItem(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Text(
value,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
);
}
String _formatTimestamp(String? timestamp) {
if (timestamp == null) return 'Inconnue';
try {
final dateTime = DateTime.parse(timestamp);
return '${dateTime.day}/${dateTime.month}/${dateTime.year} '
'${dateTime.hour.toString().padLeft(2, '0')}:'
'${dateTime.minute.toString().padLeft(2, '0')}';
} catch (e) {
return 'Format invalide';
}
}
}

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:travel_mate/components/settings/policies/policies_content.dart'; import 'package:travel_mate/components/settings/policies/policies_content.dart';
import 'settings_theme_content.dart'; import 'theme/settings_theme_content.dart';
import 'profile/profile_content.dart'; import 'profile/profile_content.dart';
class SettingsContent extends StatelessWidget { class SettingsContent extends StatelessWidget {

View File

@@ -1,8 +1,8 @@
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 '../../blocs/theme/theme_bloc.dart'; import '../../../blocs/theme/theme_bloc.dart';
import '../../blocs/theme/theme_state.dart'; import '../../../blocs/theme/theme_state.dart';
import '../../blocs/theme/theme_event.dart'; import '../../../blocs/theme/theme_event.dart';
class SettingsThemeContent extends StatelessWidget { class SettingsThemeContent extends StatelessWidget {
const SettingsThemeContent({super.key}); const SettingsThemeContent({super.key});
@@ -10,9 +10,7 @@ class SettingsThemeContent extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: const Text('Thème')),
title: const Text('Thème'),
),
body: BlocBuilder<ThemeBloc, ThemeState>( body: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state) { builder: (context, state) {
return ListView( return ListView(