feat: integrate ErrorService for consistent error display and standardize bloc error messages.

This commit is contained in:
Van Leemput Dayron
2025-12-02 13:59:40 +01:00
parent 1e70b9e09f
commit 6757cb013a
24 changed files with 927 additions and 608 deletions

View File

@@ -22,6 +22,7 @@
/// accountBloc.close(); /// accountBloc.close();
/// ``` /// ```
library; library;
import 'dart:async'; import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:travel_mate/services/error_service.dart'; import 'package:travel_mate/services/error_service.dart';
@@ -30,7 +31,7 @@ import 'account_state.dart';
import '../../repositories/account_repository.dart'; import '../../repositories/account_repository.dart';
import '../../models/account.dart'; import '../../models/account.dart';
class AccountBloc extends Bloc<AccountEvent, AccountState> { class AccountBloc extends Bloc<AccountEvent, AccountState> {
final AccountRepository _repository; final AccountRepository _repository;
StreamSubscription? _accountsSubscription; StreamSubscription? _accountsSubscription;
final _errorService = ErrorService(); final _errorService = ErrorService();
@@ -47,21 +48,27 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
Future<void> _onLoadAccountsByUserId( Future<void> _onLoadAccountsByUserId(
LoadAccountsByUserId event, LoadAccountsByUserId event,
Emitter<AccountState> emit, Emitter<AccountState> emit,
) async { ) async {
try { try {
emit(AccountLoading()); emit(AccountLoading());
await _accountsSubscription?.cancel(); await _accountsSubscription?.cancel();
_accountsSubscription = _repository.getAccountByUserId(event.userId).listen( _accountsSubscription = _repository
(accounts) { .getAccountByUserId(event.userId)
add(_AccountsUpdated(accounts)); .listen(
}, (accounts) {
onError: (error) { add(_AccountsUpdated(accounts));
add(_AccountsUpdated([], error: error.toString())); },
}, onError: (error) {
); add(_AccountsUpdated([], error: error.toString()));
},
);
} catch (e, stackTrace) { } catch (e, stackTrace) {
_errorService.logError(e.toString(), stackTrace); _errorService.logError(
emit(AccountError(e.toString())); 'AccountBloc',
'Error loading accounts: $e',
stackTrace,
);
emit(const AccountError('Impossible de charger les comptes'));
} }
} }
@@ -89,8 +96,12 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
); );
emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId')); emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId'));
} catch (e, stackTrace) { } catch (e, stackTrace) {
_errorService.logError(e.toString(), stackTrace); _errorService.logError(
emit(AccountError('Erreur lors de la création du compte: ${e.toString()}')); 'AccountBloc',
'Error creating account: $e',
stackTrace,
);
emit(const AccountError('Impossible de créer le compte'));
} }
} }
@@ -98,7 +109,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
CreateAccountWithMembers event, CreateAccountWithMembers event,
Emitter<AccountState> emit, Emitter<AccountState> emit,
) async { ) async {
try{ try {
emit(AccountLoading()); emit(AccountLoading());
final accountId = await _repository.createAccountWithMembers( final accountId = await _repository.createAccountWithMembers(
account: event.account, account: event.account,
@@ -106,8 +117,12 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
); );
emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId')); emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId'));
} catch (e, stackTrace) { } catch (e, stackTrace) {
_errorService.logError(e.toString(), stackTrace); _errorService.logError(
emit(AccountError('Erreur lors de la création du compte: ${e.toString()}')); '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<AccountEvent, AccountState> {
await _repository.addMemberToAccount(event.accountId, event.member); await _repository.addMemberToAccount(event.accountId, event.member);
emit(AccountOperationSuccess('Membre ajouté avec succès')); emit(AccountOperationSuccess('Membre ajouté avec succès'));
} catch (e, stackTrace) { } catch (e, stackTrace) {
_errorService.logError(e.toString(), stackTrace); _errorService.logError(
emit(AccountError('Erreur lors de l\'ajout du membre: ${e.toString()}')); 'AccountBloc',
'Error adding member: $e',
stackTrace,
);
emit(const AccountError('Impossible d\'ajouter le membre'));
} }
} }
@@ -131,11 +150,18 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
) async { ) async {
try { try {
emit(AccountLoading()); emit(AccountLoading());
await _repository.removeMemberFromAccount(event.accountId, event.memberId); await _repository.removeMemberFromAccount(
event.accountId,
event.memberId,
);
emit(AccountOperationSuccess('Membre supprimé avec succès')); emit(AccountOperationSuccess('Membre supprimé avec succès'));
} catch (e, stackTrace) { } catch (e, stackTrace) {
_errorService.logError(e.toString(), stackTrace); _errorService.logError(
emit(AccountError('Erreur lors de la suppression du membre: ${e.toString()}')); 'AccountBloc',
'Error removing member: $e',
stackTrace,
);
emit(const AccountError('Impossible de supprimer le membre'));
} }
} }
@@ -154,4 +180,4 @@ class _AccountsUpdated extends AccountEvent {
@override @override
List<Object?> get props => [accounts, error]; List<Object?> get props => [accounts, error];
} }

View File

@@ -56,10 +56,11 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
emit( emit(
ActivityLoaded(activities: activities, filteredActivities: activities), ActivityLoaded(activities: activities, filteredActivities: activities),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'activity_bloc', 'activity_bloc',
'Erreur chargement activités: $e', 'Erreur chargement activités: $e',
stackTrace,
); );
emit(const ActivityError('Impossible de charger les activités')); emit(const ActivityError('Impossible de charger les activités'));
} }
@@ -83,10 +84,11 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
emit( emit(
ActivityLoaded(activities: activities, filteredActivities: activities), ActivityLoaded(activities: activities, filteredActivities: activities),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'activity_bloc', 'activity_bloc',
'Erreur chargement activités: $e', 'Erreur chargement activités: $e',
stackTrace,
); );
emit(const ActivityError('Impossible de charger les activités')); emit(const ActivityError('Impossible de charger les activités'));
} }
@@ -112,8 +114,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
// Recharger les activités pour mettre à jour l'UI // Recharger les activités pour mettre à jour l'UI
add(LoadActivities(event.tripId)); add(LoadActivities(event.tripId));
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur mise à jour date: $e'); _errorService.logError(
'activity_bloc',
'Erreur mise à jour date: $e',
stackTrace,
);
emit(const ActivityError('Impossible de mettre à jour la date')); emit(const ActivityError('Impossible de mettre à jour la date'));
} }
} }
@@ -162,8 +168,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
isLoading: false, isLoading: false,
), ),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur recherche activités: $e'); _errorService.logError(
'activity_bloc',
'Erreur recherche activités: $e',
stackTrace,
);
emit(const ActivityError('Impossible de rechercher les activités')); emit(const ActivityError('Impossible de rechercher les activités'));
} }
} }
@@ -211,10 +221,11 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
isLoading: false, isLoading: false,
), ),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'activity_bloc', 'activity_bloc',
'Erreur recherche activités avec coordonnées: $e', 'Erreur recherche activités avec coordonnées: $e',
stackTrace,
); );
emit(const ActivityError('Impossible de rechercher les activités')); emit(const ActivityError('Impossible de rechercher les activités'));
} }
@@ -240,8 +251,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
emit( emit(
ActivitySearchResults(searchResults: searchResults, query: event.query), ActivitySearchResults(searchResults: searchResults, query: event.query),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur recherche textuelle: $e'); _errorService.logError(
'activity_bloc',
'Erreur recherche textuelle: $e',
stackTrace,
);
emit(const ActivityError('Impossible de rechercher les activités')); emit(const ActivityError('Impossible de rechercher les activités'));
} }
} }
@@ -292,8 +307,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
} else { } else {
emit(const ActivityError('Impossible d\'ajouter l\'activité')); emit(const ActivityError('Impossible d\'ajouter l\'activité'));
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur ajout activité: $e'); _errorService.logError(
'activity_bloc',
'Erreur ajout activité: $e',
stackTrace,
);
emit(const ActivityError('Impossible d\'ajouter l\'activité')); emit(const ActivityError('Impossible d\'ajouter l\'activité'));
} }
} }
@@ -350,8 +369,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
} else { } else {
emit(const ActivityError('Impossible d\'ajouter l\'activité')); emit(const ActivityError('Impossible d\'ajouter l\'activité'));
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur ajout activité: $e'); _errorService.logError(
'activity_bloc',
'Erreur ajout activité: $e',
stackTrace,
);
emit(const ActivityError('Impossible d\'ajouter l\'activité')); emit(const ActivityError('Impossible d\'ajouter l\'activité'));
} }
} }
@@ -418,8 +441,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
} else { } else {
emit(const ActivityError('Impossible d\'ajouter les activités')); emit(const ActivityError('Impossible d\'ajouter les activités'));
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur ajout en lot: $e'); _errorService.logError(
'activity_bloc',
'Erreur ajout en lot: $e',
stackTrace,
);
emit(const ActivityError('Impossible d\'ajouter les activités')); emit(const ActivityError('Impossible d\'ajouter les activités'));
} }
} }
@@ -479,8 +506,8 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
} else { } else {
emit(const ActivityError('Impossible d\'enregistrer le vote')); emit(const ActivityError('Impossible d\'enregistrer le vote'));
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur vote: $e'); _errorService.logError('activity_bloc', 'Erreur vote: $e', stackTrace);
emit(const ActivityError('Impossible d\'enregistrer le vote')); emit(const ActivityError('Impossible d\'enregistrer le vote'));
} }
} }
@@ -511,8 +538,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
} else { } else {
emit(const ActivityError('Impossible de supprimer l\'activité')); emit(const ActivityError('Impossible de supprimer l\'activité'));
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur suppression: $e'); _errorService.logError(
'activity_bloc',
'Erreur suppression: $e',
stackTrace,
);
emit(const ActivityError('Impossible de supprimer l\'activité')); emit(const ActivityError('Impossible de supprimer l\'activité'));
} }
} }
@@ -593,8 +624,12 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
} else { } else {
emit(const ActivityError('Impossible de mettre à jour l\'activité')); emit(const ActivityError('Impossible de mettre à jour l\'activité'));
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur mise à jour: $e'); _errorService.logError(
'activity_bloc',
'Erreur mise à jour: $e',
stackTrace,
);
emit(const ActivityError('Impossible de mettre à jour l\'activité')); emit(const ActivityError('Impossible de mettre à jour l\'activité'));
} }
} }
@@ -614,8 +649,8 @@ class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
vote: 1, vote: 1,
), ),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('activity_bloc', 'Erreur favori: $e'); _errorService.logError('activity_bloc', 'Erreur favori: $e', stackTrace);
emit(const ActivityError('Impossible de modifier les favoris')); emit(const ActivityError('Impossible de modifier les favoris'));
} }
} }

View File

@@ -77,7 +77,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(AuthUnauthenticated()); emit(AuthUnauthenticated());
} }
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
@@ -103,7 +103,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const AuthError(message: 'Invalid email or password')); emit(const AuthError(message: 'Invalid email or password'));
} }
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
@@ -132,7 +132,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const AuthError(message: 'Failed to create account')); emit(const AuthError(message: 'Failed to create account'));
} }
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
@@ -160,7 +160,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
); );
} }
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
@@ -183,7 +183,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const AuthError(message: 'Failed to create account with Google')); emit(const AuthError(message: 'Failed to create account with Google'));
} }
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
@@ -206,7 +206,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const AuthError(message: 'Failed to create account with Apple')); emit(const AuthError(message: 'Failed to create account with Apple'));
} }
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
@@ -234,7 +234,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
); );
} }
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
@@ -261,7 +261,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
await _authRepository.resetPassword(event.email); await _authRepository.resetPassword(event.email);
emit(AuthPasswordResetSent(email: event.email)); emit(AuthPasswordResetSent(email: event.email));
} catch (e) { } catch (e) {
emit(AuthError(message: e.toString())); emit(AuthError(message: e.toString().replaceAll('Exception: ', '')));
} }
} }
} }

View File

@@ -105,9 +105,13 @@ class BalanceBloc extends Bloc<BalanceEvent, BalanceState> {
emit( emit(
GroupBalancesLoaded(balances: userBalances, settlements: settlements), GroupBalancesLoaded(balances: userBalances, settlements: settlements),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('BalanceBloc', 'Error loading balance: $e'); _errorService.logError(
emit(BalanceError(e.toString())); 'BalanceBloc',
'Error loading balance: $e',
stackTrace,
);
emit(const BalanceError('Impossible de charger la balance'));
} }
} }
@@ -143,9 +147,13 @@ class BalanceBloc extends Bloc<BalanceEvent, BalanceState> {
emit( emit(
GroupBalancesLoaded(balances: userBalances, settlements: settlements), GroupBalancesLoaded(balances: userBalances, settlements: settlements),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('BalanceBloc', 'Error refreshing balance: $e'); _errorService.logError(
emit(BalanceError(e.toString())); 'BalanceBloc',
'Error refreshing balance: $e',
stackTrace,
);
emit(const BalanceError('Impossible de rafraîchir la balance'));
} }
} }
@@ -174,9 +182,15 @@ class BalanceBloc extends Bloc<BalanceEvent, BalanceState> {
// Reload balance after settlement // Reload balance after settlement
add(RefreshBalance(event.groupId)); add(RefreshBalance(event.groupId));
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('BalanceBloc', 'Error marking settlement: $e'); _errorService.logError(
emit(BalanceError(e.toString())); 'BalanceBloc',
'Error marking settlement: $e',
stackTrace,
);
emit(
const BalanceError('Impossible de marquer le règlement comme terminé'),
);
} }
} }
} }

