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