Commit dc8cc0d

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-12-13 16:52:28
extract export_import classes
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 4440284
lib/components/dialoges/enter_timeformat.dart
@@ -1,4 +1,4 @@
-import 'package:blood_pressure_app/screens/subsettings/export_field_format_documentation.dart';
+import 'package:blood_pressure_app/screens/subsettings/export_import/export_field_format_documentation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:flutter_markdown/flutter_markdown.dart';
lib/components/export_warn_banner.dart
@@ -41,7 +41,7 @@ class _ExportWarnBannerState extends State<ExportWarnBanner> {
       case ExportFormat.pdf:
         return _buildNotExportable(context);
       case ExportFormat.csv:
-        if (widget.csvExportSettings.exportHeadline == false) return _buildNotExportable(context);
+        if (widget.csvExportSettings.exportHeadline == false) return _buildNoHeadline(context);
         if (widget.csvExportSettings.fieldDelimiter != ',' && widget.csvExportSettings.fieldDelimiter != '|') return _buildNotExportable(context);
         if (widget.csvExportSettings.textDelimiter != '"' && widget.csvExportSettings.textDelimiter != "'") return _buildNotExportable(context);
         final preset = widget.csvExportSettings.exportFieldsConfiguration.activePreset;
@@ -78,6 +78,13 @@ class _ExportWarnBannerState extends State<ExportWarnBanner> {
     return _banner(localizations.exportWarnConfigNotImportable, localizations);
   }
 
+  /// Exports made with this configuration are not importable because there
+  /// is no headline to infer types.
+  Widget _buildNoHeadline(BuildContext context) {
+    final localizations = AppLocalizations.of(context)!;
+    return _banner(localizations.errNeedHeadline, localizations);
+  }
+
   /// Exports made with this configuration are not fully importable.
   Widget _buildIncompleteExport(BuildContext context, Iterable<RowDataFieldType?> missingTypes) {
     final localizations = AppLocalizations.of(context)!;
lib/l10n/app_en.arb
@@ -221,7 +221,7 @@
       }
     }
   },
-  "exportWarnConfigNotImportable": "Hey! Just a friendly heads up: the current export configuration won''t be importable. To fix it, make sure you set the export type as CSV, enable the headline, and include one of the time formats available.",
+  "exportWarnConfigNotImportable": "Hey! Just a friendly heads up: the current export configuration won''t be importable. To fix it, make sure you set the export type as CSV and include one of the time formats available.",
   "@exportWarnConfigNotImportable": {},
   "exportWarnNotEveryFieldExported": "Beware that you are not exporting all fields: {fields} {count, plural, one{is} other{are}} missing.",
   "@exportWarnNotEveryFieldExported": {
@@ -474,5 +474,7 @@
   "exportFieldsPreset": "Export fields preset",
   "@exportFieldsPreset": {},
   "remove": "Remove",
-  "@remove": {}
+  "@remove": {},
+  "manageExportColumns": "Manage export columns",
+  "@manageExportColumns": {}
 }
