feat: integrate ErrorService for consistent error display and standardize bloc error messages.
This commit is contained in:
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: ', '')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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é'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user