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
This commit is contained in:
197
lib/blocs/count/count_bloc.dart
Normal file
197
lib/blocs/count/count_bloc.dart
Normal file
@@ -0,0 +1,197 @@
|
||||
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];
|
||||
}
|
||||
Reference in New Issue
Block a user