LCOV - code coverage report
Current view: top level - lib/pages/settings/widgets - import_tab.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 1 141 0.7 %
Date: 2024-10-04 11:09:33 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/storage/app_data.dart';
       6             : import 'package:app_finance/_classes/herald/app_locale.dart';
       7             : import 'package:app_finance/_classes/storage/file_parser.dart';
       8             : import 'package:app_finance/_classes/storage/file_picker.dart';
       9             : import 'package:app_finance/_classes/storage/transaction_log.dart';
      10             : import 'package:app_finance/_classes/controller/focus_controller.dart';
      11             : import 'package:app_finance/design/form/list_selector_item.dart';
      12             : import 'package:app_finance/_classes/structure/interface_app_data.dart';
      13             : import 'package:app_finance/_classes/controller/date_format_helper.dart';
      14             : import 'package:app_finance/_classes/storage/app_preferences.dart';
      15             : import 'package:app_finance/_configs/theme_helper.dart';
      16             : import 'package:app_finance/_ext/build_context_ext.dart';
      17             : import 'package:app_finance/design/form/list_selector.dart';
      18             : import 'package:app_finance/design/form/simple_input.dart';
      19             : import 'package:app_finance/design/generic/loading_widget.dart';
      20             : import 'package:app_finance/design/wrapper/input_wrapper.dart';
      21             : import 'package:app_finance/pages/settings/widgets/recover_tab/date_time_helper_widget.dart';
      22             : import 'package:app_finance/design/wrapper/single_scroll_wrapper.dart';
      23             : import 'package:flutter/material.dart';
      24             : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
      25             : import 'package:provider/provider.dart';
      26             : 
      27             : class ImportTab extends StatefulWidget {
      28           5 :   const ImportTab({
      29             :     super.key,
      30             :   });
      31             : 
      32           0 :   @override
      33           0 :   ImportTabState createState() => ImportTabState();
      34             : }
      35             : 
      36             : class ImportTabState extends State<ImportTab> {
      37             :   late FocusController focus;
      38             :   late AppData state;
      39             :   List<List<dynamic>>? fileContent;
      40             :   StringBuffer errorMessage = StringBuffer();
      41             :   List<String> columnMap = [];
      42             :   bool isLoading = false;
      43             :   final dateFormat = TextEditingController(text: 'M/d/yyyy HH:mm');
      44             : 
      45           0 :   @override
      46             :   void initState() {
      47           0 :     focus = FocusController();
      48           0 :     super.initState();
      49             :   }
      50             : 
      51           0 :   @override
      52             :   void dispose() {
      53           0 :     focus.dispose();
      54           0 :     dateFormat.dispose();
      55           0 :     super.dispose();
      56             :   }
      57             : 
      58             :   late Map<String, String?> attrValue = {
      59             :     FileParser.attrAccountName: AppPreferences.get(AppPreferences.prefAccount),
      60             :     FileParser.attrCategoryName: AppPreferences.get(AppPreferences.prefBudget),
      61             :     FileParser.attrBillCurrency: AppPreferences.get(AppPreferences.prefCurrency),
      62             :     FileParser.attrBillType: AppLocale.labels.bill,
      63             :   };
      64             : 
      65           0 :   Future<void> pickFile(List<String> ext) async {
      66             :     try {
      67           0 :       setState(() => errorMessage.clear());
      68           0 :       final picker = FilePicker(ext);
      69           0 :       final content = await picker.pickFile();
      70           0 :       setState(() {
      71           0 :         fileContent = content;
      72           0 :         columnMap = picker.columnMap;
      73           0 :         if (columnMap.contains(FileParser.attrBillDate)) {
      74           0 :           int index = columnMap.indexOf(FileParser.attrBillDate);
      75           0 :           dateFormat.text = DateFormatHelper().detectFormat([fileContent!.last[index]], AppLocale.code);
      76             :         }
      77             :       });
      78             :     } catch (e) {
      79           0 :       setState(() => errorMessage.writeln(e.toString()));
      80             :     }
      81             :   }
      82             : 
      83           0 :   Future<void> parseFile() async {
      84           0 :     state.isLoading = true;
      85           0 :     fnSearch(AppDataType type, String value) => state.getList(type).where((e) => e.title == value).firstOrNull;
      86           0 :     fnAdd(InterfaceAppData item) {
      87           0 :       item = state.add(item);
      88           0 :       TransactionLog.save(item);
      89             :       return item;
      90             :     }
      91             : 
      92           0 :     final parser = FileParser(columnMap: columnMap, search: fnSearch, add: fnAdd);
      93           0 :     final def = {
      94           0 :       FileParser.attrAccountName: attrValue[FileParser.attrAccountName],
      95           0 :       FileParser.attrCategoryName: attrValue[FileParser.attrCategoryName],
      96           0 :       FileParser.attrBillCurrency: attrValue[FileParser.attrBillCurrency],
      97           0 :       FileParser.defDateFormat: dateFormat.text,
      98             :     };
      99           0 :     for (int i = 1; i < (fileContent?.length ?? 0); i++) {
     100             :       try {
     101           0 :         dynamic newItem = await parser.parseFileLine(fileContent![i], def);
     102           0 :         newItem = state.add(newItem, newItem.uuid);
     103           0 :         TransactionLog.save(newItem);
     104             :       } catch (e) {
     105           0 :         setState(() => errorMessage.writeln('[$i / ${fileContent?.length}] ${e.toString()}.'));
     106             :       }
     107             :     }
     108           0 :     await state.restate();
     109           0 :     setState(() => fileContent = null);
     110             :   }
     111             : 
     112           0 :   Future<void> wrapCall(Function callback) async {
     113           0 :     setState(() {
     114           0 :       isLoading = true;
     115           0 :       errorMessage.clear();
     116             :     });
     117           0 :     await callback();
     118           0 :     await Future.delayed(const Duration(seconds: 1));
     119           0 :     setState(() {
     120           0 :       isLoading = false;
     121           0 :       String isFinished = AppLocale.labels.processIsFinished;
     122           0 :       errorMessage.write(isFinished);
     123           0 :       if (errorMessage.toString() == isFinished) {
     124           0 :         Future.delayed(const Duration(seconds: 2), () => setState(() => errorMessage.clear()));
     125             :       }
     126             :     });
     127             :   }
     128             : 
     129           0 :   @override
     130             :   Widget build(BuildContext context) {
     131           0 :     final indent = ThemeHelper.getIndent(2);
     132           0 :     final width = ThemeHelper.getWidth(context, 12);
     133           0 :     final textTheme = context.textTheme;
     134           0 :     final ColorScheme colorScheme = context.colorScheme;
     135             : 
     136           0 :     return Consumer<AppData>(builder: (context, appState, _) {
     137           0 :       state = appState;
     138           0 :       return SingleScrollWrapper(
     139           0 :         controller: focus,
     140           0 :         child: Padding(
     141           0 :           padding: EdgeInsets.all(indent),
     142           0 :           child: Column(
     143           0 :             crossAxisAlignment: AppDesign.getAlignment(),
     144           0 :             children: [
     145             :               ThemeHelper.hIndent2x,
     146           0 :               if (errorMessage.toString() != '')
     147           0 :                 Text(errorMessage.toString(), style: textTheme.headlineMedium?.copyWith(color: colorScheme.error)),
     148           0 :               if (isLoading) ...[
     149           0 :                 SizedBox(height: indent * 6),
     150           0 :                 LoadingWidget(isLoading: isLoading),
     151           0 :               ] else if (fileContent != null) ...[
     152           0 :                 ...List<Widget>.generate(fileContent!.first.length, (index) {
     153           0 :                   return Column(
     154           0 :                     crossAxisAlignment: AppDesign.getAlignment(),
     155           0 :                     children: [
     156             :                       ThemeHelper.hIndent2x,
     157           0 :                       Text(
     158           0 :                         AppLocale.labels.columnMap(fileContent!.first[index]),
     159           0 :                         style: textTheme.bodyLarge,
     160             :                       ),
     161           0 :                       ListSelector<ListSelectorItem>(
     162           0 :                         options: FileParser.getMappingTypes(),
     163           0 :                         value: ListSelectorItem(id: columnMap[index], name: ''),
     164           0 :                         hintText: AppLocale.labels.columnMapTooltip(fileContent!.first[index]),
     165           0 :                         setState: (value) => setState(() {
     166           0 :                           columnMap[index] = value?.id ?? '';
     167           0 :                           if (columnMap[index] == FileParser.attrBillDate) {
     168           0 :                             dateFormat.text =
     169           0 :                                 DateFormatHelper().detectFormat([fileContent!.last[index]], AppLocale.code);
     170             :                           }
     171             :                         }),
     172             :                       ),
     173             :                     ],
     174             :                   );
     175             :                 }),
     176           0 :                 const Divider(),
     177           0 :                 if (!columnMap.contains(FileParser.attrAccountName)) ...[
     178             :                   ThemeHelper.hIndent2x,
     179           0 :                   InputWrapper(
     180             :                     type: NamedInputType.accountSelector,
     181           0 :                     title: AppLocale.labels.def('${AppLocale.labels.account}: ${AppLocale.labels.title}'),
     182           0 :                     tooltip: AppLocale.labels.titleAccountTooltip,
     183           0 :                     value: state.getByUuid(attrValue[FileParser.attrAccountName] ?? ''),
     184           0 :                     onChange: (value) => setState(() => attrValue[FileParser.attrAccountName] = value?.id),
     185             :                     width: width,
     186           0 :                     state: state,
     187             :                   ),
     188             :                 ],
     189           0 :                 if (!columnMap.contains(FileParser.attrCategoryName)) ...[
     190             :                   ThemeHelper.hIndent2x,
     191           0 :                   InputWrapper(
     192             :                     type: NamedInputType.budgetSelector,
     193           0 :                     title: AppLocale.labels.def('${AppLocale.labels.budget}: ${AppLocale.labels.title}'),
     194           0 :                     tooltip: AppLocale.labels.titleBudgetTooltip,
     195           0 :                     value: state.getByUuid(attrValue[FileParser.attrCategoryName] ?? ''),
     196           0 :                     onChange: (value) => setState(() => attrValue[FileParser.attrCategoryName] = value?.id),
     197             :                     width: width,
     198           0 :                     state: state,
     199             :                   ),
     200             :                 ],
     201           0 :                 if (!columnMap.contains(FileParser.attrBillType)) ...[
     202             :                   ThemeHelper.hIndent2x,
     203           0 :                   Text(
     204           0 :                     AppLocale.labels.def('${AppLocale.labels.bill}: ${AppLocale.labels.billTypeTooltip}'),
     205           0 :                     style: textTheme.bodyLarge,
     206             :                   ),
     207           0 :                   ListSelector<ListSelectorItem>(
     208           0 :                     value: attrValue[FileParser.attrBillType] != null
     209           0 :                         ? ListSelectorItem(id: attrValue[FileParser.attrBillType]!, name: '')
     210             :                         : null,
     211           0 :                     hintText: AppLocale.labels.billTypeTooltip,
     212           0 :                     options: [
     213           0 :                       ListSelectorItem(id: AppLocale.labels.bill, name: AppLocale.labels.bill),
     214           0 :                       ListSelectorItem(id: AppLocale.labels.flowTypeInvoice, name: AppLocale.labels.flowTypeInvoice),
     215             :                     ],
     216           0 :                     setState: (value) => setState(() => attrValue[FileParser.attrBillType] = value?.id),
     217             :                   ),
     218             :                 ],
     219           0 :                 if (!columnMap.contains(FileParser.attrBillCurrency)) ...[
     220             :                   ThemeHelper.hIndent2x,
     221           0 :                   InputWrapper.currency(
     222           0 :                     title: AppLocale.labels.def('${AppLocale.labels.bill}: ${AppLocale.labels.currency}'),
     223           0 :                     value: CurrencyProvider.find(attrValue[FileParser.attrBillCurrency]),
     224           0 :                     onChange: (value) => setState(() => attrValue[FileParser.attrBillCurrency] = value?.id),
     225             :                   ),
     226             :                 ],
     227           0 :                 Column(crossAxisAlignment: AppDesign.getAlignment(), children: [
     228             :                   ThemeHelper.hIndent4x,
     229           0 :                   Text(
     230           0 :                     AppLocale.labels.dateFormat,
     231           0 :                     style: textTheme.bodyLarge,
     232             :                   ),
     233           0 :                   SimpleInput(controller: dateFormat),
     234             :                   const DateTimeHelperWidget(),
     235             :                   ThemeHelper.hIndent4x,
     236           0 :                   SizedBox(
     237             :                     width: double.infinity,
     238           0 :                     child: FloatingActionButton(
     239             :                       heroTag: 'import_tab_parse',
     240           0 :                       onPressed: () => wrapCall(parseFile),
     241           0 :                       tooltip: AppLocale.labels.parseFile,
     242           0 :                       child: Text(
     243           0 :                         AppLocale.labels.parseFile,
     244             :                       ),
     245             :                     ),
     246             :                   ),
     247             :                 ]),
     248             :               ] else
     249           0 :                 ...List<Widget>.generate(FilePicker.fileFormats.length * 2, (index) {
     250           0 :                   if (index % 2 == 0) {
     251             :                     return ThemeHelper.hIndent2x;
     252             :                   } else {
     253           0 :                     final format = FilePicker.fileFormats[index ~/ 2];
     254           0 :                     return SizedBox(
     255             :                       width: double.infinity,
     256           0 :                       child: FloatingActionButton(
     257           0 :                         heroTag: 'import_tab_pick_$format',
     258           0 :                         onPressed: () => wrapCall(() => pickFile([format])),
     259           0 :                         tooltip: AppLocale.labels.pickFile(format),
     260           0 :                         child: Text(
     261           0 :                           AppLocale.labels.pickFile(format),
     262             :                         ),
     263             :                       ),
     264             :                     );
     265             :                   }
     266             :                 })
     267             :             ],
     268             :           ),
     269             :         ),
     270             :       );
     271             :     });
     272             :   }
     273             : }

Generated by: LCOV version 1.14