LCOV - code coverage report
Current view: top level - lib/components/widgets - budget_ytd_chart.dart Coverage Total Hit
Test: lcov.info Lines: 1.5 % 65 1
Test Date: 2026-11-14 15:04:37 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:math';
       5              : 
       6              : import 'package:app_finance/_classes/herald/app_design.dart';
       7              : import 'package:app_finance/_classes/herald/app_locale.dart';
       8              : import 'package:app_finance/_classes/herald/app_start_of_month.dart';
       9              : import 'package:app_finance/_classes/storage/app_data.dart';
      10              : import 'package:app_finance/_classes/storage/history_data.dart';
      11              : import 'package:app_finance/_classes/structure/bill_app_data.dart';
      12              : import 'package:app_finance/_classes/structure/budget_app_data.dart';
      13              : import 'package:app_finance/_classes/structure/currency/exchange.dart';
      14              : import 'package:app_finance/_configs/theme_helper.dart';
      15              : import 'package:app_finance/_ext/build_context_ext.dart';
      16              : import 'package:app_finance/_ext/date_time_ext.dart';
      17              : import 'package:app_finance/charts/column_chart.dart';
      18              : import 'package:app_finance/charts/data/data_handler.dart';
      19              : import 'package:app_finance/charts/interface/chart_data.dart';
      20              : import 'package:app_finance/design/wrapper/row_widget.dart';
      21              : import 'package:app_finance/design/wrapper/text_wrapper.dart';
      22              : import 'package:material_ui/material_ui.dart';
      23              : import 'package:provider/provider.dart';
      24              : 
      25              : class BudgetYtdChart extends StatelessWidget {
      26            2 :   const BudgetYtdChart({super.key});
      27              : 
      28            0 :   List<BudgetAppData> getBudgetHistory(AppData store) {
      29            0 :     final day = AppStartOfMonth.get();
      30            0 :     final current = DateTime.now().getStartingDay(day);
      31            0 :     final endLastYear = DateTime(current.year, current.month - 12, day);
      32            0 :     final budgets = store.getList(AppDataType.budgets).cast<BudgetAppData>().where((v) => !v.skip).toList();
      33            0 :     final exchange = Exchange(store: store);
      34              :     final budgetsAmount =
      35            0 :         budgets.fold(0.0, (v, e) => v + exchange.reform(e.amountLimit, e.currency, exchange.getDefaultCurrency()));
      36            0 :     final budgetHistory = HistoryData.getMultiLog(budgets).expand((e) => e!).toList();
      37            0 :     final budgetList = [];
      38            0 :     for (DateTime date = DateTime(current.year, current.month + 1, day);
      39            0 :         date.isAfter(endLastYear);
      40            0 :         date = DateTime(date.year, date.month - 1, day)) {
      41            0 :       final delta = budgetHistory.where((e) => e.timestamp.isAfter(date)).fold(
      42            0 :           0.0, (v, e) => v - exchange.reform(e.changedTo - e.changedFrom, e.currency, exchange.getDefaultCurrency()));
      43            0 :       budgetList.add(BudgetAppData(title: '', createdAt: date, amountLimit: budgetsAmount + delta));
      44              :     }
      45            0 :     return budgetList.cast();
      46              :   }
      47              : 
      48            0 :   List<ChartData> getData(AppData store) {
      49            0 :     final day = AppStartOfMonth.get();
      50            0 :     final currentYear = DateTime(DateTime.now().year).getStartingDay(day);
      51            0 :     final prevYear = DateTime(currentYear.year - 1).getStartingDay(day);
      52            0 :     final bills = store.getStream<BillAppData>(AppDataType.bills, filter: (e) => e.createdAt.isBefore(prevYear));
      53            0 :     final exchange = Exchange(store: store);
      54            0 :     return [
      55            0 :       ChartData(
      56            0 :         DataHandler.getAmountGroupedByMonth(
      57            0 :           getBudgetHistory(store),
      58              :           exchange: exchange,
      59              :         ),
      60              :         color: Colors.blue,
      61              :       ),
      62            0 :       ChartData(
      63            0 :         DataHandler.getAmountGroupedByMonth(
      64            0 :           bills.getTill(0.0 + currentYear.millisecondsSinceEpoch).cast(),
      65              :           exchange: exchange,
      66              :         ),
      67              :         color: Colors.red,
      68              :       ),
      69              :     ];
      70              :   }
      71              : 
      72            0 :   double getMax(List<ChartData> data) {
      73              :     double yMax = 0;
      74            0 :     if (data.isNotEmpty && data.first.data.isNotEmpty) {
      75            0 :       yMax = data.first.data.reduce((max, current) => current.dy > max.dy ? current : max).dy;
      76            0 :       if (data.last.data.isNotEmpty) {
      77            0 :         double yPrevMax = data.last.data.reduce((max, current) => current.dy > max.dy ? current : max).dy;
      78            0 :         yMax = yMax > yPrevMax ? yMax : yPrevMax;
      79              :       }
      80              :     }
      81              :     return yMax;
      82              :   }
      83              : 
      84            0 :   @override
      85              :   Widget build(BuildContext context) {
      86            0 :     final TextTheme textTheme = context.textTheme;
      87            0 :     final indent = ThemeHelper.getIndent();
      88            0 :     return Consumer<AppData>(builder: (context, appState, _) {
      89            0 :       final data = getData(appState);
      90            0 :       return LayoutBuilder(builder: (context, constraints) {
      91            0 :         final width = constraints.maxWidth - indent * 2;
      92            0 :         return Column(
      93            0 :           crossAxisAlignment: AppDesign.getAlignment(),
      94            0 :           children: [
      95            0 :             Text(
      96            0 :               '${AppLocale.labels.budgetHeadline} ${AppLocale.labels.budgetLimit}, ${Exchange.defaultCurrency?.code}',
      97            0 :               style: textTheme.bodyLarge,
      98              :             ),
      99            0 :             ColumnChart(
     100              :               width: width,
     101            0 :               height: [constraints.maxHeight - 6 * indent, 200].reduce(min).toDouble(),
     102              :               indent: indent,
     103              :               data: data,
     104            0 :               yMax: getMax(data) * 1.2,
     105              :             ),
     106            0 :             RowWidget(
     107              :               maxWidth: width,
     108              :               indent: indent,
     109              :               chunk: const [null, null],
     110            0 :               children: [
     111            0 :                 [
     112            0 :                   Padding(
     113            0 :                     padding: EdgeInsets.only(left: indent * 2),
     114            0 :                     child: TextWrapper(
     115            0 :                       AppLocale.labels.budgetHeadline,
     116            0 :                       style: textTheme.bodySmall?.copyWith(color: Colors.blue),
     117              :                     ),
     118              :                   ),
     119              :                 ],
     120            0 :                 [
     121            0 :                   Align(
     122              :                     alignment: Alignment.centerRight,
     123            0 :                     child: TextWrapper(
     124            0 :                       AppLocale.labels.billHeadline,
     125            0 :                       style: textTheme.bodySmall?.copyWith(color: Colors.red),
     126              :                     ),
     127              :                   ),
     128              :                 ],
     129              :               ],
     130              :             ),
     131              :           ],
     132              :         );
     133              :       });
     134              :     });
     135              :   }
     136              : }
        

Generated by: LCOV version 2.0-1