diff --git a/lib/blocs/account/account_bloc.dart b/lib/blocs/account/account_bloc.dart index ea59753..31d0858 100644 --- a/lib/blocs/account/account_bloc.dart +++ b/lib/blocs/account/account_bloc.dart @@ -22,6 +22,7 @@ /// accountBloc.close(); /// ``` library; + import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/services/error_service.dart'; @@ -30,7 +31,7 @@ import 'account_state.dart'; import '../../repositories/account_repository.dart'; import '../../models/account.dart'; -class AccountBloc extends Bloc { +class AccountBloc extends Bloc { final AccountRepository _repository; StreamSubscription? _accountsSubscription; final _errorService = ErrorService(); @@ -47,21 +48,27 @@ class AccountBloc extends Bloc { Future _onLoadAccountsByUserId( LoadAccountsByUserId event, Emitter emit, - ) async { + ) async { try { emit(AccountLoading()); await _accountsSubscription?.cancel(); - _accountsSubscription = _repository.getAccountByUserId(event.userId).listen( - (accounts) { - add(_AccountsUpdated(accounts)); - }, - onError: (error) { - add(_AccountsUpdated([], error: error.toString())); - }, - ); + _accountsSubscription = _repository + .getAccountByUserId(event.userId) + .listen( + (accounts) { + add(_AccountsUpdated(accounts)); + }, + onError: (error) { + add(_AccountsUpdated([], error: error.toString())); + }, + ); } catch (e, stackTrace) { - _errorService.logError(e.toString(), stackTrace); - emit(AccountError(e.toString())); + _errorService.logError( + 'AccountBloc', + 'Error loading accounts: $e', + stackTrace, + ); + emit(const AccountError('Impossible de charger les comptes')); } } @@ -89,8 +96,12 @@ class AccountBloc extends Bloc { ); emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId')); } catch (e, stackTrace) { - _errorService.logError(e.toString(), stackTrace); - emit(AccountError('Erreur lors de la création du compte: ${e.toString()}')); + _errorService.logError( + 'AccountBloc', + 'Error creating account: $e', + stackTrace, + ); + emit(const AccountError('Impossible de créer le compte')); } } @@ -98,7 +109,7 @@ class AccountBloc extends Bloc { CreateAccountWithMembers event, Emitter emit, ) async { - try{ + try { emit(AccountLoading()); final accountId = await _repository.createAccountWithMembers( account: event.account, @@ -106,8 +117,12 @@ class AccountBloc extends Bloc { ); emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId')); } catch (e, stackTrace) { - _errorService.logError(e.toString(), stackTrace); - emit(AccountError('Erreur lors de la création du compte: ${e.toString()}')); + _errorService.logError( + 'AccountBloc', + 'Error creating account with members: $e', + stackTrace, + ); + emit(const AccountError('Impossible de créer le compte')); } } @@ -120,8 +135,12 @@ class AccountBloc extends Bloc { await _repository.addMemberToAccount(event.accountId, event.member); emit(AccountOperationSuccess('Membre ajouté avec succès')); } catch (e, stackTrace) { - _errorService.logError(e.toString(), stackTrace); - emit(AccountError('Erreur lors de l\'ajout du membre: ${e.toString()}')); + _errorService.logError( + 'AccountBloc', + 'Error adding member: $e', + stackTrace, + ); + emit(const AccountError('Impossible d\'ajouter le membre')); } } @@ -131,11 +150,18 @@ class AccountBloc extends Bloc { ) async { try { emit(AccountLoading()); - await _repository.removeMemberFromAccount(event.accountId, event.memberId); + await _repository.removeMemberFromAccount( + event.accountId, + event.memberId, + ); emit(AccountOperationSuccess('Membre supprimé avec succès')); } catch (e, stackTrace) { - _errorService.logError(e.toString(), stackTrace); - emit(AccountError('Erreur lors de la suppression du membre: ${e.toString()}')); + _errorService.logError( + 'AccountBloc', + 'Error removing member: $e', + stackTrace, + ); + emit(const AccountError('Impossible de supprimer le membre')); } } @@ -154,4 +180,4 @@ class _AccountsUpdated extends AccountEvent { @override List get props => [accounts, error]; -} \ No newline at end of file +} diff --git a/lib/blocs/activity/activity_bloc.dart b/lib/blocs/activity/activity_bloc.dart index 7b61196..ad95afb 100644 --- a/lib/blocs/activity/activity_bloc.dart +++ b/lib/blocs/activity/activity_bloc.dart @@ -56,10 +56,11 @@ class ActivityBloc extends Bloc { emit( ActivityLoaded(activities: activities, filteredActivities: activities), ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'activity_bloc', 'Erreur chargement activités: $e', + stackTrace, ); emit(const ActivityError('Impossible de charger les activités')); } @@ -83,10 +84,11 @@ class ActivityBloc extends Bloc { emit( ActivityLoaded(activities: activities, filteredActivities: activities), ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'activity_bloc', 'Erreur chargement activités: $e', + stackTrace, ); emit(const ActivityError('Impossible de charger les activités')); } @@ -112,8 +114,12 @@ class ActivityBloc extends Bloc { // Recharger les activités pour mettre à jour l'UI add(LoadActivities(event.tripId)); } - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur mise à jour date: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur mise à jour date: $e', + stackTrace, + ); emit(const ActivityError('Impossible de mettre à jour la date')); } } @@ -162,8 +168,12 @@ class ActivityBloc extends Bloc { isLoading: false, ), ); - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur recherche activités: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur recherche activités: $e', + stackTrace, + ); emit(const ActivityError('Impossible de rechercher les activités')); } } @@ -211,10 +221,11 @@ class ActivityBloc extends Bloc { isLoading: false, ), ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'activity_bloc', 'Erreur recherche activités avec coordonnées: $e', + stackTrace, ); emit(const ActivityError('Impossible de rechercher les activités')); } @@ -240,8 +251,12 @@ class ActivityBloc extends Bloc { emit( ActivitySearchResults(searchResults: searchResults, query: event.query), ); - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur recherche textuelle: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur recherche textuelle: $e', + stackTrace, + ); emit(const ActivityError('Impossible de rechercher les activités')); } } @@ -292,8 +307,12 @@ class ActivityBloc extends Bloc { } else { emit(const ActivityError('Impossible d\'ajouter l\'activité')); } - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur ajout activité: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur ajout activité: $e', + stackTrace, + ); emit(const ActivityError('Impossible d\'ajouter l\'activité')); } } @@ -350,8 +369,12 @@ class ActivityBloc extends Bloc { } else { emit(const ActivityError('Impossible d\'ajouter l\'activité')); } - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur ajout activité: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur ajout activité: $e', + stackTrace, + ); emit(const ActivityError('Impossible d\'ajouter l\'activité')); } } @@ -418,8 +441,12 @@ class ActivityBloc extends Bloc { } else { emit(const ActivityError('Impossible d\'ajouter les activités')); } - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur ajout en lot: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur ajout en lot: $e', + stackTrace, + ); emit(const ActivityError('Impossible d\'ajouter les activités')); } } @@ -479,8 +506,8 @@ class ActivityBloc extends Bloc { } else { emit(const ActivityError('Impossible d\'enregistrer le vote')); } - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur vote: $e'); + } catch (e, stackTrace) { + _errorService.logError('activity_bloc', 'Erreur vote: $e', stackTrace); emit(const ActivityError('Impossible d\'enregistrer le vote')); } } @@ -511,8 +538,12 @@ class ActivityBloc extends Bloc { } else { emit(const ActivityError('Impossible de supprimer l\'activité')); } - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur suppression: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur suppression: $e', + stackTrace, + ); emit(const ActivityError('Impossible de supprimer l\'activité')); } } @@ -593,8 +624,12 @@ class ActivityBloc extends Bloc { } else { emit(const ActivityError('Impossible de mettre à jour l\'activité')); } - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur mise à jour: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'activity_bloc', + 'Erreur mise à jour: $e', + stackTrace, + ); emit(const ActivityError('Impossible de mettre à jour l\'activité')); } } @@ -614,8 +649,8 @@ class ActivityBloc extends Bloc { vote: 1, ), ); - } catch (e) { - _errorService.logError('activity_bloc', 'Erreur favori: $e'); + } catch (e, stackTrace) { + _errorService.logError('activity_bloc', 'Erreur favori: $e', stackTrace); emit(const ActivityError('Impossible de modifier les favoris')); } } diff --git a/lib/blocs/auth/auth_bloc.dart b/lib/blocs/auth/auth_bloc.dart index 1705ce6..4159abb 100644 --- a/lib/blocs/auth/auth_bloc.dart +++ b/lib/blocs/auth/auth_bloc.dart @@ -77,7 +77,7 @@ class AuthBloc extends Bloc { emit(AuthUnauthenticated()); } } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } @@ -103,7 +103,7 @@ class AuthBloc extends Bloc { emit(const AuthError(message: 'Invalid email or password')); } } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } @@ -132,7 +132,7 @@ class AuthBloc extends Bloc { emit(const AuthError(message: 'Failed to create account')); } } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } @@ -160,7 +160,7 @@ class AuthBloc extends Bloc { ); } } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } @@ -183,7 +183,7 @@ class AuthBloc extends Bloc { emit(const AuthError(message: 'Failed to create account with Google')); } } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } @@ -206,7 +206,7 @@ class AuthBloc extends Bloc { emit(const AuthError(message: 'Failed to create account with Apple')); } } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } @@ -234,7 +234,7 @@ class AuthBloc extends Bloc { ); } } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } @@ -261,7 +261,7 @@ class AuthBloc extends Bloc { await _authRepository.resetPassword(event.email); emit(AuthPasswordResetSent(email: event.email)); } catch (e) { - emit(AuthError(message: e.toString())); + emit(AuthError(message: e.toString().replaceAll('Exception: ', ''))); } } } diff --git a/lib/blocs/balance/balance_bloc.dart b/lib/blocs/balance/balance_bloc.dart index b995427..a771b68 100644 --- a/lib/blocs/balance/balance_bloc.dart +++ b/lib/blocs/balance/balance_bloc.dart @@ -105,9 +105,13 @@ class BalanceBloc extends Bloc { emit( GroupBalancesLoaded(balances: userBalances, settlements: settlements), ); - } catch (e) { - _errorService.logError('BalanceBloc', 'Error loading balance: $e'); - emit(BalanceError(e.toString())); + } catch (e, stackTrace) { + _errorService.logError( + 'BalanceBloc', + 'Error loading balance: $e', + stackTrace, + ); + emit(const BalanceError('Impossible de charger la balance')); } } @@ -143,9 +147,13 @@ class BalanceBloc extends Bloc { emit( GroupBalancesLoaded(balances: userBalances, settlements: settlements), ); - } catch (e) { - _errorService.logError('BalanceBloc', 'Error refreshing balance: $e'); - emit(BalanceError(e.toString())); + } catch (e, stackTrace) { + _errorService.logError( + 'BalanceBloc', + 'Error refreshing balance: $e', + stackTrace, + ); + emit(const BalanceError('Impossible de rafraîchir la balance')); } } @@ -174,9 +182,15 @@ class BalanceBloc extends Bloc { // Reload balance after settlement add(RefreshBalance(event.groupId)); - } catch (e) { - _errorService.logError('BalanceBloc', 'Error marking settlement: $e'); - emit(BalanceError(e.toString())); + } catch (e, stackTrace) { + _errorService.logError( + 'BalanceBloc', + 'Error marking settlement: $e', + stackTrace, + ); + emit( + const BalanceError('Impossible de marquer le règlement comme terminé'), + ); } } } diff --git a/lib/blocs/expense/expense_bloc.dart b/lib/blocs/expense/expense_bloc.dart index 0b48977..182b77e 100644 --- a/lib/blocs/expense/expense_bloc.dart +++ b/lib/blocs/expense/expense_bloc.dart @@ -72,7 +72,7 @@ class ExpenseBloc extends Bloc { ); } catch (e) { _errorService.logError('ExpenseBloc', 'Error loading expenses: $e'); - emit(ExpenseError(e.toString())); + emit(const ExpenseError('Impossible de charger les dépenses')); } } @@ -116,7 +116,7 @@ class ExpenseBloc extends Bloc { emit(const ExpenseOperationSuccess('Expense created successfully')); } catch (e) { _errorService.logError('ExpenseBloc', 'Error creating expense: $e'); - emit(ExpenseError(e.toString())); + emit(const ExpenseError('Impossible de créer la dépense')); } } @@ -141,7 +141,7 @@ class ExpenseBloc extends Bloc { emit(const ExpenseOperationSuccess('Expense updated successfully')); } catch (e) { _errorService.logError('ExpenseBloc', 'Error updating expense: $e'); - emit(ExpenseError(e.toString())); + emit(const ExpenseError('Impossible de mettre à jour la dépense')); } } @@ -162,7 +162,7 @@ class ExpenseBloc extends Bloc { emit(const ExpenseOperationSuccess('Expense deleted successfully')); } catch (e) { _errorService.logError('ExpenseBloc', 'Error deleting expense: $e'); - emit(ExpenseError(e.toString())); + emit(const ExpenseError('Impossible de supprimer la dépense')); } } @@ -184,7 +184,7 @@ class ExpenseBloc extends Bloc { emit(const ExpenseOperationSuccess('Payment marked as completed')); } catch (e) { _errorService.logError('ExpenseBloc', 'Error marking split as paid: $e'); - emit(ExpenseError(e.toString())); + emit(const ExpenseError('Impossible de marquer comme payé')); } } @@ -206,7 +206,7 @@ class ExpenseBloc extends Bloc { emit(const ExpenseOperationSuccess('Expense archived successfully')); } catch (e) { _errorService.logError('ExpenseBloc', 'Error archiving expense: $e'); - emit(ExpenseError(e.toString())); + emit(const ExpenseError('Impossible d\'archiver la dépense')); } } diff --git a/lib/blocs/group/group_bloc.dart b/lib/blocs/group/group_bloc.dart index 3e8d0ae..2ba0361 100644 --- a/lib/blocs/group/group_bloc.dart +++ b/lib/blocs/group/group_bloc.dart @@ -21,10 +21,10 @@ /// Example usage: /// ```dart /// final groupBloc = GroupBloc(groupRepository); -/// +/// /// // Load groups for a user /// groupBloc.add(LoadGroupsByUserId('userId123')); -/// +/// /// // Create a new group with members /// groupBloc.add(CreateGroupWithMembers( /// group: newGroup, @@ -32,6 +32,7 @@ /// )); /// ``` library; + import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/services/error_service.dart'; @@ -44,18 +45,18 @@ import '../../models/group.dart'; class GroupBloc extends Bloc { /// Repository for group data operations final GroupRepository _repository; - + /// Subscription to group stream for real-time updates StreamSubscription? _groupsSubscription; - + /// Service for error handling and logging final _errorService = ErrorService(); /// Constructor for GroupBloc. - /// + /// /// Initializes the bloc with the group repository and sets up event handlers /// for all group-related operations. - /// + /// /// Args: /// [_repository]: Repository for group data operations GroupBloc(this._repository) : super(GroupInitial()) { @@ -71,39 +72,45 @@ class GroupBloc extends Bloc { } /// Handles [LoadGroupsByUserId] events. - /// + /// /// Loads all groups for a specific user with real-time updates via stream subscription. /// Cancels any existing subscription before creating a new one to prevent memory leaks. - /// + /// /// Args: /// [event]: The LoadGroupsByUserId event containing the user ID /// [emit]: State emitter function Future _onLoadGroupsByUserId( LoadGroupsByUserId event, Emitter emit, - ) async { + ) async { try { emit(GroupLoading()); await _groupsSubscription?.cancel(); - _groupsSubscription = _repository.getGroupsByUserId(event.userId).listen( - (groups) { - add(_GroupsUpdated(groups)); - }, - onError: (error) { - add(_GroupsUpdated([], error: error.toString())); - }, - ); + _groupsSubscription = _repository + .getGroupsByUserId(event.userId) + .listen( + (groups) { + add(_GroupsUpdated(groups)); + }, + onError: (error) { + add(_GroupsUpdated([], error: error.toString())); + }, + ); } catch (e, stackTrace) { - _errorService.logError(e.toString(), stackTrace); - emit(GroupError(e.toString())); + _errorService.logError( + 'GroupBloc', + 'Error loading groups: $e', + stackTrace, + ); + emit(const GroupError('Impossible de charger les groupes')); } } /// Handles [_GroupsUpdated] events. - /// + /// /// Processes real-time updates from the group stream, either emitting /// the updated group list or an error state if the stream encountered an error. - /// + /// /// Args: /// [event]: The _GroupsUpdated event containing groups or error information /// [emit]: State emitter function @@ -120,10 +127,10 @@ class GroupBloc extends Bloc { } /// Handles [LoadGroupsByTrip] events. - /// + /// /// Loads the group associated with a specific trip. Since each trip typically /// has one primary group, this returns a single group or an empty list. - /// + /// /// Args: /// [event]: The LoadGroupsByTrip event containing the trip ID /// [emit]: State emitter function @@ -139,16 +146,21 @@ class GroupBloc extends Bloc { } else { emit(const GroupsLoaded([])); } - } catch (e) { - emit(GroupError(e.toString())); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupBloc', + 'Error loading group by trip: $e', + stackTrace, + ); + emit(const GroupError('Impossible de charger le groupe du voyage')); } } /// Handles [CreateGroup] events. - /// + /// /// Creates a new group without any initial members. The group creator /// can add members later using AddMemberToGroup events. - /// + /// /// Args: /// [event]: The CreateGroup event containing the group data /// [emit]: State emitter function @@ -164,17 +176,22 @@ class GroupBloc extends Bloc { ); emit(GroupCreated(groupId: groupId)); emit(const GroupOperationSuccess('Group created successfully')); - } catch (e) { - emit(GroupError('Error during creation: $e')); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupBloc', + 'Error creating group: $e', + stackTrace, + ); + emit(const GroupError('Impossible de créer le groupe')); } } /// Handles [CreateGroupWithMembers] events. - /// + /// /// Creates a new group with an initial set of members. This is useful /// for setting up complete groups in one operation, such as when /// planning a trip with known participants. - /// + /// /// Args: /// [event]: The CreateGroupWithMembers event containing group data and member list /// [emit]: State emitter function @@ -189,16 +206,21 @@ class GroupBloc extends Bloc { members: event.members, ); emit(GroupCreated(groupId: groupId)); - } catch (e) { - emit(GroupError('Error during creation: $e')); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupBloc', + 'Error creating group with members: $e', + stackTrace, + ); + emit(const GroupError('Impossible de créer le groupe')); } } /// Handles [AddMemberToGroup] events. - /// + /// /// Adds a new member to an existing group. The member will be able to /// participate in group expenses and access group features. - /// + /// /// Args: /// [event]: The AddMemberToGroup event containing group ID and member data /// [emit]: State emitter function @@ -209,16 +231,21 @@ class GroupBloc extends Bloc { try { await _repository.addMember(event.groupId, event.member); emit(const GroupOperationSuccess('Member added')); - } catch (e) { - emit(GroupError('Error during addition: $e')); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupBloc', + 'Error adding member: $e', + stackTrace, + ); + emit(const GroupError('Impossible d\'ajouter le membre')); } } /// Handles [RemoveMemberFromGroup] events. - /// + /// /// Removes a member from a group. This will affect expense calculations /// and the member will no longer have access to group features. - /// + /// /// Args: /// [event]: The RemoveMemberFromGroup event containing group ID and user ID /// [emit]: State emitter function @@ -229,16 +256,21 @@ class GroupBloc extends Bloc { try { await _repository.removeMember(event.groupId, event.userId); emit(const GroupOperationSuccess('Member removed')); - } catch (e) { - emit(GroupError('Error during removal: $e')); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupBloc', + 'Error removing member: $e', + stackTrace, + ); + emit(const GroupError('Impossible de supprimer le membre')); } } /// Handles [UpdateGroup] events. - /// + /// /// Updates group information such as name, description, or settings. /// Member lists are managed through separate add/remove member events. - /// + /// /// Args: /// [event]: The UpdateGroup event containing group ID and updated group data /// [emit]: State emitter function @@ -249,16 +281,21 @@ class GroupBloc extends Bloc { try { await _repository.updateGroup(event.groupId, event.group); emit(const GroupOperationSuccess('Group updated')); - } catch (e) { - emit(GroupError('Error during update: $e')); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupBloc', + 'Error updating group: $e', + stackTrace, + ); + emit(const GroupError('Impossible de mettre à jour le groupe')); } } /// Handles [DeleteGroup] events. - /// + /// /// Permanently deletes a group and all associated data. This action /// cannot be undone and will affect all group members and expenses. - /// + /// /// Args: /// [event]: The DeleteGroup event containing the trip ID to delete /// [emit]: State emitter function @@ -269,13 +306,18 @@ class GroupBloc extends Bloc { try { await _repository.deleteGroup(event.tripId); emit(const GroupOperationSuccess('Group deleted')); - } catch (e) { - emit(GroupError('Error during deletion: $e')); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupBloc', + 'Error deleting group: $e', + stackTrace, + ); + emit(const GroupError('Impossible de supprimer le groupe')); } } /// Cleans up resources when the bloc is closed. - /// + /// /// Cancels the group stream subscription to prevent memory leaks /// and ensure proper disposal of resources. @override @@ -286,18 +328,18 @@ class GroupBloc extends Bloc { } /// Private event for handling real-time group updates from streams. -/// +/// /// This internal event is used to process updates from the group stream /// subscription and emit appropriate states based on the received data. class _GroupsUpdated extends GroupEvent { /// List of groups received from the stream final List groups; - + /// Error message if the stream encountered an error final String? error; /// Creates a _GroupsUpdated event. - /// + /// /// Args: /// [groups]: List of groups from the stream update /// [error]: Optional error message if stream failed @@ -305,4 +347,4 @@ class _GroupsUpdated extends GroupEvent { @override List get props => [groups, error]; -} \ No newline at end of file +} diff --git a/lib/blocs/message/message_bloc.dart b/lib/blocs/message/message_bloc.dart index 8ddee00..423cc09 100644 --- a/lib/blocs/message/message_bloc.dart +++ b/lib/blocs/message/message_bloc.dart @@ -19,10 +19,10 @@ /// Example usage: /// ```dart /// final messageBloc = MessageBloc(); -/// +/// /// // Load messages for a group /// messageBloc.add(LoadMessages('groupId123')); -/// +/// /// // Send a message /// messageBloc.add(SendMessage( /// groupId: 'groupId123', @@ -30,7 +30,7 @@ /// senderId: 'userId123', /// senderName: 'John Doe', /// )); -/// +/// /// // React to a message /// messageBloc.add(ReactToMessage( /// groupId: 'groupId123', @@ -40,6 +40,7 @@ /// )); /// ``` library; + import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../models/message.dart'; @@ -52,23 +53,23 @@ import 'message_state.dart'; class MessageBloc extends Bloc { /// Service for message operations and business logic final MessageService _messageService; - + /// Subscription to message stream for real-time updates StreamSubscription>? _messagesSubscription; /// Constructor for MessageBloc. - /// + /// /// Initializes the bloc with an optional message service. If no service is provided, /// creates a default MessageService with MessageRepository. Sets up event handlers /// for all message-related operations. - /// + /// /// Args: /// [messageService]: Optional service for message operations (auto-created if null) MessageBloc({MessageService? messageService}) - : _messageService = messageService ?? MessageService( - messageRepository: MessageRepository(), - ), - super(MessageInitial()) { + : _messageService = + messageService ?? + MessageService(messageRepository: MessageRepository()), + super(MessageInitial()) { on(_onLoadMessages); on(_onSendMessage); on(_onDeleteMessage); @@ -79,10 +80,10 @@ class MessageBloc extends Bloc { } /// Handles [LoadMessages] events. - /// + /// /// Loads messages for a specific group with real-time updates via stream subscription. /// Cancels any existing subscription before creating a new one to prevent memory leaks. - /// + /// /// Args: /// [event]: The LoadMessages event containing the group ID /// [emit]: State emitter function @@ -101,31 +102,28 @@ class MessageBloc extends Bloc { add(_MessagesUpdated(messages: messages, groupId: event.groupId)); }, onError: (error) { - add(_MessagesError('Error loading messages: $error')); + add(const _MessagesError('Impossible de charger les messages')); }, ); } /// Handles [_MessagesUpdated] events. - /// + /// /// Processes real-time updates from the message stream, emitting the /// updated message list with the associated group ID. - /// + /// /// Args: /// [event]: The _MessagesUpdated event containing messages and group ID /// [emit]: State emitter function - void _onMessagesUpdated( - _MessagesUpdated event, - Emitter emit, - ) { + void _onMessagesUpdated(_MessagesUpdated event, Emitter emit) { emit(MessagesLoaded(messages: event.messages, groupId: event.groupId)); } /// Handles [SendMessage] events. - /// + /// /// Sends a new message to a group chat. The stream subscription will /// automatically update the UI with the new message, so no state is emitted here. - /// + /// /// Args: /// [event]: The SendMessage event containing message details /// [emit]: State emitter function @@ -142,15 +140,15 @@ class MessageBloc extends Bloc { senderName: event.senderName, ); } catch (e) { - emit(MessageError('Error sending message: $e')); + emit(const MessageError('Impossible d\'envoyer le message')); } } /// Handles [DeleteMessage] events. - /// + /// /// Deletes a message from the group chat. The Firestore stream will /// automatically update the UI, so no state is emitted here unless there's an error. - /// + /// /// Args: /// [event]: The DeleteMessage event containing group ID and message ID /// [emit]: State emitter function @@ -166,15 +164,15 @@ class MessageBloc extends Bloc { messageId: event.messageId, ); } catch (e) { - emit(MessageError('Error deleting message: $e')); + emit(const MessageError('Impossible de supprimer le message')); } } /// Handles [UpdateMessage] events. - /// + /// /// Updates/edits an existing message in the group chat. The Firestore stream will /// automatically update the UI with the edited message, so no state is emitted here. - /// + /// /// Args: /// [event]: The UpdateMessage event containing message ID and new text /// [emit]: State emitter function @@ -191,15 +189,15 @@ class MessageBloc extends Bloc { newText: event.newText, ); } catch (e) { - emit(MessageError('Error updating message: $e')); + emit(const MessageError('Impossible de modifier le message')); } } /// Handles [ReactToMessage] events. - /// + /// /// Adds an emoji reaction to a message. The Firestore stream will /// automatically update the UI with the new reaction, so no state is emitted here. - /// + /// /// Args: /// [event]: The ReactToMessage event containing message ID, user ID, and reaction /// [emit]: State emitter function @@ -217,15 +215,15 @@ class MessageBloc extends Bloc { reaction: event.reaction, ); } catch (e) { - emit(MessageError('Error adding reaction: $e')); + emit(const MessageError('Impossible d\'ajouter la réaction')); } } /// Handles [RemoveReaction] events. - /// + /// /// Removes a user's reaction from a message. The Firestore stream will /// automatically update the UI with the removed reaction, so no state is emitted here. - /// + /// /// Args: /// [event]: The RemoveReaction event containing message ID and user ID /// [emit]: State emitter function @@ -242,12 +240,12 @@ class MessageBloc extends Bloc { userId: event.userId, ); } catch (e) { - emit(MessageError('Error removing reaction: $e')); + emit(const MessageError('Impossible de supprimer la réaction')); } } /// Cleans up resources when the bloc is closed. - /// + /// /// Cancels the message stream subscription to prevent memory leaks /// and ensure proper disposal of resources. @override @@ -258,32 +256,29 @@ class MessageBloc extends Bloc { } /// Private event for handling real-time message updates from streams. -/// +/// /// This internal event is used to process updates from the message stream /// subscription and emit appropriate states based on the received data. class _MessagesUpdated extends MessageEvent { /// List of messages received from the stream final List messages; - + /// Group ID associated with the messages final String groupId; /// Creates a _MessagesUpdated event. - /// + /// /// Args: /// [messages]: List of messages from the stream update /// [groupId]: ID of the group these messages belong to - const _MessagesUpdated({ - required this.messages, - required this.groupId, - }); + const _MessagesUpdated({required this.messages, required this.groupId}); @override List get props => [messages, groupId]; } /// Private event for handling message stream errors. -/// +/// /// This internal event is used to process errors from the message stream /// subscription and emit appropriate error states. class _MessagesError extends MessageEvent { @@ -291,7 +286,7 @@ class _MessagesError extends MessageEvent { final String error; /// Creates a _MessagesError event. - /// + /// /// Args: /// [error]: Error message from the stream failure const _MessagesError(this.error); diff --git a/lib/blocs/trip/trip_bloc.dart b/lib/blocs/trip/trip_bloc.dart index 9ce4c68..3fba633 100644 --- a/lib/blocs/trip/trip_bloc.dart +++ b/lib/blocs/trip/trip_bloc.dart @@ -22,43 +22,46 @@ /// Example usage: /// ```dart /// final tripBloc = TripBloc(tripRepository); -/// +/// /// // Load trips for a user /// tripBloc.add(LoadTripsByUserId(userId: 'userId123')); -/// +/// /// // Create a new trip /// tripBloc.add(TripCreateRequested(trip: newTrip)); -/// +/// /// // Update a trip /// tripBloc.add(TripUpdateRequested(trip: updatedTrip)); -/// +/// /// // Delete a trip /// tripBloc.add(TripDeleteRequested(tripId: 'tripId456')); /// ``` library; + import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/models/trip.dart'; import 'trip_event.dart'; import 'trip_state.dart'; import '../../repositories/trip_repository.dart'; +import '../../services/error_service.dart'; /// BLoC that manages trip-related operations and state. class TripBloc extends Bloc { /// Repository for trip data operations final TripRepository _repository; - + final _errorService = ErrorService(); + /// Subscription to trip stream for real-time updates StreamSubscription? _tripsSubscription; - + /// Current user ID for automatic list refreshing after operations String? _currentUserId; /// Constructor for TripBloc. - /// + /// /// Initializes the bloc with the trip repository and sets up event handlers /// for all trip-related operations. - /// + /// /// Args: /// [_repository]: Repository for trip data operations TripBloc(this._repository) : super(TripInitial()) { @@ -71,11 +74,11 @@ class TripBloc extends Bloc { } /// Handles [LoadTripsByUserId] events. - /// + /// /// Loads all trips for a specific user with real-time updates via stream subscription. /// Stores the user ID for future automatic refreshing after operations and cancels /// any existing subscription to prevent memory leaks. - /// + /// /// Args: /// [event]: The LoadTripsByUserId event containing the user ID /// [emit]: State emitter function @@ -84,41 +87,45 @@ class TripBloc extends Bloc { Emitter emit, ) async { emit(TripLoading()); - + _currentUserId = event.userId; await _tripsSubscription?.cancel(); - _tripsSubscription = _repository.getTripsByUserId(event.userId).listen( - (trips) { - add(_TripsUpdated(trips)); - }, - onError: (error) { - emit(TripError(error.toString())); - }, - ); + _tripsSubscription = _repository + .getTripsByUserId(event.userId) + .listen( + (trips) { + add(_TripsUpdated(trips)); + }, + onError: (error, stackTrace) { + _errorService.logError( + 'TripBloc', + 'Error loading trips: $error', + stackTrace, + ); + emit(const TripError('Impossible de charger les voyages')); + }, + ); } /// Handles [_TripsUpdated] events. - /// + /// /// Processes real-time updates from the trip stream and emits the /// updated trip list to the UI. - /// + /// /// Args: /// [event]: The _TripsUpdated event containing the updated trip list /// [emit]: State emitter function - void _onTripsUpdated( - _TripsUpdated event, - Emitter emit, - ) { + void _onTripsUpdated(_TripsUpdated event, Emitter emit) { emit(TripLoaded(event.trips)); } /// Handles [TripCreateRequested] events. - /// + /// /// Creates a new trip and automatically refreshes the user's trip list /// to show the newly created trip. Includes a delay to allow the creation /// to complete before refreshing. - /// + /// /// Args: /// [event]: The TripCreateRequested event containing the trip data /// [emit]: State emitter function @@ -128,27 +135,27 @@ class TripBloc extends Bloc { ) async { try { emit(TripLoading()); - + final tripId = await _repository.createTrip(event.trip); - + emit(TripCreated(tripId: tripId)); - + await Future.delayed(const Duration(milliseconds: 800)); if (_currentUserId != null) { add(LoadTripsByUserId(userId: _currentUserId!)); } - - } catch (e) { - emit(TripError('Error during creation: $e')); + } catch (e, stackTrace) { + _errorService.logError('TripBloc', 'Error creating trip: $e', stackTrace); + emit(const TripError('Impossible de créer le voyage')); } } /// Handles [TripUpdateRequested] events. - /// + /// /// Updates an existing trip and automatically refreshes the user's trip list /// to show the updated information. Includes a delay to allow the update /// to complete before refreshing. - /// + /// /// Args: /// [event]: The TripUpdateRequested event containing the updated trip data /// [emit]: State emitter function @@ -163,18 +170,18 @@ class TripBloc extends Bloc { if (_currentUserId != null) { add(LoadTripsByUserId(userId: _currentUserId!)); } - - } catch (e) { - emit(TripError('Error during update: $e')); + } catch (e, stackTrace) { + _errorService.logError('TripBloc', 'Error updating trip: $e', stackTrace); + emit(const TripError('Impossible de mettre à jour le voyage')); } } /// Handles [TripDeleteRequested] events. - /// + /// /// Deletes a trip and automatically refreshes the user's trip list /// to remove the deleted trip from the UI. Includes a delay to allow /// the deletion to complete before refreshing. - /// + /// /// Args: /// [event]: The TripDeleteRequested event containing the trip ID to delete /// [emit]: State emitter function @@ -184,39 +191,36 @@ class TripBloc extends Bloc { ) async { try { await _repository.deleteTrip(event.tripId); - + emit(const TripOperationSuccess('Trip deleted successfully')); - + await Future.delayed(const Duration(milliseconds: 500)); if (_currentUserId != null) { add(LoadTripsByUserId(userId: _currentUserId!)); } - - } catch (e) { - emit(TripError('Error during deletion: $e')); + } catch (e, stackTrace) { + _errorService.logError('TripBloc', 'Error deleting trip: $e', stackTrace); + emit(const TripError('Impossible de supprimer le voyage')); } } /// Handles [ResetTrips] events. - /// + /// /// Resets the trip state to initial and cleans up resources. /// Cancels the trip stream subscription and clears the current user ID. /// This is useful for user logout or when switching contexts. - /// + /// /// Args: /// [event]: The ResetTrips event /// [emit]: State emitter function - Future _onResetTrips( - ResetTrips event, - Emitter emit, - ) async { + Future _onResetTrips(ResetTrips event, Emitter emit) async { await _tripsSubscription?.cancel(); _currentUserId = null; emit(TripInitial()); } /// Cleans up resources when the bloc is closed. - /// + /// /// Cancels the trip stream subscription to prevent memory leaks /// and ensure proper disposal of resources. @override @@ -227,7 +231,7 @@ class TripBloc extends Bloc { } /// Private event for handling real-time trip updates from streams. -/// +/// /// This internal event is used to process updates from the trip stream /// subscription and emit appropriate states based on the received data. class _TripsUpdated extends TripEvent { @@ -235,11 +239,11 @@ class _TripsUpdated extends TripEvent { final List trips; /// Creates a _TripsUpdated event. - /// + /// /// Args: /// [trips]: List of trips from the stream update const _TripsUpdated(this.trips); @override List get props => [trips]; -} \ No newline at end of file +} diff --git a/lib/blocs/user/user_bloc.dart b/lib/blocs/user/user_bloc.dart index 979c357..365b824 100644 --- a/lib/blocs/user/user_bloc.dart +++ b/lib/blocs/user/user_bloc.dart @@ -2,6 +2,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:travel_mate/services/notification_service.dart'; +import 'package:travel_mate/services/logger_service.dart'; +import 'package:travel_mate/services/error_service.dart'; import 'user_event.dart' as event; import 'user_state.dart' as state; @@ -17,6 +19,8 @@ class UserBloc extends Bloc { /// Firestore instance for user data operations. final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final _errorService = ErrorService(); + /// Creates a new [UserBloc] with initial state. /// /// Registers event handlers for all user-related events. @@ -50,7 +54,7 @@ class UserBloc extends Bloc { final notificationService = NotificationService(); await notificationService.initialize(); final fcmToken = await notificationService.getFCMToken(); - print('DEBUG: UserBloc - FCM Token retrieved: $fcmToken'); + LoggerService.info('UserBloc - FCM Token retrieved: $fcmToken'); // Fetch user data from Firestore final userDoc = await _firestore @@ -81,21 +85,22 @@ class UserBloc extends Bloc { // Update FCM token if it changed if (fcmToken != null && user.fcmToken != fcmToken) { - print('DEBUG: UserBloc - Updating FCM token in Firestore'); + LoggerService.info('UserBloc - Updating FCM token in Firestore'); await _firestore.collection('users').doc(currentUser.uid).update({ 'fcmToken': fcmToken, }); - print('DEBUG: UserBloc - FCM token updated'); + LoggerService.info('UserBloc - FCM token updated'); } else { - print( - 'DEBUG: UserBloc - FCM token not updated. Local: $fcmToken, Firestore: ${user.fcmToken}', + LoggerService.info( + 'UserBloc - FCM token not updated. Local: $fcmToken, Firestore: ${user.fcmToken}', ); } emit(state.UserLoaded(user)); } - } catch (e) { - emit(state.UserError('Error loading user: $e')); + } catch (e, stackTrace) { + _errorService.logError('UserBloc', 'Error loading user: $e', stackTrace); + emit(state.UserError('Impossible de charger l\'utilisateur')); } } @@ -124,8 +129,9 @@ class UserBloc extends Bloc { } else { emit(state.UserError('User not found')); } - } catch (e) { - emit(state.UserError('Error loading user: $e')); + } catch (e, stackTrace) { + _errorService.logError('UserBloc', 'Error loading user: $e', stackTrace); + emit(state.UserError('Impossible de charger l\'utilisateur')); } } @@ -158,8 +164,13 @@ class UserBloc extends Bloc { }); emit(state.UserLoaded(updatedUser)); - } catch (e) { - emit(state.UserError('Error updating user: $e')); + } catch (e, stackTrace) { + _errorService.logError( + 'UserBloc', + 'Error updating user: $e', + stackTrace, + ); + emit(state.UserError('Impossible de mettre à jour l\'utilisateur')); } } } diff --git a/lib/components/account/account_content.dart b/lib/components/account/account_content.dart index 2cba932..97b4f2f 100644 --- a/lib/components/account/account_content.dart +++ b/lib/components/account/account_content.dart @@ -19,7 +19,9 @@ /// The component automatically loads account data when initialized and /// provides a clean interface for managing group-based expenses. library; + import 'package:flutter/material.dart'; +import '../../services/error_service.dart'; import 'package:travel_mate/blocs/user/user_bloc.dart'; import '../../models/account.dart'; import '../../blocs/account/account_bloc.dart'; @@ -45,10 +47,10 @@ class _AccountContentState extends State { /// Repository for group data operations used for navigation final _groupRepository = GroupRepository(); // Ajouter cette ligne - @override + @override void initState() { super.initState(); - + // Load immediately without waiting for the next frame WidgetsBinding.instance.addPostFrameCallback((_) { _loadInitialData(); @@ -56,13 +58,13 @@ class _AccountContentState extends State { } /// Loads the initial account data for the current user. - /// + /// /// Retrieves the current user from UserBloc and loads their accounts /// using the AccountBloc. Handles errors gracefully with error display. void _loadInitialData() { try { final userState = context.read().state; - + if (userState is user_state.UserLoaded) { final userId = userState.user.id; context.read().add(LoadAccountsByUserId(userId)); @@ -70,65 +72,50 @@ class _AccountContentState extends State { throw Exception('User not connected'); } } catch (e) { - ErrorContent( - message: 'Error loading accounts: $e', - onRetry: () {}, - ); + ErrorContent(message: 'Error loading accounts: $e', onRetry: () {}); } } /// Navigates to the group expenses page for a specific account. - /// + /// /// Retrieves the group associated with the account and navigates to /// the group expenses management page. Shows error messages if the /// group cannot be found or if navigation fails. - /// + /// /// Args: /// [account]: The account to navigate to for expense management Future _navigateToGroupExpenses(Account account) async { try { // Retrieve the group associated with the account final group = await _groupRepository.getGroupByTripId(account.tripId); - + if (group != null && mounted) { Navigator.push( context, MaterialPageRoute( - builder: (context) => GroupExpensesPage( - account: account, - group: group, - ), + builder: (context) => + GroupExpensesPage(account: account, group: group), ), ); } else { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Group not found for this account'), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: 'Group not found for this account'); } } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error loading group: $e'), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: 'Error loading group: $e'); } } } /// Builds the main widget for the account content page. - /// + /// /// Creates a responsive UI that handles different user and account states: /// - Shows loading indicator for user authentication /// - Displays error content for user errors /// - Builds account content based on account loading states - /// + /// /// Returns: /// Widget representing the complete account page UI @override @@ -139,9 +126,12 @@ class _AccountContentState extends State { listener: (context, accountState) { if (accountState is AccountError) { ErrorContent( - message: 'Erreur de chargement des comptes : ${accountState.message}', + message: + 'Erreur de chargement des comptes : ${accountState.message}', onRetry: () { - context.read().add(LoadAccountsByUserId(user.id)); + context.read().add( + LoadAccountsByUserId(user.id), + ); }, ); } @@ -156,10 +146,7 @@ class _AccountContentState extends State { loadingWidget: const Scaffold( body: Center(child: CircularProgressIndicator()), ), - errorWidget: ErrorContent( - message: 'User error', - onRetry: () {}, - ), + errorWidget: ErrorContent(message: 'User error', onRetry: () {}), noUserWidget: const Scaffold( body: Center(child: Text('Utilisateur non connecté')), ), @@ -167,16 +154,16 @@ class _AccountContentState extends State { } /// Builds the main content based on the current account state. - /// + /// /// Handles different account loading states and renders appropriate UI: /// - Loading: Shows circular progress indicator with loading message /// - Error: Displays error content with retry functionality /// - Loaded: Renders the accounts list or empty state message - /// + /// /// Args: /// [accountState]: Current state of account loading /// [userId]: ID of the current user for reload operations - /// + /// /// Returns: /// Widget representing the account content UI Widget _buildContent(AccountState accountState, String userId) { @@ -188,11 +175,11 @@ class _AccountContentState extends State { CircularProgressIndicator(), SizedBox(height: 16), Text('Chargement des comptes...'), - ], - ), + ], + ), ); - } - + } + if (accountState is AccountError) { return ErrorContent( message: 'Erreur de chargement des comptes...', @@ -223,15 +210,15 @@ class _AccountContentState extends State { ), ], ), - ); + ); } /// Builds the empty state widget when no accounts are found. - /// + /// /// Displays a user-friendly message explaining that accounts are /// automatically created when trips are created. Shows an icon /// and informative text to guide the user. - /// + /// /// Returns: /// Widget representing the empty accounts state Widget _buildEmptyState() { @@ -241,7 +228,11 @@ class _AccountContentState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.account_balance_wallet, size: 80, color: Colors.grey), + const Icon( + Icons.account_balance_wallet, + size: 80, + color: Colors.grey, + ), const SizedBox(height: 16), const Text( 'No accounts found', @@ -260,15 +251,15 @@ class _AccountContentState extends State { } /// Builds a scrollable list of user accounts with pull-to-refresh functionality. - /// + /// /// Creates a RefreshIndicator-wrapped ListView that displays all user accounts /// in card format. Includes a header with title and description, and renders /// each account using the _buildSimpleAccountCard method. - /// + /// /// Args: /// [accounts]: List of accounts to display /// [userId]: Current user ID for refresh operations - /// + /// /// Returns: /// Widget containing the accounts list with pull-to-refresh capability Widget _buildAccountsList(List accounts, String userId) { @@ -280,28 +271,28 @@ class _AccountContentState extends State { child: ListView( padding: const EdgeInsets.all(16), children: [ - ...accounts.map((account) { + ...accounts.map((account) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: _buildSimpleAccountCard(account), ); - }) + }), ], - ) + ), ); } - + /// Builds an individual account card with account information. - /// + /// /// Creates a Material Design card displaying account details including: /// - Account name with color-coded avatar /// - Member count and member names (up to 2 displayed) /// - Navigation capability to group expenses /// - Error handling for card rendering issues - /// + /// /// Args: /// [account]: Account object containing account details - /// + /// /// Returns: /// Widget representing a single account card Widget _buildSimpleAccountCard(Account account) { @@ -309,9 +300,10 @@ class _AccountContentState extends State { final colors = [Colors.blue, Colors.purple, Colors.green, Colors.orange]; final color = colors[account.name.hashCode.abs() % colors.length]; - String memberInfo = '${account.members.length} member${account.members.length > 1 ? 's' : ''}'; + String memberInfo = + '${account.members.length} member${account.members.length > 1 ? 's' : ''}'; - if(account.members.isNotEmpty){ + if (account.members.isNotEmpty) { final names = account.members .take(2) .map((m) => m.pseudo.isNotEmpty ? m.pseudo : m.firstName) @@ -324,15 +316,19 @@ class _AccountContentState extends State { child: ListTile( leading: CircleAvatar( backgroundColor: color, - child: const Icon(Icons.account_balance_wallet, color: Colors.white), + child: const Icon( + Icons.account_balance_wallet, + color: Colors.white, + ), ), title: Text( account.name, style: const TextStyle(fontWeight: FontWeight.bold), - ), + ), subtitle: Text(memberInfo), trailing: const Icon(Icons.chevron_right), - onTap: () => _navigateToGroupExpenses(account), // Navigate to group expenses + onTap: () => + _navigateToGroupExpenses(account), // Navigate to group expenses ), ); } catch (e) { @@ -341,8 +337,8 @@ class _AccountContentState extends State { child: const ListTile( leading: Icon(Icons.error, color: Colors.red), title: Text('Display error'), - ) + ), ); } } -} \ No newline at end of file +} diff --git a/lib/components/activities/activities_page.dart b/lib/components/activities/activities_page.dart index f88487f..e18467e 100644 --- a/lib/components/activities/activities_page.dart +++ b/lib/components/activities/activities_page.dart @@ -10,6 +10,7 @@ import '../../services/activity_cache_service.dart'; import '../loading/laoding_content.dart'; import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_state.dart'; +import '../../services/error_service.dart'; class ActivitiesPage extends StatefulWidget { final Trip trip; @@ -120,22 +121,15 @@ class _ActivitiesPageState extends State return BlocListener( listener: (context, state) { if (state is ActivityError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - backgroundColor: Colors.red, - action: SnackBarAction( - label: 'Réessayer', - textColor: Colors.white, - onPressed: () { - if (_tabController.index == 2) { - _searchGoogleActivities(); - } else { - _loadActivities(); - } - }, - ), - ), + ErrorService().showError( + message: state.message, + onRetry: () { + if (_tabController.index == 2) { + _searchGoogleActivities(); + } else { + _loadActivities(); + } + }, ); } @@ -152,20 +146,14 @@ class _ActivitiesPageState extends State }); // Afficher un feedback de succès - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('${state.activity.name} ajoutée au voyage !'), - duration: const Duration(seconds: 2), - backgroundColor: Colors.green, - action: SnackBarAction( - label: 'Voir', - textColor: Colors.white, - onPressed: () { - // Revenir à l'onglet des activités du voyage - _tabController.animateTo(0); - }, - ), - ), + // Afficher un feedback de succès + ErrorService().showSnackbar( + message: '${state.activity.name} ajoutée au voyage !', + isError: false, + onRetry: () { + // Revenir à l'onglet des activités du voyage + _tabController.animateTo(0); + }, ); }); } @@ -217,21 +205,13 @@ class _ActivitiesPageState extends State _tripActivities.add(state.newlyAddedActivity!); }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( + ErrorService().showSnackbar( + message: '${state.newlyAddedActivity!.name} ajoutée au voyage !', - ), - duration: const Duration(seconds: 2), - backgroundColor: Colors.green, - action: SnackBarAction( - label: 'Voir', - textColor: Colors.white, - onPressed: () { - _tabController.animateTo(0); - }, - ), - ), + isError: false, + onRetry: () { + _tabController.animateTo(0); + }, ); }); } @@ -1026,12 +1006,9 @@ class _ActivitiesPageState extends State // Si l'activité a été trouvée et que l'utilisateur a déjà voté if (currentActivity.id.isNotEmpty && currentActivity.hasUserVoted(userId)) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Vous avez déjà voté pour cette activité'), - backgroundColor: Colors.orange, - duration: Duration(seconds: 2), - ), + ErrorService().showSnackbar( + message: 'Vous avez déjà voté pour cette activité', + isError: true, ); return; } @@ -1044,13 +1021,7 @@ class _ActivitiesPageState extends State final message = vote == 1 ? 'Vote positif ajouté !' : 'Vote négatif ajouté !'; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - duration: const Duration(seconds: 1), - backgroundColor: vote == 1 ? Colors.green : Colors.orange, - ), - ); + ErrorService().showSnackbar(message: message, isError: false); } void _addGoogleActivityToTrip(Activity activity) { diff --git a/lib/components/group/group_content.dart b/lib/components/group/group_content.dart index 8d04b7a..f013250 100644 --- a/lib/components/group/group_content.dart +++ b/lib/components/group/group_content.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:travel_mate/components/error/error_content.dart'; +import '../../services/error_service.dart'; import 'package:travel_mate/components/group/chat_group_content.dart'; import 'package:travel_mate/components/widgets/user_state_widget.dart'; import '../../blocs/user/user_bloc.dart'; @@ -50,19 +50,12 @@ class _GroupContentState extends State { return BlocConsumer( listener: (context, groupState) { if (groupState is GroupOperationSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(groupState.message), - backgroundColor: Colors.green, - ), + ErrorService().showSnackbar( + message: groupState.message, + isError: false, ); } else if (groupState is GroupError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(groupState.message), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: groupState.message); } }, builder: (context, groupState) { @@ -209,8 +202,7 @@ class _GroupContentState extends State { if (mounted) { if (retry) { if (userId == '') { - showErrorDialog( - context, + ErrorService().showError( title: 'Erreur utilisateur', message: 'Utilisateur non connecté. Veuillez vous reconnecter.', icon: Icons.error, @@ -220,8 +212,7 @@ class _GroupContentState extends State { }, ); } else { - showErrorDialog( - context, + ErrorService().showError( title: 'Erreur de chargement', message: error, icon: Icons.cloud_off, @@ -232,8 +223,7 @@ class _GroupContentState extends State { ); } } else { - showErrorDialog( - context, + ErrorService().showError( title: 'Erreur', message: error, icon: Icons.error, diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 47dab09..988ac98 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -11,6 +11,7 @@ import '../blocs/user/user_bloc.dart'; import '../blocs/user/user_event.dart'; import '../blocs/auth/auth_bloc.dart'; import '../blocs/auth/auth_event.dart'; +import '../services/error_service.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -119,12 +120,7 @@ class _HomePageState extends State { ); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Erreur lors de la déconnexion: $e'), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: 'Erreur lors de la déconnexion: $e'); } } } @@ -132,38 +128,51 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(titles[_currentIndex]), - ), + appBar: AppBar(title: Text(titles[_currentIndex])), drawer: Drawer( child: ListView( padding: EdgeInsets.zero, children: [ DrawerHeader( decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.black - : Colors.white, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.white, ), child: Text( "Travel Mate", style: TextStyle( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black, fontSize: 24, ), ), ), _buildDrawerItem(icon: Icons.home, title: "Mes voyages", index: 0), - _buildDrawerItem(icon: Icons.settings, title: "Paramètres", index: 1), + _buildDrawerItem( + icon: Icons.settings, + title: "Paramètres", + index: 1, + ), _buildDrawerItem(icon: Icons.map, title: "Carte", index: 2), - _buildDrawerItem(icon: Icons.group, title: "Chat de groupe", index: 3), - _buildDrawerItem(icon: Icons.account_balance_wallet, title: "Comptes", index: 4), + _buildDrawerItem( + icon: Icons.group, + title: "Chat de groupe", + index: 3, + ), + _buildDrawerItem( + icon: Icons.account_balance_wallet, + title: "Comptes", + index: 4, + ), const Divider(), ListTile( leading: const Icon(Icons.logout, color: Colors.red), - title: const Text("Déconnexion", style: TextStyle(color: Colors.red)), + title: const Text( + "Déconnexion", + style: TextStyle(color: Colors.red), + ), onTap: _handleLogout, // Utiliser la nouvelle méthode ), ], @@ -191,7 +200,9 @@ class _HomePageState extends State { leading: Icon(icon), title: Text(title), selected: _currentIndex == index, - selectedTileColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + selectedTileColor: Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.1), onTap: () => _onNavigationTap(index), ); } @@ -201,4 +212,4 @@ class _HomePageState extends State { _pageCache.clear(); super.dispose(); } -} \ No newline at end of file +} diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 35f82ec..631b4eb 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -4,6 +4,7 @@ import '../blocs/auth/auth_bloc.dart'; import '../blocs/auth/auth_event.dart'; import '../blocs/auth/auth_state.dart'; import 'package:sign_in_button/sign_in_button.dart'; +import '../services/error_service.dart'; /// Login page widget for user authentication. /// @@ -89,12 +90,7 @@ class _LoginPageState extends State { if (state is AuthAuthenticated) { Navigator.pushReplacementNamed(context, '/home'); } else if (state is AuthError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: state.message); } }, builder: (context, state) { diff --git a/lib/pages/resetpswd.dart b/lib/pages/resetpswd.dart index cb52076..dc3f7e6 100644 --- a/lib/pages/resetpswd.dart +++ b/lib/pages/resetpswd.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../blocs/auth/auth_bloc.dart'; import '../blocs/auth/auth_event.dart'; import '../blocs/auth/auth_state.dart'; +import '../services/error_service.dart'; class ForgotPasswordPage extends StatefulWidget { const ForgotPasswordPage({super.key}); @@ -56,20 +57,13 @@ class _ForgotPasswordPageState extends State { body: BlocListener( listener: (context, state) { if (state is AuthPasswordResetSent) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Email de réinitialisation envoyé !'), - backgroundColor: Colors.green, - ), + ErrorService().showSnackbar( + message: 'Email de réinitialisation envoyé !', + isError: false, ); Navigator.pop(context); } else if (state is AuthError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - backgroundColor: Colors.red, - ), - ); + ErrorService().showError(message: state.message); } }, child: SafeArea( diff --git a/lib/pages/signup.dart b/lib/pages/signup.dart index beb2e09..7cea84b 100644 --- a/lib/pages/signup.dart +++ b/lib/pages/signup.dart @@ -117,9 +117,7 @@ class _SignUpPageState extends State { ); Navigator.pushReplacementNamed(context, '/home'); } else if (state is AuthError) { - _errorService.showError( - message: 'Erreur lors de la création du compte', - ); + _errorService.showError(message: state.message); } }, builder: (context, state) { diff --git a/lib/repositories/account_repository.dart b/lib/repositories/account_repository.dart index 0273263..8bf6055 100644 --- a/lib/repositories/account_repository.dart +++ b/lib/repositories/account_repository.dart @@ -7,7 +7,8 @@ class AccountRepository { final FirebaseFirestore _firestore = FirebaseFirestore.instance; final _errorService = ErrorService(); - CollectionReference get _accountCollection => _firestore.collection('accounts'); + CollectionReference get _accountCollection => + _firestore.collection('accounts'); CollectionReference _membersCollection(String accountId) { return _accountCollection.doc(accountId).collection('members'); @@ -32,8 +33,13 @@ class AccountRepository { return accountRef.id; }); - } catch (e) { - throw Exception('Erreur lors de la création du compte: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de la création du compte: $e', + stackTrace, + ); + throw Exception('Impossible de créer le compte'); } } @@ -41,7 +47,6 @@ class AccountRepository { return _accountCollection .snapshots() .asyncMap((snapshot) async { - List userAccounts = []; for (var accountDoc in snapshot.docs) { @@ -54,14 +59,24 @@ class AccountRepository { .get(); if (memberDoc.exists) { final accountData = accountDoc.data() as Map; - final account = Account.fromMap(accountData, accountId); // ✅ Ajout de l'ID + final account = Account.fromMap( + accountData, + accountId, + ); // ✅ Ajout de l'ID final members = await getAccountMembers(accountId); userAccounts.add(account.copyWith(members: members)); } else { - _errorService.logInfo('account_repository.dart', 'Utilisateur NON membre de $accountId'); + _errorService.logInfo( + 'account_repository.dart', + 'Utilisateur NON membre de $accountId', + ); } } catch (e, stackTrace) { - _errorService.logError(e.toString(), stackTrace); + _errorService.logError( + 'account_repository.dart', + 'Erreur processing account doc: $e', + stackTrace, + ); } } return userAccounts; @@ -70,14 +85,19 @@ class AccountRepository { if (prev.length != next.length) return false; final prevIds = prev.map((a) => a.id).toSet(); final nextIds = next.map((a) => a.id).toSet(); - - final identical = prevIds.difference(nextIds).isEmpty && - nextIds.difference(prevIds).isEmpty; - + + final identical = + prevIds.difference(nextIds).isEmpty && + nextIds.difference(prevIds).isEmpty; + return identical; }) .handleError((error, stackTrace) { - _errorService.logError(error, stackTrace); + _errorService.logError( + 'account_repository.dart', + 'Erreur stream accounts: $error', + stackTrace, + ); return []; }); } @@ -85,16 +105,16 @@ class AccountRepository { Future> getAccountMembers(String accountId) async { try { final snapshot = await _membersCollection(accountId).get(); - return snapshot.docs - .map((doc) { - return GroupMember.fromMap( - doc.data() as Map, - doc.id, - ); - }) - .toList(); - } catch (e) { - throw Exception('Erreur lors de la récupération des membres: $e'); + return snapshot.docs.map((doc) { + return GroupMember.fromMap(doc.data() as Map, doc.id); + }).toList(); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de la récupération des membres: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer les membres'); } } @@ -105,8 +125,13 @@ class AccountRepository { return Account.fromMap(doc.data() as Map, doc.id); } return null; - } catch (e) { - throw Exception('Erreur lors de la récupération du compte: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de la récupération du compte: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer le compte'); } } @@ -117,14 +142,19 @@ class AccountRepository { .where('tripId', isEqualTo: tripId) .limit(1) .get(); - + if (querySnapshot.docs.isNotEmpty) { final doc = querySnapshot.docs.first; return Account.fromMap(doc.data(), doc.id); } return null; - } catch (e) { - throw Exception('Erreur lors de la récupération du compte: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de la récupération du compte: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer le compte'); } } @@ -132,10 +162,17 @@ class AccountRepository { try { // Mettre à jour la date de modification final updatedAccount = account.copyWith(updatedAt: DateTime.now()); - await _firestore.collection('accounts').doc(accountId).update(updatedAccount.toMap()); - } catch (e) { - _errorService.logError('account_repository.dart', 'Erreur lors de la mise à jour du compte: $e'); - throw Exception('Erreur lors de la mise à jour du compte: $e'); + await _firestore + .collection('accounts') + .doc(accountId) + .update(updatedAccount.toMap()); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de la mise à jour du compte: $e', + stackTrace, + ); + throw Exception('Impossible de mettre à jour le compte'); } } @@ -156,24 +193,28 @@ class AccountRepository { for (var memberDoc in membersSnapshot.docs) { await _membersCollection(docId).doc(memberDoc.id).delete(); } - + // Supprimer le compte await _accountCollection.doc(docId).delete(); - } catch (e) { - _errorService.logError('account_repository.dart', 'Erreur lors de la suppression du compte: $e'); - throw Exception('Erreur lors de la suppression du compte: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de la suppression du compte: $e', + stackTrace, + ); + throw Exception('Impossible de supprimer le compte'); } } Stream> watchGroupMembers(String accountId) { return _membersCollection(accountId).snapshots().map( - (snapshot) => snapshot.docs - .map((doc) => GroupMember.fromMap( - doc.data() as Map, - doc.id, - )) - .toList(), - ); + (snapshot) => snapshot.docs + .map( + (doc) => + GroupMember.fromMap(doc.data() as Map, doc.id), + ) + .toList(), + ); } Stream watchAccount(String accountId) { @@ -201,19 +242,32 @@ class AccountRepository { Future addMemberToAccount(String accountId, GroupMember member) async { try { - await _membersCollection(accountId).doc(member.userId).set(member.toMap()); - } catch (e) { - _errorService.logError('account_repository.dart', 'Erreur lors de l\'ajout du membre: $e'); - throw Exception('Erreur lors de l\'ajout du membre: $e'); + await _membersCollection( + accountId, + ).doc(member.userId).set(member.toMap()); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de l\'ajout du membre: $e', + stackTrace, + ); + throw Exception('Impossible d\'ajouter le membre'); } } - Future removeMemberFromAccount(String accountId, String memberId) async { + Future removeMemberFromAccount( + String accountId, + String memberId, + ) async { try { await _membersCollection(accountId).doc(memberId).delete(); - } catch (e) { - _errorService.logError('account_repository.dart', 'Erreur lors de la suppression du membre: $e'); - throw Exception('Erreur lors de la suppression du membre: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'account_repository.dart', + 'Erreur lors de la suppression du membre: $e', + stackTrace, + ); + throw Exception('Impossible de supprimer le membre'); } } -} \ No newline at end of file +} diff --git a/lib/repositories/auth_repository.dart b/lib/repositories/auth_repository.dart index 6d0f0ec..0444762 100644 --- a/lib/repositories/auth_repository.dart +++ b/lib/repositories/auth_repository.dart @@ -60,10 +60,10 @@ class AuthRepository { ); await _saveFCMToken(firebaseUser.user!.uid); return await getUserFromFirestore(firebaseUser.user!.uid); - } catch (e) { - _errorService.showError(message: 'Utilisateur ou mot de passe incorrect'); + } catch (e, stackTrace) { + _errorService.logError('AuthRepository', 'SignIn error: $e', stackTrace); + throw Exception('Utilisateur ou mot de passe incorrect'); } - return null; } /// Creates a new user account with email and password. @@ -108,10 +108,10 @@ class AuthRepository { await _saveFCMToken(user.id!); } return user; - } catch (e) { - _errorService.showError(message: 'Erreur lors de la création du compte'); + } catch (e, stackTrace) { + _errorService.logError('AuthRepository', 'SignUp error: $e', stackTrace); + throw Exception('Erreur lors de la création du compte'); } - return null; } /// Signs in a user using Google authentication. @@ -160,10 +160,14 @@ class AuthRepository { return user; } return null; - } catch (e) { - _errorService.showError(message: 'Erreur lors de la connexion Google'); + } catch (e, stackTrace) { + _errorService.logError( + 'AuthRepository', + 'Google SignUp error: $e', + stackTrace, + ); + throw Exception('Erreur lors de la connexion Google'); } - return null; } Future signInWithGoogle() async { @@ -178,10 +182,14 @@ class AuthRepository { } else { throw Exception('Utilisateur non trouvé'); } - } catch (e) { - _errorService.showError(message: 'Erreur lors de la connexion Google'); + } catch (e, stackTrace) { + _errorService.logError( + 'AuthRepository', + 'Google SignIn error: $e', + stackTrace, + ); + throw Exception('Erreur lors de la connexion Google'); } - return null; } /// Signs in a user using Apple authentication. @@ -228,10 +236,14 @@ class AuthRepository { return user; } return null; - } catch (e) { - _errorService.showError(message: 'Erreur lors de la connexion Apple'); + } catch (e, stackTrace) { + _errorService.logError( + 'AuthRepository', + 'Apple SignUp error: $e', + stackTrace, + ); + throw Exception('Erreur lors de la connexion Apple'); } - return null; } Future signInWithApple() async { @@ -246,10 +258,14 @@ class AuthRepository { } else { throw Exception('Utilisateur non trouvé'); } - } catch (e) { - _errorService.showError(message: 'Erreur lors de la connexion Apple'); + } catch (e, stackTrace) { + _errorService.logError( + 'AuthRepository', + 'Apple SignIn error: $e', + stackTrace, + ); + throw Exception('Erreur lors de la connexion Apple'); } - return null; } /// Signs out the current user. @@ -298,9 +314,13 @@ class AuthRepository { 'fcmToken': token, }, SetOptions(merge: true)); } - } catch (e) { + } catch (e, stackTrace) { // Non-blocking error - print('Error saving FCM token: $e'); + _errorService.logError( + 'AuthRepository', + 'Error saving FCM token: $e', + stackTrace, + ); } } } diff --git a/lib/repositories/balance_repository.dart b/lib/repositories/balance_repository.dart index 54fc036..8f0c7c3 100644 --- a/lib/repositories/balance_repository.dart +++ b/lib/repositories/balance_repository.dart @@ -37,9 +37,13 @@ class BalanceRepository { totalExpenses: totalExpenses, calculatedAt: DateTime.now(), ); - } catch (e) { - _errorService.logError('BalanceRepository', 'Erreur calcul balance: $e'); - rethrow; + } catch (e, stackTrace) { + _errorService.logError( + 'BalanceRepository', + 'Erreur calcul balance: $e', + stackTrace, + ); + throw Exception('Impossible de calculer la balance'); } } @@ -50,9 +54,13 @@ class BalanceRepository { .first; return _calculateUserBalances(expenses); - } catch (e) { - _errorService.logError('BalanceRepository', 'Erreur calcul user balances: $e'); - rethrow; + } catch (e, stackTrace) { + _errorService.logError( + 'BalanceRepository', + 'Erreur calcul user balances: $e', + stackTrace, + ); + throw Exception('Impossible de calculer les balances utilisateurs'); } } @@ -65,19 +73,17 @@ class BalanceRepository { if (expense.isArchived) continue; // Ajouter le payeur - userBalanceMap.putIfAbsent(expense.paidById, () => { - 'name': expense.paidByName, - 'paid': 0.0, - 'owed': 0.0, - }); + userBalanceMap.putIfAbsent( + expense.paidById, + () => {'name': expense.paidByName, 'paid': 0.0, 'owed': 0.0}, + ); // Ajouter les participants for (final split in expense.splits) { - userBalanceMap.putIfAbsent(split.userId, () => { - 'name': split.userName, - 'paid': 0.0, - 'owed': 0.0, - }); + userBalanceMap.putIfAbsent( + split.userId, + () => {'name': split.userName, 'paid': 0.0, 'owed': 0.0}, + ); } } @@ -100,7 +106,7 @@ class BalanceRepository { final data = entry.value; final paid = data['paid'] as double; final owed = data['owed'] as double; - + return UserBalance( userId: userId, userName: data['name'] as String, @@ -114,7 +120,7 @@ class BalanceRepository { // Algorithme d'optimisation des règlements List _calculateOptimalSettlements(List balances) { final settlements = []; - + // Séparer les créditeurs et débiteurs final creditors = balances.where((b) => b.shouldReceive).toList(); final debtors = balances.where((b) => b.shouldPay).toList(); @@ -125,10 +131,10 @@ class BalanceRepository { // Créer des copies mutables des montants final creditorsRemaining = Map.fromEntries( - creditors.map((c) => MapEntry(c.userId, c.balance)) + creditors.map((c) => MapEntry(c.userId, c.balance)), ); final debtorsRemaining = Map.fromEntries( - debtors.map((d) => MapEntry(d.userId, -d.balance)) + debtors.map((d) => MapEntry(d.userId, -d.balance)), ); // Algorithme glouton pour minimiser le nombre de transactions @@ -139,15 +145,20 @@ class BalanceRepository { if (creditAmount <= 0.01 || debtAmount <= 0.01) continue; - final settlementAmount = [creditAmount, debtAmount].reduce((a, b) => a < b ? a : b); + final settlementAmount = [ + creditAmount, + debtAmount, + ].reduce((a, b) => a < b ? a : b); - settlements.add(Settlement( - fromUserId: debtor.userId, - fromUserName: debtor.userName, - toUserId: creditor.userId, - toUserName: creditor.userName, - amount: settlementAmount, - )); + settlements.add( + Settlement( + fromUserId: debtor.userId, + fromUserName: debtor.userName, + toUserId: creditor.userId, + toUserName: creditor.userName, + amount: settlementAmount, + ), + ); creditorsRemaining[creditor.userId] = creditAmount - settlementAmount; debtorsRemaining[debtor.userId] = debtAmount - settlementAmount; @@ -156,4 +167,4 @@ class BalanceRepository { return settlements; } -} \ No newline at end of file +} diff --git a/lib/repositories/group_repository.dart b/lib/repositories/group_repository.dart index bfbba4e..7a3eb2c 100644 --- a/lib/repositories/group_repository.dart +++ b/lib/repositories/group_repository.dart @@ -35,8 +35,13 @@ class GroupRepository { return groupRef.id; }); - } catch (e) { - throw Exception('Erreur lors de la création du groupe: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur création groupe: $e', + stackTrace, + ); + throw Exception('Impossible de créer le groupe'); } } @@ -51,7 +56,11 @@ class GroupRepository { }).toList(); }) .handleError((error, stackTrace) { - _errorService.logError(error, stackTrace); + _errorService.logError( + 'GroupRepository', + 'Erreur stream groups: $error', + stackTrace, + ); return []; }); } @@ -66,8 +75,13 @@ class GroupRepository { final members = await getGroupMembers(groupId); return group.copyWith(members: members); - } catch (e) { - throw Exception('Erreur lors de la récupération du groupe: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur get group: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer le groupe'); } } @@ -104,8 +118,13 @@ class GroupRepository { final members = await getGroupMembers(doc.id); return group.copyWith(members: members); - } catch (e) { - throw Exception('Erreur lors de la récupération du groupe: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur get group by trip: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer le groupe du voyage'); } } @@ -122,10 +141,11 @@ class GroupRepository { 'Migration réussie pour le groupe $groupId', ); } - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'GroupRepository', 'Erreur de migration pour le groupe $groupId: $e', + stackTrace, ); } } @@ -136,8 +156,13 @@ class GroupRepository { return snapshot.docs.map((doc) { return GroupMember.fromMap(doc.data() as Map, doc.id); }).toList(); - } catch (e) { - throw Exception('Erreur lors de la récupération des membres: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur get members: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer les membres'); } } @@ -160,8 +185,13 @@ class GroupRepository { await _firestore.collection('trips').doc(group.tripId).update({ 'participants': FieldValue.arrayUnion([member.userId]), }); - } catch (e) { - throw Exception('Erreur lors de l\'ajout du membre: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur add member: $e', + stackTrace, + ); + throw Exception('Impossible d\'ajouter le membre'); } } @@ -184,8 +214,13 @@ class GroupRepository { await _firestore.collection('trips').doc(group.tripId).update({ 'participants': FieldValue.arrayRemove([userId]), }); - } catch (e) { - throw Exception('Erreur lors de la suppression du membre: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur remove member: $e', + stackTrace, + ); + throw Exception('Impossible de supprimer le membre'); } } @@ -197,8 +232,13 @@ class GroupRepository { group.toMap() ..['updatedAt'] = DateTime.now().millisecondsSinceEpoch, ); - } catch (e) { - throw Exception('Erreur lors de la mise à jour du groupe: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur update group: $e', + stackTrace, + ); + throw Exception('Impossible de mettre à jour le groupe'); } } @@ -226,8 +266,13 @@ class GroupRepository { } await _groupsCollection.doc(groupId).delete(); - } catch (e) { - throw Exception('Erreur lors de la suppression du groupe: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'GroupRepository', + 'Erreur delete group: $e', + stackTrace, + ); + throw Exception('Impossible de supprimer le groupe'); } } diff --git a/lib/repositories/trip_repository.dart b/lib/repositories/trip_repository.dart index bf46ad6..ffa1109 100644 --- a/lib/repositories/trip_repository.dart +++ b/lib/repositories/trip_repository.dart @@ -1,33 +1,45 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import '../models/trip.dart'; +import '../services/error_service.dart'; class TripRepository { final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final _errorService = ErrorService(); CollectionReference get _tripsCollection => _firestore.collection('trips'); // Récupérer tous les voyages d'un utilisateur - Stream> getTripsByUserId(String userId) { + Stream> getTripsByUserId(String userId) { try { return _tripsCollection .where('participants', arrayContains: userId) .snapshots() - .map((snapshot) { - final trips = snapshot.docs - .map((doc) { - try { - final data = doc.data() as Map; - return Trip.fromMap(data, doc.id); - } catch (e) { - return null; - } - }) - .whereType() - .toList(); - return trips; - }); - } catch (e) { - throw Exception('Erreur lors de la récupération des voyages: $e'); + .map((snapshot) { + final trips = snapshot.docs + .map((doc) { + try { + final data = doc.data() as Map; + return Trip.fromMap(data, doc.id); + } catch (e, stackTrace) { + _errorService.logError( + 'TripRepository', + 'Erreur parsing trip ${doc.id}: $e', + stackTrace, + ); + return null; + } + }) + .whereType() + .toList(); + return trips; + }); + } catch (e, stackTrace) { + _errorService.logError( + 'TripRepository', + 'Erreur stream trips: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer les voyages'); } } @@ -38,8 +50,13 @@ class TripRepository { // Ne pas modifier les timestamps ici, ils sont déjà au bon format final docRef = await _tripsCollection.add(tripData); return docRef.id; - } catch (e) { - throw Exception('Erreur lors de la création du voyage: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'TripRepository', + 'Erreur création trip: $e', + stackTrace, + ); + throw Exception('Impossible de créer le voyage'); } } @@ -47,14 +64,19 @@ class TripRepository { Future getTripById(String tripId) async { try { final doc = await _tripsCollection.doc(tripId).get(); - + if (!doc.exists) { return null; } - + return Trip.fromMap(doc.data() as Map, doc.id); - } catch (e) { - throw Exception('Erreur lors de la récupération du voyage: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'TripRepository', + 'Erreur get trip: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer le voyage'); } } @@ -64,10 +86,15 @@ class TripRepository { final tripData = trip.toMap(); // Mettre à jour le timestamp de modification tripData['updatedAt'] = Timestamp.now(); - + await _tripsCollection.doc(tripId).update(tripData); - } catch (e) { - throw Exception('Erreur lors de la mise à jour du voyage: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'TripRepository', + 'Erreur update trip: $e', + stackTrace, + ); + throw Exception('Impossible de mettre à jour le voyage'); } } @@ -75,8 +102,13 @@ class TripRepository { Future deleteTrip(String tripId) async { try { await _tripsCollection.doc(tripId).delete(); - } catch (e) { - throw Exception('Erreur lors de la suppression du voyage: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'TripRepository', + 'Erreur delete trip: $e', + stackTrace, + ); + throw Exception('Impossible de supprimer le voyage'); } } -} \ No newline at end of file +} diff --git a/lib/repositories/user_repository.dart b/lib/repositories/user_repository.dart index bee45b4..de73c68 100644 --- a/lib/repositories/user_repository.dart +++ b/lib/repositories/user_repository.dart @@ -1,34 +1,35 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import '../models/user.dart'; import '../services/auth_service.dart'; +import '../services/error_service.dart'; /// Repository for user data operations in Firestore. -/// +/// /// This repository provides methods for CRUD operations on user data, /// including retrieving users by ID or email, updating user information, /// and managing user profiles in the Firestore database. class UserRepository { /// Firestore instance for database operations. final FirebaseFirestore _firestore; - + /// Authentication service for user-related operations. final AuthService _authService; + final _errorService = ErrorService(); + /// Creates a new [UserRepository] with optional dependencies. - /// + /// /// If [firestore] or [authService] are not provided, default instances will be used. - UserRepository({ - FirebaseFirestore? firestore, - AuthService? authService, - }) : _firestore = firestore ?? FirebaseFirestore.instance, - _authService = authService ?? AuthService(); + UserRepository({FirebaseFirestore? firestore, AuthService? authService}) + : _firestore = firestore ?? FirebaseFirestore.instance, + _authService = authService ?? AuthService(); /// Retrieves a user by their unique ID. - /// + /// /// Fetches the user document from Firestore and converts it to a [User] model. - /// + /// /// [uid] - The unique user identifier - /// + /// /// Returns the [User] model if found, null otherwise. /// Throws an exception if the operation fails. Future getUserById(String uid) async { @@ -39,18 +40,23 @@ class UserRepository { return User.fromMap({...data, 'id': uid}); } return null; - } catch (e) { - throw Exception('Error retrieving user: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'UserRepository', + 'Error retrieving user: $e', + stackTrace, + ); + throw Exception('Impossible de récupérer l\'utilisateur'); } } /// Retrieves a user by their email address. - /// + /// /// Searches the users collection for a matching email address. /// Email comparison is case-insensitive after trimming whitespace. - /// + /// /// [email] - The email address to search for - /// + /// /// Returns the first [User] found with the matching email, null if not found. /// Throws an exception if the operation fails. Future getUserByEmail(String email) async { @@ -67,28 +73,38 @@ class UserRepository { return User.fromMap({...data, 'id': doc.id}); } return null; - } catch (e) { - throw Exception('Error searching for user: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'UserRepository', + 'Error searching for user: $e', + stackTrace, + ); + throw Exception('Impossible de trouver l\'utilisateur'); } } /// Updates an existing user in Firestore. - /// + /// /// Updates the user document with the provided user data. - /// + /// /// [user] - The user object containing updated information - /// + /// /// Returns true if the update was successful, false otherwise. Future updateUser(User user) async { try { await _firestore.collection('users').doc(user.id).update(user.toMap()); - + // Mettre à jour le displayName dans Firebase Auth await _authService.updateDisplayName(displayName: user.fullName); - + return true; - } catch (e) { - throw Exception('Erreur lors de la mise à jour: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'UserRepository', + 'Erreur lors de la mise à jour: $e', + stackTrace, + ); + throw Exception('Impossible de mettre à jour l\'utilisateur'); } } @@ -98,8 +114,13 @@ class UserRepository { await _firestore.collection('users').doc(uid).delete(); // Note: Vous devrez également supprimer le compte Firebase Auth return true; - } catch (e) { - throw Exception('Erreur lors de la suppression: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'UserRepository', + 'Erreur lors de la suppression: $e', + stackTrace, + ); + throw Exception('Impossible de supprimer l\'utilisateur'); } } @@ -113,15 +134,20 @@ class UserRepository { if (currentUser?.email == null) { throw Exception('Utilisateur non connecté ou email non disponible'); } - + await _authService.resetPasswordFromCurrentPassword( email: currentUser!.email!, currentPassword: currentPassword, newPassword: newPassword, ); return true; - } catch (e) { - throw Exception('Erreur lors du changement de mot de passe: $e'); + } catch (e, stackTrace) { + _errorService.logError( + 'UserRepository', + 'Erreur lors du changement de mot de passe: $e', + stackTrace, + ); + throw Exception('Impossible de changer le mot de passe'); } } -} \ No newline at end of file +} diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index a52c86f..0ca6869 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -171,18 +171,35 @@ class AuthService { } } on GoogleSignInException catch (e) { _errorService.logError('Google Sign-In error: $e', StackTrace.current); + _errorService.showError( + message: 'La connexion avec Google a échoué. Veuillez réessayer.', + ); rethrow; } on FirebaseAuthException catch (e) { _errorService.logError( 'Firebase error during Google Sign-In initialization: $e', StackTrace.current, ); + if (e.code == 'account-exists-with-different-credential') { + _errorService.showError( + message: + 'Un compte existe déjà avec cette adresse email. Veuillez vous connecter avec la méthode utilisée précédemment.', + ); + } else { + _errorService.showError( + message: + 'Une erreur est survenue lors de la connexion avec Google. Veuillez réessayer plus tard.', + ); + } rethrow; } catch (e) { _errorService.logError( 'Unknown error during Google Sign-In initialization: $e', StackTrace.current, ); + _errorService.showError( + message: 'Une erreur inattendue est survenue. Veuillez réessayer.', + ); rethrow; } } @@ -216,9 +233,9 @@ class AuthService { ], // Configuration for Android/Web webAuthenticationOptions: WebAuthenticationOptions( - clientId: 'be.devdayronvl.TravelMate', + clientId: 'be.devdayronvl.TravelMate.service', redirectUri: Uri.parse( - 'https://your-project-id.firebaseapp.com/__/auth/handler', + 'https://travelmate-a47f5.firebaseapp.com/__/auth/handler', ), ), ); @@ -247,24 +264,49 @@ class AuthService { return userCredential; } on SignInWithAppleException catch (e) { _errorService.logError('Apple Sign-In error: $e', StackTrace.current); + _errorService.showError( + message: 'La connexion avec Apple a échoué. Veuillez réessayer.', + ); throw FirebaseAuthException( code: 'ERROR_APPLE_SIGNIN_FAILED', - message: 'Apple Sign-In failed: ${e.toString()}', + message: 'Apple Sign-In failed', ); } on FirebaseAuthException catch (e) { _errorService.logError( 'Firebase error during Apple Sign-In: $e', StackTrace.current, ); + if (e.code == 'account-exists-with-different-credential') { + _errorService.showError( + message: + 'Un compte existe déjà avec cette adresse email. Veuillez vous connecter avec la méthode utilisée précédemment.', + ); + } else if (e.code == 'invalid-credential') { + _errorService.showError( + message: 'Les informations de connexion sont invalides.', + ); + } else if (e.code == 'user-disabled') { + _errorService.showError( + message: 'Ce compte utilisateur a été désactivé.', + ); + } else { + _errorService.showError( + message: + 'Une erreur est survenue lors de la connexion avec Apple. Veuillez réessayer plus tard.', + ); + } rethrow; } catch (e) { _errorService.logError( 'Unknown error during Apple Sign-In: $e', StackTrace.current, ); + _errorService.showError( + message: 'Une erreur inattendue est survenue. Veuillez réessayer.', + ); throw FirebaseAuthException( code: 'ERROR_APPLE_SIGNIN_UNKNOWN', - message: 'Unknown error during Apple Sign-In: $e', + message: 'Unknown error during Apple Sign-In', ); } } diff --git a/lib/services/message_service.dart b/lib/services/message_service.dart index 21dd0e7..c200c26 100644 --- a/lib/services/message_service.dart +++ b/lib/services/message_service.dart @@ -9,8 +9,8 @@ class MessageService { MessageService({ required MessageRepository messageRepository, ErrorService? errorService, - }) : _messageRepository = messageRepository, - _errorService = errorService ?? ErrorService(); + }) : _messageRepository = messageRepository, + _errorService = errorService ?? ErrorService(); // Envoyer un message Future sendMessage({ @@ -30,12 +30,13 @@ class MessageService { senderId: senderId, senderName: senderName, ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'message_service.dart', 'Erreur lors de l\'envoi du message: $e', + stackTrace, ); - rethrow; + throw Exception('Impossible d\'envoyer le message'); } } @@ -43,12 +44,13 @@ class MessageService { Stream> getMessagesStream(String groupId) { try { return _messageRepository.getMessagesStream(groupId); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'message_service.dart', 'Erreur lors de la récupération des messages: $e', + stackTrace, ); - rethrow; + throw Exception('Impossible de récupérer les messages'); } } @@ -62,12 +64,13 @@ class MessageService { groupId: groupId, messageId: messageId, ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'message_service.dart', 'Erreur lors de la suppression du message: $e', + stackTrace, ); - rethrow; + throw Exception('Impossible de supprimer le message'); } } @@ -87,12 +90,13 @@ class MessageService { messageId: messageId, newText: newText.trim(), ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'message_service.dart', 'Erreur lors de la modification du message: $e', + stackTrace, ); - rethrow; + throw Exception('Impossible de modifier le message'); } } @@ -110,12 +114,13 @@ class MessageService { userId: userId, reaction: reaction, ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'message_service.dart', 'Erreur lors de l\'ajout de la réaction: $e', + stackTrace, ); - rethrow; + throw Exception('Impossible d\'ajouter la réaction'); } } @@ -131,12 +136,13 @@ class MessageService { messageId: messageId, userId: userId, ); - } catch (e) { + } catch (e, stackTrace) { _errorService.logError( 'message_service.dart', 'Erreur lors de la suppression de la réaction: $e', + stackTrace, ); - rethrow; + throw Exception('Impossible de supprimer la réaction'); } } }