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 'package:app_finance/_classes/controller/exchange_controller.dart';
5 : import 'package:app_finance/_classes/herald/app_design.dart';
6 : import 'package:app_finance/_classes/herald/app_locale.dart';
7 : import 'package:app_finance/_classes/structure/currency/exchange.dart';
8 : import 'package:app_finance/_classes/structure/navigation/app_route.dart';
9 : import 'package:app_finance/_classes/structure/bill_app_data.dart';
10 : import 'package:app_finance/_classes/controller/focus_controller.dart';
11 : import 'package:app_finance/_classes/storage/app_preferences.dart';
12 : import 'package:app_finance/_classes/storage/app_data.dart';
13 : import 'package:app_finance/_configs/account_type.dart';
14 : import 'package:app_finance/_configs/screen_helper.dart';
15 : import 'package:app_finance/_configs/theme_helper.dart';
16 : import 'package:app_finance/_ext/build_context_ext.dart';
17 : import 'package:app_finance/_ext/double_ext.dart';
18 : import 'package:app_finance/design/wrapper/input_wrapper.dart';
19 : import 'package:app_finance/pages/bill/widgets/interface_bill_page_inject.dart';
20 : import 'package:app_finance/design/form/currency_exchange_input.dart';
21 : import 'package:app_finance/design/form/date_time_input.dart';
22 : import 'package:app_finance/design/button/full_sized_button_widget.dart';
23 : import 'package:app_finance/design/form/list_account_selector.dart';
24 : import 'package:app_finance/design/form/simple_input.dart';
25 : import 'package:app_finance/design/wrapper/row_widget.dart';
26 : import 'package:app_finance/design/wrapper/single_scroll_wrapper.dart';
27 : import 'package:flutter/material.dart';
28 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
29 :
30 : class ExpensesTab<T> extends StatefulWidget {
31 : final String? account;
32 : final String? budget;
33 : final Currency? currency;
34 : final double? bill;
35 : final String? description;
36 : final DateTime? createdAt;
37 : final AppData state;
38 : final bool isLeft;
39 : final FnBillPageCallback callback;
40 :
41 1 : const ExpensesTab({
42 : super.key,
43 : required this.state,
44 : required this.callback,
45 : this.account,
46 : this.budget,
47 : this.currency,
48 : this.bill,
49 : this.description,
50 : this.createdAt,
51 : this.isLeft = false,
52 : });
53 :
54 1 : @override
55 1 : ExpensesTabState createState() => ExpensesTabState();
56 : }
57 :
58 : class ExpensesTabState<T extends ExpensesTab> extends State<T> {
59 : late FocusController focus;
60 : String? account;
61 : Currency? accountCurrency;
62 : String? budget;
63 : Currency? budgetCurrency;
64 : Currency? currency;
65 : late TextEditingController bill;
66 : late TextEditingController description;
67 : late ExchangeController exchange;
68 : DateTime? createdAt;
69 : bool hasErrors = false;
70 : bool isPushed = false;
71 3 : late List<ListAccountSelectorItem> accountList = widget.state
72 1 : .getList(AppDataType.accounts)
73 7 : .where((e) => ![AppAccountType.deposit.toString(), AppAccountType.credit.toString()].contains(e.type))
74 3 : .map((item) => ListAccountSelectorItem(item: item))
75 1 : .toList();
76 :
77 1 : @override
78 : void initState() {
79 2 : focus = FocusController();
80 1 : final accountId = AppPreferences.get(AppPreferences.prefAccount);
81 3 : final objAccount = widget.state.getByUuid(accountId ?? '');
82 1 : final budgetId = AppPreferences.get(AppPreferences.prefBudget);
83 3 : final objBudget = widget.state.getByUuid(budgetId ?? '');
84 3 : account = widget.account ?? objAccount?.uuid;
85 3 : budget = widget.budget ?? objBudget?.uuid;
86 3 : currency = widget.currency ?? objAccount?.currency ?? objBudget?.currency ?? Exchange.defaultCurrency;
87 4 : bill = TextEditingController(text: widget.bill != null ? widget.bill.toString() : '');
88 4 : description = TextEditingController(text: widget.description);
89 3 : createdAt = widget.createdAt;
90 5 : accountCurrency = widget.state.getByUuid(account ?? '')?.currency;
91 5 : budgetCurrency = widget.state.getByUuid(budget ?? '')?.currency;
92 3 : exchange = ExchangeController({},
93 7 : store: widget.state, targetController: bill, target: currency, source: [accountCurrency, budgetCurrency]);
94 :
95 3 : widget.callback((
96 1 : buildButton: buildButton,
97 1 : buttonName: getButtonName(),
98 1 : title: getTitle(),
99 : ));
100 1 : super.initState();
101 : }
102 :
103 1 : @override
104 : dispose() {
105 1 : isPushed = false;
106 2 : description.dispose();
107 2 : exchange.dispose();
108 2 : bill.dispose();
109 2 : focus.dispose();
110 1 : super.dispose();
111 : }
112 :
113 3 : String getTitle() => AppLocale.labels.createBillHeader;
114 :
115 1 : bool hasFormErrors() {
116 8 : setState(() => hasErrors = account == null || budget == null || bill.text.isEmpty);
117 1 : return hasErrors;
118 : }
119 :
120 1 : void updateStorage() {
121 2 : AppPreferences.set(AppPreferences.prefAccount, account ?? '');
122 2 : AppPreferences.set(AppPreferences.prefBudget, budget ?? '');
123 1 : if (currency != null) {
124 2 : CurrencyProvider.pin(currency!);
125 : }
126 2 : exchange.save();
127 4 : widget.state.add(BillAppData(
128 1 : account: account ?? '',
129 1 : category: budget ?? '',
130 1 : currency: currency,
131 2 : title: description.text,
132 5 : details: double.tryParse(bill.text)?.toFixed(currency?.decimalDigits) ?? 0.0,
133 2 : createdAt: createdAt ?? DateTime.now(),
134 : ));
135 : }
136 :
137 3 : String getButtonName() => AppLocale.labels.createBillTooltip;
138 :
139 1 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
140 1 : NavigatorState nav = Navigator.of(context);
141 1 : return FullSizedButtonWidget(
142 : constraints: constraints,
143 1 : controller: focus,
144 1 : onPressed: () => {
145 2 : setState(() {
146 1 : if (hasFormErrors()) {
147 : return;
148 : }
149 1 : updateStorage();
150 1 : nav.popAndPushNamed(AppRoute.homeRoute);
151 : })
152 : },
153 1 : title: getButtonName(),
154 : icon: Icons.save,
155 : );
156 : }
157 :
158 1 : @override
159 : Widget build(BuildContext context) {
160 1 : final textTheme = context.textTheme;
161 1 : final indent = ThemeHelper.getIndent(2);
162 3 : double width = ScreenHelper.state().width - indent * 3;
163 2 : if (widget.isLeft) {
164 0 : width -= ThemeHelper.barHeight;
165 : }
166 :
167 1 : return SingleScrollWrapper(
168 1 : controller: focus,
169 1 : child: Container(
170 1 : margin: EdgeInsets.fromLTRB(indent, indent, indent, 240),
171 1 : child: Column(
172 1 : crossAxisAlignment: AppDesign.getAlignment(),
173 1 : children: [
174 1 : InputWrapper(
175 : type: NamedInputType.accountSelector,
176 : isRequired: true,
177 5 : value: account != null ? widget.state.getByUuid(account!) : null,
178 2 : title: AppLocale.labels.account,
179 2 : tooltip: AppLocale.labels.titleAccountTooltip,
180 1 : showError: hasErrors && account == null,
181 2 : state: widget.state,
182 1 : options: accountList,
183 3 : onChange: (value) => setState(() {
184 2 : account = value?.uuid;
185 : if (value != null) {
186 2 : accountCurrency = value.currency;
187 2 : currency = accountCurrency;
188 : }
189 : }),
190 : width: width,
191 : ),
192 1 : RowWidget(
193 : indent: indent,
194 1 : maxWidth: width + indent,
195 : chunk: const [125, null],
196 1 : children: [
197 1 : [
198 1 : InputWrapper.currency(
199 : type: NamedInputType.currencyShort,
200 1 : value: currency,
201 2 : title: AppLocale.labels.currency,
202 2 : tooltip: AppLocale.labels.currencyTooltip,
203 4 : onChange: (value) => setState(() => currency = value),
204 : ),
205 : ],
206 1 : [
207 1 : InputWrapper.text(
208 2 : title: AppLocale.labels.expense,
209 : isRequired: true,
210 1 : controller: bill,
211 1 : showError: hasErrors && bill.text.isEmpty,
212 2 : tooltip: AppLocale.labels.billSetTooltip,
213 : inputType: const TextInputType.numberWithOptions(decimal: true),
214 2 : formatter: [SimpleInputFormatter.filterDouble],
215 : ),
216 : ],
217 : ],
218 : ),
219 1 : CurrencyExchangeInput(
220 5 : key: ValueKey('expense${currency?.code}${accountCurrency?.code}${budgetCurrency?.code}'),
221 1 : width: width + indent,
222 : indent: indent,
223 1 : target: currency,
224 1 : controller: exchange,
225 3 : source: [accountCurrency, budgetCurrency],
226 : ),
227 1 : InputWrapper.text(
228 2 : title: AppLocale.labels.description,
229 1 : controller: description,
230 2 : tooltip: AppLocale.labels.descriptionTooltip,
231 : ),
232 1 : InputWrapper(
233 : type: NamedInputType.budgetSelector,
234 : isRequired: true,
235 5 : value: budget != null ? widget.state.getByUuid(budget!) : null,
236 2 : title: AppLocale.labels.budget,
237 1 : showError: hasErrors && budget == null,
238 2 : tooltip: AppLocale.labels.titleBudgetTooltip,
239 2 : state: widget.state,
240 3 : onChange: (value) => setState(() {
241 2 : budget = value?.uuid;
242 : if (value != null) {
243 2 : budgetCurrency = value.currency;
244 1 : currency ??= budgetCurrency;
245 : }
246 : }),
247 : width: width,
248 : ),
249 1 : Text(
250 2 : AppLocale.labels.expenseDateTime,
251 1 : style: textTheme.bodyLarge,
252 : ),
253 1 : DateTimeInput(
254 : width: width,
255 2 : value: createdAt ?? DateTime.now(),
256 0 : setState: (value) => setState(() => createdAt = value),
257 : ),
258 : ThemeHelper.formEndBox,
259 : ],
260 : ),
261 : ),
262 : );
263 : }
264 : }
|