lib/screens/subsettings/export_import/active_field_customization.dart
@@ -0,0 +1,127 @@
+import 'package:blood_pressure_app/components/settings/dropdown_list_tile.dart';
+import 'package:blood_pressure_app/model/export_import/column.dart';
+import 'package:blood_pressure_app/model/export_import/export_configuration.dart';
+import 'package:blood_pressure_app/model/storage/common_settings_interfaces.dart';
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:provider/provider.dart';
+
+/// Setting for configuring fields in the current export.
+class ActiveExportFieldCustomization extends StatelessWidget {
+  /// Create a widget for configuring fields in the current export.
+  const ActiveExportFieldCustomization({super.key,
+    required this.format,});
+
+  final ExportFormat format;
+
+  @override
+  Widget build(BuildContext context) => switch (format) {
+    ExportFormat.csv => Consumer<CsvExportSettings>(builder: _builder),
+    ExportFormat.pdf => Consumer<PdfExportSettings>(builder: _builder),
+    ExportFormat.db => const SizedBox.shrink()
+  };
+
+  Widget _builder(BuildContext context, CustomFieldsSettings settings, Widget? child) {
+    final localizations = AppLocalizations.of(context)!;
+    final fieldsConfig = settings.exportFieldsConfiguration;
+    final dropdown = DropDownListTile(
+      title: Text(localizations.exportFieldsPreset),
+      value: fieldsConfig.activePreset,
+      items: ExportImportPreset.values.map(
+              (e) => DropdownMenuItem(
+            value: e,
+            child: Text(e.localize(localizations)),
+          )
+      ).toList(),
+      onChanged: (selectedPreset) {
+        if (selectedPreset != null) {
+          fieldsConfig.activePreset = selectedPreset;
+        }
+      },
+    );
+
+    if (fieldsConfig.activePreset != ExportImportPreset.none) {
+      return dropdown;
+    }
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: [
+        dropdown,
+        Container(
+          margin: const EdgeInsets.symmetric(
+              horizontal: 16,
+              vertical: 5
+          ),
+          height: 400,
+          decoration: BoxDecoration(
+            border: Border.all(color: Theme.of(context).textTheme.labelLarge?.color ?? Colors.teal),
+            borderRadius: const BorderRadius.all(Radius.circular(10)),
+          ),
+          child: Consumer<ExportColumnsManager>(
+              builder: (context, availableColumns, child) {
+                final activeColumns = fieldsConfig.getActiveColumns(availableColumns);
+                return ReorderableListView.builder(
+                    itemBuilder: (context, idx) {
+                      if (idx >= activeColumns.length) {
+                        return ListTile(
+                          key: const Key('add field'),
+                          leading: const Icon(Icons.add),
+                          title: Text(localizations.addEntry),
+                          onTap: () async {
+                            final column = await showDialog<ExportColumn?>(context: context, builder: (context) =>
+                                SimpleDialog(
+                                  title: Text(localizations.addEntry),
+                                  insetPadding: const EdgeInsets.symmetric(
+                                    vertical: 64,
+                                  ),
+                                  children: availableColumns.getAllColumns().map((column) =>
+                                      ListTile(
+                                        title: Text(column.userTitle(localizations)),
+                                        onTap: () => Navigator.of(context).pop(column),
+                                      )
+                                  ).toList(),
+                                )
+                            );
+                            if (column != null) fieldsConfig.addUserColumn(column);
+                          },
+                        );
+                      }
+                      return ListTile(
+                        key: Key(activeColumns[idx].internalIdentifier + idx.toString()),
+                        title: Text(activeColumns[idx].userTitle(localizations)),
+                        trailing: Row(
+                          mainAxisSize: MainAxisSize.min,
+                          children: [
+                            IconButton(
+                                tooltip: localizations.remove,
+                                onPressed: () {
+                                  fieldsConfig.removeUserColumn(activeColumns[idx].internalIdentifier);
+                                },
+                                icon: const Icon(Icons.remove_circle_outline)
+                            ),
+                            const Icon(Icons.drag_handle),
+                          ],
+                        ),
+                      );
+                    },
+                    itemCount: activeColumns.length + 1,
+                    onReorder: fieldsConfig.reorderUserColumns
+                );
+              }
+          ),
+        ),
+        ListTile(
+          title: Text(localizations.manageExportColumns),
+          trailing: const Icon(Icons.arrow_forward_ios),
+          onTap: () {
+            // TODO implement adding / editing columns => separate ColumnsManagerScreen ?
+          },
+        ),
+      ],
+    );
+  }
+}
\ No newline at end of file
lib/screens/subsettings/export_import/export_button_bar.dart
@@ -0,0 +1,189 @@
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:blood_pressure_app/model/blood_pressure.dart';
+import 'package:blood_pressure_app/model/export_import/csv_converter.dart';
+import 'package:blood_pressure_app/model/export_import/pdf_converter.dart';
+import 'package:blood_pressure_app/model/export_import/record_parsing_result.dart';
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
+import 'package:blood_pressure_app/platform_integration/platform_client.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:jsaver/jSaver.dart';
+import 'package:path/path.dart';
+import 'package:provider/provider.dart';
+import 'package:sqflite/sqflite.dart';
+
+/// Button row to export and import the current configuration.
+class ExportButtonBar extends StatelessWidget {
+  /// Create buttons for im- and exporting measurements.
+  const ExportButtonBar({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final localizations = AppLocalizations.of(context)!;
+
+    return Container(
+      height: 60,
+      color: Theme.of(context).colorScheme.onInverseSurface,
+      child: Center(
+        child: Row(
+        children: [
+          Expanded(
+            flex: 50,
+            child: MaterialButton(
+              height: 60,
+              child: Text(localizations.export),
+              onPressed: () async {
+                final exportSettings = Provider.of<ExportSettings>(context, listen: false);
+                final filename = 'blood_press_${DateTime.now().toIso8601String()}';
+                switch (exportSettings.exportFormat) {
+                  case ExportFormat.db:
+                    final path = join(await getDatabasesPath(), 'blood_pressure.db');
+
+                    if (context.mounted) _exportFile(context, path, '$filename.db', 'text/db');
+                    break;
+                  case ExportFormat.csv:
+                    final csvConverter = CsvConverter(
+                      Provider.of<CsvExportSettings>(context, listen: false),
+                      Provider.of<ExportColumnsManager>(context, listen: false),
+                    );
+                    final csvString = csvConverter.create(await _getRecords(context));
+                    final data = Uint8List.fromList(utf8.encode(csvString));
+                    if (context.mounted) _exportData(context, data, '$filename.csv', 'text/csv');
+                    break;
+                  case ExportFormat.pdf:
+                    final pdfConverter = PdfConverter(
+                        Provider.of<PdfExportSettings>(context, listen: false),
+                        localizations,
+                        Provider.of<Settings>(context, listen: false),
+                        Provider.of<ExportColumnsManager>(context, listen: false)
+                    );
+                    final pdf = await pdfConverter.create(await _getRecords(context));
+                    if (context.mounted) _exportData(context, pdf, '$filename.pdf', 'text/pdf');
+                }
+              }
+            )
+          ),
+          const VerticalDivider(),
+          Expanded(
+            flex: 50,
+            child: MaterialButton(
+              height: 60,
+              child: Text(localizations.import),
+              onPressed: () async {
+                final messenger = ScaffoldMessenger.of(context);
+
+                final file = (await FilePicker.platform.pickFiles(
+                  allowMultiple: false,
+                  withData: true,
+                ))?.files.firstOrNull;
+                if (file == null) {
+                  _showError(messenger, localizations.errNoFileOpened);
+                  return;
+                }
+                if (!context.mounted) return;
+                switch(file.extension?.toLowerCase()) {
+                  case 'csv':
+                    final binaryContent = file.bytes;
+                    if (binaryContent == null) {
+                      _showError(messenger, localizations.errCantReadFile);
+                      return;
+                    }
+                    final converter = CsvConverter(
+                      Provider.of<CsvExportSettings>(context, listen: false),
+                      Provider.of<ExportColumnsManager>(context, listen: false),
+                    );
+                    final result = converter.parse(utf8.decode(binaryContent));
+                    final importedRecords = result.getOr((error) {
+                      switch (error) {
+                        case RecordParsingErrorEmptyFile():
+                          _showError(messenger, localizations.errParseEmptyCsvFile);
+                          break;
+                        case RecordParsingErrorTimeNotRestoreable():
+                          _showError(messenger, localizations.errParseTimeNotRestoreable);
+                          break;
+                        case RecordParsingErrorUnknownColumn():
+                          _showError(messenger, localizations.errParseUnknownColumn(error.title));
+                          break;
+                        case RecordParsingErrorExpectedMoreFields():
+                          _showError(messenger, localizations.errParseLineTooShort(error.lineNumber));
+                          break;
+                        case RecordParsingErrorUnparsableField():
+                          _showError(messenger, localizations.errParseFailedDecodingField(
+                              error.lineNumber, error.fieldContents));
+                          break;
+                      }
+                      return null;
+                    });
+                    if (result.hasError()) return;
+                    final model = Provider.of<BloodPressureModel>(context, listen: false);
+                    for (final record in importedRecords) { // TODO: background thread
+                      await model.add(record);
+                    }
+                    messenger.showSnackBar(SnackBar(content: Text(
+                        localizations.importSuccess(importedRecords.length))));
+                    break;
+                  case 'db':
+                    final model = Provider.of<BloodPressureModel>(context, listen: false);
+                    final importedModel = await BloodPressureModel.create(dbPath: file.path, isFullPath: true);
+                    for (final record in await importedModel.all) {
+                      await model.add(record);
+                    }
+                    break;
+                  default:
+                    _showError(messenger, localizations.errWrongImportFormat);
+                }
+              },
+            )
+          ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  void _showError(ScaffoldMessengerState messenger, String text) =>
+      messenger.showSnackBar(SnackBar(content: Text(text)));
+
+  /// Get the records that should be exported.
+  Future<List<BloodPressureRecord>> _getRecords(BuildContext context) {
+    final range = Provider.of<IntervallStoreManager>(context, listen: false).exportPage.currentRange;
+    final model = Provider.of<BloodPressureModel>(context, listen: false);
+    return model.getInTimeRange(range.start, range.end);
+  }
+
+  /// Save to default export path or share by providing a path.
+  Future<void> _exportFile(BuildContext context, String path, String fullFileName, String mimeType) async {
+    final settings = Provider.of<ExportSettings>(context, listen: false);
+    if (settings.defaultExportDir.isEmpty) {
+      await PlatformClient.shareFile(path, mimeType, fullFileName);
+    } else {
+      JSaver.instance.save(
+          fromPath: path,
+          androidPathOptions: AndroidPathOptions(toDefaultDirectory: true)
+      );
+      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+          content: Text(AppLocalizations.of(context)!.success(settings.defaultExportDir))));
+    }
+  }
+
+  /// Save to default export path or share by providing binary data.
+  Future<void> _exportData(BuildContext context, Uint8List data, String fullFileName, String mimeType) async {
+    final settings = Provider.of<ExportSettings>(context, listen: false);
+    if (settings.defaultExportDir.isEmpty) {
+      await PlatformClient.shareData(data, mimeType, fullFileName);
+    } else {
+      final file = File(joinPath(Directory.systemTemp.path, fullFileName));
+      file.writeAsBytesSync(data);
+      await _exportFile(context, file.path, fullFileName, mimeType);
+    }
+  }
+}
\ No newline at end of file
lib/screens/subsettings/export_column_data.dart โ†’ lib/screens/subsettings/export_import/export_column_data.dart
@@ -2,7 +2,7 @@ import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/export_import/legacy_column.dart';
 import 'package:blood_pressure_app/model/export_options.dart';
-import 'package:blood_pressure_app/screens/subsettings/export_field_format_documentation.dart';
+import 'package:blood_pressure_app/screens/subsettings/export_import/export_field_format_documentation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 
lib/screens/subsettings/export_field_format_documentation.dart โ†’ lib/screens/subsettings/export_import/export_field_format_documentation.dart
File renamed without changes
lib/screens/subsettings/export_import/export_import_screen.dart
@@ -0,0 +1,181 @@
+
+import 'package:blood_pressure_app/components/diabled.dart';
+import 'package:blood_pressure_app/components/display_interval_picker.dart';
+import 'package:blood_pressure_app/components/export_warn_banner.dart';
+import 'package:blood_pressure_app/components/settings/settings_widgets.dart';
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
+import 'package:blood_pressure_app/model/storage/storage.dart';
+import 'package:blood_pressure_app/screens/subsettings/export_import/active_field_customization.dart';
+import 'package:blood_pressure_app/screens/subsettings/export_import/export_button_bar.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:jsaver/jSaver.dart';
+import 'package:provider/provider.dart';
+
+class ExportImportScreen extends StatelessWidget {
+  const ExportImportScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final localizations = AppLocalizations.of(context)!;
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(localizations.exportImport),
+        backgroundColor: Theme.of(context).primaryColor,
+      ),
+      body: Consumer<ExportSettings>(builder: (context, settings, child) {
+        return SingleChildScrollView(
+          child: Column(
+            children: [
+              Consumer<CsvExportSettings>(builder: (context, csvExportSettings, child) =>
+                Consumer<ExportColumnsManager>(builder: (context, availableColumns, child) =>
+                  ExportWarnBanner(
+                    exportSettings: settings,
+                    csvExportSettings: csvExportSettings,
+                    availableColumns: availableColumns
+                  ),
+                ),
+              ),
+              const SizedBox(
+                height: 15,
+              ),
+              Disabled(
+                disabled: settings.exportFormat == ExportFormat.db,
+                child: const IntervalPicker(type: IntervallStoreManagerLocation.exportPage,),
+              ),
+              ListTile(
+                title: Text(localizations.exportDir),
+                subtitle: settings.defaultExportDir.isNotEmpty ? Text(settings.defaultExportDir) : null,
+                onTap: () async {
+                  if (settings.defaultExportDir.isEmpty) {
+                    final appDir = await JSaver.instance.setDefaultSavingDirectory();
+                    settings.defaultExportDir = appDir.value;
+                  } else {
+                    settings.defaultExportDir = '';
+                  }
+                }
+              ),
+              SwitchListTile(
+                title: Text(localizations.exportAfterEveryInput),
+                subtitle: Text(localizations.exportAfterEveryInputDesc),
+                value: settings.exportAfterEveryEntry,
+                onChanged: (value) {
+                  settings.exportAfterEveryEntry = value;
+                },
+              ),
+              DropDownListTile<ExportFormat>(
+                key: const Key('exportFormat'),
+                title: Text(localizations.exportFormat),
+                value: settings.exportFormat,
+                items: [
+                  DropdownMenuItem(
+                      value: ExportFormat.csv, child: Text(localizations.csv)),
+                  DropdownMenuItem(
+                      value: ExportFormat.pdf, child: Text(localizations.pdf)),
+                  DropdownMenuItem(
+                      value: ExportFormat.db, child: Text(localizations.db)),
+                ],
+                onChanged: (ExportFormat? value) {
+                  if (value != null) {
+                    settings.exportFormat = value;
+                  }
+                },
+              ),
+              if (settings.exportFormat == ExportFormat.csv)
+                Consumer<CsvExportSettings>(builder: (context, csvExportSettings, child) =>
+                  Column(
+                    children: [
+                      InputListTile(
+                        label: localizations.fieldDelimiter,
+                        value: csvExportSettings.fieldDelimiter,
+                        onSubmit: (value) {
+                          csvExportSettings.fieldDelimiter = value;
+                        },
+                      ),
+                      InputListTile(
+                        label: localizations.textDelimiter,
+                        value: csvExportSettings.textDelimiter,
+                        onSubmit: (value) {
+                          csvExportSettings.textDelimiter = value;
+                        },
+                      ),
+                      SwitchListTile(
+                        title: Text(localizations.exportCsvHeadline),
+                        subtitle: Text(localizations.exportCsvHeadlineDesc),
+                        value: csvExportSettings.exportHeadline,
+                        onChanged: (value) {
+                          csvExportSettings.exportHeadline = value;
+                        }
+                      ),
+                    ],
+                  )
+                ),
+              if (settings.exportFormat == ExportFormat.pdf)
+                Consumer<PdfExportSettings>(builder: (context, pdfExportSettings, child) =>
+                  Column(
+                    children: [
+                      SwitchListTile(
+                          title: Text(localizations.exportPdfExportTitle),
+                          value: pdfExportSettings.exportTitle,
+                          onChanged: (value) {
+                            pdfExportSettings.exportTitle = value;
+                          }),
+                      SwitchListTile(
+                          title: Text(localizations.exportPdfExportStatistics),
+                          value: pdfExportSettings.exportStatistics,
+                          onChanged: (value) {
+                            pdfExportSettings.exportStatistics = value;
+                          }),
+                      SwitchListTile(
+                          title: Text(localizations.exportPdfExportData),
+                          value: pdfExportSettings.exportData,
+                          onChanged: (value) {
+                            pdfExportSettings.exportData = value;
+                          }),
+                      if (pdfExportSettings.exportData)
+                        Column(
+                          children: [
+                            NumberInputListTile(
+                              value: pdfExportSettings.headerHeight,
+                              label: localizations.exportPdfHeaderHeight,
+                              onParsableSubmit: (value) {
+                                pdfExportSettings.headerHeight = value;
+                              },
+                            ),
+                            NumberInputListTile(
+                              value: pdfExportSettings.cellHeight,
+                              label: localizations.exportPdfCellHeight,
+                              onParsableSubmit: (value) {
+                                pdfExportSettings.cellHeight = value;
+                              },
+                            ),
+                            NumberInputListTile(
+                              value: pdfExportSettings.headerFontSize,
+                              label: localizations.exportPdfHeaderFontSize,
+                              onParsableSubmit: (value) {
+                                pdfExportSettings.headerFontSize = value;
+                              },
+                            ),
+                            NumberInputListTile(
+                              value: pdfExportSettings.cellFontSize,
+                              label: localizations.exportPdfCellFontSize,
+                              onParsableSubmit: (value) {
+                                pdfExportSettings.cellFontSize = value;
+                              },
+                            ),
+                          ],
+                        ),
+                    ]
+                  )
+                ),
+              ActiveExportFieldCustomization(
+                format: settings.exportFormat,
+              ),
+            ],
+          ),
+        );
+      }),
+      bottomNavigationBar: const ExportButtonBar(),
+    );
+  }
+}
lib/screens/subsettings/export_import_screen.dart
@@ -1,460 +0,0 @@
-import 'dart:convert';
-import 'dart:io';
-import 'dart:typed_data';
-
-import 'package:blood_pressure_app/components/diabled.dart';
-import 'package:blood_pressure_app/components/display_interval_picker.dart';
-import 'package:blood_pressure_app/components/export_warn_banner.dart';
-import 'package:blood_pressure_app/components/settings/settings_widgets.dart';
-import 'package:blood_pressure_app/model/blood_pressure.dart';
-import 'package:blood_pressure_app/model/export_import/column.dart';
-import 'package:blood_pressure_app/model/export_import/csv_converter.dart';
-import 'package:blood_pressure_app/model/export_import/export_configuration.dart';
-import 'package:blood_pressure_app/model/export_import/pdf_converter.dart';
-import 'package:blood_pressure_app/model/export_import/record_parsing_result.dart';
-import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
-import 'package:blood_pressure_app/model/storage/storage.dart';
-import 'package:blood_pressure_app/platform_integration/platform_client.dart';
-import 'package:file_picker/file_picker.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:jsaver/jSaver.dart';
-import 'package:provider/provider.dart';
-import 'package:sqflite/sqflite.dart';
-
-class ExportImportScreen extends StatelessWidget {
-  const ExportImportScreen({super.key});
-
-  @override
-  Widget build(BuildContext context) {
-    final localizations = AppLocalizations.of(context)!;
-    return Scaffold(
-      appBar: AppBar(
-        title: Text(localizations.exportImport),
-        backgroundColor: Theme.of(context).primaryColor,
-      ),
-      body: Consumer<ExportSettings>(builder: (context, settings, child) {
-        return SingleChildScrollView(
-          child: Column(
-            children: [
-              Consumer<CsvExportSettings>(builder: (context, csvExportSettings, child) =>
-                Consumer<ExportColumnsManager>(builder: (context, availableColumns, child) =>
-                  ExportWarnBanner(
-                    exportSettings: settings,
-                    csvExportSettings: csvExportSettings,
-                    availableColumns: availableColumns
-                  ),
-                ),
-              ),
-              const SizedBox(
-                height: 15,
-              ),
-              Disabled(
-                disabled: settings.exportFormat == ExportFormat.db,
-                child: const IntervalPicker(type: IntervallStoreManagerLocation.exportPage,),
-              ),
-              ListTile(
-                title: Text(localizations.exportDir),
-                subtitle: settings.defaultExportDir.isNotEmpty ? Text(settings.defaultExportDir) : null,
-                onTap: () async {
-                  if (settings.defaultExportDir.isEmpty) {
-                    final appDir = await JSaver.instance.setDefaultSavingDirectory();
-                    settings.defaultExportDir = appDir.value;
-                  } else {
-                    settings.defaultExportDir = '';
-                  }
-                }
-              ),
-              SwitchListTile(
-                title: Text(localizations.exportAfterEveryInput),
-                subtitle: Text(localizations.exportAfterEveryInputDesc),
-                value: settings.exportAfterEveryEntry,
-                onChanged: (value) {
-                  settings.exportAfterEveryEntry = value;
-                },
-              ),
-              DropDownListTile<ExportFormat>(
-                key: const Key('exportFormat'),
-                title: Text(localizations.exportFormat),
-                value: settings.exportFormat,
-                items: [
-                  DropdownMenuItem(
-                      value: ExportFormat.csv, child: Text(localizations.csv)),
-                  DropdownMenuItem(
-                      value: ExportFormat.pdf, child: Text(localizations.pdf)),
-                  DropdownMenuItem(
-                      value: ExportFormat.db, child: Text(localizations.db)),
-                ],
-                onChanged: (ExportFormat? value) {
-                  if (value != null) {
-                    settings.exportFormat = value;
-                  }
-                },
-              ),
-              if (settings.exportFormat == ExportFormat.csv)
-                Consumer<CsvExportSettings>(builder: (context, csvExportSettings, child) =>
-                  Column(
-                    children: [
-                      InputListTile(
-                        label: localizations.fieldDelimiter,
-                        value: csvExportSettings.fieldDelimiter,
-                        onSubmit: (value) {
-                          csvExportSettings.fieldDelimiter = value;
-                        },
-                      ),
-                      InputListTile(
-                        label: localizations.textDelimiter,
-                        value: csvExportSettings.textDelimiter,
-                        onSubmit: (value) {
-                          csvExportSettings.textDelimiter = value;
-                        },
-                      ),
-                      SwitchListTile(
-                        title: Text(localizations.exportCsvHeadline),
-                        subtitle: Text(localizations.exportCsvHeadlineDesc),
-                        value: csvExportSettings.exportHeadline,
-                        onChanged: (value) {
-                          csvExportSettings.exportHeadline = value;
-                        }
-                      ),
-                    ],
-                  )
-                ),
-              if (settings.exportFormat == ExportFormat.pdf)
-                Consumer<PdfExportSettings>(builder: (context, pdfExportSettings, child) =>
-                  Column(
-                    children: [
-                      SwitchListTile(
-                          title: Text(localizations.exportPdfExportTitle),
-                          value: pdfExportSettings.exportTitle,
-                          onChanged: (value) {
-                            pdfExportSettings.exportTitle = value;
-                          }),
-                      SwitchListTile(
-                          title: Text(localizations.exportPdfExportStatistics),
-                          value: pdfExportSettings.exportStatistics,
-                          onChanged: (value) {
-                            pdfExportSettings.exportStatistics = value;
-                          }),
-                      SwitchListTile(
-                          title: Text(localizations.exportPdfExportData),
-                          value: pdfExportSettings.exportData,
-                          onChanged: (value) {
-                            pdfExportSettings.exportData = value;
-                          }),
-                      if (pdfExportSettings.exportData)
-                        Column(
-                          children: [
-                            NumberInputListTile(
-                              value: pdfExportSettings.headerHeight,
-                              label: localizations.exportPdfHeaderHeight,
-                              onParsableSubmit: (value) {
-                                pdfExportSettings.headerHeight = value;
-                              },
-                            ),
-                            NumberInputListTile(
-                              value: pdfExportSettings.cellHeight,
-                              label: localizations.exportPdfCellHeight,
-                              onParsableSubmit: (value) {
-                                pdfExportSettings.cellHeight = value;
-                              },
-                            ),
-                            NumberInputListTile(
-                              value: pdfExportSettings.headerFontSize,
-                              label: localizations.exportPdfHeaderFontSize,
-                              onParsableSubmit: (value) {
-                                pdfExportSettings.headerFontSize = value;
-                              },
-                            ),
-                            NumberInputListTile(
-                              value: pdfExportSettings.cellFontSize,
-                              label: localizations.exportPdfCellFontSize,
-                              onParsableSubmit: (value) {
-                                pdfExportSettings.cellFontSize = value;
-                              },
-                            ),
-                          ],
-                        ),
-                    ]
-                  )
-                ),
-              ExportFieldCustomisationSetting(
-                format: settings.exportFormat,
-              ),
-            ],
-          ),
-        );
-      }),
-      bottomNavigationBar: const ExportImportButtons(),
-    );
-  }
-}
-
-class ExportFieldCustomisationSetting extends StatelessWidget { // TODO: consider extracting class into file
-  const ExportFieldCustomisationSetting({super.key,
-    required this.format,});
-
-  final ExportFormat format;
-
-  @override
-  Widget build(BuildContext context) => switch (format) {
-      ExportFormat.csv => Consumer<CsvExportSettings>(builder: _builder),
-      ExportFormat.pdf => Consumer<PdfExportSettings>(builder: _builder),
-      ExportFormat.db => const SizedBox.shrink()
-    };
-
-  Widget _builder(BuildContext context, CustomFieldsSettings settings, Widget? child) {
-    final localizations = AppLocalizations.of(context)!;
-    final fieldsConfig = settings.exportFieldsConfiguration;
-    final dropdown = DropDownListTile(
-      title: Text(localizations.exportFieldsPreset),
-      value: fieldsConfig.activePreset,
-      items: ExportImportPreset.values.map(
-              (e) => DropdownMenuItem(
-            value: e,
-            child: Text(e.localize(localizations)),
-          )
-      ).toList(),
-      onChanged: (selectedPreset) {
-        if (selectedPreset != null) {
-          fieldsConfig.activePreset = selectedPreset;
-        }
-      },
-    );
-
-    if (fieldsConfig.activePreset != ExportImportPreset.none) {
-      return dropdown;
-    }
-    return Column(
-      mainAxisSize: MainAxisSize.min,
-        children: [
-          dropdown,
-          Container(
-            margin: const EdgeInsets.all(16),
-            height: 400,
-            decoration: BoxDecoration(
-              border: Border.all(color: Theme.of(context).textTheme.labelLarge?.color ?? Colors.teal),
-              borderRadius: const BorderRadius.all(Radius.circular(10)),
-            ),
-            child: Consumer<ExportColumnsManager>(
-              builder: (context, availableColumns, child) {
-                final activeColumns = fieldsConfig.getActiveColumns(availableColumns);
-                return ReorderableListView.builder(
-                    itemBuilder: (context, idx) {
-                      if (idx >= activeColumns.length) {
-                        return ListTile(
-                          key: const Key('add field'),
-                          leading: const Icon(Icons.add),
-                          title: Text(localizations.addEntry),
-                          onTap: () async {
-                            final column = await showDialog<ExportColumn?>(context: context, builder: (context) =>
-                              SimpleDialog(
-                                title: Text(localizations.addEntry),
-                                insetPadding: EdgeInsets.symmetric(
-                                  vertical: 64,
-                                ),
-                                children: availableColumns.getAllColumns().map((column) =>
-                                  ListTile(
-                                    title: Text(column.userTitle(localizations)),
-                                    onTap: () => Navigator.of(context).pop(column),
-                                  )
-                                ).toList(),
-                              )
-                            );
-                            if (column != null) fieldsConfig.addUserColumn(column);
-                          },
-                        );
-                      }
-                      return ListTile(
-                        key: Key(activeColumns[idx].internalIdentifier + idx.toString()),
-                        title: Text(activeColumns[idx].userTitle(localizations)),
-                        trailing: Row(
-                          mainAxisSize: MainAxisSize.min,
-                          children: [
-                            IconButton(
-                              tooltip: localizations.remove,
-                              onPressed: () {
-                                fieldsConfig.removeUserColumn(activeColumns[idx].internalIdentifier);
-                              },
-                              icon: const Icon(Icons.remove_circle_outline)
-                            ),
-                            const Icon(Icons.drag_handle),
-                          ],
-                        ),
-                      );
-                    },
-                    itemCount: activeColumns.length + 1,
-                    onReorder: fieldsConfig.reorderUserColumns
-                );
-              }
-            ),
-          ),
-          // TODO implement adding / editing columns => separate ColumnsManagerScreen ?
-        ],
-    );
-  }
-}
-
-class ExportImportButtons extends StatelessWidget {
-  const ExportImportButtons({super.key});
-
-  @override
-  Widget build(BuildContext context) {
-    final localizations = AppLocalizations.of(context)!;
-
-    return Container(
-      height: 60,
-      color: Theme.of(context).colorScheme.onInverseSurface,
-      child: Center(
-        child: Row(
-          children: [
-            Expanded(
-                flex: 50,
-                child: MaterialButton(
-                  height: 60,
-                  child: Text(localizations.export),
-                  onPressed: () async {
-                    final exportSettings = Provider.of<ExportSettings>(context, listen: false);
-                    final filename = 'blood_press_${DateTime.now().toIso8601String()}';
-                    switch (exportSettings.exportFormat) {
-                      case ExportFormat.db:
-                        final path = joinPath(await getDatabasesPath(), 'blood_pressure.db');
-
-                        if (context.mounted) _exportFile(context, path, '$filename.db', 'text/db');
-                        break;
-                      case ExportFormat.csv:
-                        final csvConverter = CsvConverter(
-                          Provider.of<CsvExportSettings>(context, listen: false),
-                          Provider.of<ExportColumnsManager>(context, listen: false),
-                        );
-                        final csvString = csvConverter.create(await _getRecords(context));
-                        final data = Uint8List.fromList(utf8.encode(csvString));
-                        if (context.mounted) _exportData(context, data, '$filename.csv', 'text/csv');
-                        break;
-                      case ExportFormat.pdf:
-                        final pdfConverter = PdfConverter(
-                            Provider.of<PdfExportSettings>(context, listen: false), 
-                            localizations,
-                            Provider.of<Settings>(context, listen: false),
-                            Provider.of<ExportColumnsManager>(context, listen: false)
-                        );
-                        final pdf = await pdfConverter.create(await _getRecords(context));
-                        if (context.mounted) _exportData(context, pdf, '$filename.pdf', 'text/pdf');
-                    }
-                  }
-                )),
-            const VerticalDivider(),
-            Expanded(
-                flex: 50,
-                child: MaterialButton(
-                  height: 60,
-                  child: Text(localizations.import),
-                  onPressed: () async {
-                    final messenger = ScaffoldMessenger.of(context);
-
-                    final file = (await FilePicker.platform.pickFiles(
-                      allowMultiple: false,
-                      withData: true,
-                    ))?.files.firstOrNull;
-                    if (file == null) {
-                      _showError(messenger, localizations.errNoFileOpened);
-                      return;
-                    }
-                    if (!context.mounted) return;
-                    switch(file.extension?.toLowerCase()) {
-                      case 'csv':
-                        final binaryContent = file.bytes;
-                        if (binaryContent == null) {
-                          _showError(messenger, localizations.errCantReadFile);
-                          return;
-                        }
-                        final converter = CsvConverter(
-                          Provider.of<CsvExportSettings>(context, listen: false),
-                          Provider.of<ExportColumnsManager>(context, listen: false),
-                        );
-                        final result = converter.parse(utf8.decode(binaryContent));
-                        final importedRecords = result.getOr((error) {
-                          switch (error) {
-                            case RecordParsingErrorEmptyFile():
-                              _showError(messenger, localizations.errParseEmptyCsvFile);
-                              break;
-                            case RecordParsingErrorTimeNotRestoreable():
-                              _showError(messenger, localizations.errParseTimeNotRestoreable);
-                              break;
-                            case RecordParsingErrorUnknownColumn():
-                              _showError(messenger, localizations.errParseUnknownColumn(error.title));
-                              break;
-                            case RecordParsingErrorExpectedMoreFields():
-                              _showError(messenger, localizations.errParseLineTooShort(error.lineNumber));
-                              break;
-                            case RecordParsingErrorUnparsableField():
-                              _showError(messenger, localizations.errParseFailedDecodingField(
-                                  error.lineNumber, error.fieldContents));
-                              break;
-                          }
-                          return null;
-                        });
-                        if (result.hasError()) return;
-                        final model = Provider.of<BloodPressureModel>(context, listen: false);
-                        for (final record in importedRecords) { // TODO: background thread
-                          await model.add(record);
-                        }
-                        messenger.showSnackBar(SnackBar(content: Text(
-                            localizations.importSuccess(importedRecords.length))));
-                        break;
-                      case 'db':
-                        final model = Provider.of<BloodPressureModel>(context, listen: false);
-                        final importedModel = await BloodPressureModel.create(dbPath: file.path, isFullPath: true);
-                        for (final record in await importedModel.all) {
-                          await model.add(record);
-                        }
-                        break;
-                      default:
-                        _showError(messenger, localizations.errWrongImportFormat);
-                    }
-                  },
-                )
-            ),
-          ],
-        ),
-      ),
-    );
-  }
-
-  void _showError(ScaffoldMessengerState messenger, String text) =>
-    messenger.showSnackBar(SnackBar(content: Text(text)));
-  
-  /// Get the records that should be exported.
-  Future<List<BloodPressureRecord>> _getRecords(BuildContext context) {
-    final range = Provider.of<IntervallStoreManager>(context, listen: false).exportPage.currentRange;
-    final model = Provider.of<BloodPressureModel>(context, listen: false);
-    return model.getInTimeRange(range.start, range.end);
-  }
-  
-  /// Save to default export path or share by providing a path. 
-  Future<void> _exportFile(BuildContext context, String path, String fullFileName, String mimeType) async {
-    final settings = Provider.of<ExportSettings>(context, listen: false);
-    if (settings.defaultExportDir.isEmpty) {
-      await PlatformClient.shareFile(path, mimeType, fullFileName);
-    } else {
-      JSaver.instance.save(
-          fromPath: path,
-          androidPathOptions: AndroidPathOptions(toDefaultDirectory: true)
-      );
-      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
-          content: Text(AppLocalizations.of(context)!.success(settings.defaultExportDir))));
-    }
-  }
-
-  /// Save to default export path or share by providing binary data.
-  Future<void> _exportData(BuildContext context, Uint8List data, String fullFileName, String mimeType) async {
-    final settings = Provider.of<ExportSettings>(context, listen: false);
-    if (settings.defaultExportDir.isEmpty) {
-      await PlatformClient.shareData(data, mimeType, fullFileName);
-    } else {
-      final file = File(joinPath(Directory.systemTemp.path, fullFileName));
-      file.writeAsBytesSync(data);
-      await _exportFile(context, file.path, fullFileName, mimeType);
-    }
-  }
-}
lib/screens/settings.dart
@@ -9,7 +9,7 @@ import 'package:blood_pressure_app/model/iso_lang_names.dart';
 import 'package:blood_pressure_app/model/storage/storage.dart';
 import 'package:blood_pressure_app/platform_integration/platform_client.dart';
 import 'package:blood_pressure_app/screens/subsettings/delete_data.dart';
-import 'package:blood_pressure_app/screens/subsettings/export_import_screen.dart';
+import 'package:blood_pressure_app/screens/subsettings/export_import/export_import_screen.dart';
 import 'package:blood_pressure_app/screens/subsettings/graph_markings.dart';
 import 'package:blood_pressure_app/screens/subsettings/version.dart';
 import 'package:blood_pressure_app/screens/subsettings/warn_about.dart';