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 :
6 : import 'package:app_finance/_classes/controller/iterator_controller.dart';
7 : import 'package:app_finance/_classes/herald/app_locale.dart';
8 : import 'package:app_finance/_classes/controller/flow_state_machine.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/account_app_data.dart';
12 : import 'package:app_finance/_classes/structure/bill_app_data.dart';
13 : import 'package:app_finance/_classes/structure/budget_app_data.dart';
14 : import 'package:app_finance/_classes/structure/currency/exchange.dart';
15 : import 'package:app_finance/_configs/account_type.dart';
16 : import 'package:app_finance/_configs/theme_helper.dart';
17 : import 'package:app_finance/_classes/structure/navigation/app_route.dart';
18 : import 'package:app_finance/_ext/date_time_ext.dart';
19 : import 'package:app_finance/pages/_interfaces/abstract_page_state.dart';
20 : import 'package:app_finance/pages/budget/widgets/budget_header_widget.dart';
21 : import 'package:app_finance/design/generic/base_line_widget.dart';
22 : import 'package:app_finance/design/generic/base_list_infinite_widget.dart';
23 : import 'package:app_finance/design/wrapper/confirmation_wrapper.dart';
24 : import 'package:app_finance/design/wrapper/tab_widget.dart';
25 : import 'package:flutter/material.dart';
26 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
27 : import 'package:intl/intl.dart';
28 :
29 : class BudgetViewPage extends StatefulWidget {
30 : final String uuid;
31 :
32 0 : const BudgetViewPage({
33 : super.key,
34 : required this.uuid,
35 : });
36 :
37 0 : @override
38 0 : BudgetViewPageState createState() => BudgetViewPageState();
39 : }
40 :
41 : class BudgetViewPageState extends AbstractPageState<BudgetViewPage> with TickerProviderStateMixin {
42 : late double width;
43 : int focus = 0;
44 :
45 0 : @override
46 : String getTitle() {
47 0 : final item = super.state.getByUuid(widget.uuid) as BudgetAppData;
48 0 : return item.title;
49 : }
50 :
51 0 : @override
52 : String getButtonName() => '';
53 :
54 0 : @override
55 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
56 0 : double indent = ThemeHelper.getIndent(4);
57 0 : NavigatorState nav = Navigator.of(context);
58 0 : return Container(
59 0 : margin: EdgeInsets.only(left: 2 * indent, right: 2 * indent),
60 0 : child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
61 0 : FloatingActionButton(
62 : heroTag: 'budget_view_page_edit',
63 0 : onPressed: () => nav.pushNamed(AppRoute.budgetEditRoute, arguments: {routeArguments.uuid: widget.uuid}),
64 0 : tooltip: AppLocale.labels.editBudgetTooltip,
65 : child: const Icon(Icons.edit),
66 : ),
67 0 : FloatingActionButton(
68 : heroTag: 'budget_view_page_deactivate',
69 0 : onPressed: () => ConfirmationWrapper.show(
70 : context,
71 0 : () => FlowStateMachine.deactivate(nav, store: super.state, uuid: widget.uuid),
72 : ),
73 0 : tooltip: AppLocale.labels.deleteBudgetTooltip,
74 : child: const Icon(Icons.delete),
75 : ),
76 : ]),
77 : );
78 : }
79 :
80 0 : Widget buildListWidget(item, BuildContext context) {
81 0 : final value = (item.delta as double).toCurrency(currency: item.currency, withPattern: false);
82 0 : return BaseLineWidget(
83 : uuid: '',
84 : title: '',
85 0 : description: (item.timestamp as DateTime).yMEd(),
86 : progress: 1.0,
87 0 : details: item.delta > 0 ? '+$value' : value,
88 : color: Colors.transparent,
89 0 : width: width,
90 : );
91 : }
92 :
93 0 : Widget buildLineWidget(item, BuildContext context) {
94 0 : return BaseLineWidget(
95 0 : uuid: item.uuid ?? '',
96 0 : title: item.title ?? '',
97 0 : description: item.description ?? '',
98 0 : details: item.detailsFormatted,
99 0 : progress: item.progress,
100 0 : color: item.color ?? Colors.transparent,
101 0 : icon: item.icon ?? Icons.radio_button_unchecked_sharp,
102 0 : hidden: item.hidden,
103 0 : width: width,
104 : route: AppRoute.billViewRoute,
105 : );
106 : }
107 :
108 0 : InterfaceIterator<num, dynamic, dynamic> _getSummary() {
109 0 : var exchange = Exchange(store: state);
110 0 : var category = super.state.getByUuid(widget.uuid) as BudgetAppData;
111 0 : var bills = state.getStream(AppDataType.bills, filter: (o) => o.category != widget.uuid);
112 0 : DateTime now = DateTime.now();
113 0 : DateTime curr = DateTime(now.year, now.month, 1);
114 : int iteration = 0;
115 : BillAppData? item;
116 0 : AccountAppData summary = AccountAppData(
117 0 : type: AppAccountType.cash.toString(),
118 0 : title: DateFormat.MMMM().format(curr),
119 0 : description: curr.year.toString(),
120 0 : currency: category.currency,
121 : progress: 1,
122 : );
123 0 : var data = SplayTreeMap<num, AccountAppData>();
124 : while (true) {
125 0 : item = bills.next;
126 0 : if (item == null || item.createdAt.isBefore(curr)) {
127 0 : if (category.amountLimit < summary.details) {
128 0 : summary.color = Colors.red;
129 : }
130 0 : data[iteration] = summary.clone();
131 : if (item == null) {
132 : break;
133 : }
134 0 : iteration++;
135 0 : curr = DateTime(now.year, now.month - iteration, 1);
136 0 : summary.details = 0;
137 0 : summary.color = null;
138 0 : summary.title = DateFormat.MMMM().format(curr);
139 0 : summary.description = curr.year.toString();
140 : }
141 0 : summary.details = summary.details + exchange.reform(item.details ?? 0.0, item.currency, summary.currency);
142 : }
143 0 : return IteratorController(data, transform: (v) => v);
144 : }
145 :
146 0 : @override
147 : Widget buildContent(BuildContext context, BoxConstraints constraints) {
148 0 : final indent = ThemeHelper.getIndent();
149 0 : final pageWidth = ThemeHelper.getWidth(context, 3, constraints);
150 0 : width = pageWidth - indent;
151 0 : bool isLeft = ThemeHelper.isNavRight(context, constraints);
152 : if (isLeft) {
153 0 : width -= ThemeHelper.barHeight;
154 : }
155 0 : final boundary = DateTime(DateTime.now().year, DateTime.now().month).millisecondsSinceEpoch + 0.0;
156 0 : return Padding(
157 0 : padding: EdgeInsets.only(top: indent),
158 0 : child: Column(
159 0 : children: [
160 0 : BudgetHeaderWidget(item: state.getByUuid(widget.uuid) as BudgetAppData, width: pageWidth),
161 : ThemeHelper.hIndent05,
162 : const Divider(height: 2),
163 0 : Expanded(
164 0 : child: TabWidget(
165 : type: TabType.secondary,
166 : isLeft: isLeft,
167 0 : focus: focus,
168 0 : callback: (data) => setState(() => focus = data),
169 0 : tabs: [
170 0 : Tab(text: AppLocale.labels.billHeadline),
171 0 : Tab(text: AppLocale.labels.summary),
172 0 : Tab(text: AppLocale.labels.budgetLimitHeadline),
173 : ],
174 0 : children: [
175 0 : BaseListInfiniteWidget(
176 : stream:
177 0 : state.getStream(AppDataType.bills, boundary: boundary, filter: (o) => o.category != widget.uuid),
178 0 : width: width,
179 0 : buildListWidget: buildLineWidget,
180 : ),
181 0 : BaseListInfiniteWidget(
182 0 : stream: _getSummary(),
183 0 : width: width,
184 0 : buildListWidget: buildLineWidget,
185 : ),
186 0 : BaseListInfiniteWidget(
187 0 : stream: HistoryData.getStream(widget.uuid),
188 0 : width: width,
189 0 : buildListWidget: buildListWidget,
190 : ),
191 : ],
192 : ),
193 : ),
194 : ],
195 : ),
196 : );
197 : }
198 : }
|