import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; import 'package:krezus/domains/transaction/models/transaction_account.dart'; import 'package:krezus/domains/transaction/models/transaction_category.dart'; import 'package:krezus/domains/transaction/models/transaction_date.dart'; import 'package:krezus/domains/transaction/models/transaction_description.dart'; import 'package:krezus/domains/transaction/models/transaction_line.dart'; import 'package:krezus/domains/transaction/models/transaction_value.dart'; import 'package:krezus/repositories/metadata/models/account.dart'; import 'package:krezus/repositories/metadata/models/category.dart'; import 'package:krezus/repositories/transactions/models/transaction.dart'; import 'package:krezus/repositories/transactions/transactions_repository.dart'; import 'package:uuid/uuid.dart'; part 'transaction_event.dart'; part 'transaction_state.dart'; class TransactionBloc extends Bloc { final TransactionsRepository _transactionsRepository; TransactionBloc({required TransactionsRepository transactionsRepository}) : _transactionsRepository = transactionsRepository, super(const TransactionState()) { on(_onAccountLoad); on(_onTransactionDateChange); on(_onTransactionCategoryChange); on(_onTransactionDescriptionChange); on(_onTransactionAccountChange); on(_onTransactionValueChange); on(_onTransactionOpenAddDialog); on(_onTransactionHideAddDialog); on(_onTransactionAddDialog); on(_onTransactionSetCurrent); on(_onTransactionDeleteCurrent); on(_onTransactionFilterCategory); on(_onTransactionFilterAccount); _transactionsRepository .getTransactionsStream() .listen((transactions) => add(TransactionsLoad(transactions))); } FutureOr _onAccountLoad(TransactionsLoad event, Emitter emit) { var computeResult = _computeTransactionLine(event.transactions); emit(state.copyWith( transactions: event.transactions, transactionsLines: computeResult.list, transactionsLinesFiltered: _applyFilters(computeResult.list), globalTotal: computeResult.globalTotal, accountsTotals: computeResult.accountsTotals, )); } ({List list, double globalTotal, Map accountsTotals, List categories}) _computeTransactionLine(List transactions) { double globalTotal = 0; Map accountsTotals = {}; List output = []; Set categories = {}; for(var transaction in transactions) { double subTotal = globalTotal + transaction.value; globalTotal = subTotal; double accountTotal = accountsTotals[transaction.account] ?? 0; accountTotal += transaction.value; accountsTotals[transaction.account] = accountTotal; categories.add(transaction.category); output.add(TransactionLine(transaction: transaction, subTotal: subTotal)); } output.sort((a, b) => b.transaction.date.compareTo(a.transaction.date)); return (list: output, globalTotal: globalTotal, accountsTotals: accountsTotals, categories: categories.toList()); } FutureOr _onTransactionDateChange( TransactionDateChange event, Emitter emit ) { final transactionDate = TransactionDate.dirty(event.date); emit(state.copyWith( transactionDate: transactionDate, isValid: Formz.validate([transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]), )); } FutureOr _onTransactionCategoryChange( TransactionCategoryChange event, Emitter emit ) { final transactionCategory = TransactionCategory.dirty(event.category); emit(state.copyWith( transactionCategory: transactionCategory, isValid: Formz.validate([state.transactionDate, transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]), )); } FutureOr _onTransactionDescriptionChange( TransactionDescriptionChange event, Emitter emit ) { final transactionDescription = TransactionDescription.dirty(event.description); emit(state.copyWith( transactionDescription: transactionDescription, isValid: Formz.validate([state.transactionDate, state.transactionCategory, transactionDescription, state.transactionAccount, state.transactionValue]), )); } FutureOr _onTransactionAccountChange( TransactionAccountChange event, Emitter emit ) { final transactionAccount = TransactionAccount.dirty(event.account); emit(state.copyWith( transactionAccount: transactionAccount, isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, transactionAccount, state.transactionValue]), )); } FutureOr _onTransactionValueChange( TransactionValueChange event, Emitter emit ) { try { final transactionValue = TransactionValue.dirty(double.parse(event.value)); emit(state.copyWith( transactionValue: transactionValue, isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]), )); } catch (e) { const transactionValue = TransactionValue.dirty(double.infinity); emit(state.copyWith( transactionValue: transactionValue, isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]), )); } } FutureOr _onTransactionOpenAddDialog( TransactionOpenAddDialog event, Emitter emit ) { emit(state.copyWith(showAddDialog: true)); } FutureOr _onTransactionHideAddDialog( TransactionHideAddDialog event, Emitter emit ) { emit(state.copyWith(showAddDialog: false)); } FutureOr _onTransactionAddDialog( TransactionAdd event, Emitter emit ) { if (state.isValid) { List transactions = state.transactions; Transaction? currentTransaction = state.currentTransaction; if (currentTransaction != null) { transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); } transactions.add(Transaction( uuid: const Uuid().v8(), date: state.transactionDate.value ?? DateTime.now(), category: state.transactionCategory.value, description: state.transactionDescription.value, account: state.transactionAccount.value, value: state.transactionValue.value )); final computeResult = _computeTransactionLine(transactions); _transactionsRepository.saveTransactions(transactions); emit(state.copyWith( currentTransaction: null, // transactionDate: const TransactionDate.pure(), // transactionCategory: const TransactionCategory.pure(), // transactionDescription: const TransactionDescription.pure(), // transactionAccount: const TransactionAccount.pure(), // transactionValue: const TransactionValue.pure(), transactions: transactions, transactionsLines: computeResult.list, transactionsLinesFiltered: _applyFilters(computeResult.list), globalTotal: computeResult.globalTotal, accountsTotals: computeResult.accountsTotals, )); } } FutureOr _onTransactionSetCurrent( TransactionSetCurrent event, Emitter emit ) { Transaction? transaction = event.transaction; if (transaction == null) { emit(state.copyWith( currentTransaction: event.transaction, transactionDate: const TransactionDate.pure(), transactionCategory: const TransactionCategory.pure(), transactionDescription: const TransactionDescription.pure(), transactionAccount: const TransactionAccount.pure(), transactionValue: const TransactionValue.pure(), )); } else { emit(state.copyWith( currentTransaction: transaction, transactionDate: TransactionDate.dirty(transaction.date), transactionCategory: TransactionCategory.dirty(transaction.category), transactionDescription: TransactionDescription.dirty(transaction.description), transactionAccount: TransactionAccount.dirty(transaction.account), transactionValue: TransactionValue.dirty(transaction.value), )); } } FutureOr _onTransactionDeleteCurrent( TransactionDeleteCurrent event, Emitter emit ) { Transaction? currentTransaction = state.currentTransaction; if (currentTransaction != null) { List transactions = state.transactions; transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); final computeResult = _computeTransactionLine(transactions); _transactionsRepository.saveTransactions(transactions); emit(state.copyWith( currentTransaction: null, transactionDate: const TransactionDate.pure(), transactionCategory: const TransactionCategory.pure(), transactionDescription: const TransactionDescription.pure(), transactionAccount: const TransactionAccount.pure(), transactionValue: const TransactionValue.pure(), transactions: transactions, transactionsLines: computeResult.list, transactionsLinesFiltered: _applyFilters(computeResult.list), globalTotal: computeResult.globalTotal, accountsTotals: computeResult.accountsTotals, )); } } FutureOr _onTransactionFilterCategory(TransactionFilterCategory event, Emitter emit) { emit(TransactionState( globalTotal: state.globalTotal, accountsTotals: state.accountsTotals, transactions: state.transactions, transactionsLines: state.transactionsLines, transactionsLinesFiltered: state.transactionsLinesFiltered, transactionDate: state.transactionDate, transactionCategory: state.transactionCategory, transactionDescription: state.transactionDescription, transactionAccount: state.transactionAccount, transactionValue: state.transactionValue, isValid: state.isValid, showAddDialog: state.showAddDialog, currentTransaction: state.currentTransaction, categoryFilter: event.category, accountFilter: state.accountFilter, )); emit(state.copyWith( transactionsLinesFiltered: _applyFilters(state.transactionsLines), )); } FutureOr _onTransactionFilterAccount(TransactionFilterAccount event, Emitter emit) { emit(TransactionState( globalTotal: state.globalTotal, accountsTotals: state.accountsTotals, transactions: state.transactions, transactionsLines: state.transactionsLines, transactionsLinesFiltered: state.transactionsLinesFiltered, transactionDate: state.transactionDate, transactionCategory: state.transactionCategory, transactionDescription: state.transactionDescription, transactionAccount: state.transactionAccount, transactionValue: state.transactionValue, isValid: state.isValid, showAddDialog: state.showAddDialog, currentTransaction: state.currentTransaction, categoryFilter: state.categoryFilter, accountFilter: event.account, )); emit(state.copyWith( transactionsLinesFiltered: _applyFilters(state.transactionsLines), )); } List _applyFilters(List transactionsLines) { List transactionsLinesFiltered = transactionsLines; String? categoryLabel = state.categoryFilter?.label; if (categoryLabel != null) { transactionsLinesFiltered = transactionsLinesFiltered.where((transaction) => transaction.transaction.category == categoryLabel).toList(); } String? accountLabel = state.accountFilter?.label; if (accountLabel != null) { transactionsLinesFiltered = transactionsLinesFiltered.where((transaction) => transaction.transaction.account == accountLabel).toList(); } return transactionsLinesFiltered; } }