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