From 0162eb67f52194673e3386c6cda7adbb1dc8eb53 Mon Sep 17 00:00:00 2001 From: Dayron Date: Wed, 15 Oct 2025 11:43:21 +0200 Subject: [PATCH] feat: Implement centralized error handling with ErrorService; replace print statements with logging in services and blocs feat: Add ErrorContent widget for displaying error messages in dialogs and bottom sheets refactor: Update GroupBloc and GroupRepository to utilize ErrorService for error logging refactor: Enhance user and trip services to log errors using ErrorService refactor: Clean up debug print statements in GroupContent and related components --- lib/blocs/group/group_bloc.dart | 27 +-- lib/components/error/error_content.dart | 237 +++++++++++++++++++ lib/components/group/group_content.dart | 143 ++++------- lib/components/home/create_trip_content.dart | 4 +- lib/data/models/trip.dart | 4 - lib/main.dart | 2 + lib/repositories/group_repository.dart | 39 +-- lib/services/auth_service.dart | 8 +- lib/services/error_service.dart | 82 +++++++ lib/services/group_service.dart | 13 +- lib/services/trip_service.dart | 34 ++- lib/services/user_service.dart | 26 +- 12 files changed, 422 insertions(+), 197 deletions(-) create mode 100644 lib/components/error/error_content.dart create mode 100644 lib/services/error_service.dart diff --git a/lib/blocs/group/group_bloc.dart b/lib/blocs/group/group_bloc.dart index 53e3f4e..550ba46 100644 --- a/lib/blocs/group/group_bloc.dart +++ b/lib/blocs/group/group_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:travel_mate/services/error_service.dart'; import 'group_event.dart'; import 'group_state.dart'; import '../../repositories/group_repository.dart'; @@ -8,6 +9,7 @@ import '../../data/models/group.dart'; class GroupBloc extends Bloc { final GroupRepository _repository; StreamSubscription? _groupsSubscription; + final _errorService = ErrorService(); GroupBloc(this._repository) : super(GroupInitial()) { on(_onLoadGroupsByUserId); @@ -24,33 +26,20 @@ class GroupBloc extends Bloc { Future _onLoadGroupsByUserId( LoadGroupsByUserId event, Emitter emit, - ) async { - print('===== GroupBloc: _onLoadGroupsByUserId START ====='); - + ) async { try { emit(GroupLoading()); - print('>>> GroupBloc: État GroupLoading émis'); - await _groupsSubscription?.cancel(); - print('>>> GroupBloc: Ancien subscription annulé'); - _groupsSubscription = _repository.getGroupsByUserId(event.userId).listen( (groups) { - print('===== GroupBloc: Stream reçu ${groups.length} groupes ====='); - // Utiliser un événement interne au lieu d'émettre directement add(_GroupsUpdated(groups)); }, onError: (error) { - print('===== GroupBloc: Erreur stream: $error ====='); add(_GroupsUpdated([], error: error.toString())); }, ); - - print('>>> GroupBloc: Subscription créé avec succès'); } catch (e, stackTrace) { - print('===== GroupBloc: Exception _onLoadGroupsByUserId ====='); - print('Exception: $e'); - print('StackTrace: $stackTrace'); + _errorService.logError(e.toString(), stackTrace); emit(GroupError(e.toString())); } } @@ -60,16 +49,11 @@ class GroupBloc extends Bloc { _GroupsUpdated event, Emitter emit, ) async { - print('===== GroupBloc: _onGroupsUpdated ====='); - print('Groupes reçus: ${event.groups.length}'); - if (event.error != null) { - print('>>> Émission GroupError: ${event.error}'); + _errorService.logError(event.error!, StackTrace.current); emit(GroupError(event.error!)); } else { - print('>>> Émission GroupsLoaded avec ${event.groups.length} groupes'); emit(GroupsLoaded(event.groups)); - print('>>> GroupsLoaded émis avec succès !'); } } @@ -172,7 +156,6 @@ class GroupBloc extends Bloc { @override Future close() { - print('===== GroupBloc: close() ====='); _groupsSubscription?.cancel(); return super.close(); } diff --git a/lib/components/error/error_content.dart b/lib/components/error/error_content.dart new file mode 100644 index 0000000..b948e05 --- /dev/null +++ b/lib/components/error/error_content.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; + +class ErrorContent extends StatelessWidget { + final String title; + final String message; + final VoidCallback? onRetry; + final VoidCallback? onClose; + final IconData icon; + final Color? iconColor; + + const ErrorContent({ + super.key, + this.title = 'Une erreur est survenue', + required this.message, + this.onRetry, + this.onClose, + this.icon = Icons.error_outline, + this.iconColor, + }); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final defaultIconColor = iconColor ?? Colors.red[400]; + + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 10, + spreadRadius: 2, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Bouton fermer en haut à droite + if (onClose != null) + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.close), + onPressed: onClose, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ), + + const SizedBox(height: 8), + + // Icône d'erreur + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: defaultIconColor?.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon( + icon, + size: 48, + color: defaultIconColor, + ), + ), + + const SizedBox(height: 24), + + // Titre + Text( + title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black87, + ), + textAlign: TextAlign.center, + ), + + const SizedBox(height: 12), + + // Message d'erreur + Text( + message, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + height: 1.5, + ), + textAlign: TextAlign.center, + ), + + const SizedBox(height: 24), + + // Boutons d'action + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (onRetry != null) ...[ + ElevatedButton.icon( + onPressed: onRetry, + icon: const Icon(Icons.refresh), + label: const Text('Réessayer'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + ), + ), + if (onClose != null) const SizedBox(width: 12), + ], + if (onClose != null) + OutlinedButton.icon( + onPressed: onClose, + icon: const Icon(Icons.close), + label: const Text('Fermer'), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + ), + ), + ], + ), + ], + ), + ); + } +} + +// Fonction helper pour afficher l'erreur en dialog +void showErrorDialog( + BuildContext context, { + String title = 'Une erreur est survenue', + required String message, + VoidCallback? onRetry, + IconData icon = Icons.error_outline, + Color? iconColor, + bool barrierDismissible = true, +}) { + showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (BuildContext dialogContext) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: ErrorContent( + title: title, + message: message, + icon: icon, + iconColor: iconColor, + onRetry: onRetry != null + ? () { + Navigator.of(dialogContext).pop(); + onRetry(); + } + : null, + onClose: () => Navigator.of(dialogContext).pop(), + ), + ); + }, + ); +} + +// Fonction helper pour afficher l'erreur en bottom sheet +void showErrorBottomSheet( + BuildContext context, { + String title = 'Une erreur est survenue', + required String message, + VoidCallback? onRetry, + IconData icon = Icons.error_outline, + Color? iconColor, + bool isDismissible = true, +}) { + showModalBottomSheet( + context: context, + isDismissible: isDismissible, + enableDrag: isDismissible, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (BuildContext sheetContext) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: ErrorContent( + title: title, + message: message, + icon: icon, + iconColor: iconColor, + onRetry: onRetry != null + ? () { + Navigator.of(sheetContext).pop(); + onRetry(); + } + : null, + onClose: () => Navigator.of(sheetContext).pop(), + ), + ), + ); + }, + ); +} + +// Fonction helper pour afficher en SnackBar (pour erreurs mineures) +void showErrorSnackBar( + BuildContext context, { + required String message, + VoidCallback? onRetry, + Duration duration = const Duration(seconds: 4), +}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.red[400], + duration: duration, + action: onRetry != null + ? SnackBarAction( + label: 'Réessayer', + textColor: Colors.white, + onPressed: onRetry, + ) + : null, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); +} \ No newline at end of file diff --git a/lib/components/group/group_content.dart b/lib/components/group/group_content.dart index d6af09f..4e49e28 100644 --- a/lib/components/group/group_content.dart +++ b/lib/components/group/group_content.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:travel_mate/components/error/error_content.dart'; import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_state.dart' as user_state; import '../../blocs/group/group_bloc.dart'; @@ -18,55 +19,39 @@ class _GroupContentState extends State { @override void initState() { super.initState(); - print('===== GroupContent: initState ====='); // Charger immédiatement sans attendre le prochain frame WidgetsBinding.instance.addPostFrameCallback((_) { - print('===== PostFrameCallback déclenché ====='); _loadInitialData(); }); } void _loadInitialData() { - print('===== _loadInitialData START ====='); try { final userState = context.read().state; - print('UserBloc state type: ${userState.runtimeType}'); - print('UserBloc state: $userState'); if (userState is user_state.UserLoaded) { final userId = userState.user.id; - print('✓ User chargé, ID: $userId'); - print('>>> Envoi de LoadGroupsByUserId <<<'); context.read().add(LoadGroupsByUserId(userId)); - print('>>> LoadGroupsByUserId envoyé <<<'); } else { - print('✗ UserState n\'est pas UserLoaded'); + throw Exception('Utilisateur non connecté'); } - } catch (e, stackTrace) { - print('===== ERREUR _loadInitialData ====='); - print('Exception: $e'); - print('StackTrace: $stackTrace'); + } catch (e) { + _buildErrorState(e.toString(), '', true); } } @override Widget build(BuildContext context) { - print('===== GroupContent: build ====='); - return BlocBuilder( builder: (context, userState) { - print('>>> UserBloc builder - state type: ${userState.runtimeType}'); - if (userState is user_state.UserLoading) { - print('État: UserLoading'); return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } if (userState is user_state.UserError) { - print('État: UserError - ${userState.message}'); return Scaffold( body: Center( child: Column( @@ -82,23 +67,15 @@ class _GroupContentState extends State { } if (userState is! user_state.UserLoaded) { - print('État: Utilisateur non connecté'); return const Scaffold( body: Center(child: Text('Utilisateur non connecté')), ); } - - print('✓ État: UserLoaded'); final user = userState.user; return BlocConsumer( listener: (context, groupState) { - print('===== GroupBloc LISTENER ====='); - print('State type: ${groupState.runtimeType}'); - print('State: $groupState'); - if (groupState is GroupOperationSuccess) { - print('>>> GroupOperationSuccess: ${groupState.message}'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(groupState.message), @@ -106,7 +83,6 @@ class _GroupContentState extends State { ), ); } else if (groupState is GroupError) { - print('>>> GroupError: ${groupState.message}'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(groupState.message), @@ -116,19 +92,6 @@ class _GroupContentState extends State { } }, builder: (context, groupState) { - print('===== GroupBloc BUILDER ====='); - print('State type: ${groupState.runtimeType}'); - print('State: $groupState'); - - // TEST: Afficher le type exact - if (groupState is GroupsLoaded) { - print('✓✓✓ GroupsLoaded détecté ! ✓✓✓'); - print('Nombre de groupes: ${groupState.groups.length}'); - for (var i = 0; i < groupState.groups.length; i++) { - print(' Groupe $i: ${groupState.groups[i].name}'); - } - } - return Scaffold( body: SafeArea( child: _buildContent(groupState, user.id), @@ -141,11 +104,7 @@ class _GroupContentState extends State { } Widget _buildContent(GroupState groupState, String userId) { - print('===== _buildContent ====='); - print('State type: ${groupState.runtimeType}'); - if (groupState is GroupLoading) { - print('>>> Affichage: Loading'); return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -159,24 +118,18 @@ class _GroupContentState extends State { } if (groupState is GroupError) { - print('>>> Affichage: Error'); - return _buildErrorState(groupState.message, userId); + return _buildErrorState(groupState.message, userId, true); } if (groupState is GroupsLoaded) { - print('>>> Affichage: GroupsLoaded'); - print('Groupes: ${groupState.groups.length}'); if (groupState.groups.isEmpty) { - print('>>> Affichage: Empty'); return _buildEmptyState(); } - print('>>> Affichage: Liste des groupes'); return _buildGroupsList(groupState.groups, userId); } - print('>>> Affichage: Initial/Unknown'); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -185,7 +138,6 @@ class _GroupContentState extends State { const SizedBox(height: 16), ElevatedButton( onPressed: () { - print('>>> Bouton refresh cliqué'); context.read().add(LoadGroupsByUserId(userId)); }, child: const Text('Charger les groupes'), @@ -196,12 +148,8 @@ class _GroupContentState extends State { } Widget _buildGroupsList(List groups, String userId) { - print('===== _buildGroupsList ====='); - print('Nombre de groupes à afficher: ${groups.length}'); - return RefreshIndicator( onRefresh: () async { - print('>>> Pull to refresh'); context.read().add(LoadGroupsByUserId(userId)); await Future.delayed(const Duration(milliseconds: 500)); }, @@ -221,20 +169,17 @@ class _GroupContentState extends State { // IMPORTANT: Utiliser un simple Column au lieu de GridView pour tester ...groups.map((group) { - print('Création widget pour: ${group.name}'); return Padding( padding: const EdgeInsets.only(bottom: 12), child: _buildSimpleGroupCard(group), ); - }).toList(), + }) ], ), ); } Widget _buildSimpleGroupCard(Group group) { - print('===== _buildSimpleGroupCard: ${group.name} ====='); - try { final colors = [Colors.blue, Colors.purple, Colors.green, Colors.orange]; final color = colors[group.name.hashCode.abs() % colors.length]; @@ -248,8 +193,6 @@ class _GroupContentState extends State { .join(', '); memberInfo += '\n$names'; } - - print('Card créée avec succès'); return Card( elevation: 2, @@ -265,14 +208,11 @@ class _GroupContentState extends State { subtitle: Text(memberInfo), trailing: const Icon(Icons.chevron_right), onTap: () { - print('Tap sur: ${group.name}'); _openGroupChat(group); }, ), ); - } catch (e, stackTrace) { - print('ERREUR dans _buildSimpleGroupCard: $e'); - print('StackTrace: $stackTrace'); + } catch (e) { return Card( color: Colors.red[100], child: const ListTile( @@ -284,7 +224,6 @@ class _GroupContentState extends State { } Widget _buildEmptyState() { - print('===== _buildEmptyState ====='); return Center( child: Padding( padding: const EdgeInsets.all(32), @@ -306,39 +245,49 @@ class _GroupContentState extends State { ); } - Widget _buildErrorState(String error, String userId) { - print('===== _buildErrorState ====='); - return Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error, size: 64, color: Colors.red), - const SizedBox(height: 16), - const Text('Erreur', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), - const SizedBox(height: 8), - Text(error, textAlign: TextAlign.center, style: const TextStyle(fontSize: 12)), - const SizedBox(height: 16), - ElevatedButton.icon( - onPressed: () { - print('>>> Bouton réessayer cliqué'); + Widget _buildErrorState(String error, String userId, bool retry) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + if (retry) { + if (userId == '') { + showErrorDialog( + context, + title: 'Erreur utilisateur', + message: 'Utilisateur non connecté. Veuillez vous reconnecter.', + icon: Icons.error, + iconColor: Colors.red, + onRetry: () { + Navigator.of(context).popUntil((route) => route.isFirst); + }, + ); + } else { + showErrorDialog( + context, + title: 'Erreur de chargement', + message: error, + icon: Icons.cloud_off, + iconColor: Colors.orange, + onRetry: () { context.read().add(LoadGroupsByUserId(userId)); }, - icon: const Icon(Icons.refresh), - label: const Text('Réessayer'), - ), - ], - ), - ), - ); + ); + } + } else { + showErrorDialog( + context, + title: 'Erreur', + message: error, + icon: Icons.error, + iconColor: Colors.red, + ); + } + } + }); + + return const Center(child: CircularProgressIndicator()); } void _openGroupChat(Group group) { - print('===== _openGroupChat: ${group.name} ====='); - print('Group ID: ${group.id}'); - print('Group members: ${group.members.length}'); - try { // Afficher juste un message, pas de navigation pour l'instant if (mounted) { @@ -359,7 +308,7 @@ class _GroupContentState extends State { // ); } catch (e) { - print('ERREUR openGroupChat: $e'); + _buildErrorState(e.toString(), '', false); } } } \ No newline at end of file diff --git a/lib/components/home/create_trip_content.dart b/lib/components/home/create_trip_content.dart index d59b5b2..b277036 100644 --- a/lib/components/home/create_trip_content.dart +++ b/lib/components/home/create_trip_content.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/data/models/trip.dart'; +import 'package:travel_mate/services/error_service.dart'; import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_state.dart' as user_state; import '../../blocs/trip/trip_bloc.dart'; @@ -19,6 +20,7 @@ class CreateTripContent extends StatefulWidget { } class _CreateTripContentState extends State { + final _errorService = ErrorService(); final _formKey = GlobalKey(); final _titleController = TextEditingController(); final _descriptionController = TextEditingController(); @@ -528,7 +530,7 @@ class _CreateTripContentState extends State { } } } catch (e) { - print('Erreur lors de la récupération de l\'utilisateur: $e'); + _errorService.logError('Erreur lors de la récupération de l\'utilisateur $email: $e', StackTrace.current); } } diff --git a/lib/data/models/trip.dart b/lib/data/models/trip.dart index de2cf16..3d054c7 100644 --- a/lib/data/models/trip.dart +++ b/lib/data/models/trip.dart @@ -62,7 +62,6 @@ class Trip { // Essayer de parser comme ISO 8601 return DateTime.parse(dateValue); } catch (e) { - print('Erreur parsing date string: $dateValue - $e'); return DateTime.now(); } } @@ -72,12 +71,9 @@ class Trip { // Traiter comme millisecondes return DateTime.fromMillisecondsSinceEpoch(dateValue); } catch (e) { - print('Erreur parsing date int: $dateValue - $e'); return DateTime.now(); } } - - print('Type de date non supporté: ${dateValue.runtimeType} - $dateValue'); return DateTime.now(); } diff --git a/lib/main.dart b/lib/main.dart index bec3373..8e97b25 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:travel_mate/services/error_service.dart'; import 'blocs/auth/auth_bloc.dart'; import 'blocs/auth/auth_event.dart'; import 'blocs/auth/auth_state.dart'; @@ -69,6 +70,7 @@ class MyApp extends StatelessWidget { builder: (context, themeState) { return MaterialApp( title: 'Travel Mate', + navigatorKey: ErrorService.navigatorKey, themeMode: themeState.themeMode, theme: ThemeData( colorScheme: ColorScheme.fromSeed( diff --git a/lib/repositories/group_repository.dart b/lib/repositories/group_repository.dart index 2ebd2c1..713aeb1 100644 --- a/lib/repositories/group_repository.dart +++ b/lib/repositories/group_repository.dart @@ -1,9 +1,11 @@ import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:travel_mate/services/error_service.dart'; import '../data/models/group.dart'; import '../data/models/group_member.dart'; class GroupRepository { final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final _errorService = ErrorService(); CollectionReference get _groupsCollection => _firestore.collection('groups'); @@ -34,22 +36,16 @@ class GroupRepository { } } - Stream> getGroupsByUserId(String userId) { - print('===== GroupRepository: getGroupsByUserId START ====='); - print('UserId recherché: $userId'); - + Stream> getGroupsByUserId(String userId) { return _groupsCollection .snapshots() .asyncMap((snapshot) async { - print('===== GroupRepository: Nouveau snapshot (${DateTime.now()}) ====='); - print('Nombre de documents: ${snapshot.docs.length}'); List userGroups = []; for (var groupDoc in snapshot.docs) { try { final groupId = groupDoc.id; - print('--- Vérification groupe: $groupId ---'); // Vérifier si l'utilisateur est membre final memberDoc = await groupDoc.reference @@ -57,34 +53,24 @@ class GroupRepository { .doc(userId) .get(); - print('Membre existe dans $groupId: ${memberDoc.exists}'); - if (memberDoc.exists) { - print('✓ Utilisateur trouvé dans $groupId'); - final groupData = groupDoc.data() as Map; final group = Group.fromMap(groupData, groupId); final members = await getGroupMembers(groupId); - print('${members.length} membres chargés pour $groupId'); - userGroups.add(group.copyWith(members: members)); } else { - print('✗ Utilisateur NON membre de $groupId'); + _errorService.logInfo('group_repository.dart','Utilisateur NON membre de $groupId'); } } catch (e, stackTrace) { - print('ERREUR groupe ${groupDoc.id}: $e'); - print('StackTrace: $stackTrace'); + _errorService.logError(e.toString(), stackTrace); } } - - print('===== Retour: ${userGroups.length} groupes ====='); return userGroups; }) .distinct((prev, next) { // Comparer les listes pour éviter les doublons if (prev.length != next.length) { - print('>>> Changement détecté: ${prev.length} -> ${next.length} groupes'); return false; } @@ -95,17 +81,10 @@ class GroupRepository { final identical = prevIds.difference(nextIds).isEmpty && nextIds.difference(prevIds).isEmpty; - if (!identical) { - print('>>> Changement détecté: IDs différents'); - } else { - print('>>> Données identiques, émission ignorée'); - } - return identical; }) .handleError((error, stackTrace) { - print('ERREUR stream: $error'); - print('StackTrace: $stackTrace'); + _errorService.logError(error, stackTrace); return []; }); } @@ -146,10 +125,7 @@ class GroupRepository { Future> getGroupMembers(String groupId) async { try { - print('Chargement membres pour: $groupId'); - final snapshot = await _membersCollection(groupId).get(); - print('${snapshot.docs.length} membres trouvés'); - + final snapshot = await _membersCollection(groupId).get(); return snapshot.docs .map((doc) { return GroupMember.fromMap( @@ -159,7 +135,6 @@ class GroupRepository { }) .toList(); } catch (e) { - print('ERREUR getGroupMembers: $e'); throw Exception('Erreur lors de la récupération des membres: $e'); } } diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 1f109a9..72a6ab4 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,7 +1,9 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +import 'package:travel_mate/services/error_service.dart'; class AuthService { + final _errorService = ErrorService(); final FirebaseAuth firebaseAuth = FirebaseAuth.instance; User? get currentUser => firebaseAuth.currentUser; @@ -89,13 +91,13 @@ class AuthService { } } on GoogleSignInException catch (e) { - print('Erreur lors de l\'initialisation de Google Sign-In: $e'); + _errorService.logError('Erreur Google Sign-In: $e', StackTrace.current); rethrow; } on FirebaseAuthException catch (e) { - print('Erreur Firebase lors de l\'initialisation de Google Sign-In: $e'); + _errorService.logError('Erreur Firebase lors de l\'initialisation de Google Sign-In: $e', StackTrace.current); rethrow; } catch (e) { - print('Erreur inconnue lors de l\'initialisation de Google Sign-In: $e'); + _errorService.logError('Erreur inconnue lors de l\'initialisation de Google Sign-In: $e', StackTrace.current); rethrow; } } diff --git a/lib/services/error_service.dart b/lib/services/error_service.dart new file mode 100644 index 0000000..3f41c07 --- /dev/null +++ b/lib/services/error_service.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import '../components/error/error_content.dart'; + +class ErrorService { + static final ErrorService _instance = ErrorService._internal(); + factory ErrorService() => _instance; + ErrorService._internal(); + + // GlobalKey pour accéder au context depuis n'importe où + static GlobalKey navigatorKey = GlobalKey(); + + // Afficher une erreur en dialog + void showError({ + required String message, + String title = 'Erreur', + VoidCallback? onRetry, + IconData icon = Icons.error_outline, + Color? iconColor, + }) { + final context = navigatorKey.currentContext; + if (context != null) { + showErrorDialog( + context, + title: title, + message: message, + icon: icon, + iconColor: iconColor, + onRetry: onRetry, + ); + } + } + + // Afficher une erreur en snackbar + void showSnackbar({ + required String message, + VoidCallback? onRetry, + bool isError = true, + }) { + final context = navigatorKey.currentContext; + if (context != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: isError ? Colors.red[400] : Colors.green[600], + duration: const Duration(seconds: 4), + action: onRetry != null + ? SnackBarAction( + label: 'Réessayer', + textColor: Colors.white, + onPressed: onRetry, + ) + : null, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + } + } + + // Logger dans la console (développement) + void logError(String source, dynamic error, [StackTrace? stackTrace]) { + print('═══════════════════════════════════'); + print('❌ ERREUR dans $source'); + print('Message: $error'); + if (stackTrace != null) { + print('StackTrace: $stackTrace'); + } + print('═══════════════════════════════════'); + } + + // Logger une info (développement) + void logInfo(String source, String message) { + print('ℹ️ [$source] $message'); + } + + // Logger un succès + void logSuccess(String source, String message) { + print('✅ [$source] $message'); + } +} \ No newline at end of file diff --git a/lib/services/group_service.dart b/lib/services/group_service.dart index edacb24..f202316 100644 --- a/lib/services/group_service.dart +++ b/lib/services/group_service.dart @@ -1,7 +1,9 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:travel_mate/data/models/group.dart'; +import 'package:travel_mate/services/error_service.dart'; class GroupService { + final _errorService = ErrorService(); final FirebaseFirestore _firestore = FirebaseFirestore.instance; Stream> getGroupsStream() { @@ -17,7 +19,7 @@ class GroupService { await _firestore.collection('groups').add(group.toMap()); return true; } catch (e) { - print('Erreur lors de la création du groupe: $e'); + _errorService.logError('Erreur lors de la création du groupe: $e', StackTrace.current); return false; } } @@ -27,7 +29,7 @@ class GroupService { await _firestore.collection('groups').doc(group.id).update(group.toMap()); return true; } catch (e) { - print('Erreur lors de la mise à jour du groupe: $e'); + _errorService.logError('Erreur lors de la mise à jour du groupe: $e', StackTrace.current); return false; } } @@ -37,7 +39,7 @@ class GroupService { await _firestore.collection('groups').doc(groupId).delete(); return true; } catch (e) { - print('Erreur lors de la suppression du groupe: $e'); + _errorService.logError('Erreur lors de la suppression du groupe: $e', StackTrace.current); return false; } } @@ -49,10 +51,9 @@ class GroupService { .where('members', arrayContains: userId) .snapshots() .map((snapshot) { - print('Groupes trouvés pour l\'utilisateur $userId: ${snapshot.docs.length}'); return snapshot.docs.map((doc) { final group = Group.fromMap(doc.data(), doc.id); - print('Groupe: ${group.name}, Membres: ${group.members.length}'); + _errorService.logError('Groupe: ${group.name}, Membres: ${group.members.length}', StackTrace.current); return group; }).toList(); }); @@ -65,6 +66,4 @@ class GroupService { Future addMemberToGroup(String groupId, String memberId) async { // TODO: Implémenter l'ajout d'un membre à un groupe } - - } \ No newline at end of file diff --git a/lib/services/trip_service.dart b/lib/services/trip_service.dart index 7e91e5d..184fda9 100644 --- a/lib/services/trip_service.dart +++ b/lib/services/trip_service.dart @@ -1,7 +1,9 @@ import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:travel_mate/services/error_service.dart'; import '../data/models/trip.dart'; class TripService { + final _errorService = ErrorService(); final FirebaseFirestore _firestore = FirebaseFirestore.instance; static const String _tripsCollection = 'trips'; @@ -18,7 +20,7 @@ class TripService { return Trip.fromMap({...data, 'id': doc.id}); }).toList(); } catch (e) { - print('Erreur lors du chargement des voyages: $e'); + _errorService.logError('Erreur lors du chargement des voyages: $e', StackTrace.current); return []; } } @@ -39,16 +41,13 @@ class TripService { await _firestore.collection(_tripsCollection).add(tripData); return true; } catch (e) { - print('Erreur lors de l\'ajout du voyage: $e'); + _errorService.logError('Erreur lors de l\'ajout du voyage: $e', StackTrace.current); return false; } } // Stream pour écouter les voyages d'un utilisateur en temps réel Stream> getTripsStreamByUser(String userId, String userEmail) { - print('=== STREAM CRÉÉ ==='); - print('UserId: $userId'); - return _firestore .collection(_tripsCollection) .snapshots() @@ -88,8 +87,7 @@ class TripService { } } } catch (e, stackTrace) { - print('Erreur lors du traitement du document ${doc.id}: $e'); - print('StackTrace: $stackTrace'); + _errorService.logError('Erreur lors du traitement du document ${doc.id}: $e', stackTrace); } } @@ -98,15 +96,14 @@ class TripService { try { return b.createdAt.compareTo(a.createdAt); } catch (e) { - print('Erreur lors du tri: $e'); + _errorService.logError('Erreur lors du tri: $e', StackTrace.current); return 0; } }); return trips; }).handleError((error, stackTrace) { - print('Erreur dans le stream: $error'); - print('StackTrace: $stackTrace'); + _errorService.logError('Erreur dans le stream: $error', stackTrace); return []; }); } @@ -130,7 +127,7 @@ class TripService { trips.add(trip); } } catch (e) { - print('Erreur lors de la conversion du voyage créé ${doc.id}: $e'); + _errorService.logError('Erreur lors de la conversion du voyage créé ${doc.id}: $e', StackTrace.current); } } @@ -144,7 +141,7 @@ class TripService { }); return trips; } catch (e) { - print('Erreur lors de la récupération des voyages: $e'); + _errorService.logError('Erreur lors de la récupération des voyages: $e', StackTrace.current); return []; } } @@ -183,8 +180,7 @@ class TripService { return trip; } catch (e, stackTrace) { - print('Erreur lors de la conversion du document $docId: $e'); - print('StackTrace: $stackTrace'); + _errorService.logError('Erreur lors de la conversion du document $docId: $e', stackTrace); return null; } } @@ -202,7 +198,7 @@ class TripService { .update(tripData); return true; } catch (e) { - print('Erreur lors de la mise à jour du voyage: $e'); + _errorService.logError('Erreur lors de la mise à jour du voyage: $e', StackTrace.current); return false; } } @@ -213,7 +209,7 @@ class TripService { await _firestore.collection(_tripsCollection).doc(tripId).delete(); return true; } catch (e) { - print('Erreur lors de la suppression du voyage: $e'); + _errorService.logError('Erreur lors de la suppression du voyage: $e', StackTrace.current); return false; } } @@ -232,7 +228,7 @@ class TripService { } return null; } catch (e) { - print('Erreur lors de la récupération du voyage: $e'); + _errorService.logError('Erreur lors de la récupération du voyage: $e', StackTrace.current); return null; } } @@ -246,7 +242,7 @@ class TripService { }); return true; } catch (e) { - print('Erreur lors de l\'ajout du participant: $e'); + _errorService.logError('Erreur lors de l\'ajout du participant: $e', StackTrace.current); return false; } } @@ -260,7 +256,7 @@ class TripService { }); return true; } catch (e) { - print('Erreur lors du retrait du participant: $e'); + _errorService.logError('Erreur lors du retrait du participant: $e', StackTrace.current); return false; } } diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart index 327ce43..c835ca6 100644 --- a/lib/services/user_service.dart +++ b/lib/services/user_service.dart @@ -1,8 +1,10 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:travel_mate/services/error_service.dart'; import '../blocs/user/user_state.dart'; class UserService { + final _errorService = ErrorService(); final FirebaseFirestore _firestore; final FirebaseAuth _auth; static const String _usersCollection = 'users'; @@ -32,7 +34,7 @@ class UserService { .set(user.toJson()); return true; } catch (e) { - print('Erreur lors de la création de l\'utilisateur: $e'); + _errorService.logError('Erreur lors de la création de l\'utilisateur: $e', StackTrace.current); return false; } } @@ -53,7 +55,7 @@ class UserService { } return null; } catch (e) { - print('Erreur lors de la récupération de l\'utilisateur: $e'); + _errorService.logError('Erreur lors de la récupération de l\'utilisateur: $e', StackTrace.current); return null; } } @@ -76,7 +78,7 @@ class UserService { } return null; } catch (e) { - print('Erreur lors de la récupération de l\'utilisateur par email: $e'); + _errorService.logError('Erreur lors de la récupération de l\'utilisateur par email: $e', StackTrace.current); return null; } } @@ -95,7 +97,7 @@ class UserService { } return null; } catch (e) { - print('Erreur lors de la récupération de l\'ID utilisateur: $e'); + _errorService.logError('Erreur lors de la récupération de l\'ID utilisateur: $e', StackTrace.current); return null; } } @@ -109,7 +111,7 @@ class UserService { .update(userData); return true; } catch (e) { - print('Erreur lors de la mise à jour de l\'utilisateur: $e'); + _errorService.logError('Erreur lors de la mise à jour de l\'utilisateur: $e', StackTrace.current); return false; } } @@ -123,7 +125,7 @@ class UserService { .delete(); return true; } catch (e) { - print('Erreur lors de la suppression de l\'utilisateur: $e'); + _errorService.logError('Erreur lors de la suppression de l\'utilisateur: $e', StackTrace.current); return false; } } @@ -139,7 +141,7 @@ class UserService { return querySnapshot.docs.isNotEmpty; } catch (e) { - print('Erreur lors de la vérification de l\'email: $e'); + _errorService.logError('Erreur lors de la vérification de l\'email: $e', StackTrace.current); return false; } } @@ -171,7 +173,7 @@ class UserService { return users; } catch (e) { - print('Erreur lors de la récupération des utilisateurs: $e'); + _errorService.logError('Erreur lors de la récupération des utilisateurs: $e', StackTrace.current); return []; } } @@ -190,7 +192,7 @@ class UserService { }); }).toList(); } catch (e) { - print('Erreur lors de la récupération de tous les utilisateurs: $e'); + _errorService.logError('Erreur lors de la récupération de tous les utilisateurs: $e', StackTrace.current); return []; } } @@ -254,7 +256,7 @@ class UserService { return usersMap.values.toList(); } catch (e) { - print('Erreur lors de la recherche d\'utilisateurs: $e'); + _errorService.logError('Erreur lors de la recherche d\'utilisateurs: $e', StackTrace.current); return []; } } @@ -270,7 +272,7 @@ class UserService { }); return true; } catch (e) { - print('Erreur lors de la mise à jour de la dernière connexion: $e'); + _errorService.logError('Erreur lors de la mise à jour de la dernière connexion: $e', StackTrace.current); return false; } } @@ -284,7 +286,7 @@ class UserService { .get(); return doc.exists; } catch (e) { - print('Erreur lors de la vérification de l\'existence de l\'utilisateur: $e'); + _errorService.logError('Erreur lors de la vérification de l\'existence de l\'utilisateur: $e', StackTrace.current); return false; } }