Line data Source code
1 : // Copyright 2025 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/iterator_controller.dart';
5 : import 'package:app_finance/_classes/controller/focus_controller.dart';
6 : import 'package:app_finance/_classes/herald/app_design.dart';
7 : import 'package:app_finance/_classes/herald/app_locale.dart';
8 : import 'package:app_finance/_classes/storage/app_data.dart';
9 : import 'package:app_finance/_classes/structure/bill_app_data.dart';
10 : import 'package:app_finance/_configs/account_type.dart';
11 : import 'package:app_finance/_configs/screen_helper.dart';
12 : import 'package:app_finance/_configs/theme_helper.dart';
13 : import 'package:app_finance/_ext/build_context_ext.dart';
14 : import 'package:app_finance/design/form/date_range_input.dart';
15 : import 'package:app_finance/design/form/list_account_selector.dart';
16 : import 'package:app_finance/design/form/simple_input.dart';
17 : import 'package:app_finance/design/wrapper/input_wrapper.dart';
18 : import 'package:app_finance/design/wrapper/row_widget.dart';
19 : import 'package:app_finance/design/wrapper/single_scroll_wrapper.dart';
20 : import 'package:app_finance/design/wrapper/text_wrapper.dart';
21 : import 'package:app_finance/pages/bill/bill_page.dart';
22 : import 'package:material_ui/material_ui.dart';
23 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
24 :
25 : class BillSearchPage extends StatefulWidget {
26 0 : const BillSearchPage({super.key});
27 :
28 0 : @override
29 0 : BillViewPageState createState() => BillViewPageState();
30 : }
31 :
32 : class BillViewPageState extends BillPageState<BillSearchPage> {
33 : String? account;
34 : String? budget;
35 : String? type;
36 : Currency? currency;
37 : DateTime? startDate;
38 : DateTime? endDate;
39 : late TextEditingController description;
40 : late TextEditingController amountFrom;
41 : late TextEditingController amountTo;
42 : late FocusController focus;
43 : late List<ListAccountSelectorItem> accountList =
44 : state.getList(AppDataType.accounts).map((item) => ListAccountSelectorItem(item: item)).toList();
45 : late List<ListAccountSelectorItem> budgetList =
46 : state.getList(AppDataType.budgets).map((item) => ListAccountSelectorItem(item: item)).toList();
47 :
48 0 : @override
49 : void initState() {
50 0 : description = TextEditingController();
51 0 : amountFrom = TextEditingController();
52 0 : amountTo = TextEditingController();
53 0 : focus = FocusController();
54 0 : super.initState();
55 : }
56 :
57 0 : @override
58 : dispose() {
59 0 : description.dispose();
60 0 : amountFrom.dispose();
61 0 : amountTo.dispose();
62 0 : focus.dispose();
63 0 : super.dispose();
64 : }
65 :
66 0 : @override
67 0 : String getTitle() => AppLocale.labels.searchTooltip;
68 :
69 0 : @override
70 0 : String getButtonName() => AppLocale.labels.searchTooltip;
71 :
72 0 : @override
73 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
74 0 : return FloatingActionButton(
75 : heroTag: 'bill_search_page',
76 0 : onPressed: () => setState(clearState),
77 0 : tooltip: getButtonName(),
78 : child: const Icon(Icons.search),
79 : );
80 : }
81 :
82 0 : bool getContentFilter(BillAppData item) {
83 0 : final descriptionMatch = item.title.toLowerCase().contains(description.text.toLowerCase());
84 0 : final accountMatch = account == null || item.account == account;
85 0 : final budgetMatch = budget == null || item.category == budget;
86 0 : final typeMatch = type == null || state.getByUuid(item.account)?.type == type;
87 0 : final currencyMatch = currency == null || item.currency == currency;
88 0 : final startDateMatch = startDate == null || item.createdAt.isAfter(startDate!);
89 0 : final endDateMatch = endDate == null || item.createdAt.isBefore(endDate!);
90 0 : final amountFromMatch = amountFrom.text.isEmpty || item.details >= double.tryParse(amountFrom.text)!;
91 0 : final amountToMatch = amountTo.text.isEmpty || item.details <= double.tryParse(amountTo.text)!;
92 : return !(descriptionMatch &&
93 : accountMatch &&
94 : budgetMatch &&
95 : typeMatch &&
96 : currencyMatch &&
97 : startDateMatch &&
98 : endDateMatch &&
99 : amountFromMatch &&
100 : amountToMatch);
101 : }
102 :
103 0 : @override
104 0 : InterfaceIterator getContentStream() => state.getStream<BillAppData>(AppDataType.bills, filter: getContentFilter);
105 :
106 0 : @override
107 : Widget addHeaderWidget() {
108 0 : final width = ScreenHelper.state().width - ThemeHelper.getIndent(4);
109 0 : final indent = ThemeHelper.getIndent(2);
110 0 : return SliverToBoxAdapter(
111 0 : child: SingleScrollWrapper(
112 0 : controller: focus,
113 0 : child: Column(
114 0 : crossAxisAlignment: AppDesign.getAlignment(),
115 0 : children: [
116 0 : InputWrapper.text(
117 0 : title: AppLocale.labels.description,
118 0 : controller: description,
119 0 : tooltip: AppLocale.labels.descriptionTooltip,
120 : ),
121 0 : RowWidget(
122 : indent: indent,
123 0 : maxWidth: width + indent,
124 : chunk: const [0.5, 0.5],
125 0 : children: [
126 0 : [
127 0 : InputWrapper.text(
128 0 : title: AppLocale.labels.amountFrom,
129 0 : tooltip: AppLocale.labels.billSetTooltip,
130 0 : controller: amountFrom,
131 : inputType: const TextInputType.numberWithOptions(decimal: true),
132 0 : formatter: [
133 0 : SimpleInputFormatter.filterDouble,
134 : ],
135 : ),
136 : ],
137 0 : [
138 0 : InputWrapper.text(
139 0 : title: AppLocale.labels.amountTo,
140 0 : tooltip: AppLocale.labels.billSetTooltip,
141 0 : controller: amountTo,
142 : inputType: const TextInputType.numberWithOptions(decimal: true),
143 0 : formatter: [
144 0 : SimpleInputFormatter.filterDouble,
145 : ],
146 : ),
147 : ],
148 : ],
149 : ),
150 0 : InputWrapper.select(
151 0 : value: type,
152 0 : title: AppLocale.labels.accountType,
153 0 : tooltip: AppLocale.labels.accountTypeTooltip,
154 0 : options: AccountType.getList(),
155 0 : onChange: (value) => setState(() {
156 0 : if (value != type) clearState();
157 0 : type = value;
158 : }),
159 : ),
160 0 : InputWrapper(
161 : type: NamedInputType.accountSelector,
162 0 : value: account != null ? state.getByUuid(account!) : null,
163 0 : title: AppLocale.labels.account,
164 0 : tooltip: AppLocale.labels.titleAccountTooltip,
165 0 : state: state,
166 0 : options: accountList,
167 0 : onChange: (value) => setState(() {
168 0 : if (value?.uuid != account) clearState();
169 0 : account = value?.uuid;
170 : }),
171 : width: width,
172 : ),
173 0 : InputWrapper(
174 : type: NamedInputType.budgetSelector,
175 0 : value: budget != null ? state.getByUuid(budget!) : null,
176 0 : title: AppLocale.labels.budget,
177 0 : tooltip: AppLocale.labels.titleBudgetTooltip,
178 0 : state: state,
179 0 : options: budgetList,
180 0 : onChange: (value) => setState(() {
181 0 : if (value?.uuid != budget) clearState();
182 0 : budget = value?.uuid;
183 : }),
184 : width: width,
185 : ),
186 0 : InputWrapper.currency(
187 0 : value: currency,
188 0 : title: AppLocale.labels.currency,
189 0 : tooltip: AppLocale.labels.currencyTooltip,
190 0 : onChange: (value) => setState(() {
191 0 : if (value != currency) clearState();
192 0 : currency = value;
193 : }),
194 : ),
195 0 : TextWrapper(
196 0 : AppLocale.labels.dateRange,
197 0 : style: context.textTheme.bodyLarge,
198 : ),
199 0 : DateRangeInput(
200 0 : valueFrom: startDate,
201 0 : value: endDate,
202 0 : setState: (start, end) => setState(() {
203 0 : startDate = start;
204 0 : endDate = end;
205 0 : clearState();
206 : }),
207 : ),
208 : ],
209 : ),
210 : ),
211 : );
212 : }
213 : }
|