diff --git a/lib/components/account/add_expense_dialog.dart b/lib/components/account/add_expense_dialog.dart index 0fe0699..fc3475a 100644 --- a/lib/components/account/add_expense_dialog.dart +++ b/lib/components/account/add_expense_dialog.dart @@ -63,6 +63,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:travel_mate/models/expense_split.dart'; +import '../../services/error_service.dart'; import '../../blocs/expense/expense_bloc.dart'; import '../../blocs/expense/expense_event.dart'; import '../../blocs/expense/expense_state.dart'; @@ -191,11 +192,8 @@ class _AddExpenseDialogState extends State { if (fileSize > 5 * 1024 * 1024) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('L\'image ne doit pas dépasser 5 Mo'), - backgroundColor: Colors.red, - ), + ErrorService().showError( + message: 'L\'image ne doit pas dépasser 5 Mo', ); } return; @@ -247,11 +245,8 @@ class _AddExpenseDialogState extends State { }).toList(); if (selectedSplits.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Veuillez sélectionner au moins un participant'), - backgroundColor: Colors.red, - ), + ErrorService().showError( + message: 'Veuillez sélectionner au moins un participant', ); return; } @@ -299,22 +294,16 @@ class _AddExpenseDialogState extends State { if (mounted) { Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - widget.expenseToEdit == null - ? 'Dépense ajoutée' - : 'Dépense modifiée', - ), - backgroundColor: Colors.green, - ), + ErrorService().showSnackbar( + message: widget.expenseToEdit == null + ? 'Dépense ajoutée' + : 'Dépense modifiée', + isError: false, ); } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erreur: $e'), backgroundColor: Colors.red), - ); + ErrorService().showError(message: 'Erreur: $e'); } } finally { if (mounted) { diff --git a/lib/components/account/group_expenses_page.dart b/lib/components/account/group_expenses_page.dart index e45e161..2f986d3 100644 --- a/lib/components/account/group_expenses_page.dart +++ b/lib/components/account/group_expenses_page.dart @@ -15,6 +15,7 @@ import 'balances_tab.dart'; import 'expenses_tab.dart'; import '../../models/user_balance.dart'; import '../../models/expense.dart'; +import '../../services/error_service.dart'; class GroupExpensesPage extends StatefulWidget { final Account account; @@ -93,20 +94,13 @@ class _GroupExpensesPageState extends State BlocListener( listener: (context, state) { if (state is ExpenseOperationSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - backgroundColor: Colors.green, - ), + ErrorService().showSnackbar( + message: state.message, + isError: false, ); _loadData(); // Recharger les données après une opération } else if (state is ExpenseError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: state.message); } else if (state is ExpensesLoaded) { // Rafraîchir les balances quand les dépenses changent (ex: via stream) context.read().add( @@ -393,12 +387,7 @@ class _GroupExpensesPageState extends State AddExpenseDialog(group: widget.group, currentUser: userState.user), ); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Erreur: utilisateur non connecté'), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: 'Erreur: utilisateur non connecté'); } } diff --git a/lib/components/error/error_content.dart b/lib/components/error/error_content.dart index d204c92..b824b96 100644 --- a/lib/components/error/error_content.dart +++ b/lib/components/error/error_content.dart @@ -1,31 +1,31 @@ import 'package:flutter/material.dart'; /// A reusable error display component. -/// +/// /// This widget provides a consistent way to display error messages throughout /// the application. It supports customizable titles, messages, icons, and /// action buttons for retry and close operations. class ErrorContent extends StatelessWidget { /// The error title to display final String title; - + /// The error message to display final String message; - + /// Optional callback for retry action final VoidCallback? onRetry; - + /// Optional callback for close action final VoidCallback? onClose; - + /// Icon to display with the error final IconData icon; - + /// Color of the error icon final Color? iconColor; /// Creates a new [ErrorContent] widget. - /// + /// /// [message] is required, other parameters are optional with sensible defaults. const ErrorContent({ super.key, @@ -79,11 +79,7 @@ class ErrorContent extends StatelessWidget { color: defaultIconColor?.withValues(alpha: 0.1), shape: BoxShape.circle, ), - child: Icon( - icon, - size: 48, - color: defaultIconColor, - ), + child: Icon(icon, size: 48, color: defaultIconColor), ), const SizedBox(height: 24), @@ -167,9 +163,7 @@ void showErrorDialog( barrierDismissible: barrierDismissible, builder: (BuildContext dialogContext) { return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: ErrorContent( title: title, message: message, @@ -187,70 +181,3 @@ void showErrorDialog( }, ); } - -// 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/chat_group_content.dart b/lib/components/group/chat_group_content.dart index d587a39..307df71 100644 --- a/lib/components/group/chat_group_content.dart +++ b/lib/components/group/chat_group_content.dart @@ -10,6 +10,7 @@ import '../../models/group.dart'; import '../../models/group_member.dart'; import '../../models/message.dart'; import '../../repositories/group_repository.dart'; +import '../../services/error_service.dart'; /// Chat group content widget for group messaging functionality. /// @@ -220,12 +221,7 @@ class _ChatGroupContentState extends State { child: BlocConsumer( listener: (context, state) { if (state is MessageError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: state.message); } }, builder: (context, state) { @@ -871,20 +867,15 @@ class _ChatGroupContentState extends State { // Le stream listener va automatiquement mettre à jour les membres // Pas besoin de fermer le dialog ou de faire un refresh manuel - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Pseudo modifié en "$newPseudo"'), - backgroundColor: Colors.green, - ), + ErrorService().showSnackbar( + message: 'Pseudo modifié en "$newPseudo"', + isError: false, ); } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Erreur lors de la modification du pseudo: $e'), - backgroundColor: Colors.red, - ), + ErrorService().showError( + message: 'Erreur lors de la modification du pseudo: $e', ); } } diff --git a/lib/components/home/create_trip_content.dart b/lib/components/home/create_trip_content.dart index ba8201d..9649fb5 100644 --- a/lib/components/home/create_trip_content.dart +++ b/lib/components/home/create_trip_content.dart @@ -876,17 +876,16 @@ class _CreateTripContentState extends State { final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(email)) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Email invalide'))); + _errorService.showError(message: 'Email invalide'); } return; } if (_participants.contains(email)) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Ce participant est déjà ajouté')), + _errorService.showSnackbar( + message: 'Ce participant est déjà ajouté', + isError: true, ); } return; @@ -962,11 +961,9 @@ class _CreateTripContentState extends State { } if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Groupe et compte mis à jour avec succès !'), - backgroundColor: Colors.green, - ), + _errorService.showSnackbar( + message: 'Groupe et compte mis à jour avec succès !', + isError: false, ); setState(() { _isLoading = false; @@ -1048,11 +1045,9 @@ class _CreateTripContentState extends State { ); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Voyage, groupe et compte créés avec succès !'), - backgroundColor: Colors.green, - ), + _errorService.showSnackbar( + message: 'Voyage, groupe et compte créés avec succès !', + isError: false, ); setState(() { _isLoading = false; @@ -1066,9 +1061,7 @@ class _CreateTripContentState extends State { ); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erreur: $e'), backgroundColor: Colors.red), - ); + _errorService.showError(message: 'Erreur: $e'); setState(() { _isLoading = false; }); @@ -1083,8 +1076,9 @@ class _CreateTripContentState extends State { if (_startDate == null || _endDate == null) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Veuillez sélectionner les dates')), + _errorService.showSnackbar( + message: 'Veuillez sélectionner les dates', + isError: true, ); } return; @@ -1129,14 +1123,10 @@ class _CreateTripContentState extends State { // Continuer sans coordonnées en cas d'erreur tripWithCoordinates = trip; if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( + _errorService.showSnackbar( + message: 'Voyage créé sans géolocalisation (pas d\'impact sur les fonctionnalités)', - ), - backgroundColor: Colors.orange, - duration: Duration(seconds: 2), - ), + isError: true, // Warning displayed as error for now ); } } @@ -1167,9 +1157,7 @@ class _CreateTripContentState extends State { } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Erreur: $e'), backgroundColor: Colors.red), - ); + _errorService.showError(message: 'Erreur: $e'); setState(() { _isLoading = false; @@ -1200,11 +1188,9 @@ class _CreateTripContentState extends State { }); } else { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Utilisateur non trouvé: $email'), - backgroundColor: Colors.orange, - ), + _errorService.showSnackbar( + message: 'Utilisateur non trouvé: $email', + isError: true, ); } } diff --git a/lib/components/home/home_content.dart b/lib/components/home/home_content.dart index 19673cc..554b008 100644 --- a/lib/components/home/home_content.dart +++ b/lib/components/home/home_content.dart @@ -11,6 +11,7 @@ import '../../blocs/trip/trip_bloc.dart'; import '../../blocs/trip/trip_state.dart'; import '../../blocs/trip/trip_event.dart'; import '../../models/trip.dart'; +import '../../services/error_service.dart'; /// Home content widget for the main application dashboard. /// @@ -79,26 +80,16 @@ class _HomeContentState extends State return BlocConsumer( listener: (context, tripState) { if (tripState is TripOperationSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(tripState.message), - backgroundColor: Colors.green, - ), + ErrorService().showSnackbar( + message: tripState.message, + isError: false, ); } else if (tripState is TripError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(tripState.message), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: tripState.message); } else if (tripState is TripCreated) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Voyage en cours de création...'), - backgroundColor: Colors.blue, - duration: Duration(seconds: 1), - ), + ErrorService().showSnackbar( + message: 'Voyage en cours de création...', + isError: false, ); } }, diff --git a/lib/components/home/show_trip_details_content.dart b/lib/components/home/show_trip_details_content.dart index c38e2df..8cc8665 100644 --- a/lib/components/home/show_trip_details_content.dart +++ b/lib/components/home/show_trip_details_content.dart @@ -547,11 +547,9 @@ class _ShowTripDetailsContentState extends State { } void _showComingSoon(String feature) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('$feature - Fonctionnalité à venir'), - backgroundColor: Colors.blue, - ), + _errorService.showSnackbar( + message: '$feature - Fonctionnalité à venir', + isError: false, ); } @@ -868,11 +866,8 @@ class _ShowTripDetailsContentState extends State { _addParticipantByEmail(emailController.text); Navigator.pop(context); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Veuillez entrer un email valide'), - backgroundColor: Colors.red, - ), + _errorService.showError( + message: 'Veuillez entrer un email valide', ); } }, @@ -940,11 +935,9 @@ class _ShowTripDetailsContentState extends State { TripUpdateRequested(trip: updatedTrip), ); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('${user.prenom} a été ajouté au voyage'), - backgroundColor: Colors.green, - ), + _errorService.showSnackbar( + message: '${user.prenom} a été ajouté au voyage', + isError: false, ); // Rafraîchir la page diff --git a/lib/components/map/map_content.dart b/lib/components/map/map_content.dart index e471e80..11f87d1 100644 --- a/lib/components/map/map_content.dart +++ b/lib/components/map/map_content.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'dart:ui' as ui; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import '../../services/error_service.dart'; class MapContent extends StatefulWidget { final String? initialSearchQuery; @@ -416,13 +417,7 @@ class _MapContentState extends State { void _showError(String message) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: Colors.red, - behavior: SnackBarBehavior.floating, - ), - ); + ErrorService().showError(message: message); } } diff --git a/lib/components/settings/profile/profile_content.dart b/lib/components/settings/profile/profile_content.dart index 94e923c..e88538f 100644 --- a/lib/components/settings/profile/profile_content.dart +++ b/lib/components/settings/profile/profile_content.dart @@ -502,11 +502,9 @@ class ProfileContent extends StatelessWidget { ); Navigator.of(dialogContext).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Profil mis à jour !'), - backgroundColor: Colors.green, - ), + _errorService.showSnackbar( + message: 'Profil mis à jour !', + isError: false, ); } }, @@ -668,11 +666,9 @@ class ProfileContent extends StatelessWidget { if (context.mounted) { LoggerService.info('DEBUG: Affichage du succès'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Photo de profil mise à jour !'), - backgroundColor: Colors.green, - ), + _errorService.showSnackbar( + message: 'Photo de profil mise à jour !', + isError: false, ); } } catch (e, stackTrace) { @@ -736,22 +732,16 @@ class ProfileContent extends StatelessWidget { if (currentPasswordController.text.isEmpty || newPasswordController.text.isEmpty || confirmPasswordController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Tous les champs sont requis'), - backgroundColor: Colors.red, - ), + _errorService.showError( + message: 'Tous les champs sont requis', ); return; } if (newPasswordController.text != confirmPasswordController.text) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Les mots de passe ne correspondent pas'), - backgroundColor: Colors.red, - ), + _errorService.showError( + message: 'Les mots de passe ne correspondent pas', ); return; } @@ -765,11 +755,9 @@ class ProfileContent extends StatelessWidget { if (context.mounted) { Navigator.of(dialogContext).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Mot de passe changé !'), - backgroundColor: Colors.green, - ), + _errorService.showSnackbar( + message: 'Mot de passe changé !', + isError: false, ); } } catch (e) { @@ -823,11 +811,8 @@ class ProfileContent extends StatelessWidget { TextButton( onPressed: () async { if (confirmationController.text != 'CONFIRMER') { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Veuillez écrire CONFIRMER pour valider'), - backgroundColor: Colors.red, - ), + _errorService.showError( + message: 'Veuillez écrire CONFIRMER pour valider', ); return; } @@ -848,14 +833,10 @@ class ProfileContent extends StatelessWidget { if (e.code == 'requires-recent-login') { if (context.mounted) { Navigator.of(dialogContext).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( + _errorService.showSnackbar( + message: 'Par sécurité, veuillez vous reconnecter avant de supprimer votre compte', - ), - backgroundColor: Colors.orange, - duration: Duration(seconds: 4), - ), + isError: true, // It's a warning/error ); } } else { diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 0ca6869..f57bed1 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -233,7 +233,7 @@ class AuthService { ], // Configuration for Android/Web webAuthenticationOptions: WebAuthenticationOptions( - clientId: 'be.devdayronvl.TravelMate.service', + clientId: 'be.devdayronvl.travel_mate.service', redirectUri: Uri.parse( 'https://travelmate-a47f5.firebaseapp.com/__/auth/handler', ),