import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:krezus/repositories/metadata/models/budget.dart'; import 'package:krezus/repositories/metadata/metadata_repository.dart'; import 'package:krezus/repositories/transactions/transactions_repository.dart'; part 'budget_event.dart'; part 'budget_state.dart'; class BudgetBloc extends Bloc { final MetadataRepository _metadataRepository; final TransactionsRepository _transactionsRepository; Timer? setValueTimer; BudgetBloc({required MetadataRepository metadataRepository, required TransactionsRepository transactionsRepository}) : _metadataRepository = metadataRepository, _transactionsRepository = transactionsRepository, super(const BudgetState()) { on(_onBudgetsLoad); on(_onBudgetAdd); on(_onBudgetRemove); on(_onBudgetSetValue); on(_onBudgetSetLabel); on(_onBudgetCompareNext); on(_onBudgetComparePrevious); on(_onBudgetSetCompare); on(_onBudgetSetInitial); _metadataRepository .getBudgetsStream() .listen((budgets) => add(BudgetsLoad(budgets))); _transactionsRepository .getTransactionsStream() .listen((transactions) => add(BudgetSetCompare())); } _onBudgetsLoad( BudgetsLoad event, Emitter emit ) { emit(_computeState(event.budgets, null)); } FutureOr _onBudgetAdd(BudgetAdd event, Emitter emit) { Budget budget = Budget( label: event.label, ); List budgets = _computeBudgets(budget); _saveBudget(budgets); emit(_computeState(budgets, null)); } FutureOr _onBudgetRemove(BudgetRemove event, Emitter emit) { List budgets = _metadataRepository.getBudgets(); Budget budgetToRemove = event.budget; budgets.removeWhere((budget) => budget.label == budgetToRemove.label); emit(_computeState(_metadataRepository.saveBudgets(budgets), null)); } void _onBudgetSetValue(BudgetSetValue event, Emitter emit) { Budget budgetToUpdate = event.budget; double newValue = event.value; if (state.remainingBudget - (event.value - budgetToUpdate.value) < 0) { newValue = event.budget.value + state.remainingBudget; } // if (state.remainingBudget - event.value < 0 && state.remainingBudget < 10) { // budgetToUpdate.value = // } else { // budgetToUpdate.value = event.value; // } budgetToUpdate.value = newValue; List budgets = _computeBudgets(budgetToUpdate); emit(_computeState(budgets, null)); setValueTimer?.cancel(); setValueTimer = Timer( const Duration(milliseconds: 100), () { _saveBudget(budgets); } ); } void _onBudgetSetLabel(BudgetSetLabel event, Emitter emit) { Budget budgetToUpdate = event.budget; budgetToUpdate.label = event.label; List budgets = _computeBudgets(budgetToUpdate); _saveBudget(budgets); emit(_computeState(budgets, null)); } void _onBudgetCompareNext(BudgetCompareNext event, Emitter emit) { num compareMonth = state.compareMonth; num compareYear = state.compareYear; if (state.compareMonth == 12) { compareMonth = 1; compareYear++; } else { compareMonth++; } if (state.lastDate != null && state.lastDate!.isBefore(DateTime(compareYear.toInt(), compareMonth.toInt()))) { return; } final compareResult = _computeCompareBudget(state.budgets, compareYear, compareMonth); emit(state.copyWith( compareBudgets: compareResult.$1, otherBudgets: compareResult.$2, compareMonth: compareMonth, compareYear: compareYear, )); } void _onBudgetComparePrevious(BudgetComparePrevious event, Emitter emit) { num compareMonth = state.compareMonth; num compareYear = state.compareYear; if (state.compareMonth == 1) { compareMonth = 12; compareYear--; } else { compareMonth--; } if (state.firstDate != null && state.firstDate!.isAfter(DateTime(compareYear.toInt(), compareMonth.toInt()))) { return; } final compareResult = _computeCompareBudget(state.budgets, compareYear, compareMonth); emit(state.copyWith( compareBudgets: compareResult.$1, otherBudgets: compareResult.$2, compareMonth: compareMonth, compareYear: compareYear, )); } _onBudgetSetCompare(BudgetSetCompare event, Emitter emit) { DateTime firstDate = DateTime.now(); DateTime lastDate = DateTime.fromMicrosecondsSinceEpoch(0); for (var transaction in _transactionsRepository.getTransactions()) { if (firstDate.compareTo(transaction.date) > 0) { firstDate = transaction.date; } if (lastDate.compareTo(transaction.date) < 0) { lastDate = transaction.date; } } num compareMonth = state.compareMonth; num compareYear = state.compareYear; if (compareMonth == 1 && compareYear == 2000) { compareMonth = lastDate.month; compareYear = lastDate.year; } final compareResult = _computeCompareBudget(state.budgets, compareYear, compareMonth); emit(state.copyWith( firstDate: firstDate, lastDate: lastDate, compareMonth: compareMonth, compareYear: compareYear, compareBudgets: compareResult.$1, otherBudgets: compareResult.$2, )); } void _onBudgetSetInitial(BudgetSetInitial event, Emitter emit) { emit(_computeState(state.budgets, event.value)); } List _computeBudgets(Budget budgetToSave) { List budgets = _metadataRepository.getBudgets(); try { Budget budgetFound = budgets.firstWhere((category) => category.label == budgetToSave.label); budgetFound.value = budgetToSave.value; } catch (e) { if (budgets.isEmpty) { budgets = [budgetToSave]; } else { budgets.add(budgetToSave); } } return budgets; } List _saveBudget(List budgets) { return _metadataRepository.saveBudgets(budgets); } (List compareBudgets, List otherBudgets) _computeCompareBudget(List budgets, num year, num month) { Map compareBudgetMap = { for (var budget in budgets) budget.label : Budget(label: budget.label)}; Budget otherBudget = Budget(label: 'Hors budget'); Map otherBudgetMap = {}; List compareBudgets = []; _transactionsRepository.getTransactions() .where((transaction) => transaction.value < 0 && transaction.date.year == year && transaction.date.month == month) .forEach((transaction) { Budget? budget = compareBudgetMap[transaction.category]; if (budget == null) { otherBudget.value += transaction.value.abs(); Budget? otherBudgetFromMap = otherBudgetMap[transaction.category]; if (otherBudgetFromMap == null) { otherBudgetMap[transaction.category] = Budget(label: transaction.category, value: transaction.value.abs()); } else { otherBudgetFromMap.value += transaction.value.abs(); } } else { budget.value += transaction.value.abs(); } }); compareBudgets.addAll(compareBudgetMap.values); compareBudgets.add(otherBudget); return (compareBudgets, otherBudgetMap.values.toList()..sort((a, b) => b.value.compareTo(a.value))); } BudgetState _computeState(List budgets, double? initialBudget) { final compareResult = _computeCompareBudget(state.budgets, state.compareYear, state.compareMonth); final budgetValues = budgets.map((budget) => budget.value); final budgetReducedValues = budgetValues.isEmpty ? 0 : budgetValues.reduce((value, element) => value + element); return state.copyWith( budgets: budgets, initialBudget: (initialBudget ?? state.initialBudget), remainingBudget: (initialBudget ?? state.initialBudget) - budgetReducedValues, compareBudgets: compareResult.$1, otherBudgets: compareResult.$2, ); } }