View File

@@ -72,7 +72,7 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
); );
} catch (e) { } catch (e) {
_errorService.logError('ExpenseBloc', 'Error loading expenses: $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<ExpenseEvent, ExpenseState> {
emit(const ExpenseOperationSuccess('Expense created successfully')); emit(const ExpenseOperationSuccess('Expense created successfully'));
} catch (e) { } catch (e) {
_errorService.logError('ExpenseBloc', 'Error creating expense: $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<ExpenseEvent, ExpenseState> {
emit(const ExpenseOperationSuccess('Expense updated successfully')); emit(const ExpenseOperationSuccess('Expense updated successfully'));
} catch (e) { } catch (e) {
_errorService.logError('ExpenseBloc', 'Error updating expense: $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<ExpenseEvent, ExpenseState> {
emit(const ExpenseOperationSuccess('Expense deleted successfully')); emit(const ExpenseOperationSuccess('Expense deleted successfully'));
} catch (e) { } catch (e) {
_errorService.logError('ExpenseBloc', 'Error deleting expense: $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<ExpenseEvent, ExpenseState> {
emit(const ExpenseOperationSuccess('Payment marked as completed')); emit(const ExpenseOperationSuccess('Payment marked as completed'));
} catch (e) { } catch (e) {
_errorService.logError('ExpenseBloc', 'Error marking split as paid: $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<ExpenseEvent, ExpenseState> {
emit(const ExpenseOperationSuccess('Expense archived successfully')); emit(const ExpenseOperationSuccess('Expense archived successfully'));
} catch (e) { } catch (e) {
_errorService.logError('ExpenseBloc', 'Error archiving expense: $e'); _errorService.logError('ExpenseBloc', 'Error archiving expense: $e');
emit(ExpenseError(e.toString())); emit(const ExpenseError('Impossible d\'archiver la dépense'));
} }
} }

View File

@@ -21,10 +21,10 @@
/// Example usage: /// Example usage:
/// ```dart /// ```dart
/// final groupBloc = GroupBloc(groupRepository); /// final groupBloc = GroupBloc(groupRepository);
/// ///
/// // Load groups for a user /// // Load groups for a user
/// groupBloc.add(LoadGroupsByUserId('userId123')); /// groupBloc.add(LoadGroupsByUserId('userId123'));
/// ///
/// // Create a new group with members /// // Create a new group with members
/// groupBloc.add(CreateGroupWithMembers( /// groupBloc.add(CreateGroupWithMembers(
/// group: newGroup, /// group: newGroup,
@@ -32,6 +32,7 @@
/// )); /// ));
/// ``` /// ```
library; library;
import 'dart:async'; import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:travel_mate/services/error_service.dart'; import 'package:travel_mate/services/error_service.dart';
@@ -44,18 +45,18 @@ import '../../models/group.dart';
class GroupBloc extends Bloc<GroupEvent, GroupState> { class GroupBloc extends Bloc<GroupEvent, GroupState> {
/// Repository for group data operations /// Repository for group data operations
final GroupRepository _repository; final GroupRepository _repository;
/// Subscription to group stream for real-time updates /// Subscription to group stream for real-time updates
StreamSubscription? _groupsSubscription; StreamSubscription? _groupsSubscription;
/// Service for error handling and logging /// Service for error handling and logging
final _errorService = ErrorService(); final _errorService = ErrorService();
/// Constructor for GroupBloc. /// Constructor for GroupBloc.
/// ///
/// Initializes the bloc with the group repository and sets up event handlers /// Initializes the bloc with the group repository and sets up event handlers
/// for all group-related operations. /// for all group-related operations.
/// ///
/// Args: /// Args:
/// [_repository]: Repository for group data operations /// [_repository]: Repository for group data operations
GroupBloc(this._repository) : super(GroupInitial()) { GroupBloc(this._repository) : super(GroupInitial()) {
@@ -71,39 +72,45 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
} }
/// Handles [LoadGroupsByUserId] events. /// Handles [LoadGroupsByUserId] events.
/// ///
/// Loads all groups for a specific user with real-time updates via stream subscription. /// 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. /// Cancels any existing subscription before creating a new one to prevent memory leaks.
/// ///
/// Args: /// Args:
/// [event]: The LoadGroupsByUserId event containing the user ID /// [event]: The LoadGroupsByUserId event containing the user ID
/// [emit]: State emitter function /// [emit]: State emitter function
Future<void> _onLoadGroupsByUserId( Future<void> _onLoadGroupsByUserId(
LoadGroupsByUserId event, LoadGroupsByUserId event,
Emitter<GroupState> emit, Emitter<GroupState> emit,
) async { ) async {
try { try {
emit(GroupLoading()); emit(GroupLoading());
await _groupsSubscription?.cancel(); await _groupsSubscription?.cancel();
_groupsSubscription = _repository.getGroupsByUserId(event.userId).listen( _groupsSubscription = _repository
(groups) { .getGroupsByUserId(event.userId)
add(_GroupsUpdated(groups)); .listen(
}, (groups) {
onError: (error) { add(_GroupsUpdated(groups));
add(_GroupsUpdated([], error: error.toString())); },
}, onError: (error) {
); add(_GroupsUpdated([], error: error.toString()));
},
);
} catch (e, stackTrace) { } catch (e, stackTrace) {
_errorService.logError(e.toString(), stackTrace); _errorService.logError(
emit(GroupError(e.toString())); 'GroupBloc',
'Error loading groups: $e',
stackTrace,
);
emit(const GroupError('Impossible de charger les groupes'));
} }
} }
/// Handles [_GroupsUpdated] events. /// Handles [_GroupsUpdated] events.
/// ///
/// Processes real-time updates from the group stream, either emitting /// Processes real-time updates from the group stream, either emitting
/// the updated group list or an error state if the stream encountered an error. /// the updated group list or an error state if the stream encountered an error.
/// ///
/// Args: /// Args:
/// [event]: The _GroupsUpdated event containing groups or error information /// [event]: The _GroupsUpdated event containing groups or error information
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -120,10 +127,10 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
} }
/// Handles [LoadGroupsByTrip] events. /// Handles [LoadGroupsByTrip] events.
/// ///
/// Loads the group associated with a specific trip. Since each trip typically /// 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. /// has one primary group, this returns a single group or an empty list.
/// ///
/// Args: /// Args:
/// [event]: The LoadGroupsByTrip event containing the trip ID /// [event]: The LoadGroupsByTrip event containing the trip ID
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -139,16 +146,21 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
} else { } else {
emit(const GroupsLoaded([])); emit(const GroupsLoaded([]));
} }
} catch (e) { } catch (e, stackTrace) {
emit(GroupError(e.toString())); _errorService.logError(
'GroupBloc',
'Error loading group by trip: $e',
stackTrace,
);
emit(const GroupError('Impossible de charger le groupe du voyage'));
} }
} }
/// Handles [CreateGroup] events. /// Handles [CreateGroup] events.
/// ///
/// Creates a new group without any initial members. The group creator /// Creates a new group without any initial members. The group creator
/// can add members later using AddMemberToGroup events. /// can add members later using AddMemberToGroup events.
/// ///
/// Args: /// Args:
/// [event]: The CreateGroup event containing the group data /// [event]: The CreateGroup event containing the group data
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -164,17 +176,22 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
); );
emit(GroupCreated(groupId: groupId)); emit(GroupCreated(groupId: groupId));
emit(const GroupOperationSuccess('Group created successfully')); emit(const GroupOperationSuccess('Group created successfully'));
} catch (e) { } catch (e, stackTrace) {
emit(GroupError('Error during creation: $e')); _errorService.logError(
'GroupBloc',
'Error creating group: $e',
stackTrace,
);
emit(const GroupError('Impossible de créer le groupe'));
} }
} }
/// Handles [CreateGroupWithMembers] events. /// Handles [CreateGroupWithMembers] events.
/// ///
/// Creates a new group with an initial set of members. This is useful /// Creates a new group with an initial set of members. This is useful
/// for setting up complete groups in one operation, such as when /// for setting up complete groups in one operation, such as when
/// planning a trip with known participants. /// planning a trip with known participants.
/// ///
/// Args: /// Args:
/// [event]: The CreateGroupWithMembers event containing group data and member list /// [event]: The CreateGroupWithMembers event containing group data and member list
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -189,16 +206,21 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
members: event.members, members: event.members,
); );
emit(GroupCreated(groupId: groupId)); emit(GroupCreated(groupId: groupId));
} catch (e) { } catch (e, stackTrace) {
emit(GroupError('Error during creation: $e')); _errorService.logError(
'GroupBloc',
'Error creating group with members: $e',
stackTrace,
);
emit(const GroupError('Impossible de créer le groupe'));
} }
} }
/// Handles [AddMemberToGroup] events. /// Handles [AddMemberToGroup] events.
/// ///
/// Adds a new member to an existing group. The member will be able to /// Adds a new member to an existing group. The member will be able to
/// participate in group expenses and access group features. /// participate in group expenses and access group features.
/// ///
/// Args: /// Args:
/// [event]: The AddMemberToGroup event containing group ID and member data /// [event]: The AddMemberToGroup event containing group ID and member data
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -209,16 +231,21 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
try { try {
await _repository.addMember(event.groupId, event.member); await _repository.addMember(event.groupId, event.member);
emit(const GroupOperationSuccess('Member added')); emit(const GroupOperationSuccess('Member added'));
} catch (e) { } catch (e, stackTrace) {
emit(GroupError('Error during addition: $e')); _errorService.logError(
'GroupBloc',
'Error adding member: $e',
stackTrace,
);
emit(const GroupError('Impossible d\'ajouter le membre'));
} }
} }
/// Handles [RemoveMemberFromGroup] events. /// Handles [RemoveMemberFromGroup] events.
/// ///
/// Removes a member from a group. This will affect expense calculations /// Removes a member from a group. This will affect expense calculations
/// and the member will no longer have access to group features. /// and the member will no longer have access to group features.
/// ///
/// Args: /// Args:
/// [event]: The RemoveMemberFromGroup event containing group ID and user ID /// [event]: The RemoveMemberFromGroup event containing group ID and user ID
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -229,16 +256,21 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
try { try {
await _repository.removeMember(event.groupId, event.userId); await _repository.removeMember(event.groupId, event.userId);
emit(const GroupOperationSuccess('Member removed')); emit(const GroupOperationSuccess('Member removed'));
} catch (e) { } catch (e, stackTrace) {
emit(GroupError('Error during removal: $e')); _errorService.logError(
'GroupBloc',
'Error removing member: $e',
stackTrace,
);
emit(const GroupError('Impossible de supprimer le membre'));
} }
} }
/// Handles [UpdateGroup] events. /// Handles [UpdateGroup] events.
/// ///
/// Updates group information such as name, description, or settings. /// Updates group information such as name, description, or settings.
/// Member lists are managed through separate add/remove member events. /// Member lists are managed through separate add/remove member events.
/// ///
/// Args: /// Args:
/// [event]: The UpdateGroup event containing group ID and updated group data /// [event]: The UpdateGroup event containing group ID and updated group data
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -249,16 +281,21 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
try { try {
await _repository.updateGroup(event.groupId, event.group); await _repository.updateGroup(event.groupId, event.group);
emit(const GroupOperationSuccess('Group updated')); emit(const GroupOperationSuccess('Group updated'));
} catch (e) { } catch (e, stackTrace) {
emit(GroupError('Error during update: $e')); _errorService.logError(
'GroupBloc',
'Error updating group: $e',
stackTrace,
);
emit(const GroupError('Impossible de mettre à jour le groupe'));
} }
} }
/// Handles [DeleteGroup] events. /// Handles [DeleteGroup] events.
/// ///
/// Permanently deletes a group and all associated data. This action /// Permanently deletes a group and all associated data. This action
/// cannot be undone and will affect all group members and expenses. /// cannot be undone and will affect all group members and expenses.
/// ///
/// Args: /// Args:
/// [event]: The DeleteGroup event containing the trip ID to delete /// [event]: The DeleteGroup event containing the trip ID to delete
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -269,13 +306,18 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
try { try {
await _repository.deleteGroup(event.tripId); await _repository.deleteGroup(event.tripId);
emit(const GroupOperationSuccess('Group deleted')); emit(const GroupOperationSuccess('Group deleted'));
} catch (e) { } catch (e, stackTrace) {
emit(GroupError('Error during deletion: $e')); _errorService.logError(
'GroupBloc',
'Error deleting group: $e',
stackTrace,
);
emit(const GroupError('Impossible de supprimer le groupe'));
} }
} }
/// Cleans up resources when the bloc is closed. /// Cleans up resources when the bloc is closed.
/// ///
/// Cancels the group stream subscription to prevent memory leaks /// Cancels the group stream subscription to prevent memory leaks
/// and ensure proper disposal of resources. /// and ensure proper disposal of resources.
@override @override
@@ -286,18 +328,18 @@ class GroupBloc extends Bloc<GroupEvent, GroupState> {
} }
/// Private event for handling real-time group updates from streams. /// Private event for handling real-time group updates from streams.
/// ///
/// This internal event is used to process updates from the group stream /// This internal event is used to process updates from the group stream
/// subscription and emit appropriate states based on the received data. /// subscription and emit appropriate states based on the received data.
class _GroupsUpdated extends GroupEvent { class _GroupsUpdated extends GroupEvent {
/// List of groups received from the stream /// List of groups received from the stream
final List<Group> groups; final List<Group> groups;
/// Error message if the stream encountered an error /// Error message if the stream encountered an error
final String? error; final String? error;
/// Creates a _GroupsUpdated event. /// Creates a _GroupsUpdated event.
/// ///
/// Args: /// Args:
/// [groups]: List of groups from the stream update /// [groups]: List of groups from the stream update
/// [error]: Optional error message if stream failed /// [error]: Optional error message if stream failed
@@ -305,4 +347,4 @@ class _GroupsUpdated extends GroupEvent {
@override @override
List<Object?> get props => [groups, error]; List<Object?> get props => [groups, error];
} }

View File

@@ -19,10 +19,10 @@
/// Example usage: /// Example usage:
/// ```dart /// ```dart
/// final messageBloc = MessageBloc(); /// final messageBloc = MessageBloc();
/// ///
/// // Load messages for a group /// // Load messages for a group
/// messageBloc.add(LoadMessages('groupId123')); /// messageBloc.add(LoadMessages('groupId123'));
/// ///
/// // Send a message /// // Send a message
/// messageBloc.add(SendMessage( /// messageBloc.add(SendMessage(
/// groupId: 'groupId123', /// groupId: 'groupId123',
@@ -30,7 +30,7 @@
/// senderId: 'userId123', /// senderId: 'userId123',
/// senderName: 'John Doe', /// senderName: 'John Doe',
/// )); /// ));
/// ///
/// // React to a message /// // React to a message
/// messageBloc.add(ReactToMessage( /// messageBloc.add(ReactToMessage(
/// groupId: 'groupId123', /// groupId: 'groupId123',
@@ -40,6 +40,7 @@
/// )); /// ));
/// ``` /// ```
library; library;
import 'dart:async'; import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/message.dart'; import '../../models/message.dart';
@@ -52,23 +53,23 @@ import 'message_state.dart';
class MessageBloc extends Bloc<MessageEvent, MessageState> { class MessageBloc extends Bloc<MessageEvent, MessageState> {
/// Service for message operations and business logic /// Service for message operations and business logic
final MessageService _messageService; final MessageService _messageService;
/// Subscription to message stream for real-time updates /// Subscription to message stream for real-time updates
StreamSubscription<List<Message>>? _messagesSubscription; StreamSubscription<List<Message>>? _messagesSubscription;
/// Constructor for MessageBloc. /// Constructor for MessageBloc.
/// ///
/// Initializes the bloc with an optional message service. If no service is provided, /// Initializes the bloc with an optional message service. If no service is provided,
/// creates a default MessageService with MessageRepository. Sets up event handlers /// creates a default MessageService with MessageRepository. Sets up event handlers
/// for all message-related operations. /// for all message-related operations.
/// ///
/// Args: /// Args:
/// [messageService]: Optional service for message operations (auto-created if null) /// [messageService]: Optional service for message operations (auto-created if null)
MessageBloc({MessageService? messageService}) MessageBloc({MessageService? messageService})
: _messageService = messageService ?? MessageService( : _messageService =
messageRepository: MessageRepository(), messageService ??
), MessageService(messageRepository: MessageRepository()),
super(MessageInitial()) { super(MessageInitial()) {
on<LoadMessages>(_onLoadMessages); on<LoadMessages>(_onLoadMessages);
on<SendMessage>(_onSendMessage); on<SendMessage>(_onSendMessage);
on<DeleteMessage>(_onDeleteMessage); on<DeleteMessage>(_onDeleteMessage);
@@ -79,10 +80,10 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
} }
/// Handles [LoadMessages] events. /// Handles [LoadMessages] events.
/// ///
/// Loads messages for a specific group with real-time updates via stream subscription. /// 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. /// Cancels any existing subscription before creating a new one to prevent memory leaks.
/// ///
/// Args: /// Args:
/// [event]: The LoadMessages event containing the group ID /// [event]: The LoadMessages event containing the group ID
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -101,31 +102,28 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
add(_MessagesUpdated(messages: messages, groupId: event.groupId)); add(_MessagesUpdated(messages: messages, groupId: event.groupId));
}, },
onError: (error) { onError: (error) {
add(_MessagesError('Error loading messages: $error')); add(const _MessagesError('Impossible de charger les messages'));
}, },
); );
} }
/// Handles [_MessagesUpdated] events. /// Handles [_MessagesUpdated] events.
/// ///
/// Processes real-time updates from the message stream, emitting the /// Processes real-time updates from the message stream, emitting the
/// updated message list with the associated group ID. /// updated message list with the associated group ID.
/// ///
/// Args: /// Args:
/// [event]: The _MessagesUpdated event containing messages and group ID /// [event]: The _MessagesUpdated event containing messages and group ID
/// [emit]: State emitter function /// [emit]: State emitter function
void _onMessagesUpdated( void _onMessagesUpdated(_MessagesUpdated event, Emitter<MessageState> emit) {
_MessagesUpdated event,
Emitter<MessageState> emit,
) {
emit(MessagesLoaded(messages: event.messages, groupId: event.groupId)); emit(MessagesLoaded(messages: event.messages, groupId: event.groupId));
} }
/// Handles [SendMessage] events. /// Handles [SendMessage] events.
/// ///
/// Sends a new message to a group chat. The stream subscription will /// 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. /// automatically update the UI with the new message, so no state is emitted here.
/// ///
/// Args: /// Args:
/// [event]: The SendMessage event containing message details /// [event]: The SendMessage event containing message details
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -142,15 +140,15 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
senderName: event.senderName, senderName: event.senderName,
); );
} catch (e) { } catch (e) {
emit(MessageError('Error sending message: $e')); emit(const MessageError('Impossible d\'envoyer le message'));
} }
} }
/// Handles [DeleteMessage] events. /// Handles [DeleteMessage] events.
/// ///
/// Deletes a message from the group chat. The Firestore stream will /// 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. /// automatically update the UI, so no state is emitted here unless there's an error.
/// ///
/// Args: /// Args:
/// [event]: The DeleteMessage event containing group ID and message ID /// [event]: The DeleteMessage event containing group ID and message ID
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -166,15 +164,15 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
messageId: event.messageId, messageId: event.messageId,
); );
} catch (e) { } catch (e) {
emit(MessageError('Error deleting message: $e')); emit(const MessageError('Impossible de supprimer le message'));
} }
} }
/// Handles [UpdateMessage] events. /// Handles [UpdateMessage] events.
/// ///
/// Updates/edits an existing message in the group chat. The Firestore stream will /// 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. /// automatically update the UI with the edited message, so no state is emitted here.
/// ///
/// Args: /// Args:
/// [event]: The UpdateMessage event containing message ID and new text /// [event]: The UpdateMessage event containing message ID and new text
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -191,15 +189,15 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
newText: event.newText, newText: event.newText,
); );
} catch (e) { } catch (e) {
emit(MessageError('Error updating message: $e')); emit(const MessageError('Impossible de modifier le message'));
} }
} }
/// Handles [ReactToMessage] events. /// Handles [ReactToMessage] events.
/// ///
/// Adds an emoji reaction to a message. The Firestore stream will /// 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. /// automatically update the UI with the new reaction, so no state is emitted here.
/// ///
/// Args: /// Args:
/// [event]: The ReactToMessage event containing message ID, user ID, and reaction /// [event]: The ReactToMessage event containing message ID, user ID, and reaction
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -217,15 +215,15 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
reaction: event.reaction, reaction: event.reaction,
); );
} catch (e) { } catch (e) {
emit(MessageError('Error adding reaction: $e')); emit(const MessageError('Impossible d\'ajouter la réaction'));
} }
} }
/// Handles [RemoveReaction] events. /// Handles [RemoveReaction] events.
/// ///
/// Removes a user's reaction from a message. The Firestore stream will /// 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. /// automatically update the UI with the removed reaction, so no state is emitted here.
/// ///
/// Args: /// Args:
/// [event]: The RemoveReaction event containing message ID and user ID /// [event]: The RemoveReaction event containing message ID and user ID
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -242,12 +240,12 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
userId: event.userId, userId: event.userId,
); );
} catch (e) { } 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. /// Cleans up resources when the bloc is closed.
/// ///
/// Cancels the message stream subscription to prevent memory leaks /// Cancels the message stream subscription to prevent memory leaks
/// and ensure proper disposal of resources. /// and ensure proper disposal of resources.
@override @override
@@ -258,32 +256,29 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
} }
/// Private event for handling real-time message updates from streams. /// Private event for handling real-time message updates from streams.
/// ///
/// This internal event is used to process updates from the message stream /// This internal event is used to process updates from the message stream
/// subscription and emit appropriate states based on the received data. /// subscription and emit appropriate states based on the received data.
class _MessagesUpdated extends MessageEvent { class _MessagesUpdated extends MessageEvent {
/// List of messages received from the stream /// List of messages received from the stream
final List<Message> messages; final List<Message> messages;
/// Group ID associated with the messages /// Group ID associated with the messages
final String groupId; final String groupId;
/// Creates a _MessagesUpdated event. /// Creates a _MessagesUpdated event.
/// ///
/// Args: /// Args:
/// [messages]: List of messages from the stream update /// [messages]: List of messages from the stream update
/// [groupId]: ID of the group these messages belong to /// [groupId]: ID of the group these messages belong to
const _MessagesUpdated({ const _MessagesUpdated({required this.messages, required this.groupId});
required this.messages,
required this.groupId,
});
@override @override
List<Object?> get props => [messages, groupId]; List<Object?> get props => [messages, groupId];
} }
/// Private event for handling message stream errors. /// Private event for handling message stream errors.
/// ///
/// This internal event is used to process errors from the message stream /// This internal event is used to process errors from the message stream
/// subscription and emit appropriate error states. /// subscription and emit appropriate error states.
class _MessagesError extends MessageEvent { class _MessagesError extends MessageEvent {
@@ -291,7 +286,7 @@ class _MessagesError extends MessageEvent {
final String error; final String error;
/// Creates a _MessagesError event. /// Creates a _MessagesError event.
/// ///
/// Args: /// Args:
/// [error]: Error message from the stream failure /// [error]: Error message from the stream failure
const _MessagesError(this.error); const _MessagesError(this.error);

View File

@@ -22,43 +22,46 @@
/// Example usage: /// Example usage:
/// ```dart /// ```dart
/// final tripBloc = TripBloc(tripRepository); /// final tripBloc = TripBloc(tripRepository);
/// ///
/// // Load trips for a user /// // Load trips for a user
/// tripBloc.add(LoadTripsByUserId(userId: 'userId123')); /// tripBloc.add(LoadTripsByUserId(userId: 'userId123'));
/// ///
/// // Create a new trip /// // Create a new trip
/// tripBloc.add(TripCreateRequested(trip: newTrip)); /// tripBloc.add(TripCreateRequested(trip: newTrip));
/// ///
/// // Update a trip /// // Update a trip
/// tripBloc.add(TripUpdateRequested(trip: updatedTrip)); /// tripBloc.add(TripUpdateRequested(trip: updatedTrip));
/// ///
/// // Delete a trip /// // Delete a trip
/// tripBloc.add(TripDeleteRequested(tripId: 'tripId456')); /// tripBloc.add(TripDeleteRequested(tripId: 'tripId456'));
/// ``` /// ```
library; library;
import 'dart:async'; import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:travel_mate/models/trip.dart'; import 'package:travel_mate/models/trip.dart';
import 'trip_event.dart'; import 'trip_event.dart';
import 'trip_state.dart'; import 'trip_state.dart';
import '../../repositories/trip_repository.dart'; import '../../repositories/trip_repository.dart';
import '../../services/error_service.dart';
/// BLoC that manages trip-related operations and state. /// BLoC that manages trip-related operations and state.
class TripBloc extends Bloc<TripEvent, TripState> { class TripBloc extends Bloc<TripEvent, TripState> {
/// Repository for trip data operations /// Repository for trip data operations
final TripRepository _repository; final TripRepository _repository;
final _errorService = ErrorService();
/// Subscription to trip stream for real-time updates /// Subscription to trip stream for real-time updates
StreamSubscription? _tripsSubscription; StreamSubscription? _tripsSubscription;
/// Current user ID for automatic list refreshing after operations /// Current user ID for automatic list refreshing after operations
String? _currentUserId; String? _currentUserId;
/// Constructor for TripBloc. /// Constructor for TripBloc.
/// ///
/// Initializes the bloc with the trip repository and sets up event handlers /// Initializes the bloc with the trip repository and sets up event handlers
/// for all trip-related operations. /// for all trip-related operations.
/// ///
/// Args: /// Args:
/// [_repository]: Repository for trip data operations /// [_repository]: Repository for trip data operations
TripBloc(this._repository) : super(TripInitial()) { TripBloc(this._repository) : super(TripInitial()) {
@@ -71,11 +74,11 @@ class TripBloc extends Bloc<TripEvent, TripState> {
} }
/// Handles [LoadTripsByUserId] events. /// Handles [LoadTripsByUserId] events.
/// ///
/// Loads all trips for a specific user with real-time updates via stream subscription. /// 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 /// Stores the user ID for future automatic refreshing after operations and cancels
/// any existing subscription to prevent memory leaks. /// any existing subscription to prevent memory leaks.
/// ///
/// Args: /// Args:
/// [event]: The LoadTripsByUserId event containing the user ID /// [event]: The LoadTripsByUserId event containing the user ID
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -84,41 +87,45 @@ class TripBloc extends Bloc<TripEvent, TripState> {
Emitter<TripState> emit, Emitter<TripState> emit,
) async { ) async {
emit(TripLoading()); emit(TripLoading());
_currentUserId = event.userId; _currentUserId = event.userId;
await _tripsSubscription?.cancel(); await _tripsSubscription?.cancel();
_tripsSubscription = _repository.getTripsByUserId(event.userId).listen( _tripsSubscription = _repository
(trips) { .getTripsByUserId(event.userId)
add(_TripsUpdated(trips)); .listen(
}, (trips) {
onError: (error) { add(_TripsUpdated(trips));
emit(TripError(error.toString())); },
}, onError: (error, stackTrace) {
); _errorService.logError(
'TripBloc',
'Error loading trips: $error',
stackTrace,
);
emit(const TripError('Impossible de charger les voyages'));
},
);
} }
/// Handles [_TripsUpdated] events. /// Handles [_TripsUpdated] events.
/// ///
/// Processes real-time updates from the trip stream and emits the /// Processes real-time updates from the trip stream and emits the
/// updated trip list to the UI. /// updated trip list to the UI.
/// ///
/// Args: /// Args:
/// [event]: The _TripsUpdated event containing the updated trip list /// [event]: The _TripsUpdated event containing the updated trip list
/// [emit]: State emitter function /// [emit]: State emitter function
void _onTripsUpdated( void _onTripsUpdated(_TripsUpdated event, Emitter<TripState> emit) {
_TripsUpdated event,
Emitter<TripState> emit,
) {
emit(TripLoaded(event.trips)); emit(TripLoaded(event.trips));
} }
/// Handles [TripCreateRequested] events. /// Handles [TripCreateRequested] events.
/// ///
/// Creates a new trip and automatically refreshes the user's trip list /// 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 show the newly created trip. Includes a delay to allow the creation
/// to complete before refreshing. /// to complete before refreshing.
/// ///
/// Args: /// Args:
/// [event]: The TripCreateRequested event containing the trip data /// [event]: The TripCreateRequested event containing the trip data
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -128,27 +135,27 @@ class TripBloc extends Bloc<TripEvent, TripState> {
) async { ) async {
try { try {
emit(TripLoading()); emit(TripLoading());
final tripId = await _repository.createTrip(event.trip); final tripId = await _repository.createTrip(event.trip);
emit(TripCreated(tripId: tripId)); emit(TripCreated(tripId: tripId));
await Future.delayed(const Duration(milliseconds: 800)); await Future.delayed(const Duration(milliseconds: 800));
if (_currentUserId != null) { if (_currentUserId != null) {
add(LoadTripsByUserId(userId: _currentUserId!)); add(LoadTripsByUserId(userId: _currentUserId!));
} }
} catch (e, stackTrace) {
} catch (e) { _errorService.logError('TripBloc', 'Error creating trip: $e', stackTrace);
emit(TripError('Error during creation: $e')); emit(const TripError('Impossible de créer le voyage'));
} }
} }
/// Handles [TripUpdateRequested] events. /// Handles [TripUpdateRequested] events.
/// ///
/// Updates an existing trip and automatically refreshes the user's trip list /// 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 show the updated information. Includes a delay to allow the update
/// to complete before refreshing. /// to complete before refreshing.
/// ///
/// Args: /// Args:
/// [event]: The TripUpdateRequested event containing the updated trip data /// [event]: The TripUpdateRequested event containing the updated trip data
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -163,18 +170,18 @@ class TripBloc extends Bloc<TripEvent, TripState> {
if (_currentUserId != null) { if (_currentUserId != null) {
add(LoadTripsByUserId(userId: _currentUserId!)); add(LoadTripsByUserId(userId: _currentUserId!));
} }
} catch (e, stackTrace) {
} catch (e) { _errorService.logError('TripBloc', 'Error updating trip: $e', stackTrace);
emit(TripError('Error during update: $e')); emit(const TripError('Impossible de mettre à jour le voyage'));
} }
} }
/// Handles [TripDeleteRequested] events. /// Handles [TripDeleteRequested] events.
/// ///
/// Deletes a trip and automatically refreshes the user's trip list /// Deletes a trip and automatically refreshes the user's trip list
/// to remove the deleted trip from the UI. Includes a delay to allow /// to remove the deleted trip from the UI. Includes a delay to allow
/// the deletion to complete before refreshing. /// the deletion to complete before refreshing.
/// ///
/// Args: /// Args:
/// [event]: The TripDeleteRequested event containing the trip ID to delete /// [event]: The TripDeleteRequested event containing the trip ID to delete
/// [emit]: State emitter function /// [emit]: State emitter function
@@ -184,39 +191,36 @@ class TripBloc extends Bloc<TripEvent, TripState> {
) async { ) async {
try { try {
await _repository.deleteTrip(event.tripId); await _repository.deleteTrip(event.tripId);
emit(const TripOperationSuccess('Trip deleted successfully')); emit(const TripOperationSuccess('Trip deleted successfully'));
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
if (_currentUserId != null) { if (_currentUserId != null) {
add(LoadTripsByUserId(userId: _currentUserId!)); add(LoadTripsByUserId(userId: _currentUserId!));
} }
} catch (e, stackTrace) {
} catch (e) { _errorService.logError('TripBloc', 'Error deleting trip: $e', stackTrace);
emit(TripError('Error during deletion: $e')); emit(const TripError('Impossible de supprimer le voyage'));
} }
} }
/// Handles [ResetTrips] events. /// Handles [ResetTrips] events.
/// ///
/// Resets the trip state to initial and cleans up resources. /// Resets the trip state to initial and cleans up resources.
/// Cancels the trip stream subscription and clears the current user ID. /// Cancels the trip stream subscription and clears the current user ID.
/// This is useful for user logout or when switching contexts. /// This is useful for user logout or when switching contexts.
/// ///
/// Args: /// Args:
/// [event]: The ResetTrips event /// [event]: The ResetTrips event
/// [emit]: State emitter function /// [emit]: State emitter function
Future<void> _onResetTrips( Future<void> _onResetTrips(ResetTrips event, Emitter<TripState> emit) async {
ResetTrips event,
Emitter<TripState> emit,
) async {
await _tripsSubscription?.cancel(); await _tripsSubscription?.cancel();
_currentUserId = null; _currentUserId = null;
emit(TripInitial()); emit(TripInitial());
} }
/// Cleans up resources when the bloc is closed. /// Cleans up resources when the bloc is closed.
/// ///
/// Cancels the trip stream subscription to prevent memory leaks /// Cancels the trip stream subscription to prevent memory leaks
/// and ensure proper disposal of resources. /// and ensure proper disposal of resources.
@override @override
@@ -227,7 +231,7 @@ class TripBloc extends Bloc<TripEvent, TripState> {
} }
/// Private event for handling real-time trip updates from streams. /// Private event for handling real-time trip updates from streams.
/// ///
/// This internal event is used to process updates from the trip stream /// This internal event is used to process updates from the trip stream
/// subscription and emit appropriate states based on the received data. /// subscription and emit appropriate states based on the received data.
class _TripsUpdated extends TripEvent { class _TripsUpdated extends TripEvent {
@@ -235,11 +239,11 @@ class _TripsUpdated extends TripEvent {
final List<Trip> trips; final List<Trip> trips;
/// Creates a _TripsUpdated event. /// Creates a _TripsUpdated event.
/// ///
/// Args: /// Args:
/// [trips]: List of trips from the stream update /// [trips]: List of trips from the stream update
const _TripsUpdated(this.trips); const _TripsUpdated(this.trips);
@override @override
List<Object?> get props => [trips]; List<Object?> get props => [trips];
} }

View File

@@ -2,6 +2,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:travel_mate/services/notification_service.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_event.dart' as event;
import 'user_state.dart' as state; import 'user_state.dart' as state;
@@ -17,6 +19,8 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
/// Firestore instance for user data operations. /// Firestore instance for user data operations.
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final _errorService = ErrorService();
/// Creates a new [UserBloc] with initial state. /// Creates a new [UserBloc] with initial state.
/// ///
/// Registers event handlers for all user-related events. /// Registers event handlers for all user-related events.
@@ -50,7 +54,7 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
final notificationService = NotificationService(); final notificationService = NotificationService();
await notificationService.initialize(); await notificationService.initialize();
final fcmToken = await notificationService.getFCMToken(); final fcmToken = await notificationService.getFCMToken();
print('DEBUG: UserBloc - FCM Token retrieved: $fcmToken'); LoggerService.info('UserBloc - FCM Token retrieved: $fcmToken');
// Fetch user data from Firestore // Fetch user data from Firestore
final userDoc = await _firestore final userDoc = await _firestore
@@ -81,21 +85,22 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
// Update FCM token if it changed // Update FCM token if it changed
if (fcmToken != null && user.fcmToken != fcmToken) { 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({ await _firestore.collection('users').doc(currentUser.uid).update({
'fcmToken': fcmToken, 'fcmToken': fcmToken,
}); });
print('DEBUG: UserBloc - FCM token updated'); LoggerService.info('UserBloc - FCM token updated');
} else { } else {
print( LoggerService.info(
'DEBUG: UserBloc - FCM token not updated. Local: $fcmToken, Firestore: ${user.fcmToken}', 'UserBloc - FCM token not updated. Local: $fcmToken, Firestore: ${user.fcmToken}',
); );
} }
emit(state.UserLoaded(user)); emit(state.UserLoaded(user));
} }
} catch (e) { } catch (e, stackTrace) {
emit(state.UserError('Error loading user: $e')); _errorService.logError('UserBloc', 'Error loading user: $e', stackTrace);
emit(state.UserError('Impossible de charger l\'utilisateur'));
} }
} }
@@ -124,8 +129,9 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
} else { } else {
emit(state.UserError('User not found')); emit(state.UserError('User not found'));
} }
} catch (e) { } catch (e, stackTrace) {
emit(state.UserError('Error loading user: $e')); _errorService.logError('UserBloc', 'Error loading user: $e', stackTrace);
emit(state.UserError('Impossible de charger l\'utilisateur'));
} }
} }
@@ -158,8 +164,13 @@ class UserBloc extends Bloc<event.UserEvent, state.UserState> {
}); });
emit(state.UserLoaded(updatedUser)); emit(state.UserLoaded(updatedUser));
} catch (e) { } catch (e, stackTrace) {
emit(state.UserError('Error updating user: $e')); _errorService.logError(
'UserBloc',
'Error updating user: $e',
stackTrace,
);
emit(state.UserError('Impossible de mettre à jour l\'utilisateur'));
} }
} }
} }

