LCOV - code coverage report
Current view: top level - lib/pages/bill - bill_search_page.dart Coverage Total Hit
Test: lcov.info Lines: 91.1 % 112 102
Test Date: 2026-11-14 15:08:11 Functions: - 0 0

            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            1 :   const BillSearchPage({super.key});
      27              : 
      28            1 :   @override
      29            1 :   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            1 :   late List<ListAccountSelectorItem> accountList =
      44            6 :       state.getList(AppDataType.accounts).map((item) => ListAccountSelectorItem(item: item)).toList();
      45            1 :   late List<ListAccountSelectorItem> budgetList =
      46            6 :       state.getList(AppDataType.budgets).map((item) => ListAccountSelectorItem(item: item)).toList();
      47              : 
      48            1 :   @override
      49              :   void initState() {
      50            2 :     description = TextEditingController();
      51            2 :     amountFrom = TextEditingController();
      52            2 :     amountTo = TextEditingController();
      53            2 :     focus = FocusController();
      54            1 :     super.initState();
      55              :   }
      56              : 
      57            1 :   @override
      58              :   dispose() {
      59            2 :     description.dispose();
      60            2 :     amountFrom.dispose();
      61            2 :     amountTo.dispose();
      62            2 :     focus.dispose();
      63            1 :     super.dispose();
      64              :   }
      65              : 
      66            1 :   @override
      67            2 :   String getTitle() => AppLocale.labels.searchTooltip;
      68              : 
      69            1 :   @override
      70            2 :   String getButtonName() => AppLocale.labels.searchTooltip;
      71              : 
      72            1 :   @override
      73              :   Widget buildButton(BuildContext context, BoxConstraints constraints) {
      74            1 :     return FloatingActionButton(
      75              :       heroTag: 'bill_search_page',
      76            3 :       onPressed: () => setState(clearState),
      77            1 :       tooltip: getButtonName(),
      78              :       child: const Icon(Icons.search),
      79              :     );
      80              :   }
      81              : 
      82            1 :   bool getContentFilter(BillAppData item) {
      83            6 :     final descriptionMatch = item.title.toLowerCase().contains(description.text.toLowerCase());
      84            4 :     final accountMatch = account == null || item.account == account;
      85            4 :     final budgetMatch = budget == null || item.category == budget;
      86            1 :     final typeMatch = type == null || state.getByUuid(item.account)?.type == type;
      87            1 :     final currencyMatch = currency == null || item.currency == currency;
      88            1 :     final startDateMatch = startDate == null || item.createdAt.isAfter(startDate!);
      89            1 :     final endDateMatch = endDate == null || item.createdAt.isBefore(endDate!);
      90            3 :     final amountFromMatch = amountFrom.text.isEmpty || item.details >= double.tryParse(amountFrom.text)!;
      91            3 :     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            1 :   @override
     104            3 :   InterfaceIterator getContentStream() => state.getStream<BillAppData>(AppDataType.bills, filter: getContentFilter);
     105              : 
     106            1 :   @override
     107              :   Widget addHeaderWidget() {
     108            3 :     final width = ScreenHelper.state().width - ThemeHelper.getIndent(4);
     109            1 :     final indent = ThemeHelper.getIndent(2);
     110            1 :     return SliverToBoxAdapter(
     111            1 :       child: SingleScrollWrapper(
     112            1 :         controller: focus,
     113            1 :         child: Column(
     114            1 :           crossAxisAlignment: AppDesign.getAlignment(),
     115            1 :           children: [
     116            1 :             InputWrapper.text(
     117            2 :               title: AppLocale.labels.description,
     118            1 :               controller: description,
     119            2 :               tooltip: AppLocale.labels.descriptionTooltip,
     120              :             ),
     121            1 :             RowWidget(
     122              :               indent: indent,
     123            1 :               maxWidth: width + indent,
     124              :               chunk: const [0.5, 0.5],
     125            1 :               children: [
     126            1 :                 [
     127            1 :                   InputWrapper.text(
     128            2 :                     title: AppLocale.labels.amountFrom,
     129            2 :                     tooltip: AppLocale.labels.billSetTooltip,
     130            1 :                     controller: amountFrom,
     131              :                     inputType: const TextInputType.numberWithOptions(decimal: true),
     132            1 :                     formatter: [
     133            1 :                       SimpleInputFormatter.filterDouble,
     134              :                     ],
     135              :                   ),
     136              :                 ],
     137            1 :                 [
     138            1 :                   InputWrapper.text(
     139            2 :                     title: AppLocale.labels.amountTo,
     140            2 :                     tooltip: AppLocale.labels.billSetTooltip,
     141            1 :                     controller: amountTo,
     142              :                     inputType: const TextInputType.numberWithOptions(decimal: true),
     143            1 :                     formatter: [
     144            1 :                       SimpleInputFormatter.filterDouble,
     145              :                     ],
     146              :                   ),
     147              :                 ],
     148              :               ],
     149              :             ),
     150            1 :             InputWrapper.select(
     151            1 :               value: type,
     152            2 :               title: AppLocale.labels.accountType,
     153            2 :               tooltip: AppLocale.labels.accountTypeTooltip,
     154            1 :               options: AccountType.getList(),
     155            0 :               onChange: (value) => setState(() {
     156            0 :                 if (value != type) clearState();
     157            0 :                 type = value;
     158              :               }),
     159              :             ),
     160            1 :             InputWrapper(
     161              :               type: NamedInputType.accountSelector,
     162            4 :               value: account != null ? state.getByUuid(account!) : null,
     163            2 :               title: AppLocale.labels.account,
     164            2 :               tooltip: AppLocale.labels.titleAccountTooltip,
     165            1 :               state: state,
     166            1 :               options: accountList,
     167            3 :               onChange: (value) => setState(() {
     168            4 :                 if (value?.uuid != account) clearState();
     169            2 :                 account = value?.uuid;
     170              :               }),
     171              :               width: width,
     172              :             ),
     173            1 :             InputWrapper(
     174              :               type: NamedInputType.budgetSelector,
     175            4 :               value: budget != null ? state.getByUuid(budget!) : null,
     176            2 :               title: AppLocale.labels.budget,
     177            2 :               tooltip: AppLocale.labels.titleBudgetTooltip,
     178            1 :               state: state,
     179            1 :               options: budgetList,
     180            3 :               onChange: (value) => setState(() {
     181            4 :                 if (value?.uuid != budget) clearState();
     182            2 :                 budget = value?.uuid;
     183              :               }),
     184              :               width: width,
     185              :             ),
     186            1 :             InputWrapper.currency(
     187            1 :               value: currency,
     188            2 :               title: AppLocale.labels.currency,
     189            2 :               tooltip: AppLocale.labels.currencyTooltip,
     190            0 :               onChange: (value) => setState(() {
     191            0 :                 if (value != currency) clearState();
     192            0 :                 currency = value;
     193              :               }),
     194              :             ),
     195            1 :             TextWrapper(
     196            2 :               AppLocale.labels.dateRange,
     197            3 :               style: context.textTheme.bodyLarge,
     198              :             ),
     199            1 :             DateRangeInput(
     200            1 :               valueFrom: startDate,
     201            1 :               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              : }
        

Generated by: LCOV version 2.0-1