LCOV - code coverage report
Current view: top level - lib/pages/budget - budget_add_page.dart Coverage Total Hit
Test: lcov.info Lines: 77.2 % 136 105
Test Date: 2026-11-14 15:08:11 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.0-1