Files
TravelMate/lib/blocs/count/count_bloc.dart
Dayron ce754c1e6c feat: Add expense management features with tabs for expenses, balances, and settlements
- Implemented ExpensesTab to display a list of expenses with details.
- Created GroupExpensesPage to manage group expenses with a tabbed interface.
- Added SettlementsTab to show optimized settlements between users.
- Developed data models for Expense and Balance, including necessary methods for serialization.
- Introduced CountRepository for Firestore interactions related to expenses.
- Added CountService to handle business logic for expenses and settlements.
- Integrated image picker for receipt uploads.
- Updated main.dart to include CountBloc and CountRepository.
- Enhanced pubspec.yaml with new dependencies for image picking and Firebase storage.

Not Tested yet
2025-10-20 19:22:57 +02:00

198 lines
5.0 KiB
Dart

import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../data/models/expense.dart';
import '../../data/models/balance.dart';
import '../../services/count_service.dart';
import '../../repositories/count_repository.dart';
import 'count_event.dart';
import 'count_state.dart';
class CountBloc extends Bloc<CountEvent, CountState> {
final CountService _countService;
StreamSubscription<List<Expense>>? _expensesSubscription;
Map<ExpenseCurrency, double> _exchangeRates = {};
CountBloc({CountService? countService})
: _countService = countService ?? CountService(
countRepository: CountRepository(),
),
super(CountInitial()) {
on<LoadExpenses>(_onLoadExpenses);
on<CreateExpense>(_onCreateExpense);
on<UpdateExpense>(_onUpdateExpense);
on<DeleteExpense>(_onDeleteExpense);
on<ArchiveExpense>(_onArchiveExpense);
on<MarkSplitAsPaid>(_onMarkSplitAsPaid);
on<LoadExchangeRates>(_onLoadExchangeRates);
on<_ExpensesUpdated>(_onExpensesUpdated);
}
Future<void> _onLoadExpenses(
LoadExpenses event,
Emitter<CountState> emit,
) async {
emit(CountLoading());
// Charger les taux de change
if (_exchangeRates.isEmpty) {
_exchangeRates = await _countService.getExchangeRates();
}
await _expensesSubscription?.cancel();
_expensesSubscription = _countService
.getExpensesStream(event.groupId, includeArchived: event.includeArchived)
.listen(
(expenses) {
add(_ExpensesUpdated(
groupId: event.groupId,
expenses: expenses,
));
},
onError: (error) {
add(_ExpensesError('Erreur lors du chargement des dépenses: $error'));
},
);
}
void _onExpensesUpdated(
_ExpensesUpdated event,
Emitter<CountState> emit,
) {
// Récupérer les membres du groupe et calculer les balances
final memberIds = <String>{};
final memberNames = <String, String>{};
for (final expense in event.expenses) {
memberIds.add(expense.paidById);
memberNames[expense.paidById] = expense.paidByName;
for (final split in expense.splits) {
memberIds.add(split.userId);
memberNames[split.userId] = split.userName;
}
}
final balances = _countService.calculateBalances(
event.expenses,
memberIds.toList(),
memberNames,
);
final settlements = _countService.calculateOptimizedSettlements(balances);
emit(ExpensesLoaded(
groupId: event.groupId,
expenses: event.expenses,
balances: balances,
settlements: settlements,
exchangeRates: _exchangeRates,
));
}
Future<void> _onCreateExpense(
CreateExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.createExpense(
event.expense,
receiptImage: event.receiptImage,
);
} catch (e) {
emit(CountError('Erreur lors de la création de la dépense: $e'));
}
}
Future<void> _onUpdateExpense(
UpdateExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.updateExpense(
event.expense,
newReceiptImage: event.newReceiptImage,
);
} catch (e) {
emit(CountError('Erreur lors de la modification de la dépense: $e'));
}
}
Future<void> _onDeleteExpense(
DeleteExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.deleteExpense(event.groupId, event.expenseId);
} catch (e) {
emit(CountError('Erreur lors de la suppression de la dépense: $e'));
}
}
Future<void> _onArchiveExpense(
ArchiveExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.archiveExpense(event.groupId, event.expenseId);
} catch (e) {
emit(CountError('Erreur lors de l\'archivage de la dépense: $e'));
}
}
Future<void> _onMarkSplitAsPaid(
MarkSplitAsPaid event,
Emitter<CountState> emit,
) async {
try {
await _countService.markSplitAsPaid(
event.groupId,
event.expenseId,
event.userId,
);
} catch (e) {
emit(CountError('Erreur lors du marquage du paiement: $e'));
}
}
Future<void> _onLoadExchangeRates(
LoadExchangeRates event,
Emitter<CountState> emit,
) async {
try {
_exchangeRates = await _countService.getExchangeRates();
} catch (e) {
emit(CountError('Erreur lors du chargement des taux de change: $e'));
}
}
@override
Future<void> close() {
_expensesSubscription?.cancel();
return super.close();
}
}
// Events internes
class _ExpensesUpdated extends CountEvent {
final String groupId;
final List<Expense> expenses;
const _ExpensesUpdated({
required this.groupId,
required this.expenses,
});
@override
List<Object?> get props => [groupId, expenses];
}
class _ExpensesError extends CountEvent {
final String error;
const _ExpensesError(this.error);
@override
List<Object?> get props => [error];
}