View File

@@ -19,7 +19,9 @@
/// The component automatically loads account data when initialized and /// The component automatically loads account data when initialized and
/// provides a clean interface for managing group-based expenses. /// provides a clean interface for managing group-based expenses.
library; library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../services/error_service.dart';
import 'package:travel_mate/blocs/user/user_bloc.dart'; import 'package:travel_mate/blocs/user/user_bloc.dart';
import '../../models/account.dart'; import '../../models/account.dart';
import '../../blocs/account/account_bloc.dart'; import '../../blocs/account/account_bloc.dart';
@@ -45,10 +47,10 @@ class _AccountContentState extends State<AccountContent> {
/// Repository for group data operations used for navigation /// Repository for group data operations used for navigation
final _groupRepository = GroupRepository(); // Ajouter cette ligne final _groupRepository = GroupRepository(); // Ajouter cette ligne
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Load immediately without waiting for the next frame // Load immediately without waiting for the next frame
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_loadInitialData(); _loadInitialData();
@@ -56,13 +58,13 @@ class _AccountContentState extends State<AccountContent> {
} }
/// Loads the initial account data for the current user. /// Loads the initial account data for the current user.
/// ///
/// Retrieves the current user from UserBloc and loads their accounts /// Retrieves the current user from UserBloc and loads their accounts
/// using the AccountBloc. Handles errors gracefully with error display. /// using the AccountBloc. Handles errors gracefully with error display.
void _loadInitialData() { void _loadInitialData() {
try { try {
final userState = context.read<UserBloc>().state; final userState = context.read<UserBloc>().state;
if (userState is user_state.UserLoaded) { if (userState is user_state.UserLoaded) {
final userId = userState.user.id; final userId = userState.user.id;
context.read<AccountBloc>().add(LoadAccountsByUserId(userId)); context.read<AccountBloc>().add(LoadAccountsByUserId(userId));
@@ -70,65 +72,50 @@ class _AccountContentState extends State<AccountContent> {
throw Exception('User not connected'); throw Exception('User not connected');
} }
} catch (e) { } catch (e) {
ErrorContent( ErrorContent(message: 'Error loading accounts: $e', onRetry: () {});
message: 'Error loading accounts: $e',
onRetry: () {},
);
} }
} }
/// Navigates to the group expenses page for a specific account. /// Navigates to the group expenses page for a specific account.
/// ///
/// Retrieves the group associated with the account and navigates to /// Retrieves the group associated with the account and navigates to
/// the group expenses management page. Shows error messages if the /// the group expenses management page. Shows error messages if the
/// group cannot be found or if navigation fails. /// group cannot be found or if navigation fails.
/// ///
/// Args: /// Args:
/// [account]: The account to navigate to for expense management /// [account]: The account to navigate to for expense management
Future<void> _navigateToGroupExpenses(Account account) async { Future<void> _navigateToGroupExpenses(Account account) async {
try { try {
// Retrieve the group associated with the account // Retrieve the group associated with the account
final group = await _groupRepository.getGroupByTripId(account.tripId); final group = await _groupRepository.getGroupByTripId(account.tripId);
if (group != null && mounted) { if (group != null && mounted) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => GroupExpensesPage( builder: (context) =>
account: account, GroupExpensesPage(account: account, group: group),
group: group,
),
), ),
); );
} else { } else {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showError(message: 'Group not found for this account');
const SnackBar(
content: Text('Group not found for this account'),
backgroundColor: Colors.red,
),
);
} }
} }
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showError(message: 'Error loading group: $e');
SnackBar(
content: Text('Error loading group: $e'),
backgroundColor: Colors.red,
),
);
} }
} }
} }
/// Builds the main widget for the account content page. /// Builds the main widget for the account content page.
/// ///
/// Creates a responsive UI that handles different user and account states: /// Creates a responsive UI that handles different user and account states:
/// - Shows loading indicator for user authentication /// - Shows loading indicator for user authentication
/// - Displays error content for user errors /// - Displays error content for user errors
/// - Builds account content based on account loading states /// - Builds account content based on account loading states
/// ///
/// Returns: /// Returns:
/// Widget representing the complete account page UI /// Widget representing the complete account page UI
@override @override
@@ -139,9 +126,12 @@ class _AccountContentState extends State<AccountContent> {
listener: (context, accountState) { listener: (context, accountState) {
if (accountState is AccountError) { if (accountState is AccountError) {
ErrorContent( ErrorContent(
message: 'Erreur de chargement des comptes : ${accountState.message}', message:
'Erreur de chargement des comptes : ${accountState.message}',
onRetry: () { onRetry: () {
context.read<AccountBloc>().add(LoadAccountsByUserId(user.id)); context.read<AccountBloc>().add(
LoadAccountsByUserId(user.id),
);
}, },
); );
} }
@@ -156,10 +146,7 @@ class _AccountContentState extends State<AccountContent> {
loadingWidget: const Scaffold( loadingWidget: const Scaffold(
body: Center(child: CircularProgressIndicator()), body: Center(child: CircularProgressIndicator()),
), ),
errorWidget: ErrorContent( errorWidget: ErrorContent(message: 'User error', onRetry: () {}),
message: 'User error',
onRetry: () {},
),
noUserWidget: const Scaffold( noUserWidget: const Scaffold(
body: Center(child: Text('Utilisateur non connecté')), body: Center(child: Text('Utilisateur non connecté')),
), ),
@@ -167,16 +154,16 @@ class _AccountContentState extends State<AccountContent> {
} }
/// Builds the main content based on the current account state. /// Builds the main content based on the current account state.
/// ///
/// Handles different account loading states and renders appropriate UI: /// Handles different account loading states and renders appropriate UI:
/// - Loading: Shows circular progress indicator with loading message /// - Loading: Shows circular progress indicator with loading message
/// - Error: Displays error content with retry functionality /// - Error: Displays error content with retry functionality
/// - Loaded: Renders the accounts list or empty state message /// - Loaded: Renders the accounts list or empty state message
/// ///
/// Args: /// Args:
/// [accountState]: Current state of account loading /// [accountState]: Current state of account loading
/// [userId]: ID of the current user for reload operations /// [userId]: ID of the current user for reload operations
/// ///
/// Returns: /// Returns:
/// Widget representing the account content UI /// Widget representing the account content UI
Widget _buildContent(AccountState accountState, String userId) { Widget _buildContent(AccountState accountState, String userId) {
@@ -188,11 +175,11 @@ class _AccountContentState extends State<AccountContent> {
CircularProgressIndicator(), CircularProgressIndicator(),
SizedBox(height: 16), SizedBox(height: 16),
Text('Chargement des comptes...'), Text('Chargement des comptes...'),
], ],
), ),
); );
} }
if (accountState is AccountError) { if (accountState is AccountError) {
return ErrorContent( return ErrorContent(
message: 'Erreur de chargement des comptes...', message: 'Erreur de chargement des comptes...',
@@ -223,15 +210,15 @@ class _AccountContentState extends State<AccountContent> {
), ),
], ],
), ),
); );
} }
/// Builds the empty state widget when no accounts are found. /// Builds the empty state widget when no accounts are found.
/// ///
/// Displays a user-friendly message explaining that accounts are /// Displays a user-friendly message explaining that accounts are
/// automatically created when trips are created. Shows an icon /// automatically created when trips are created. Shows an icon
/// and informative text to guide the user. /// and informative text to guide the user.
/// ///
/// Returns: /// Returns:
/// Widget representing the empty accounts state /// Widget representing the empty accounts state
Widget _buildEmptyState() { Widget _buildEmptyState() {
@@ -241,7 +228,11 @@ class _AccountContentState extends State<AccountContent> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ 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 SizedBox(height: 16),
const Text( const Text(
'No accounts found', 'No accounts found',
@@ -260,15 +251,15 @@ class _AccountContentState extends State<AccountContent> {
} }
/// Builds a scrollable list of user accounts with pull-to-refresh functionality. /// Builds a scrollable list of user accounts with pull-to-refresh functionality.
/// ///
/// Creates a RefreshIndicator-wrapped ListView that displays all user accounts /// Creates a RefreshIndicator-wrapped ListView that displays all user accounts
/// in card format. Includes a header with title and description, and renders /// in card format. Includes a header with title and description, and renders
/// each account using the _buildSimpleAccountCard method. /// each account using the _buildSimpleAccountCard method.
/// ///
/// Args: /// Args:
/// [accounts]: List of accounts to display /// [accounts]: List of accounts to display
/// [userId]: Current user ID for refresh operations /// [userId]: Current user ID for refresh operations
/// ///
/// Returns: /// Returns:
/// Widget containing the accounts list with pull-to-refresh capability /// Widget containing the accounts list with pull-to-refresh capability
Widget _buildAccountsList(List<Account> accounts, String userId) { Widget _buildAccountsList(List<Account> accounts, String userId) {
@@ -280,28 +271,28 @@ class _AccountContentState extends State<AccountContent> {
child: ListView( child: ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
...accounts.map((account) { ...accounts.map((account) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: _buildSimpleAccountCard(account), child: _buildSimpleAccountCard(account),
); );
}) }),
], ],
) ),
); );
} }
/// Builds an individual account card with account information. /// Builds an individual account card with account information.
/// ///
/// Creates a Material Design card displaying account details including: /// Creates a Material Design card displaying account details including:
/// - Account name with color-coded avatar /// - Account name with color-coded avatar
/// - Member count and member names (up to 2 displayed) /// - Member count and member names (up to 2 displayed)
/// - Navigation capability to group expenses /// - Navigation capability to group expenses
/// - Error handling for card rendering issues /// - Error handling for card rendering issues
/// ///
/// Args: /// Args:
/// [account]: Account object containing account details /// [account]: Account object containing account details
/// ///
/// Returns: /// Returns:
/// Widget representing a single account card /// Widget representing a single account card
Widget _buildSimpleAccountCard(Account account) { Widget _buildSimpleAccountCard(Account account) {
@@ -309,9 +300,10 @@ class _AccountContentState extends State<AccountContent> {
final colors = [Colors.blue, Colors.purple, Colors.green, Colors.orange]; final colors = [Colors.blue, Colors.purple, Colors.green, Colors.orange];
final color = colors[account.name.hashCode.abs() % colors.length]; 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 final names = account.members
.take(2) .take(2)
.map((m) => m.pseudo.isNotEmpty ? m.pseudo : m.firstName) .map((m) => m.pseudo.isNotEmpty ? m.pseudo : m.firstName)
@@ -324,15 +316,19 @@ class _AccountContentState extends State<AccountContent> {
child: ListTile( child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: color, backgroundColor: color,
child: const Icon(Icons.account_balance_wallet, color: Colors.white), child: const Icon(
Icons.account_balance_wallet,
color: Colors.white,
),
), ),
title: Text( title: Text(
account.name, account.name,
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
subtitle: Text(memberInfo), subtitle: Text(memberInfo),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () => _navigateToGroupExpenses(account), // Navigate to group expenses onTap: () =>
_navigateToGroupExpenses(account), // Navigate to group expenses
), ),
); );
} catch (e) { } catch (e) {
@@ -341,8 +337,8 @@ class _AccountContentState extends State<AccountContent> {
child: const ListTile( child: const ListTile(
leading: Icon(Icons.error, color: Colors.red), leading: Icon(Icons.error, color: Colors.red),
title: Text('Display error'), title: Text('Display error'),
) ),
); );
} }
} }
} }

