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 1 : 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 : }
|