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/herald/app_design.dart';
5 : import 'package:app_finance/_classes/herald/app_locale.dart';
6 : import 'package:app_finance/_classes/structure/budget_app_data.dart';
7 : import 'package:app_finance/_classes/controller/focus_controller.dart';
8 : import 'package:app_finance/_classes/storage/app_preferences.dart';
9 : import 'package:app_finance/_configs/budget_type.dart';
10 : import 'package:app_finance/_configs/custom_color_scheme.dart';
11 : import 'package:app_finance/_configs/theme_helper.dart';
12 : import 'package:app_finance/_ext/build_context_ext.dart';
13 : import 'package:app_finance/_ext/date_time_ext.dart';
14 : import 'package:app_finance/design/form/checkbox_input.dart';
15 : import 'package:app_finance/design/wrapper/input_wrapper.dart';
16 : import 'package:app_finance/design/wrapper/text_wrapper.dart';
17 : import 'package:app_finance/pages/_interfaces/abstract_add_page.dart';
18 : import 'package:app_finance/design/button/full_sized_button_widget.dart';
19 : import 'package:app_finance/design/form/simple_input.dart';
20 : import 'package:app_finance/design/wrapper/row_widget.dart';
21 : import 'package:app_finance/design/wrapper/single_scroll_wrapper.dart';
22 : import 'package:app_finance/pages/budget/widgets/budget_type_widget.dart';
23 : import 'package:material_ui/material_ui.dart';
24 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
25 :
26 : class BudgetAddPage extends AbstractAddPage {
27 : final String? uuid;
28 : final String? title;
29 : final String? type;
30 : final double? budgetLimit;
31 : final IconData? icon;
32 : final MaterialColor? color;
33 : final Currency? currency;
34 :
35 2 : const BudgetAddPage({
36 : super.key,
37 : this.uuid,
38 : this.title,
39 : this.type,
40 : this.budgetLimit,
41 : this.icon,
42 : this.color,
43 : this.currency,
44 : });
45 :
46 0 : @override
47 0 : BudgetAddPageState createState() => BudgetAddPageState();
48 : }
49 :
50 : class BudgetAddPageState<T extends BudgetAddPage> extends AbstractAddPageState<T> {
51 : late FocusController focus;
52 : late TextEditingController title;
53 : late String? type;
54 : late TextEditingController budgetLimit;
55 : IconData? icon;
56 : MaterialColor? color;
57 : Currency? currency;
58 : Map<int, double> amountSet = {};
59 : bool skip = false;
60 :
61 0 : @override
62 : void initState() {
63 0 : focus = FocusController();
64 0 : title = TextEditingController(text: widget.title);
65 0 : type = widget.type ?? AppBudgetType.month.name;
66 0 : budgetLimit = TextEditingController(text: widget.budgetLimit != null ? widget.budgetLimit.toString() : '');
67 0 : icon = widget.icon;
68 0 : color = widget.color;
69 0 : final currencyId = AppPreferences.get(AppPreferences.prefCurrency);
70 0 : currency = widget.currency ?? CurrencyProvider.find(currencyId);
71 0 : super.initState();
72 : }
73 :
74 0 : @override
75 : void dispose() {
76 0 : focus.dispose();
77 0 : title.dispose();
78 0 : budgetLimit.dispose();
79 0 : super.dispose();
80 : }
81 :
82 0 : @override
83 0 : String getTitle() => AppLocale.labels.createBudgetHeader;
84 :
85 0 : @override
86 : bool hasFormErrors() {
87 0 : setState(() => hasError = title.text.isEmpty || currency == null);
88 0 : return hasError;
89 : }
90 :
91 0 : @override
92 : void updateStorage() {
93 0 : if (currency != null) {
94 0 : CurrencyProvider.pin(currency!);
95 : }
96 0 : super.state.add(BudgetAppData(
97 0 : title: title.text,
98 0 : type: type ?? AppBudgetType.month.name,
99 0 : amountLimit: double.tryParse(budgetLimit.text) ?? 0.0,
100 0 : amountSet: amountSet,
101 : progress: 0.0,
102 0 : color: color ?? Colors.red,
103 : hidden: false,
104 0 : currency: currency,
105 0 : icon: icon,
106 0 : skip: skip,
107 0 : )..setState(state));
108 : }
109 :
110 0 : @override
111 0 : String getButtonName() => AppLocale.labels.createBudgetTooltip;
112 :
113 0 : @override
114 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
115 0 : NavigatorState nav = Navigator.of(context);
116 0 : return FullSizedButtonWidget(
117 : constraints: constraints,
118 0 : controller: focus,
119 0 : onPressed: () => triggerActionButton(nav),
120 0 : title: getButtonName(),
121 : icon: Icons.save,
122 : );
123 : }
124 :
125 0 : @override
126 : Widget buildContent(BuildContext context, BoxConstraints constraints) {
127 0 : final textTheme = context.textTheme;
128 0 : double indent = ThemeHelper.getIndent(2);
129 0 : double width = ThemeHelper.getWidth(context, 6, constraints);
130 :
131 0 : return SingleScrollWrapper(
132 0 : controller: focus,
133 0 : child: Container(
134 0 : margin: EdgeInsets.fromLTRB(indent, indent, indent, 240),
135 0 : child: Column(
136 0 : crossAxisAlignment: AppDesign.getAlignment(),
137 0 : children: [
138 0 : InputWrapper.text(
139 : isRequired: true,
140 0 : controller: title,
141 0 : title: AppLocale.labels.title,
142 0 : tooltip: AppLocale.labels.titleBudgetTooltip,
143 0 : showError: hasError && title.text.isEmpty,
144 : ),
145 0 : InputWrapper.select(
146 : isRequired: true,
147 0 : isDisabled: widget.uuid != null,
148 0 : value: type,
149 0 : title: AppLocale.labels.budgetType,
150 0 : tooltip: AppLocale.labels.budgetType,
151 0 : showError: hasError && type == null,
152 0 : options: BudgetType.getList(),
153 0 : onChange: (value) => setState(() => type = value),
154 : ),
155 0 : RowWidget(
156 : indent: indent,
157 0 : maxWidth: width + indent,
158 : chunk: const [0.5, 0.5],
159 0 : children: [
160 0 : [
161 0 : InputWrapper.icon(
162 0 : value: icon,
163 0 : title: AppLocale.labels.icon,
164 0 : onChange: (value) => setState(() => icon = value),
165 : ),
166 : ],
167 0 : [
168 0 : InputWrapper.color(
169 0 : value: color,
170 0 : title: AppLocale.labels.color,
171 0 : onChange: (value) => setState(() => color = value),
172 : ),
173 : ],
174 : ],
175 : ),
176 0 : Row(
177 : mainAxisAlignment: MainAxisAlignment.spaceBetween,
178 0 : textDirection: AppDesign.getAlignment<TextDirection>(),
179 0 : children: [
180 0 : TextWrapper(
181 0 : AppLocale.labels.budgetLimit,
182 0 : style: textTheme.bodyLarge,
183 : ),
184 0 : amountSet.isEmpty
185 0 : ? IconButton(
186 : icon: const Icon(Icons.vertical_split),
187 0 : tooltip: AppLocale.labels.splitTooltip,
188 0 : onPressed: () => setState(() {
189 0 : final result = Map<int, double>.from(amountSet);
190 0 : for (int i = 1; i <= 12; i++) {
191 0 : result[i] = 1.0;
192 : }
193 0 : amountSet = result;
194 : }),
195 : )
196 0 : : IconButton(
197 : icon: const Icon(Icons.delete),
198 0 : tooltip: AppLocale.labels.splitCancelTooltip,
199 0 : onPressed: () => setState(() => amountSet = {}),
200 : ),
201 : ],
202 : ),
203 0 : SimpleInput(
204 0 : key: ValueKey(AppLocale.labels.budgetLimit),
205 0 : controller: budgetLimit,
206 : type: const TextInputType.numberWithOptions(decimal: true),
207 0 : tooltip: AppLocale.labels.balanceTooltip,
208 0 : formatter: [
209 0 : SimpleInputFormatter.filterDouble,
210 : ],
211 : ),
212 0 : BudgetTypeWidget(controller: budgetLimit),
213 : ThemeHelper.hIndent2x,
214 0 : if (amountSet.isNotEmpty) ...[
215 0 : Text(
216 0 : AppLocale.labels.budgetRelativeLimit,
217 0 : style: textTheme.bodyLarge,
218 : ),
219 0 : ...amountSet.entries.map((e) {
220 0 : return RowWidget(
221 : indent: indent,
222 0 : maxWidth: width + indent,
223 : chunk: const [100, null],
224 0 : children: [
225 0 : [
226 0 : Text(
227 0 : DateTime(DateTime.now().year, e.key).fullMonth(),
228 0 : style: textTheme.headlineMedium,
229 : ),
230 : ],
231 0 : [
232 0 : Container(
233 0 : color: context.colorScheme.fieldBackground,
234 0 : child: Slider(
235 0 : value: e.value,
236 0 : onChanged: (v) => setState(() => amountSet[e.key] = v),
237 : min: 0.0,
238 : max: 4.0,
239 : divisions: 15,
240 0 : label: e.value.toStringAsFixed(2),
241 : ),
242 : ),
243 : ],
244 : ],
245 : );
246 : }),
247 0 : ThemeHelper.hIndent2x,
248 : ],
249 0 : InputWrapper.currency(
250 : isRequired: true,
251 0 : showError: hasError && currency == null,
252 0 : value: currency,
253 0 : title: AppLocale.labels.currency,
254 0 : tooltip: AppLocale.labels.currencyTooltip,
255 0 : onChange: (value) => setState(() => currency = value),
256 : ),
257 0 : CheckboxInput(
258 0 : label: AppLocale.labels.skipFromTotals,
259 0 : value: skip,
260 0 : onChanged: (value) => setState(() => skip = value),
261 : constraints: constraints,
262 : ),
263 0 : ThemeHelper.hIndent2x,
264 : ],
265 : ),
266 : ),
267 : );
268 : }
269 : }
|