View File

@@ -10,6 +10,7 @@ import '../../services/activity_cache_service.dart';
import '../loading/laoding_content.dart'; import '../loading/laoding_content.dart';
import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_bloc.dart';
import '../../blocs/user/user_state.dart'; import '../../blocs/user/user_state.dart';
import '../../services/error_service.dart';
class ActivitiesPage extends StatefulWidget { class ActivitiesPage extends StatefulWidget {
final Trip trip; final Trip trip;
@@ -120,22 +121,15 @@ class _ActivitiesPageState extends State<ActivitiesPage>
return BlocListener<ActivityBloc, ActivityState>( return BlocListener<ActivityBloc, ActivityState>(
listener: (context, state) { listener: (context, state) {
if (state is ActivityError) { if (state is ActivityError) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showError(
SnackBar( message: state.message,
content: Text(state.message), onRetry: () {
backgroundColor: Colors.red, if (_tabController.index == 2) {
action: SnackBarAction( _searchGoogleActivities();
label: 'Réessayer', } else {
textColor: Colors.white, _loadActivities();
onPressed: () { }
if (_tabController.index == 2) { },
_searchGoogleActivities();
} else {
_loadActivities();
}
},
),
),
); );
} }
@@ -152,20 +146,14 @@ class _ActivitiesPageState extends State<ActivitiesPage>
}); });
// Afficher un feedback de succès // Afficher un feedback de succès
ScaffoldMessenger.of(context).showSnackBar( // Afficher un feedback de succès
SnackBar( ErrorService().showSnackbar(
content: Text('${state.activity.name} ajoutée au voyage !'), message: '${state.activity.name} ajoutée au voyage !',
duration: const Duration(seconds: 2), isError: false,
backgroundColor: Colors.green, onRetry: () {
action: SnackBarAction( // Revenir à l'onglet des activités du voyage
label: 'Voir', _tabController.animateTo(0);
textColor: Colors.white, },
onPressed: () {
// Revenir à l'onglet des activités du voyage
_tabController.animateTo(0);
},
),
),
); );
}); });
} }
@@ -217,21 +205,13 @@ class _ActivitiesPageState extends State<ActivitiesPage>
_tripActivities.add(state.newlyAddedActivity!); _tripActivities.add(state.newlyAddedActivity!);
}); });
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showSnackbar(
SnackBar( message:
content: Text(
'${state.newlyAddedActivity!.name} ajoutée au voyage !', '${state.newlyAddedActivity!.name} ajoutée au voyage !',
), isError: false,
duration: const Duration(seconds: 2), onRetry: () {
backgroundColor: Colors.green, _tabController.animateTo(0);
action: SnackBarAction( },
label: 'Voir',
textColor: Colors.white,
onPressed: () {
_tabController.animateTo(0);
},
),
),
); );
}); });
} }
@@ -1026,12 +1006,9 @@ class _ActivitiesPageState extends State<ActivitiesPage>
// Si l'activité a été trouvée et que l'utilisateur a déjà voté // Si l'activité a été trouvée et que l'utilisateur a déjà voté
if (currentActivity.id.isNotEmpty && currentActivity.hasUserVoted(userId)) { if (currentActivity.id.isNotEmpty && currentActivity.hasUserVoted(userId)) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showSnackbar(
const SnackBar( message: 'Vous avez déjà voté pour cette activité',
content: Text('Vous avez déjà voté pour cette activité'), isError: true,
backgroundColor: Colors.orange,
duration: Duration(seconds: 2),
),
); );
return; return;
} }
@@ -1044,13 +1021,7 @@ class _ActivitiesPageState extends State<ActivitiesPage>
final message = vote == 1 final message = vote == 1
? 'Vote positif ajouté !' ? 'Vote positif ajouté !'
: 'Vote négatif ajouté !'; : 'Vote négatif ajouté !';
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showSnackbar(message: message, isError: false);
SnackBar(
content: Text(message),
duration: const Duration(seconds: 1),
backgroundColor: vote == 1 ? Colors.green : Colors.orange,
),
);
} }
void _addGoogleActivityToTrip(Activity activity) { void _addGoogleActivityToTrip(Activity activity) {

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '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/group/chat_group_content.dart';
import 'package:travel_mate/components/widgets/user_state_widget.dart'; import 'package:travel_mate/components/widgets/user_state_widget.dart';
import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_bloc.dart';
@@ -50,19 +50,12 @@ class _GroupContentState extends State<GroupContent> {
return BlocConsumer<GroupBloc, GroupState>( return BlocConsumer<GroupBloc, GroupState>(
listener: (context, groupState) { listener: (context, groupState) {
if (groupState is GroupOperationSuccess) { if (groupState is GroupOperationSuccess) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showSnackbar(
SnackBar( message: groupState.message,
content: Text(groupState.message), isError: false,
backgroundColor: Colors.green,
),
); );
} else if (groupState is GroupError) { } else if (groupState is GroupError) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showError(message: groupState.message);
SnackBar(
content: Text(groupState.message),
backgroundColor: Colors.red,
),
);
} }
}, },
builder: (context, groupState) { builder: (context, groupState) {
@@ -209,8 +202,7 @@ class _GroupContentState extends State<GroupContent> {
if (mounted) { if (mounted) {
if (retry) { if (retry) {
if (userId == '') { if (userId == '') {
showErrorDialog( ErrorService().showError(
context,
title: 'Erreur utilisateur', title: 'Erreur utilisateur',
message: 'Utilisateur non connecté. Veuillez vous reconnecter.', message: 'Utilisateur non connecté. Veuillez vous reconnecter.',
icon: Icons.error, icon: Icons.error,
@@ -220,8 +212,7 @@ class _GroupContentState extends State<GroupContent> {
}, },
); );
} else { } else {
showErrorDialog( ErrorService().showError(
context,
title: 'Erreur de chargement', title: 'Erreur de chargement',
message: error, message: error,
icon: Icons.cloud_off, icon: Icons.cloud_off,
@@ -232,8 +223,7 @@ class _GroupContentState extends State<GroupContent> {
); );
} }
} else { } else {
showErrorDialog( ErrorService().showError(
context,
title: 'Erreur', title: 'Erreur',
message: error, message: error,
icon: Icons.error, icon: Icons.error,

View File

@@ -11,6 +11,7 @@ import '../blocs/user/user_bloc.dart';
import '../blocs/user/user_event.dart'; import '../blocs/user/user_event.dart';
import '../blocs/auth/auth_bloc.dart'; import '../blocs/auth/auth_bloc.dart';
import '../blocs/auth/auth_event.dart'; import '../blocs/auth/auth_event.dart';
import '../services/error_service.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({super.key}); const HomePage({super.key});
@@ -119,12 +120,7 @@ class _HomePageState extends State<HomePage> {
); );
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showError(message: 'Erreur lors de la déconnexion: $e');
SnackBar(
content: Text('Erreur lors de la déconnexion: $e'),
backgroundColor: Colors.red,
),
);
} }
} }
} }
@@ -132,38 +128,51 @@ class _HomePageState extends State<HomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: Text(titles[_currentIndex])),
title: Text(titles[_currentIndex]),
),
drawer: Drawer( drawer: Drawer(
child: ListView( child: ListView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
children: [ children: [
DrawerHeader( DrawerHeader(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark color: Theme.of(context).brightness == Brightness.dark
? Colors.black ? Colors.black
: Colors.white, : Colors.white,
), ),
child: Text( child: Text(
"Travel Mate", "Travel Mate",
style: TextStyle( style: TextStyle(
color: Theme.of(context).brightness == Brightness.dark color: Theme.of(context).brightness == Brightness.dark
? Colors.white ? Colors.white
: Colors.black, : Colors.black,
fontSize: 24, fontSize: 24,
), ),
), ),
), ),
_buildDrawerItem(icon: Icons.home, title: "Mes voyages", index: 0), _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.map, title: "Carte", index: 2),
_buildDrawerItem(icon: Icons.group, title: "Chat de groupe", index: 3), _buildDrawerItem(
_buildDrawerItem(icon: Icons.account_balance_wallet, title: "Comptes", index: 4), icon: Icons.group,
title: "Chat de groupe",
index: 3,
),
_buildDrawerItem(
icon: Icons.account_balance_wallet,
title: "Comptes",
index: 4,
),
const Divider(), const Divider(),
ListTile( ListTile(
leading: const Icon(Icons.logout, color: Colors.red), 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 onTap: _handleLogout, // Utiliser la nouvelle méthode
), ),
], ],
@@ -191,7 +200,9 @@ class _HomePageState extends State<HomePage> {
leading: Icon(icon), leading: Icon(icon),
title: Text(title), title: Text(title),
selected: _currentIndex == index, 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), onTap: () => _onNavigationTap(index),
); );
} }
@@ -201,4 +212,4 @@ class _HomePageState extends State<HomePage> {
_pageCache.clear(); _pageCache.clear();
super.dispose(); super.dispose();
} }
} }

