LCOV - code coverage report
Current view: top level - lib/pages/budget - budget_add_page.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 89 118 75.4 %
Date: 2024-10-04 11:12:13 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/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           2 :   const BudgetAddPage({
      32             :     super.key,
      33             :     this.title,
      34             :     this.budgetLimit,
      35             :     this.icon,
      36             :     this.color,
      37             :     this.currency,
      38             :   });
      39             : 
      40           1 :   @override
      41           1 :   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           1 :   @override
      54             :   void initState() {
      55           2 :     focus = FocusController();
      56           4 :     title = TextEditingController(text: widget.title);
      57           4 :     budgetLimit = TextEditingController(text: widget.budgetLimit != null ? widget.budgetLimit.toString() : '');
      58           3 :     icon = widget.icon;
      59           3 :     color = widget.color;
      60           1 :     final currencyId = AppPreferences.get(AppPreferences.prefCurrency);
      61           4 :     currency = widget.currency ?? CurrencyProvider.find(currencyId);
      62           1 :     super.initState();
      63             :   }
      64             : 
      65           1 :   @override
      66             :   void dispose() {
      67           2 :     focus.dispose();
      68           2 :     title.dispose();
      69           2 :     budgetLimit.dispose();
      70           1 :     super.dispose();
      71             :   }
      72             : 
      73           1 :   @override
      74             :   String getTitle() {
      75           2 :     return AppLocale.labels.createBudgetHeader;
      76             :   }
      77             : 
      78           1 :   @override
      79             :   bool hasFormErrors() {
      80           7 :     setState(() => hasError = title.text.isEmpty || currency == null);
      81           1 :     return hasError;
      82             :   }
      83             : 
      84           1 :   @override
      85             :   void updateStorage() {
      86           1 :     if (currency != null) {
      87           2 :       CurrencyProvider.pin(currency!);
      88             :     }
      89           3 :     super.state.add(BudgetAppData(
      90           2 :           title: title.text,
      91           3 :           amountLimit: double.tryParse(budgetLimit.text) ?? 0.0,
      92           1 :           amountSet: amountSet,
      93             :           progress: 0.0,
      94           1 :           color: color ?? Colors.red,
      95             :           hidden: false,
      96           1 :           currency: currency,
      97           1 :           icon: icon,
      98           2 :         )..setState(state));
      99             :   }
     100             : 
     101           1 :   @override
     102           2 :   String getButtonName() => AppLocale.labels.createBudgetTooltip;
     103             : 
     104           1 :   @override
     105             :   Widget buildButton(BuildContext context, BoxConstraints constraints) {
     106           1 :     NavigatorState nav = Navigator.of(context);
     107           1 :     return FullSizedButtonWidget(
     108             :       constraints: constraints,
     109           1 :       controller: focus,
     110           2 :       onPressed: () => triggerActionButton(nav),
     111           1 :       title: getButtonName(),
     112             :       icon: Icons.save,
     113             :     );
     114             :   }
     115             : 
     116           1 :   @override
     117             :   Widget buildContent(BuildContext context, BoxConstraints constraints) {
     118           1 :     final textTheme = context.textTheme;
     119           1 :     double indent = ThemeHelper.getIndent(2);
     120           1 :     double width = ThemeHelper.getWidth(context, 6, constraints);
     121             : 
     122           1 :     return SingleScrollWrapper(
     123           1 :       controller: focus,
     124           1 :       child: Container(
     125           1 :         margin: EdgeInsets.fromLTRB(indent, indent, indent, 240),
     126           1 :         child: Column(
     127           1 :           crossAxisAlignment: AppDesign.getAlignment(),
     128           1 :           children: [
     129           1 :             InputWrapper.text(
     130             :               isRequired: true,
     131           1 :               controller: title,
     132           2 :               title: AppLocale.labels.title,
     133           2 :               tooltip: AppLocale.labels.titleBudgetTooltip,
     134           1 :               showError: hasError && title.text.isEmpty,
     135             :             ),
     136           1 :             RowWidget(
     137             :               indent: indent,
     138           1 :               maxWidth: width + indent,
     139             :               chunk: const [0.5, 0.5],
     140           1 :               children: [
     141           1 :                 [
     142           1 :                   InputWrapper.icon(
     143           1 :                     value: icon,
     144           2 :                     title: AppLocale.labels.icon,
     145           0 :                     onChange: (value) => setState(() => icon = value),
     146             :                   ),
     147             :                 ],
     148           1 :                 [
     149           1 :                   InputWrapper.color(
     150           1 :                     value: color,
     151           2 :                     title: AppLocale.labels.color,
     152           0 :                     onChange: (value) => setState(() => color = value),
     153             :                   ),
     154             :                 ],
     155             :               ],
     156             :             ),
     157           1 :             Row(
     158             :               mainAxisAlignment: MainAxisAlignment.spaceBetween,
     159           1 :               children: [
     160           1 :                 TextWrapper(
     161           2 :                   AppLocale.labels.budgetLimit,
     162           1 :                   style: textTheme.bodyLarge,
     163             :                 ),
     164           2 :                 amountSet.isEmpty
     165           1 :                     ? IconButton(
     166             :                         icon: const Icon(Icons.vertical_split),
     167           2 :                         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           1 :             SimpleInput(
     184           3 :               key: ValueKey(AppLocale.labels.budgetLimit),
     185           1 :               controller: budgetLimit,
     186             :               type: const TextInputType.numberWithOptions(decimal: true),
     187           2 :               tooltip: AppLocale.labels.balanceTooltip,
     188           1 :               formatter: [
     189           2 :                 FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,4}')),
     190             :               ],
     191             :             ),
     192             :             ThemeHelper.hIndent2x,
     193           2 :             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           1 :             InputWrapper.currency(
     229             :               isRequired: true,
     230           1 :               showError: hasError && currency == null,
     231           1 :               value: currency,
     232           2 :               title: AppLocale.labels.currency,
     233           2 :               tooltip: AppLocale.labels.currencyTooltip,
     234           4 :               onChange: (value) => setState(() => currency = value),
     235             :             ),
     236             :           ],
     237             :         ),
     238             :       ),
     239             :     );
     240             :   }
     241             : }

Generated by: LCOV version 1.14