feat: Add logger service and improve expense dialog with enhanced receipt management and calculation logic.

This commit is contained in:
Van Leemput Dayron
2025-11-28 12:54:54 +01:00
parent cad9d42128
commit fd710b8cb8
35 changed files with 2148 additions and 1296 deletions

View File

@@ -7,7 +7,7 @@ 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
@@ -15,18 +15,18 @@ import 'expense_state.dart';
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({
@@ -34,10 +34,11 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
ExpenseService? expenseService,
ErrorService? errorService,
}) : _expenseRepository = expenseRepository,
_expenseService = expenseService ?? ExpenseService(expenseRepository: expenseRepository),
_expenseService =
expenseService ??
ExpenseService(expenseRepository: expenseRepository),
_errorService = errorService ?? ErrorService(),
super(ExpenseInitial()) {
on<LoadExpensesByGroup>(_onLoadExpensesByGroup);
on<ExpensesUpdated>(_onExpensesUpdated);
on<CreateExpense>(_onCreateExpense);
@@ -48,7 +49,7 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// 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(
@@ -56,15 +57,18 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
Emitter<ExpenseState> emit,
) async {
try {
emit(ExpenseLoading());
// Emit empty state initially to avoid infinite spinner
// The stream will update with actual data when available
emit(const ExpensesLoaded(expenses: []));
await _expensesSubscription?.cancel();
_expensesSubscription = _expenseRepository
.getExpensesStream(event.groupId)
.listen(
(expenses) => add(ExpensesUpdated(expenses)),
onError: (error) => add(ExpensesUpdated([], error: error.toString())),
onError: (error) =>
add(ExpensesUpdated([], error: error.toString())),
);
} catch (e) {
_errorService.logError('ExpenseBloc', 'Error loading expenses: $e');
@@ -73,10 +77,10 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// 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
@@ -92,11 +96,11 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// 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
@@ -105,7 +109,10 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
Emitter<ExpenseState> emit,
) async {
try {
await _expenseService.createExpenseWithValidation(event.expense, event.receiptImage);
await _expenseService.createExpenseWithValidation(
event.expense,
event.receiptImage,
);
emit(const ExpenseOperationSuccess('Expense created successfully'));
} catch (e) {
_errorService.logError('ExpenseBloc', 'Error creating expense: $e');
@@ -114,11 +121,11 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// 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
@@ -127,7 +134,10 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
Emitter<ExpenseState> emit,
) async {
try {
await _expenseService.updateExpenseWithValidation(event.expense, event.newReceiptImage);
await _expenseService.updateExpenseWithValidation(
event.expense,
event.newReceiptImage,
);
emit(const ExpenseOperationSuccess('Expense updated successfully'));
} catch (e) {
_errorService.logError('ExpenseBloc', 'Error updating expense: $e');
@@ -136,10 +146,10 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// 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
@@ -157,11 +167,11 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// 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
@@ -179,11 +189,11 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// 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
@@ -201,7 +211,7 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
}
/// Cleans up resources when the bloc is closed.
///
///
/// Cancels the expense stream subscription to prevent memory leaks
/// and ensure proper disposal of resources.
@override
@@ -209,4 +219,4 @@ class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
_expensesSubscription?.cancel();
return super.close();
}
}
}