View File

@@ -4,6 +4,7 @@ import '../blocs/auth/auth_bloc.dart';
import '../blocs/auth/auth_event.dart'; import '../blocs/auth/auth_event.dart';
import '../blocs/auth/auth_state.dart'; import '../blocs/auth/auth_state.dart';
import 'package:sign_in_button/sign_in_button.dart'; import 'package:sign_in_button/sign_in_button.dart';
import '../services/error_service.dart';
/// Login page widget for user authentication. /// Login page widget for user authentication.
/// ///
@@ -89,12 +90,7 @@ class _LoginPageState extends State<LoginPage> {
if (state is AuthAuthenticated) { if (state is AuthAuthenticated) {
Navigator.pushReplacementNamed(context, '/home'); Navigator.pushReplacementNamed(context, '/home');
} else if (state is AuthError) { } else if (state is AuthError) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showError(message: state.message);
SnackBar(
content: Text(state.message),
backgroundColor: Colors.red,
),
);
} }
}, },
builder: (context, state) { builder: (context, state) {

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/auth/auth_bloc.dart'; import '../blocs/auth/auth_bloc.dart';
import '../blocs/auth/auth_event.dart'; import '../blocs/auth/auth_event.dart';
import '../blocs/auth/auth_state.dart'; import '../blocs/auth/auth_state.dart';
import '../services/error_service.dart';
class ForgotPasswordPage extends StatefulWidget { class ForgotPasswordPage extends StatefulWidget {
const ForgotPasswordPage({super.key}); const ForgotPasswordPage({super.key});
@@ -56,20 +57,13 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
body: BlocListener<AuthBloc, AuthState>( body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) { listener: (context, state) {
if (state is AuthPasswordResetSent) { if (state is AuthPasswordResetSent) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showSnackbar(
SnackBar( message: 'Email de réinitialisation envoyé !',
content: Text('Email de réinitialisation envoyé !'), isError: false,
backgroundColor: Colors.green,
),
); );
Navigator.pop(context); Navigator.pop(context);
} else if (state is AuthError) { } else if (state is AuthError) {
ScaffoldMessenger.of(context).showSnackBar( ErrorService().showError(message: state.message);
SnackBar(
content: Text(state.message),
backgroundColor: Colors.red,
),
);
} }
}, },
child: SafeArea( child: SafeArea(

View File

@@ -117,9 +117,7 @@ class _SignUpPageState extends State<SignUpPage> {
); );
Navigator.pushReplacementNamed(context, '/home'); Navigator.pushReplacementNamed(context, '/home');
} else if (state is AuthError) { } else if (state is AuthError) {
_errorService.showError( _errorService.showError(message: state.message);
message: 'Erreur lors de la création du compte',
);
} }
}, },
builder: (context, state) { builder: (context, state) {

View File

@@ -7,7 +7,8 @@ class AccountRepository {
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final _errorService = ErrorService(); final _errorService = ErrorService();
CollectionReference get _accountCollection => _firestore.collection('accounts'); CollectionReference get _accountCollection =>
_firestore.collection('accounts');
CollectionReference _membersCollection(String accountId) { CollectionReference _membersCollection(String accountId) {
return _accountCollection.doc(accountId).collection('members'); return _accountCollection.doc(accountId).collection('members');
@@ -32,8 +33,13 @@ class AccountRepository {
return accountRef.id; return accountRef.id;
}); });
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la création du compte: $e'); _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 return _accountCollection
.snapshots() .snapshots()
.asyncMap((snapshot) async { .asyncMap((snapshot) async {
List<Account> userAccounts = []; List<Account> userAccounts = [];
for (var accountDoc in snapshot.docs) { for (var accountDoc in snapshot.docs) {
@@ -54,14 +59,24 @@ class AccountRepository {
.get(); .get();
if (memberDoc.exists) { if (memberDoc.exists) {
final accountData = accountDoc.data() as Map<String, dynamic>; final accountData = accountDoc.data() as Map<String, dynamic>;
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); final members = await getAccountMembers(accountId);
userAccounts.add(account.copyWith(members: members)); userAccounts.add(account.copyWith(members: members));
} else { } else {
_errorService.logInfo('account_repository.dart', 'Utilisateur NON membre de $accountId'); _errorService.logInfo(
'account_repository.dart',
'Utilisateur NON membre de $accountId',
);
} }
} catch (e, stackTrace) { } catch (e, stackTrace) {
_errorService.logError(e.toString(), stackTrace); _errorService.logError(
'account_repository.dart',
'Erreur processing account doc: $e',
stackTrace,
);
} }
} }
return userAccounts; return userAccounts;
@@ -70,14 +85,19 @@ class AccountRepository {
if (prev.length != next.length) return false; if (prev.length != next.length) return false;
final prevIds = prev.map((a) => a.id).toSet(); final prevIds = prev.map((a) => a.id).toSet();
final nextIds = next.map((a) => a.id).toSet(); final nextIds = next.map((a) => a.id).toSet();
final identical = prevIds.difference(nextIds).isEmpty && final identical =
nextIds.difference(prevIds).isEmpty; prevIds.difference(nextIds).isEmpty &&
nextIds.difference(prevIds).isEmpty;
return identical; return identical;
}) })
.handleError((error, stackTrace) { .handleError((error, stackTrace) {
_errorService.logError(error, stackTrace); _errorService.logError(
'account_repository.dart',
'Erreur stream accounts: $error',
stackTrace,
);
return <Account>[]; return <Account>[];
}); });
} }
@@ -85,16 +105,16 @@ class AccountRepository {
Future<List<GroupMember>> getAccountMembers(String accountId) async { Future<List<GroupMember>> getAccountMembers(String accountId) async {
try { try {
final snapshot = await _membersCollection(accountId).get(); final snapshot = await _membersCollection(accountId).get();
return snapshot.docs return snapshot.docs.map((doc) {
.map((doc) { return GroupMember.fromMap(doc.data() as Map<String, dynamic>, doc.id);
return GroupMember.fromMap( }).toList();
doc.data() as Map<String, dynamic>, } catch (e, stackTrace) {
doc.id, _errorService.logError(
); 'account_repository.dart',
}) 'Erreur lors de la récupération des membres: $e',
.toList(); stackTrace,
} catch (e) { );
throw Exception('Erreur lors de la récupération des membres: $e'); throw Exception('Impossible de récupérer les membres');
} }
} }
@@ -105,8 +125,13 @@ class AccountRepository {
return Account.fromMap(doc.data() as Map<String, dynamic>, doc.id); return Account.fromMap(doc.data() as Map<String, dynamic>, doc.id);
} }
return null; return null;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la récupération du compte: $e'); _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) .where('tripId', isEqualTo: tripId)
.limit(1) .limit(1)
.get(); .get();
if (querySnapshot.docs.isNotEmpty) { if (querySnapshot.docs.isNotEmpty) {
final doc = querySnapshot.docs.first; final doc = querySnapshot.docs.first;
return Account.fromMap(doc.data(), doc.id); return Account.fromMap(doc.data(), doc.id);
} }
return null; return null;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la récupération du compte: $e'); _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 { try {
// Mettre à jour la date de modification // Mettre à jour la date de modification
final updatedAccount = account.copyWith(updatedAt: DateTime.now()); final updatedAccount = account.copyWith(updatedAt: DateTime.now());
await _firestore.collection('accounts').doc(accountId).update(updatedAccount.toMap()); await _firestore
} catch (e) { .collection('accounts')
_errorService.logError('account_repository.dart', 'Erreur lors de la mise à jour du compte: $e'); .doc(accountId)
throw Exception('Erreur lors de la mise à jour du compte: $e'); .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) { for (var memberDoc in membersSnapshot.docs) {
await _membersCollection(docId).doc(memberDoc.id).delete(); await _membersCollection(docId).doc(memberDoc.id).delete();
} }
// Supprimer le compte // Supprimer le compte
await _accountCollection.doc(docId).delete(); await _accountCollection.doc(docId).delete();
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('account_repository.dart', 'Erreur lors de la suppression du compte: $e'); _errorService.logError(
throw Exception('Erreur lors de la suppression du compte: $e'); 'account_repository.dart',
'Erreur lors de la suppression du compte: $e',
stackTrace,
);
throw Exception('Impossible de supprimer le compte');
} }
} }
Stream<List<GroupMember>> watchGroupMembers(String accountId) { Stream<List<GroupMember>> watchGroupMembers(String accountId) {
return _membersCollection(accountId).snapshots().map( return _membersCollection(accountId).snapshots().map(
(snapshot) => snapshot.docs (snapshot) => snapshot.docs
.map((doc) => GroupMember.fromMap( .map(
doc.data() as Map<String, dynamic>, (doc) =>
doc.id, GroupMember.fromMap(doc.data() as Map<String, dynamic>, doc.id),
)) )
.toList(), .toList(),
); );
} }
Stream<Account?> watchAccount(String accountId) { Stream<Account?> watchAccount(String accountId) {
@@ -201,19 +242,32 @@ class AccountRepository {
Future<void> addMemberToAccount(String accountId, GroupMember member) async { Future<void> addMemberToAccount(String accountId, GroupMember member) async {
try { try {
await _membersCollection(accountId).doc(member.userId).set(member.toMap()); await _membersCollection(
} catch (e) { accountId,
_errorService.logError('account_repository.dart', 'Erreur lors de l\'ajout du membre: $e'); ).doc(member.userId).set(member.toMap());
throw Exception('Erreur lors de l\'ajout du membre: $e'); } 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<void> removeMemberFromAccount(String accountId, String memberId) async { Future<void> removeMemberFromAccount(
String accountId,
String memberId,
) async {
try { try {
await _membersCollection(accountId).doc(memberId).delete(); await _membersCollection(accountId).doc(memberId).delete();
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('account_repository.dart', 'Erreur lors de la suppression du membre: $e'); _errorService.logError(
throw Exception('Erreur lors de la suppression du membre: $e'); 'account_repository.dart',
'Erreur lors de la suppression du membre: $e',
stackTrace,
);
throw Exception('Impossible de supprimer le membre');
} }
} }
} }

View File

@@ -60,10 +60,10 @@ class AuthRepository {
); );
await _saveFCMToken(firebaseUser.user!.uid); await _saveFCMToken(firebaseUser.user!.uid);
return await getUserFromFirestore(firebaseUser.user!.uid); return await getUserFromFirestore(firebaseUser.user!.uid);
} catch (e) { } catch (e, stackTrace) {
_errorService.showError(message: 'Utilisateur ou mot de passe incorrect'); _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. /// Creates a new user account with email and password.
@@ -108,10 +108,10 @@ class AuthRepository {
await _saveFCMToken(user.id!); await _saveFCMToken(user.id!);
} }
return user; return user;
} catch (e) { } catch (e, stackTrace) {
_errorService.showError(message: 'Erreur lors de la création du compte'); _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. /// Signs in a user using Google authentication.
@@ -160,10 +160,14 @@ class AuthRepository {
return user; return user;
} }
return null; return null;
} catch (e) { } catch (e, stackTrace) {
_errorService.showError(message: 'Erreur lors de la connexion Google'); _errorService.logError(
'AuthRepository',
'Google SignUp error: $e',
stackTrace,
);
throw Exception('Erreur lors de la connexion Google');
} }
return null;
} }
Future<User?> signInWithGoogle() async { Future<User?> signInWithGoogle() async {
@@ -178,10 +182,14 @@ class AuthRepository {
} else { } else {
throw Exception('Utilisateur non trouvé'); throw Exception('Utilisateur non trouvé');
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.showError(message: 'Erreur lors de la connexion Google'); _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. /// Signs in a user using Apple authentication.
@@ -228,10 +236,14 @@ class AuthRepository {
return user; return user;
} }
return null; return null;
} catch (e) { } catch (e, stackTrace) {
_errorService.showError(message: 'Erreur lors de la connexion Apple'); _errorService.logError(
'AuthRepository',
'Apple SignUp error: $e',
stackTrace,
);
throw Exception('Erreur lors de la connexion Apple');
} }
return null;
} }
Future<User?> signInWithApple() async { Future<User?> signInWithApple() async {
@@ -246,10 +258,14 @@ class AuthRepository {
} else { } else {
throw Exception('Utilisateur non trouvé'); throw Exception('Utilisateur non trouvé');
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.showError(message: 'Erreur lors de la connexion Apple'); _errorService.logError(
'AuthRepository',
'Apple SignIn error: $e',
stackTrace,
);
throw Exception('Erreur lors de la connexion Apple');
} }
return null;
} }
/// Signs out the current user. /// Signs out the current user.
@@ -298,9 +314,13 @@ class AuthRepository {
'fcmToken': token, 'fcmToken': token,
}, SetOptions(merge: true)); }, SetOptions(merge: true));
} }
} catch (e) { } catch (e, stackTrace) {
// Non-blocking error // Non-blocking error
print('Error saving FCM token: $e'); _errorService.logError(
'AuthRepository',
'Error saving FCM token: $e',
stackTrace,
);
} }
} }
} }

