Commit 062e27b
Changed files (5)
lib
l10n
model
screens
subsettings
lib/l10n/app_en.arb
@@ -439,5 +439,36 @@
"needlePinBarWidth": "Color thickness",
"@needlePinBarWidth": {},
"needlePinBarWidthDesc": "The width of the lines colored entries make on the graph.",
- "@needlePinBarWidthDesc": {}
+ "@needlePinBarWidthDesc": {},
+ "errParseEmptyCsvFile": "There are not enough lines in the csv file to parse the record.",
+ "@errParseEmptyCsvFile": {},
+ "errParseTimeNotRestoreable": "There is no column that allows restoring a timestamp.",
+ "@errParseTimeNotRestoreable": {},
+ "errParseUnknownColumn": "There is no column with title \"{title}\".",
+ "@errParseUnknownColumn": {
+ "placeholders": {
+ "title": {
+ "type": "String"
+ }
+ }
+ },
+ "errParseLineTooShort": "Line {lineNumber} has fewer columns than the first line.",
+ "@errParseLineTooShort": {
+ "placeholders": {
+ "lineNumber": {
+ "type": "int"
+ }
+ }
+ },
+ "errParseFailedDecodingField": "Decoding field \"{fieldContent}\" in line {lineNumber} failed.",
+ "@errParseFailedDecodingField": {
+ "placeholders": {
+ "lineNumber": {
+ "type": "int"
+ },
+ "fieldContent": {
+ "type": "String"
+ }
+ }
+ }
}
lib/model/export_import/record_parsing_result.dart
@@ -44,6 +44,7 @@ class RecordParsingErrorEmptyFile implements RecordParsingError {}
class RecordParsingErrorTimeNotRestoreable implements RecordParsingError {}
/// There is no column with this csv title that can be reversed.
+/// TODO: remove reversed limitation
class RecordParsingErrorUnknownColumn implements RecordParsingError {
RecordParsingErrorUnknownColumn(this.title);
lib/model/storage/export_columns_store.dart
@@ -46,7 +46,7 @@ class ExportColumnsManager extends ChangeNotifier { // TODO: separate ExportColu
ExportColumn? getColumn(String identifier) =>
firstWhere((c) => c.internalIdentifier == identifier);
- // TODO test
+ // TODO test / fix
/// Get the first of column that satisfies [test].
///
/// Checks in the order:
lib/model/export_import.dart
@@ -28,7 +28,7 @@ extension PdfCompatability on Color {
PdfColor toPdfColor() => PdfColor(red / 256, green / 256, blue / 256, opacity);
}
-// TODO: more testing
+@Deprecated('replaced with export_import directory')
class ExportFileCreator {
final Settings settings;
final ExportSettings exportSettings;
@@ -251,6 +251,7 @@ class ExportFileCreator {
}
}
+@Deprecated('replaced with export_import directory')
class Exporter {
final Iterable<BloodPressureRecord> data;
final Settings settings;
lib/screens/subsettings/export_import_screen.dart
@@ -1,13 +1,19 @@
+import 'dart:convert';
+
import 'package:blood_pressure_app/components/consistent_future_builder.dart';
import 'package:blood_pressure_app/components/diabled.dart';
import 'package:blood_pressure_app/components/display_interval_picker.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.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/legacy_column.dart';
+import 'package:blood_pressure_app/model/export_import/record_parsing_result.dart';
import 'package:blood_pressure_app/model/export_options.dart';
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
import 'package:blood_pressure_app/model/storage/storage.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';
@@ -256,8 +262,66 @@ class ExportImportButtons extends StatelessWidget {
child: MaterialButton(
height: 60,
child: Text(localizations.import),
- onPressed: () async =>
- Exporter.load(context, [], await ExportConfigurationModel.get(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':
+ // TODO
+ break;
+ default:
+ showError(messenger, localizations.errWrongImportFormat);
+ }
+ },
)
),
],
@@ -265,6 +329,10 @@ class ExportImportButtons extends StatelessWidget {
),
);
}
+
+ void showError(ScaffoldMessengerState messenger, String text) =>
+ messenger.showSnackBar(SnackBar(content: Text(text)));
+
}
class ExportWarnBanner extends StatefulWidget {