- 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
198 lines
5.0 KiB
Dart
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];
|
|
}
|