View File

@@ -37,9 +37,13 @@ class BalanceRepository {
totalExpenses: totalExpenses, totalExpenses: totalExpenses,
calculatedAt: DateTime.now(), calculatedAt: DateTime.now(),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('BalanceRepository', 'Erreur calcul balance: $e'); _errorService.logError(
rethrow; 'BalanceRepository',
'Erreur calcul balance: $e',
stackTrace,
);
throw Exception('Impossible de calculer la balance');
} }
} }
@@ -50,9 +54,13 @@ class BalanceRepository {
.first; .first;
return _calculateUserBalances(expenses); return _calculateUserBalances(expenses);
} catch (e) { } catch (e, stackTrace) {
_errorService.logError('BalanceRepository', 'Erreur calcul user balances: $e'); _errorService.logError(
rethrow; '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; if (expense.isArchived) continue;
// Ajouter le payeur // Ajouter le payeur
userBalanceMap.putIfAbsent(expense.paidById, () => { userBalanceMap.putIfAbsent(
'name': expense.paidByName, expense.paidById,
'paid': 0.0, () => {'name': expense.paidByName, 'paid': 0.0, 'owed': 0.0},
'owed': 0.0, );
});
// Ajouter les participants // Ajouter les participants
for (final split in expense.splits) { for (final split in expense.splits) {
userBalanceMap.putIfAbsent(split.userId, () => { userBalanceMap.putIfAbsent(
'name': split.userName, split.userId,
'paid': 0.0, () => {'name': split.userName, 'paid': 0.0, 'owed': 0.0},
'owed': 0.0, );
});
} }
} }
@@ -100,7 +106,7 @@ class BalanceRepository {
final data = entry.value; final data = entry.value;
final paid = data['paid'] as double; final paid = data['paid'] as double;
final owed = data['owed'] as double; final owed = data['owed'] as double;
return UserBalance( return UserBalance(
userId: userId, userId: userId,
userName: data['name'] as String, userName: data['name'] as String,
@@ -114,7 +120,7 @@ class BalanceRepository {
// Algorithme d'optimisation des règlements // Algorithme d'optimisation des règlements
List<Settlement> _calculateOptimalSettlements(List<UserBalance> balances) { List<Settlement> _calculateOptimalSettlements(List<UserBalance> balances) {
final settlements = <Settlement>[]; final settlements = <Settlement>[];
// Séparer les créditeurs et débiteurs // Séparer les créditeurs et débiteurs
final creditors = balances.where((b) => b.shouldReceive).toList(); final creditors = balances.where((b) => b.shouldReceive).toList();
final debtors = balances.where((b) => b.shouldPay).toList(); final debtors = balances.where((b) => b.shouldPay).toList();
@@ -125,10 +131,10 @@ class BalanceRepository {
// Créer des copies mutables des montants // Créer des copies mutables des montants
final creditorsRemaining = Map.fromEntries( final creditorsRemaining = Map.fromEntries(
creditors.map((c) => MapEntry(c.userId, c.balance)) creditors.map((c) => MapEntry(c.userId, c.balance)),
); );
final debtorsRemaining = Map.fromEntries( 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 // Algorithme glouton pour minimiser le nombre de transactions
@@ -139,15 +145,20 @@ class BalanceRepository {
if (creditAmount <= 0.01 || debtAmount <= 0.01) continue; 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( settlements.add(
fromUserId: debtor.userId, Settlement(
fromUserName: debtor.userName, fromUserId: debtor.userId,
toUserId: creditor.userId, fromUserName: debtor.userName,
toUserName: creditor.userName, toUserId: creditor.userId,
amount: settlementAmount, toUserName: creditor.userName,
)); amount: settlementAmount,
),
);
creditorsRemaining[creditor.userId] = creditAmount - settlementAmount; creditorsRemaining[creditor.userId] = creditAmount - settlementAmount;
debtorsRemaining[debtor.userId] = debtAmount - settlementAmount; debtorsRemaining[debtor.userId] = debtAmount - settlementAmount;
@@ -156,4 +167,4 @@ class BalanceRepository {
return settlements; return settlements;
} }
} }

View File

@@ -35,8 +35,13 @@ class GroupRepository {
return groupRef.id; return groupRef.id;
}); });
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la création du groupe: $e'); _errorService.logError(
'GroupRepository',
'Erreur création groupe: $e',
stackTrace,
);
throw Exception('Impossible de créer le groupe');
} }
} }
@@ -51,7 +56,11 @@ class GroupRepository {
}).toList(); }).toList();
}) })
.handleError((error, stackTrace) { .handleError((error, stackTrace) {
_errorService.logError(error, stackTrace); _errorService.logError(
'GroupRepository',
'Erreur stream groups: $error',
stackTrace,
);
return <Group>[]; return <Group>[];
}); });
} }
@@ -66,8 +75,13 @@ class GroupRepository {
final members = await getGroupMembers(groupId); final members = await getGroupMembers(groupId);
return group.copyWith(members: members); return group.copyWith(members: members);
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la récupération du groupe: $e'); _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); final members = await getGroupMembers(doc.id);
return group.copyWith(members: members); return group.copyWith(members: members);
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la récupération du groupe: $e'); _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', 'Migration réussie pour le groupe $groupId',
); );
} }
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'GroupRepository', 'GroupRepository',
'Erreur de migration pour le groupe $groupId: $e', 'Erreur de migration pour le groupe $groupId: $e',
stackTrace,
); );
} }
} }
@@ -136,8 +156,13 @@ class GroupRepository {
return snapshot.docs.map((doc) { return snapshot.docs.map((doc) {
return GroupMember.fromMap(doc.data() as Map<String, dynamic>, doc.id); return GroupMember.fromMap(doc.data() as Map<String, dynamic>, doc.id);
}).toList(); }).toList();
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la récupération des membres: $e'); _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({ await _firestore.collection('trips').doc(group.tripId).update({
'participants': FieldValue.arrayUnion([member.userId]), 'participants': FieldValue.arrayUnion([member.userId]),
}); });
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de l\'ajout du membre: $e'); _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({ await _firestore.collection('trips').doc(group.tripId).update({
'participants': FieldValue.arrayRemove([userId]), 'participants': FieldValue.arrayRemove([userId]),
}); });
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la suppression du membre: $e'); _errorService.logError(
'GroupRepository',
'Erreur remove member: $e',
stackTrace,
);
throw Exception('Impossible de supprimer le membre');
} }
} }
@@ -197,8 +232,13 @@ class GroupRepository {
group.toMap() group.toMap()
..['updatedAt'] = DateTime.now().millisecondsSinceEpoch, ..['updatedAt'] = DateTime.now().millisecondsSinceEpoch,
); );
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la mise à jour du groupe: $e'); _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(); await _groupsCollection.doc(groupId).delete();
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la suppression du groupe: $e'); _errorService.logError(
'GroupRepository',
'Erreur delete group: $e',
stackTrace,
);
throw Exception('Impossible de supprimer le groupe');
} }
} }

