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