LCOV - code coverage report
Current view: top level - lib/_classes/storage - app_data.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 96 144 66.7 %
Date: 2024-10-04 11:12:13 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2023 The terCAD team. All rights reserved.
       2             : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
       3             : 
       4             : import 'dart:collection';
       5             : import 'package:app_finance/_classes/controller/iterator_controller.dart';
       6             : import 'package:app_finance/_classes/herald/app_sync.dart';
       7             : import 'package:app_finance/_classes/math/goal_recalculation.dart';
       8             : import 'package:app_finance/_classes/math/invoice_recalculation.dart';
       9             : import 'package:app_finance/_classes/storage/history_data.dart';
      10             : import 'package:app_finance/_classes/structure/account_app_data.dart';
      11             : import 'package:app_finance/_classes/structure/bill_app_data.dart';
      12             : import 'package:app_finance/_classes/math/bill_recalculation.dart';
      13             : import 'package:app_finance/_classes/structure/budget_app_data.dart';
      14             : import 'package:app_finance/_classes/math/budget_recalculation.dart';
      15             : import 'package:app_finance/_classes/structure/currency_app_data.dart';
      16             : import 'package:app_finance/_classes/structure/currency/exchange.dart';
      17             : import 'package:app_finance/_classes/structure/goal_app_data.dart';
      18             : import 'package:app_finance/_classes/structure/interface_app_data.dart';
      19             : import 'package:app_finance/_classes/structure/invoice_app_data.dart';
      20             : import 'package:app_finance/_classes/structure/summary_app_data.dart';
      21             : import 'package:app_finance/_classes/math/total_recalculation.dart';
      22             : import 'package:app_finance/_classes/storage/transaction_log.dart';
      23             : import 'package:app_finance/_ext/iterable_ext.dart';
      24             : import 'package:flutter/material.dart';
      25             : import 'package:uuid/uuid.dart';
      26             : 
      27             : enum AppDataType {
      28             :   goals,
      29             :   bills,
      30             :   accounts,
      31             :   budgets,
      32             :   currencies,
      33             :   invoice,
      34             : }
      35             : 
      36             : typedef AppDataGetter = ({
      37             :   List<dynamic> list,
      38             :   double total,
      39             :   InterfaceIterator stream,
      40             : });
      41             : 
      42             : class AppData extends ChangeNotifier {
      43             :   final AppSync appSync;
      44             :   bool isLoading = false;
      45             :   final _hashTable = HashMap<String, dynamic>();
      46             :   final _data = <AppDataType, SummaryAppData>{};
      47             : 
      48           2 :   AppData(this.appSync) : super() {
      49           1 :     isLoading = true;
      50           2 :     for (var key in AppDataType.values) {
      51           3 :       _data[key] = SummaryAppData();
      52             :     }
      53           2 :     Exchange(store: this).getDefaultCurrency();
      54           3 :     TransactionLog.load(this).then((_) async => await restate()).then((_) => appSync.follow(AppData, _stream));
      55             :   }
      56             : 
      57           1 :   @override
      58             :   dispose() {
      59           1 :     super.dispose();
      60           2 :     appSync.unfollow(AppData);
      61             :   }
      62             : 
      63           0 :   void _stream(String value) {
      64             :     try {
      65           0 :       TransactionLog.add(this, value, true, true);
      66             :     } catch (e) {
      67             :       //...
      68             :     }
      69             :   }
      70             : 
      71           0 :   Future<void> flush() async {
      72           0 :     isLoading = true;
      73           0 :     _hashTable.clear();
      74           0 :     _data.updateAll((key, value) => SummaryAppData(total: 0, list: []));
      75           0 :     await TransactionLog.load(this);
      76           0 :     await restate();
      77             :   }
      78             : 
      79           0 :   Future<void> restate() async {
      80           0 :     await updateTotals(AppDataType.values);
      81           0 :     isLoading = false;
      82           0 :     notifyListeners();
      83             :   }
      84             : 
      85           1 :   void _set(AppDataType property, dynamic value) {
      86           3 :     _hashTable[value.uuid] = value;
      87           5 :     _data[property]?.add(value.uuid, updatedAt: value.createdAt);
      88           1 :     if (!isLoading) {
      89           1 :       TransactionLog.save(value);
      90           3 :       appSync.send(value.toStream());
      91             :     }
      92           1 :     _notify();
      93             :   }
      94             : 
      95           1 :   void _notify([_]) {
      96           1 :     if (!isLoading) {
      97           4 :       WidgetsBinding.instance.addPostFrameCallback((_) => notifyListeners());
      98             :     }
      99             :   }
     100             : 
     101           1 :   dynamic add(InterfaceAppData value, [String? uuid]) {
     102           2 :     value.uuid = uuid ?? const Uuid().v4();
     103           1 :     _update(null, value);
     104           2 :     return getByUuid(value.uuid!);
     105             :   }
     106             : 
     107           1 :   void update(String uuid, InterfaceAppData value, [bool createIfMissing = false]) {
     108           1 :     var initial = getByUuid(uuid, false);
     109             :     if (initial != null || createIfMissing) {
     110           1 :       _update(initial, value);
     111             :     }
     112             :   }
     113             : 
     114           1 :   Future<void> updateTotals(List<AppDataType> scope) async {
     115           1 :     final accountTotal = getTotal(AppDataType.accounts);
     116           1 :     final exchange = Exchange(store: this);
     117           1 :     final rec = TotalRecalculation(exchange: exchange);
     118           2 :     for (AppDataType type in scope) {
     119           4 :       await rec.updateTotal(type, _data[type], _hashTable);
     120             :     }
     121           1 :     if (scope.contains(AppDataType.accounts)) {
     122           3 :       rec.updateGoals(getList(AppDataType.goals, false), accountTotal, getTotal(AppDataType.accounts));
     123             :     }
     124             :   }
     125             : 
     126           1 :   void _update(InterfaceAppData? initial, InterfaceAppData change) {
     127           2 :     if (change.getType() != AppDataType.budgets) {
     128           4 :       HistoryData.addLog(change.uuid, change, initial?.details ?? 0.0, change.details);
     129             :     }
     130           1 :     switch (change.getType()) {
     131           1 :       case AppDataType.accounts:
     132           1 :         _updateAccount(initial as AccountAppData?, change as AccountAppData);
     133             :         break;
     134           1 :       case AppDataType.bills:
     135           1 :         (change as BillAppData).setState(this);
     136           1 :         _updateBill(initial as BillAppData?, change);
     137             :         break;
     138           1 :       case AppDataType.budgets:
     139           1 :         (change as BudgetAppData).setState(this);
     140           1 :         _updateBudget(initial as BudgetAppData?, change);
     141           3 :         HistoryData.addLog(change.uuid, change, initial?.amountLimit ?? 0.0, change.amountLimit);
     142             :         break;
     143           1 :       case AppDataType.goals:
     144           0 :         _updateGoal(initial as GoalAppData?, change as GoalAppData);
     145             :         break;
     146           1 :       case AppDataType.currencies:
     147           1 :         _updateCurrency(initial as CurrencyAppData?, change as CurrencyAppData);
     148             :         break;
     149           0 :       case AppDataType.invoice:
     150           0 :         (change as InvoiceAppData).setState(this);
     151           0 :         _updateInvoice(initial as InvoiceAppData?, change);
     152             :         break;
     153             :     }
     154             :   }
     155             : 
     156           0 :   void _updateInvoice(InvoiceAppData? initial, InvoiceAppData change) {
     157           0 :     _set(AppDataType.invoice, change);
     158           0 :     AccountAppData? currAccount = getByUuid(change.account, false);
     159             :     AccountAppData? prevAccount;
     160             :     if (initial != null) {
     161           0 :       prevAccount = getByUuid(initial.account, false);
     162             :       if (prevAccount != null) {
     163           0 :         _data[AppDataType.accounts]?.add(initial.account);
     164             :       }
     165             :     }
     166             :     if (currAccount != null) {
     167           0 :       final rec = InvoiceRecalculation(change, initial)..exchange = Exchange(store: this);
     168           0 :       rec.updateAccount(currAccount, prevAccount);
     169           0 :       _data[AppDataType.accounts]?.add(change.account);
     170           0 :       if (change.accountFrom != null) {
     171           0 :         rec.updateAccount(getByUuid(change.accountFrom!, false),
     172           0 :             initial != null && initial.accountFrom != null ? getByUuid(initial.accountFrom!, false) : null, true);
     173           0 :         _data[AppDataType.accounts]?.add(change.accountFrom!);
     174             :       }
     175             :     }
     176           0 :     if (!isLoading) {
     177           0 :       updateTotals([AppDataType.accounts]).then(_notify);
     178             :     }
     179             :   }
     180             : 
     181           1 :   void _updateAccount(AccountAppData? initial, AccountAppData change) {
     182           1 :     _set(AppDataType.accounts, change);
     183           1 :     if (!isLoading) {
     184           4 :       updateTotals([AppDataType.accounts]).then(_notify);
     185             :     }
     186             :   }
     187             : 
     188           1 :   void _updateBill(BillAppData? initial, BillAppData change) {
     189           2 :     AccountAppData? currAccount = getByUuid(change.account, false);
     190             :     AccountAppData? prevAccount;
     191           2 :     BudgetAppData? currBudget = getByUuid(change.category, false);
     192             :     BudgetAppData? prevBudget;
     193             :     if (initial != null) {
     194           0 :       prevAccount = getByUuid(initial.account, false);
     195             :       if (prevAccount != null) {
     196           0 :         _data[AppDataType.accounts]?.add(initial.account);
     197             :       }
     198           0 :       prevBudget = getByUuid(initial.category, false);
     199             :       if (prevBudget != null) {
     200           0 :         _data[AppDataType.budgets]?.add(initial.category);
     201             :       }
     202             :     }
     203           3 :     final rec = BillRecalculation(change: change, initial: initial)..exchange = Exchange(store: this);
     204             :     if (currAccount != null) {
     205           1 :       rec.updateAccount(currAccount, prevAccount);
     206           4 :       _data[AppDataType.accounts]?.add(change.account);
     207             :     }
     208             :     if (currBudget != null) {
     209           1 :       rec.updateBudget(currBudget, prevBudget);
     210           4 :       _data[AppDataType.budgets]?.add(change.category);
     211             :     }
     212           1 :     _set(AppDataType.bills, change);
     213           1 :     if (!isLoading) {
     214           4 :       updateTotals([AppDataType.bills, AppDataType.accounts, AppDataType.budgets]).then(_notify);
     215             :     }
     216             :   }
     217             : 
     218           1 :   void _updateBudget(BudgetAppData? initial, BudgetAppData change) {
     219           1 :     BudgetRecalculation(change: change, initial: initial)
     220           2 :       ..exchange = Exchange(store: this)
     221           1 :       ..updateBudget();
     222           1 :     _set(AppDataType.budgets, change);
     223           1 :     if (!isLoading) {
     224           4 :       updateTotals([AppDataType.budgets]).then(_notify);
     225             :     }
     226             :   }
     227             : 
     228           0 :   void _updateGoal(GoalAppData? initial, GoalAppData change) {
     229           0 :     GoalRecalculation(change: change, initial: initial)
     230           0 :       ..exchange = Exchange(store: this)
     231           0 :       ..updateGoal();
     232           0 :     _set(AppDataType.goals, change);
     233           0 :     if (!isLoading) {
     234           0 :       updateTotals([AppDataType.goals]).then(_notify);
     235             :     }
     236             :   }
     237             : 
     238           1 :   void _updateCurrency(CurrencyAppData? initial, CurrencyAppData change) {
     239           1 :     _set(AppDataType.currencies, change);
     240           1 :     if (!isLoading) {
     241           3 :       updateTotals(AppDataType.values).then(_notify);
     242             :     }
     243             :   }
     244             : 
     245           1 :   AppDataGetter get(AppDataType property) {
     246             :     return (
     247           1 :       list: getList(property),
     248           1 :       total: getTotal(property),
     249           1 :       stream: getStream<InterfaceAppData>(property),
     250             :     );
     251             :   }
     252             : 
     253           1 :   List<dynamic> getList(AppDataType property, [bool isClone = true]) {
     254           3 :     return (_data[property]?.list ?? [])
     255           3 :         .map((uuid) => getByUuid(uuid, isClone))
     256           3 :         .where((element) => !element.hidden)
     257           1 :         .toList();
     258             :   }
     259             : 
     260           1 :   InterfaceIterator getStream<M extends InterfaceAppData>(AppDataType property,
     261             :       {bool inverse = true, double? boundary, Function? filter}) {
     262           2 :     if (_data[property] == null) {
     263           0 :       return IteratorController<num, dynamic, M>(SplayTreeMap<num, dynamic>(), transform: getByUuid);
     264             :     }
     265           4 :     return _data[property]!.origin.toStream<M>(
     266             :           inverse,
     267           1 :           transform: getByUuid,
     268             :           boundary: boundary,
     269           0 :           filter: (M v) => v.hidden || filter?.call(v) == true,
     270             :         );
     271             :   }
     272             : 
     273           0 :   List<dynamic> getActualList(AppDataType property, [bool isClone = true]) {
     274           0 :     return (_data[property]?.listActual ?? [])
     275           0 :         .map((uuid) => getByUuid(uuid, isClone))
     276           0 :         .where((element) => !element.hidden)
     277           0 :         .toList();
     278             :   }
     279             : 
     280           1 :   double getTotal(AppDataType property) {
     281           3 :     return _data[property]?.total ?? 0.0;
     282             :   }
     283             : 
     284           1 :   dynamic getByUuid(String uuid, [bool isClone = true]) {
     285           1 :     if (uuid == '') return null;
     286           5 :     var obj = isClone ? _hashTable[uuid]?.clone() : _hashTable[uuid];
     287           3 :     if (obj is BillAppData || obj is BudgetAppData || obj is InvoiceAppData) {
     288           1 :       obj.setState(this);
     289             :     }
     290             :     return obj;
     291             :   }
     292             : }

Generated by: LCOV version 1.14