View File

@@ -1,33 +1,45 @@
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/trip.dart'; import '../models/trip.dart';
import '../services/error_service.dart';
class TripRepository { class TripRepository {
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final _errorService = ErrorService();
CollectionReference get _tripsCollection => _firestore.collection('trips'); CollectionReference get _tripsCollection => _firestore.collection('trips');
// Récupérer tous les voyages d'un utilisateur // Récupérer tous les voyages d'un utilisateur
Stream<List<Trip>> getTripsByUserId(String userId) { Stream<List<Trip>> getTripsByUserId(String userId) {
try { try {
return _tripsCollection return _tripsCollection
.where('participants', arrayContains: userId) .where('participants', arrayContains: userId)
.snapshots() .snapshots()
.map((snapshot) { .map((snapshot) {
final trips = snapshot.docs final trips = snapshot.docs
.map((doc) { .map((doc) {
try { try {
final data = doc.data() as Map<String, dynamic>; final data = doc.data() as Map<String, dynamic>;
return Trip.fromMap(data, doc.id); return Trip.fromMap(data, doc.id);
} catch (e) { } catch (e, stackTrace) {
return null; _errorService.logError(
} 'TripRepository',
}) 'Erreur parsing trip ${doc.id}: $e',
.whereType<Trip>() stackTrace,
.toList(); );
return trips; return null;
}); }
} catch (e) { })
throw Exception('Erreur lors de la récupération des voyages: $e'); .whereType<Trip>()
.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 // Ne pas modifier les timestamps ici, ils sont déjà au bon format
final docRef = await _tripsCollection.add(tripData); final docRef = await _tripsCollection.add(tripData);
return docRef.id; return docRef.id;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la création du voyage: $e'); _errorService.logError(
'TripRepository',
'Erreur création trip: $e',
stackTrace,
);
throw Exception('Impossible de créer le voyage');
} }
} }
@@ -47,14 +64,19 @@ class TripRepository {
Future<Trip?> getTripById(String tripId) async { Future<Trip?> getTripById(String tripId) async {
try { try {
final doc = await _tripsCollection.doc(tripId).get(); final doc = await _tripsCollection.doc(tripId).get();
if (!doc.exists) { if (!doc.exists) {
return null; return null;
} }
return Trip.fromMap(doc.data() as Map<String, dynamic>, doc.id); return Trip.fromMap(doc.data() as Map<String, dynamic>, doc.id);
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la récupération du voyage: $e'); _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(); final tripData = trip.toMap();
// Mettre à jour le timestamp de modification // Mettre à jour le timestamp de modification
tripData['updatedAt'] = Timestamp.now(); tripData['updatedAt'] = Timestamp.now();
await _tripsCollection.doc(tripId).update(tripData); await _tripsCollection.doc(tripId).update(tripData);
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la mise à jour du voyage: $e'); _errorService.logError(
'TripRepository',
'Erreur update trip: $e',
stackTrace,
);
throw Exception('Impossible de mettre à jour le voyage');
} }
} }
@@ -75,8 +102,13 @@ class TripRepository {
Future<void> deleteTrip(String tripId) async { Future<void> deleteTrip(String tripId) async {
try { try {
await _tripsCollection.doc(tripId).delete(); await _tripsCollection.doc(tripId).delete();
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la suppression du voyage: $e'); _errorService.logError(
'TripRepository',
'Erreur delete trip: $e',
stackTrace,
);
throw Exception('Impossible de supprimer le voyage');
} }
} }
} }

View File

@@ -1,34 +1,35 @@
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/user.dart'; import '../models/user.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import '../services/error_service.dart';
/// Repository for user data operations in Firestore. /// Repository for user data operations in Firestore.
/// ///
/// This repository provides methods for CRUD operations on user data, /// This repository provides methods for CRUD operations on user data,
/// including retrieving users by ID or email, updating user information, /// including retrieving users by ID or email, updating user information,
/// and managing user profiles in the Firestore database. /// and managing user profiles in the Firestore database.
class UserRepository { class UserRepository {
/// Firestore instance for database operations. /// Firestore instance for database operations.
final FirebaseFirestore _firestore; final FirebaseFirestore _firestore;
/// Authentication service for user-related operations. /// Authentication service for user-related operations.
final AuthService _authService; final AuthService _authService;
final _errorService = ErrorService();
/// Creates a new [UserRepository] with optional dependencies. /// Creates a new [UserRepository] with optional dependencies.
/// ///
/// If [firestore] or [authService] are not provided, default instances will be used. /// If [firestore] or [authService] are not provided, default instances will be used.
UserRepository({ UserRepository({FirebaseFirestore? firestore, AuthService? authService})
FirebaseFirestore? firestore, : _firestore = firestore ?? FirebaseFirestore.instance,
AuthService? authService, _authService = authService ?? AuthService();
}) : _firestore = firestore ?? FirebaseFirestore.instance,
_authService = authService ?? AuthService();
/// Retrieves a user by their unique ID. /// Retrieves a user by their unique ID.
/// ///
/// Fetches the user document from Firestore and converts it to a [User] model. /// Fetches the user document from Firestore and converts it to a [User] model.
/// ///
/// [uid] - The unique user identifier /// [uid] - The unique user identifier
/// ///
/// Returns the [User] model if found, null otherwise. /// Returns the [User] model if found, null otherwise.
/// Throws an exception if the operation fails. /// Throws an exception if the operation fails.
Future<User?> getUserById(String uid) async { Future<User?> getUserById(String uid) async {
@@ -39,18 +40,23 @@ class UserRepository {
return User.fromMap({...data, 'id': uid}); return User.fromMap({...data, 'id': uid});
} }
return null; return null;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Error retrieving user: $e'); _errorService.logError(
'UserRepository',
'Error retrieving user: $e',
stackTrace,
);
throw Exception('Impossible de récupérer l\'utilisateur');
} }
} }
/// Retrieves a user by their email address. /// Retrieves a user by their email address.
/// ///
/// Searches the users collection for a matching email address. /// Searches the users collection for a matching email address.
/// Email comparison is case-insensitive after trimming whitespace. /// Email comparison is case-insensitive after trimming whitespace.
/// ///
/// [email] - The email address to search for /// [email] - The email address to search for
/// ///
/// Returns the first [User] found with the matching email, null if not found. /// Returns the first [User] found with the matching email, null if not found.
/// Throws an exception if the operation fails. /// Throws an exception if the operation fails.
Future<User?> getUserByEmail(String email) async { Future<User?> getUserByEmail(String email) async {
@@ -67,28 +73,38 @@ class UserRepository {
return User.fromMap({...data, 'id': doc.id}); return User.fromMap({...data, 'id': doc.id});
} }
return null; return null;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Error searching for user: $e'); _errorService.logError(
'UserRepository',
'Error searching for user: $e',
stackTrace,
);
throw Exception('Impossible de trouver l\'utilisateur');
} }
} }
/// Updates an existing user in Firestore. /// Updates an existing user in Firestore.
/// ///
/// Updates the user document with the provided user data. /// Updates the user document with the provided user data.
/// ///
/// [user] - The user object containing updated information /// [user] - The user object containing updated information
/// ///
/// Returns true if the update was successful, false otherwise. /// Returns true if the update was successful, false otherwise.
Future<bool> updateUser(User user) async { Future<bool> updateUser(User user) async {
try { try {
await _firestore.collection('users').doc(user.id).update(user.toMap()); await _firestore.collection('users').doc(user.id).update(user.toMap());
// Mettre à jour le displayName dans Firebase Auth // Mettre à jour le displayName dans Firebase Auth
await _authService.updateDisplayName(displayName: user.fullName); await _authService.updateDisplayName(displayName: user.fullName);
return true; return true;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la mise à jour: $e'); _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(); await _firestore.collection('users').doc(uid).delete();
// Note: Vous devrez également supprimer le compte Firebase Auth // Note: Vous devrez également supprimer le compte Firebase Auth
return true; return true;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors de la suppression: $e'); _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) { if (currentUser?.email == null) {
throw Exception('Utilisateur non connecté ou email non disponible'); throw Exception('Utilisateur non connecté ou email non disponible');
} }
await _authService.resetPasswordFromCurrentPassword( await _authService.resetPasswordFromCurrentPassword(
email: currentUser!.email!, email: currentUser!.email!,
currentPassword: currentPassword, currentPassword: currentPassword,
newPassword: newPassword, newPassword: newPassword,
); );
return true; return true;
} catch (e) { } catch (e, stackTrace) {
throw Exception('Erreur lors du changement de mot de passe: $e'); _errorService.logError(
'UserRepository',
'Erreur lors du changement de mot de passe: $e',
stackTrace,
);
throw Exception('Impossible de changer le mot de passe');
} }
} }
} }

