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 1 : 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 1 : @override
47 1 : 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 1 : @override
62 : void initState() {
63 2 : focus = FocusController();
64 4 : title = TextEditingController(text: widget.title);
65 4 : type = widget.type ?? AppBudgetType.month.name;
66 4 : budgetLimit = TextEditingController(text: widget.budgetLimit != null ? widget.budgetLimit.toString() : '');
67 3 : icon = widget.icon;
68 3 : color = widget.color;
69 1 : final currencyId = AppPreferences.get(AppPreferences.prefCurrency);
70 4 : currency = widget.currency ?? CurrencyProvider.find(currencyId);
71 1 : super.initState();
72 : }
73 :
74 1 : @override
75 : void dispose() {
76 2 : focus.dispose();
77 2 : title.dispose();
78 2 : budgetLimit.dispose();
79 1 : super.dispose();
80 : }
81 :
82 1 : @override
83 2 : String getTitle() => AppLocale.labels.createBudgetHeader;
84 :
85 1 : @override
86 : bool hasFormErrors() {
87 7 : setState(() => hasError = title.text.isEmpty || currency == null);
88 1 : return hasError;
89 : }
90 :
91 1 : @override
92 : void updateStorage() {
93 1 : if (currency != null) {
94 2 : CurrencyProvider.pin(currency!);
95 : }
96 3 : super.state.add(BudgetAppData(
97 2 : title: title.text,
98 1 : type: type ?? AppBudgetType.month.name,
99 3 : amountLimit: double.tryParse(budgetLimit.text) ?? 0.0,
100 1 : amountSet: amountSet,
101 : progress: 0.0,
102 1 : color: color ?? Colors.red,
103 : hidden: false,
104 1 : currency: currency,
105 1 : icon: icon,
106 1 : skip: skip,
107 2 : )..setState(state));
108 : }
109 :
110 1 : @override
111 2 : String getButtonName() => AppLocale.labels.createBudgetTooltip;
112 :
113 1 : @override
114 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
115 1 : NavigatorState nav = Navigator.of(context);
116 1 : return FullSizedButtonWidget(
117 : constraints: constraints,
118 1 : controller: focus,
119 2 : onPressed: () => triggerActionButton(nav),
120 1 : title: getButtonName(),
121 : icon: Icons.save,
122 : );
123 : }
124 :
125 1 : @override
126 : Widget buildContent(BuildContext context, BoxConstraints constraints) {
127 1 : final textTheme = context.textTheme;
128 1 : double indent = ThemeHelper.getIndent(2);
129 1 : double width = ThemeHelper.getWidth(context, 6, constraints);
130 :
131 1 : return SingleScrollWrapper(
132 1 : controller: focus,
133 1 : child: Container(
134 1 : margin: EdgeInsets.fromLTRB(indent, indent, indent, 240),
135 1 : child: Column(
136 1 : crossAxisAlignment: AppDesign.getAlignment(),
137 1 : children: [
138 1 : InputWrapper.text(
139 : isRequired: true,
140 1 : controller: title,
141 2 : title: AppLocale.labels.title,
142 2 : tooltip: AppLocale.labels.titleBudgetTooltip,
143 1 : showError: hasError && title.text.isEmpty,
144 : ),
145 1 : InputWrapper.select(
146 : isRequired: true,
147 2 : isDisabled: widget.uuid != null,
148 1 : value: type,
149 2 : title: AppLocale.labels.budgetType,
150 2 : tooltip: AppLocale.labels.budgetType,
151 1 : showError: hasError && type == null,
152 1 : options: BudgetType.getList(),
153 0 : onChange: (value) => setState(() => type = value),
154 : ),
155 1 : RowWidget(
156 : indent: indent,
157 1 : maxWidth: width + indent,
158 : chunk: const [0.5, 0.5],
159 1 : children: [
160 1 : [
161 1 : InputWrapper.icon(
162 1 : value: icon,
163 2 : title: AppLocale.labels.icon,
164 0 : onChange: (value) => setState(() => icon = value),
165 : ),
166 : ],
167 1 : [
168 1 : InputWrapper.color(
169 1 : value: color,
170 2 : title: AppLocale.labels.color,
171 0 : onChange: (value) => setState(() => color = value),
172 : ),
173 : ],
174 : ],
175 : ),
176 1 : Row(
177 : mainAxisAlignment: MainAxisAlignment.spaceBetween,
178 1 : textDirection: AppDesign.getAlignment<TextDirection>(),
179 1 : children: [
180 1 : TextWrapper(
181 2 : AppLocale.labels.budgetLimit,
182 1 : style: textTheme.bodyLarge,
183 : ),
184 2 : amountSet.isEmpty
185 1 : ? IconButton(
186 : icon: const Icon(Icons.vertical_split),
187 2 : 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 1 : SimpleInput(
204 3 : key: ValueKey(AppLocale.labels.budgetLimit),
205 1 : controller: budgetLimit,
206 : type: const TextInputType.numberWithOptions(decimal: true),
207 2 : tooltip: AppLocale.labels.balanceTooltip,
208 1 : formatter: [
209 1 : SimpleInputFormatter.filterDouble,
210 : ],
211 : ),
212 2 : BudgetTypeWidget(controller: budgetLimit),
213 : ThemeHelper.hIndent2x,
214 2 : 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 1 : InputWrapper.currency(
250 : isRequired: true,
251 1 : showError: hasError && currency == null,
252 1 : value: currency,
253 2 : title: AppLocale.labels.currency,
254 2 : tooltip: AppLocale.labels.currencyTooltip,
255 4 : onChange: (value) => setState(() => currency = value),
256 : ),
257 1 : CheckboxInput(
258 2 : label: AppLocale.labels.skipFromTotals,
259 1 : value: skip,
260 0 : onChanged: (value) => setState(() => skip = value),
261 : constraints: constraints,
262 : ),
263 1 : ThemeHelper.hIndent2x,
264 : ],
265 : ),
266 : ),
267 : );
268 : }
269 : }
|