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_design.dart';
8 : import 'package:app_finance/_classes/herald/app_locale.dart';
9 : import 'package:app_finance/_classes/controller/flow_state_machine.dart';
10 : import 'package:app_finance/_classes/herald/app_start_of_month.dart';
11 : import 'package:app_finance/_classes/storage/app_data.dart';
12 : import 'package:app_finance/_classes/structure/account_app_data.dart';
13 : import 'package:app_finance/_classes/structure/account_summary_data.dart';
14 : import 'package:app_finance/_classes/structure/bill_app_data.dart';
15 : import 'package:app_finance/_classes/structure/currency/exchange.dart';
16 : import 'package:app_finance/_classes/structure/invoice_app_data.dart';
17 : import 'package:app_finance/_configs/custom_text_theme.dart';
18 : import 'package:app_finance/_configs/screen_helper.dart';
19 : import 'package:app_finance/_configs/theme_helper.dart';
20 : import 'package:app_finance/_classes/structure/navigation/app_route.dart';
21 : import 'package:app_finance/_ext/build_context_ext.dart';
22 : import 'package:app_finance/_ext/date_time_ext.dart';
23 : import 'package:app_finance/design/wrapper/row_widget.dart';
24 : import 'package:app_finance/design/wrapper/text_wrapper.dart';
25 : import 'package:app_finance/pages/_interfaces/abstract_page_state.dart';
26 : import 'package:app_finance/pages/account/widgets/account_line_widget.dart';
27 : import 'package:app_finance/design/generic/base_line_widget.dart';
28 : import 'package:app_finance/design/generic/base_list_infinite_widget.dart';
29 : import 'package:app_finance/design/wrapper/confirmation_wrapper.dart';
30 : import 'package:app_finance/design/wrapper/tab_widget.dart';
31 : import 'package:material_ui/material_ui.dart';
32 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
33 : import 'package:intl/intl.dart' as intl;
34 :
35 : class AccountViewPage extends StatefulWidget {
36 : final String uuid;
37 :
38 0 : const AccountViewPage({
39 : super.key,
40 : required this.uuid,
41 : });
42 :
43 0 : @override
44 0 : AccountViewPageState createState() => AccountViewPageState();
45 : }
46 :
47 : class AccountViewPageState extends AbstractPageState<AccountViewPage> {
48 : late double width;
49 : int focus = 0;
50 :
51 0 : @override
52 : String getTitle() {
53 0 : final item = super.state.getByUuid(widget.uuid) as AccountAppData;
54 0 : return item.title;
55 : }
56 :
57 0 : @override
58 : String getButtonName() => '';
59 :
60 0 : @override
61 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
62 0 : double indent = ThemeHelper.getIndent(4);
63 0 : NavigatorState nav = Navigator.of(context);
64 0 : return Container(
65 0 : margin: EdgeInsets.only(left: 2 * indent, right: 2 * indent),
66 0 : child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
67 0 : FloatingActionButton(
68 : heroTag: 'account_view_page_edit',
69 0 : onPressed: () => nav.pushNamed(AppRoute.accountEditRoute, arguments: {routeArguments.uuid: widget.uuid}),
70 0 : tooltip: AppLocale.labels.editAccountTooltip,
71 : child: const Icon(Icons.edit),
72 : ),
73 0 : FloatingActionButton(
74 : heroTag: 'account_view_page_deactivate',
75 0 : onPressed: () => ConfirmationWrapper.show(
76 : context,
77 0 : () => FlowStateMachine.deactivate(nav, store: super.state, uuid: widget.uuid),
78 : ),
79 0 : tooltip: AppLocale.labels.deleteAccountTooltip,
80 : child: const Icon(Icons.delete),
81 : ),
82 : ]),
83 : );
84 : }
85 :
86 0 : String _getTitle(dynamic item) {
87 : String prefix = '';
88 0 : if (item is InvoiceAppData && item.accountFrom != null) {
89 0 : if (item.accountFrom == widget.uuid) {
90 0 : final obj = state.getByUuid(item.account) as AccountAppData;
91 0 : prefix = "[${AppLocale.labels.transferHeadline} ${AppLocale.labels.to} '${obj.title}'] ";
92 : } else {
93 0 : final obj = state.getByUuid(item.accountFrom!) as AccountAppData;
94 0 : prefix = "[${AppLocale.labels.transferHeadline} ${AppLocale.labels.from} '${obj.title}'] ";
95 : }
96 : }
97 0 : return item != null ? '$prefix${item.title}' : '';
98 : }
99 :
100 0 : String _getRoute(dynamic item) => switch (item.runtimeType) {
101 0 : BillAppData => AppRoute.billViewRoute,
102 0 : InvoiceAppData => AppRoute.invoiceViewRoute,
103 : _ => '',
104 : };
105 :
106 0 : InterfaceIterator<num, dynamic, dynamic> _getSummary() {
107 0 : var exchange = Exchange(store: state);
108 0 : var account = super.state.getByUuid(widget.uuid) as AccountAppData;
109 0 : var bills = state.getStream(AppDataType.bills, filter: (o) => o.account != widget.uuid);
110 0 : var inv = state.getStream(AppDataType.invoice, filter: (o) => o.account != widget.uuid || o.accountFrom != null);
111 0 : DateTime now = DateTime.now();
112 0 : int startingDay = AppStartOfMonth.get();
113 0 : DateTime curr = now.getStartingDay(startingDay);
114 0 : var data = SplayTreeMap<num, AccountSummaryData>();
115 : int increment = 0;
116 0 : while (!(bills.isFinished && inv.isFinished)) {
117 0 : var boundary = curr.millisecondsSinceEpoch.toDouble();
118 0 : var billList = bills.getTill(boundary);
119 0 : var invList = inv.getTill(boundary);
120 0 : data[increment] = AccountSummaryData(
121 0 : title: intl.DateFormat.MMMM().format(curr),
122 0 : description: curr.year.toString(),
123 0 : currency: account.currency,
124 0 : invoices: invList.fold(0.0, (v, e) => v + exchange.reform(e.details ?? 0.0, e.currency, account.currency)),
125 0 : bills: billList.fold(0.0, (v, e) => v + exchange.reform(e.details ?? 0.0, e.currency, account.currency)),
126 : );
127 0 : increment++;
128 0 : curr = DateTime(now.year, now.month - increment, now.day).getStartingDay(startingDay);
129 : }
130 0 : return IteratorController(data, transform: (v) => v);
131 : }
132 :
133 0 : Widget buildSummaryWidget(item, BuildContext context) {
134 0 : final textTheme = context.textTheme;
135 0 : final indent = ThemeHelper.getIndent();
136 0 : final alignment = AppDesign.isRightToLeft() ? Alignment.centerLeft : Alignment.centerRight;
137 0 : return RowWidget(
138 : indent: indent,
139 0 : alignment: AppDesign.getAlignment<MainAxisAlignment>(),
140 0 : maxWidth: width,
141 0 : chunk: [indent, null, null, null, indent],
142 0 : children: [
143 : const [ThemeHelper.emptyBox],
144 0 : [
145 0 : Column(
146 0 : crossAxisAlignment: AppDesign.getAlignment(),
147 0 : children: [
148 0 : TextWrapper(
149 0 : item.title,
150 0 : style: textTheme.headlineMedium,
151 : ),
152 0 : TextWrapper(
153 0 : item.description,
154 0 : style: textTheme.bodySmall,
155 : ),
156 : ThemeHelper.hIndent
157 : ],
158 : ),
159 : ],
160 0 : [
161 0 : Align(
162 : alignment: alignment,
163 0 : child: TextWrapper(
164 0 : (item.bills as double).toCurrency(currency: item.currency, withPattern: true),
165 0 : style: textTheme.numberMedium,
166 : ),
167 : ),
168 : ],
169 0 : [
170 0 : Align(
171 : alignment: alignment,
172 0 : child: TextWrapper(
173 0 : (item.invoices as double).toCurrency(currency: item.currency, withPattern: true),
174 0 : style: textTheme.numberMedium,
175 : ),
176 : ),
177 : ],
178 : const [ThemeHelper.emptyBox],
179 : ],
180 : );
181 : }
182 :
183 0 : Widget buildLineWidget(item, BuildContext context) {
184 0 : return BaseLineWidget(
185 0 : uuid: item.uuid ?? '',
186 0 : title: _getTitle(item),
187 0 : description: item.description ?? '',
188 0 : details: item.detailsFormatted,
189 0 : progress: item.progress,
190 0 : color: item.color ?? Colors.transparent,
191 0 : icon: item.icon ?? Icons.radio_button_unchecked_sharp,
192 0 : hidden: item.hidden,
193 0 : skip: item.skip,
194 0 : width: width,
195 0 : route: _getRoute(item),
196 : );
197 : }
198 :
199 0 : Widget buildLineTransferWidget(item, BuildContext context) {
200 0 : final account = state.getByUuid(item?.accountFrom ?? '');
201 0 : return BaseLineWidget(
202 0 : uuid: item.uuid ?? '',
203 0 : title: _getTitle(item),
204 0 : description: item.description ?? '',
205 0 : details: item.detailsFormatted,
206 0 : progress: item.progress,
207 0 : color: account?.color ?? Colors.transparent,
208 0 : icon: account?.icon ?? Icons.radio_button_unchecked_sharp,
209 0 : hidden: item.hidden,
210 0 : skip: item.skip,
211 0 : width: width,
212 0 : route: _getRoute(item),
213 : );
214 : }
215 :
216 0 : @override
217 : Widget buildContent(BuildContext context, BoxConstraints constraints) {
218 0 : width = ThemeHelper.getWidth(context, 4, constraints);
219 0 : final isLeft = ScreenHelper.state().isLeftBar;
220 : if (isLeft) {
221 0 : width -= ThemeHelper.barHeight;
222 : }
223 0 : return Padding(
224 0 : padding: EdgeInsets.only(top: ThemeHelper.getIndent()),
225 0 : child: Column(
226 0 : children: [
227 0 : AccountLineWidget(item: state.getByUuid(widget.uuid) as AccountAppData, width: width, count: 1),
228 : ThemeHelper.hIndent05,
229 : const Divider(height: 2),
230 0 : Expanded(
231 0 : child: TabWidget(
232 : type: TabType.secondary,
233 : isLeft: isLeft,
234 0 : focus: focus,
235 0 : callback: (data) => setState(() => focus = data),
236 0 : tabs: [
237 0 : Tab(text: AppLocale.labels.summary),
238 0 : Tab(text: AppLocale.labels.billHeadline),
239 0 : Tab(text: AppLocale.labels.invoiceHeadline),
240 0 : Tab(text: AppLocale.labels.transfersHeadline),
241 : ],
242 0 : children: [
243 0 : BaseListInfiniteWidget(
244 0 : stream: _getSummary(),
245 0 : width: width,
246 0 : buildListWidget: buildSummaryWidget,
247 : ),
248 0 : BaseListInfiniteWidget(
249 0 : stream: state.getStream(AppDataType.bills, filter: (o) => o.account != widget.uuid),
250 0 : width: width,
251 0 : buildListWidget: buildLineWidget,
252 : ),
253 0 : BaseListInfiniteWidget(
254 0 : stream: state.getStream(
255 : AppDataType.invoice,
256 0 : filter: (o) => o.account != widget.uuid || o.accountFrom != null,
257 : ),
258 0 : width: width,
259 0 : buildListWidget: buildLineWidget,
260 : ),
261 0 : BaseListInfiniteWidget(
262 0 : stream: state.getStream(
263 : AppDataType.invoice,
264 0 : filter: (o) => o.account != widget.uuid && o.accountFrom != widget.uuid || o.accountFrom == null,
265 : ),
266 0 : width: width,
267 0 : buildListWidget: buildLineTransferWidget,
268 : ),
269 : ],
270 : ),
271 : ),
272 : ],
273 : ),
274 : );
275 : }
276 : }
|