View File

@@ -171,18 +171,35 @@ class AuthService {
} }
} on GoogleSignInException catch (e) { } on GoogleSignInException catch (e) {
_errorService.logError('Google Sign-In error: $e', StackTrace.current); _errorService.logError('Google Sign-In error: $e', StackTrace.current);
_errorService.showError(
message: 'La connexion avec Google a échoué. Veuillez réessayer.',
);
rethrow; rethrow;
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
_errorService.logError( _errorService.logError(
'Firebase error during Google Sign-In initialization: $e', 'Firebase error during Google Sign-In initialization: $e',
StackTrace.current, 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; rethrow;
} catch (e) { } catch (e) {
_errorService.logError( _errorService.logError(
'Unknown error during Google Sign-In initialization: $e', 'Unknown error during Google Sign-In initialization: $e',
StackTrace.current, StackTrace.current,
); );
_errorService.showError(
message: 'Une erreur inattendue est survenue. Veuillez réessayer.',
);
rethrow; rethrow;
} }
} }
@@ -216,9 +233,9 @@ class AuthService {
], ],
// Configuration for Android/Web // Configuration for Android/Web
webAuthenticationOptions: WebAuthenticationOptions( webAuthenticationOptions: WebAuthenticationOptions(
clientId: 'be.devdayronvl.TravelMate', clientId: 'be.devdayronvl.TravelMate.service',
redirectUri: Uri.parse( 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; return userCredential;
} on SignInWithAppleException catch (e) { } on SignInWithAppleException catch (e) {
_errorService.logError('Apple Sign-In error: $e', StackTrace.current); _errorService.logError('Apple Sign-In error: $e', StackTrace.current);
_errorService.showError(
message: 'La connexion avec Apple a échoué. Veuillez réessayer.',
);
throw FirebaseAuthException( throw FirebaseAuthException(
code: 'ERROR_APPLE_SIGNIN_FAILED', code: 'ERROR_APPLE_SIGNIN_FAILED',
message: 'Apple Sign-In failed: ${e.toString()}', message: 'Apple Sign-In failed',
); );
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
_errorService.logError( _errorService.logError(
'Firebase error during Apple Sign-In: $e', 'Firebase error during Apple Sign-In: $e',
StackTrace.current, 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; rethrow;
} catch (e) { } catch (e) {
_errorService.logError( _errorService.logError(
'Unknown error during Apple Sign-In: $e', 'Unknown error during Apple Sign-In: $e',
StackTrace.current, StackTrace.current,
); );
_errorService.showError(
message: 'Une erreur inattendue est survenue. Veuillez réessayer.',
);
throw FirebaseAuthException( throw FirebaseAuthException(
code: 'ERROR_APPLE_SIGNIN_UNKNOWN', code: 'ERROR_APPLE_SIGNIN_UNKNOWN',
message: 'Unknown error during Apple Sign-In: $e', message: 'Unknown error during Apple Sign-In',
); );
} }
} }

View File

@@ -9,8 +9,8 @@ class MessageService {
MessageService({ MessageService({
required MessageRepository messageRepository, required MessageRepository messageRepository,
ErrorService? errorService, ErrorService? errorService,
}) : _messageRepository = messageRepository, }) : _messageRepository = messageRepository,
_errorService = errorService ?? ErrorService(); _errorService = errorService ?? ErrorService();
// Envoyer un message // Envoyer un message
Future<void> sendMessage({ Future<void> sendMessage({
@@ -30,12 +30,13 @@ class MessageService {
senderId: senderId, senderId: senderId,
senderName: senderName, senderName: senderName,
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'message_service.dart', 'message_service.dart',
'Erreur lors de l\'envoi du message: $e', 'Erreur lors de l\'envoi du message: $e',
stackTrace,
); );
rethrow; throw Exception('Impossible d\'envoyer le message');
} }
} }
@@ -43,12 +44,13 @@ class MessageService {
Stream<List<Message>> getMessagesStream(String groupId) { Stream<List<Message>> getMessagesStream(String groupId) {
try { try {
return _messageRepository.getMessagesStream(groupId); return _messageRepository.getMessagesStream(groupId);
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'message_service.dart', 'message_service.dart',
'Erreur lors de la récupération des messages: $e', '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, groupId: groupId,
messageId: messageId, messageId: messageId,
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'message_service.dart', 'message_service.dart',
'Erreur lors de la suppression du message: $e', '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, messageId: messageId,
newText: newText.trim(), newText: newText.trim(),
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'message_service.dart', 'message_service.dart',
'Erreur lors de la modification du message: $e', '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, userId: userId,
reaction: reaction, reaction: reaction,
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'message_service.dart', 'message_service.dart',
'Erreur lors de l\'ajout de la réaction: $e', '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, messageId: messageId,
userId: userId, userId: userId,
); );
} catch (e) { } catch (e, stackTrace) {
_errorService.logError( _errorService.logError(
'message_service.dart', 'message_service.dart',
'Erreur lors de la suppression de la réaction: $e', 'Erreur lors de la suppression de la réaction: $e',
stackTrace,
); );
rethrow; throw Exception('Impossible de supprimer la réaction');
} }
} }
} }