/// A BLoC (Business Logic Component) that manages balance-related operations for groups. /// /// This bloc handles the calculation and management of user balances within groups, /// including optimal settlement calculations and marking settlements as completed. /// /// The bloc processes three main events: /// - [LoadGroupBalances]: Loads and calculates all user balances for a specific group /// - [RefreshBalance]: Refreshes the balance data while maintaining the current state when possible /// - [MarkSettlementAsCompleted]: Records a settlement transaction between users /// /// Dependencies: /// - [BalanceRepository]: Repository for balance data operations /// - [BalanceService]: Service for balance calculations and settlement logic /// - [ErrorService]: Service for error logging and handling /// /// Example usage: /// ```dart /// final balanceBloc = BalanceBloc( /// balanceRepository: balanceRepository, /// expenseRepository: expenseRepository, /// ); /// /// // Load balances for a group /// balanceBloc.add(LoadGroupBalances('groupId123')); /// /// // Mark a settlement as completed /// balanceBloc.add(MarkSettlementAsCompleted( /// groupId: 'groupId123', /// fromUserId: 'user1', /// toUserId: 'user2', /// amount: 50.0, /// )); /// ``` library; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../repositories/balance_repository.dart'; import '../../repositories/expense_repository.dart'; import '../../services/balance_service.dart'; import '../../services/error_service.dart'; import 'balance_event.dart'; import 'balance_state.dart'; class BalanceBloc extends Bloc { final BalanceRepository _balanceRepository; final BalanceService _balanceService; final ErrorService _errorService; /// Constructor for BalanceBloc. /// /// Initializes the bloc with required repositories and optional services. /// Sets up event handlers for balance-related operations. /// /// Args: /// [balanceRepository]: Repository for balance data operations /// [expenseRepository]: Repository for expense data operations /// [balanceService]: Optional service for balance calculations (auto-created if null) /// [errorService]: Optional service for error handling (auto-created if null) BalanceBloc({ required BalanceRepository balanceRepository, required ExpenseRepository expenseRepository, BalanceService? balanceService, ErrorService? errorService, }) : _balanceRepository = balanceRepository, _balanceService = balanceService ?? BalanceService( balanceRepository: balanceRepository, expenseRepository: expenseRepository, ), _errorService = errorService ?? ErrorService(), super(BalanceInitial()) { on(_onLoadGroupBalance); on(_onRefreshBalance); on(_onMarkSettlementAsCompleted); } /// Handles [LoadGroupBalances] events. /// /// Loads and calculates user balances for a specific group along with /// optimal settlement recommendations. This provides a complete overview /// of who owes money to whom and the most efficient payment strategy. /// /// Args: /// [event]: The LoadGroupBalances event containing the group ID /// [emit]: State emitter function Future _onLoadGroupBalance( LoadGroupBalances event, Emitter emit, ) async { try { // Emit empty state initially to avoid infinite spinner emit(const GroupBalancesLoaded(balances: [], settlements: [])); // Calculate group user balances final userBalances = await _balanceRepository.calculateGroupUserBalances( event.groupId, ); // Calculate optimal settlements final settlements = await _balanceService.calculateOptimalSettlements( event.groupId, ); emit( GroupBalancesLoaded(balances: userBalances, settlements: settlements), ); } catch (e, stackTrace) { _errorService.logError( 'BalanceBloc', 'Error loading balance: $e', stackTrace, ); emit(const BalanceError('Impossible de charger la balance')); } } /// Handles [RefreshBalance] events. /// /// Refreshes the balance data for a group while trying to maintain the current /// state when possible to provide a smoother user experience. Only shows loading /// state if there's no existing balance data. /// /// Args: /// [event]: The RefreshBalance event containing the group ID /// [emit]: State emitter function Future _onRefreshBalance( RefreshBalance event, Emitter emit, ) async { try { // Keep current state during refresh if possible if (state is! GroupBalancesLoaded) { emit(BalanceLoading()); } // Calculate group user balances final userBalances = await _balanceRepository.calculateGroupUserBalances( event.groupId, ); // Calculate optimal settlements final settlements = await _balanceService.calculateOptimalSettlements( event.groupId, ); emit( GroupBalancesLoaded(balances: userBalances, settlements: settlements), ); } catch (e, stackTrace) { _errorService.logError( 'BalanceBloc', 'Error refreshing balance: $e', stackTrace, ); emit(const BalanceError('Impossible de rafraîchir la balance')); } } /// Handles [MarkSettlementAsCompleted] events. /// /// Records a settlement transaction between two users, marking that /// a debt has been paid. This updates the balance calculations and /// automatically refreshes the group balance data to reflect the change. /// /// Args: /// [event]: The MarkSettlementAsCompleted event containing settlement details /// [emit]: State emitter function Future _onMarkSettlementAsCompleted( MarkSettlementAsCompleted event, Emitter emit, ) async { try { await _balanceService.markSettlementAsCompleted( groupId: event.groupId, fromUserId: event.fromUserId, toUserId: event.toUserId, amount: event.amount, ); emit(const BalanceOperationSuccess('Settlement marked as completed')); // Reload balance after settlement add(RefreshBalance(event.groupId)); } catch (e, stackTrace) { _errorService.logError( 'BalanceBloc', 'Error marking settlement: $e', stackTrace, ); emit( const BalanceError('Impossible de marquer le règlement comme terminé'), ); } } }