import 'dart:convert'; import 'dart:io'; import 'package:csv/csv.dart'; import 'package:equatable/equatable.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:formz/formz.dart'; import 'package:tunas/domains/account/models/transaction_account.dart'; import 'package:tunas/domains/account/models/transaction_category.dart'; import 'package:tunas/domains/account/models/transaction_date.dart'; import 'package:tunas/domains/account/models/transaction_description.dart'; import 'package:tunas/domains/account/models/transaction_line.dart'; import 'package:tunas/domains/account/models/transaction_value.dart'; import 'package:tunas/repositories/account/account_repository.dart'; import 'package:tunas/repositories/account/models/transaction.dart'; import 'package:uuid/uuid.dart'; part 'account_event.dart'; part 'account_state.dart'; class AccountBloc extends Bloc { final AccountRepository _accountRepository; AccountBloc({required AccountRepository accountRepository}) : _accountRepository = accountRepository, super(const AccountState()) { on(_onAccountLoad); on(_onAccountImportJSON); on(_onAccountImportCSV); // on(_onAccountImportJSON); // on(_onAccountImportJSON); on(_onTransactionDateChange); on(_onTransactionCategoryChange); on(_onTransactionDescriptionChange); on(_onTransactionAccountChange); on(_onTransactionValueChange); on(_onTransactionOpenAddDialog); on(_onTransactionHideAddDialog); on(_onTransactionAddDialog); on(_onTransactionSetCurrent); on(_onTransactionDeleteCurrent); _accountRepository .getTransactionsStream() .listen((transactions) => add(AccountLoad(transactions))); } _onAccountLoad(AccountLoad event, Emitter emit) { var computeResult = _computeTransactionLine(event.transactions); emit(state.copyWith( transactions: event.transactions, transactionsLines: computeResult.list, globalTotal: computeResult.globalTotal, accountsTotals: computeResult.accountsTotals, categories: computeResult.categories )); } _onAccountImportCSV( AccountImportCSV event, Emitter emit) async { FilePickerResult? result = await FilePicker.platform.pickFiles(); final csvPath = result?.files.first.path; if (csvPath != null) { final File csv = File(csvPath); final String csvFileContent = await csv.readAsString(); final List> csvList = const CsvToListConverter(fieldDelimiter: '|').convert(csvFileContent); final transactions = csvList .map((line) => Transaction( uuid: const Uuid().v8(), date: DateTime.parse(line[0]), category: line[1], description: line[2], account: line[3], value: double.parse(line[4])) ) .toList(); await _accountRepository.saveTransactions(transactions); } } _onAccountImportJSON( AccountImportJSON event, Emitter emit) async { FilePickerResult? result = await FilePicker.platform.pickFiles(); final jsonPath = result?.files.first.path; if (jsonPath != null) { final File json = File(jsonPath); final String jsonString = await json.readAsString(); final List jsonList = jsonDecode(jsonString); final List transactions = jsonList.map((transaction) => Transaction.fromJson(transaction)).toList(); await _accountRepository.saveTransactions(transactions); } } ({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()); } _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]), )); } _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]), )); } _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]), )); } _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]), )); } _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]), )); } } _onTransactionOpenAddDialog( TransactionOpenAddDialog event, Emitter emit ) { emit(state.copyWith(showAddDialog: true)); } _onTransactionHideAddDialog( TransactionHideAddDialog event, Emitter emit ) { emit(state.copyWith(showAddDialog: false)); } _onTransactionAddDialog( TransactionAdd event, Emitter emit ) async { 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); await _accountRepository.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, globalTotal: computeResult.globalTotal, accountsTotals: computeResult.accountsTotals, )); } } _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), )); } } _onTransactionDeleteCurrent( TransactionDeleteCurrent event, Emitter emit ) async { Transaction? currentTransaction = state.currentTransaction; if (currentTransaction != null) { List transactions = state.transactions; transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); final computeResult = _computeTransactionLine(transactions); await _accountRepository.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, globalTotal: computeResult.globalTotal, accountsTotals: computeResult.accountsTotals, )); } } }