- Updated Group, Trip, User, and other model classes to include comprehensive documentation for better understanding and maintainability. - Improved error handling and logging in services, including AuthService, ErrorService, and StorageService. - Added validation and business logic explanations in ExpenseService and TripService. - Refactored method comments to follow a consistent format across the codebase. - Translated error messages and comments from French to English for consistency.
212 lines
7.5 KiB
Dart
212 lines
7.5 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import '../../repositories/expense_repository.dart';
|
|
import '../../services/expense_service.dart';
|
|
import '../../services/error_service.dart';
|
|
import 'expense_event.dart';
|
|
import 'expense_state.dart';
|
|
|
|
/// BLoC for managing expense operations and state.
|
|
///
|
|
/// This BLoC handles expense-related operations including loading expenses,
|
|
/// creating new expenses, updating existing ones, deleting expenses, and
|
|
/// managing expense splits. It coordinates with the expense repository and
|
|
/// service to provide business logic and data persistence.
|
|
class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
|
|
/// Repository for expense data operations.
|
|
final ExpenseRepository _expenseRepository;
|
|
|
|
/// Service for expense business logic and validation.
|
|
final ExpenseService _expenseService;
|
|
|
|
/// Service for error handling and logging.
|
|
final ErrorService _errorService;
|
|
|
|
/// Subscription to the expenses stream for real-time updates.
|
|
StreamSubscription? _expensesSubscription;
|
|
|
|
/// Creates a new [ExpenseBloc] with required dependencies.
|
|
///
|
|
/// [expenseRepository] is required for data operations.
|
|
/// [expenseService] and [errorService] have default implementations if not provided.
|
|
ExpenseBloc({
|
|
required ExpenseRepository expenseRepository,
|
|
ExpenseService? expenseService,
|
|
ErrorService? errorService,
|
|
}) : _expenseRepository = expenseRepository,
|
|
_expenseService = expenseService ?? ExpenseService(expenseRepository: expenseRepository),
|
|
_errorService = errorService ?? ErrorService(),
|
|
super(ExpenseInitial()) {
|
|
|
|
on<LoadExpensesByGroup>(_onLoadExpensesByGroup);
|
|
on<ExpensesUpdated>(_onExpensesUpdated);
|
|
on<CreateExpense>(_onCreateExpense);
|
|
on<UpdateExpense>(_onUpdateExpense);
|
|
on<DeleteExpense>(_onDeleteExpense);
|
|
on<MarkSplitAsPaid>(_onMarkSplitAsPaid);
|
|
on<ArchiveExpense>(_onArchiveExpense);
|
|
}
|
|
|
|
/// Handles [LoadExpensesByGroup] events.
|
|
///
|
|
/// Sets up a stream subscription to receive real-time updates for expenses
|
|
/// in the specified group. Cancels any existing subscription before creating a new one.
|
|
Future<void> _onLoadExpensesByGroup(
|
|
LoadExpensesByGroup event,
|
|
Emitter<ExpenseState> emit,
|
|
) async {
|
|
try {
|
|
emit(ExpenseLoading());
|
|
|
|
await _expensesSubscription?.cancel();
|
|
|
|
_expensesSubscription = _expenseRepository
|
|
.getExpensesStream(event.groupId)
|
|
.listen(
|
|
(expenses) => add(ExpensesUpdated(expenses)),
|
|
onError: (error) => add(ExpensesUpdated([], error: error.toString())),
|
|
);
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseBloc', 'Error loading expenses: $e');
|
|
emit(ExpenseError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Handles [ExpensesUpdated] events.
|
|
///
|
|
/// Processes real-time updates from the expense stream, either emitting
|
|
/// the updated expense list or an error state if the stream encountered an error.
|
|
///
|
|
/// Args:
|
|
/// [event]: The ExpensesUpdated event containing expenses or error information
|
|
/// [emit]: State emitter function
|
|
Future<void> _onExpensesUpdated(
|
|
ExpensesUpdated event,
|
|
Emitter<ExpenseState> emit,
|
|
) async {
|
|
if (event.error != null) {
|
|
emit(ExpenseError(event.error!));
|
|
} else {
|
|
emit(ExpensesLoaded(expenses: event.expenses));
|
|
}
|
|
}
|
|
|
|
/// Handles [CreateExpense] events.
|
|
///
|
|
/// Creates a new expense with validation and optional receipt image upload.
|
|
/// Uses the expense service to handle business logic and validation,
|
|
/// including currency conversion and split calculations.
|
|
///
|
|
/// Args:
|
|
/// [event]: The CreateExpense event containing expense data and optional receipt
|
|
/// [emit]: State emitter function
|
|
Future<void> _onCreateExpense(
|
|
CreateExpense event,
|
|
Emitter<ExpenseState> emit,
|
|
) async {
|
|
try {
|
|
await _expenseService.createExpenseWithValidation(event.expense, event.receiptImage);
|
|
emit(const ExpenseOperationSuccess('Expense created successfully'));
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseBloc', 'Error creating expense: $e');
|
|
emit(ExpenseError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Handles [UpdateExpense] events.
|
|
///
|
|
/// Updates an existing expense with validation and optional new receipt image.
|
|
/// Uses the expense service to handle business logic, validation, and
|
|
/// recalculation of splits if expense details change.
|
|
///
|
|
/// Args:
|
|
/// [event]: The UpdateExpense event containing updated expense data and optional new receipt
|
|
/// [emit]: State emitter function
|
|
Future<void> _onUpdateExpense(
|
|
UpdateExpense event,
|
|
Emitter<ExpenseState> emit,
|
|
) async {
|
|
try {
|
|
await _expenseService.updateExpenseWithValidation(event.expense, event.newReceiptImage);
|
|
emit(const ExpenseOperationSuccess('Expense updated successfully'));
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseBloc', 'Error updating expense: $e');
|
|
emit(ExpenseError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Handles [DeleteExpense] events.
|
|
///
|
|
/// Permanently deletes an expense from the database. This action
|
|
/// cannot be undone and will affect group balance calculations.
|
|
///
|
|
/// Args:
|
|
/// [event]: The DeleteExpense event containing the expense ID to delete
|
|
/// [emit]: State emitter function
|
|
Future<void> _onDeleteExpense(
|
|
DeleteExpense event,
|
|
Emitter<ExpenseState> emit,
|
|
) async {
|
|
try {
|
|
await _expenseRepository.deleteExpense(event.expenseId);
|
|
emit(const ExpenseOperationSuccess('Expense deleted successfully'));
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseBloc', 'Error deleting expense: $e');
|
|
emit(ExpenseError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Handles [MarkSplitAsPaid] events.
|
|
///
|
|
/// Marks a user's portion of an expense split as paid, updating the
|
|
/// expense's split information and affecting balance calculations.
|
|
/// This helps track who has settled their portion of shared expenses.
|
|
///
|
|
/// Args:
|
|
/// [event]: The MarkSplitAsPaid event containing expense ID and user ID
|
|
/// [emit]: State emitter function
|
|
Future<void> _onMarkSplitAsPaid(
|
|
MarkSplitAsPaid event,
|
|
Emitter<ExpenseState> emit,
|
|
) async {
|
|
try {
|
|
await _expenseRepository.markSplitAsPaid(event.expenseId, event.userId);
|
|
emit(const ExpenseOperationSuccess('Payment marked as completed'));
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseBloc', 'Error marking split as paid: $e');
|
|
emit(ExpenseError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Handles [ArchiveExpense] events.
|
|
///
|
|
/// Archives an expense, moving it out of the active expense list
|
|
/// while preserving it for historical records and audit purposes.
|
|
/// Archived expenses are not included in current balance calculations.
|
|
///
|
|
/// Args:
|
|
/// [event]: The ArchiveExpense event containing the expense ID to archive
|
|
/// [emit]: State emitter function
|
|
Future<void> _onArchiveExpense(
|
|
ArchiveExpense event,
|
|
Emitter<ExpenseState> emit,
|
|
) async {
|
|
try {
|
|
await _expenseRepository.archiveExpense(event.expenseId);
|
|
emit(const ExpenseOperationSuccess('Expense archived successfully'));
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseBloc', 'Error archiving expense: $e');
|
|
emit(ExpenseError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Cleans up resources when the bloc is closed.
|
|
///
|
|
/// Cancels the expense stream subscription to prevent memory leaks
|
|
/// and ensure proper disposal of resources.
|
|
@override
|
|
Future<void> close() {
|
|
_expensesSubscription?.cancel();
|
|
return super.close();
|
|
}
|
|
} |