Commit 614da72

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-12-16 18:17:33
implement column for time serialization
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 8f5c825
lib/components/dialoges/add_export_column_dialoge.dart
@@ -99,6 +99,7 @@ class _AddExportColumnDialogeState extends State<AddExportColumnDialoge> {
               onSaved: (value) => setState(() {formatPattern = value!;}),
             ),
             const SizedBox(height: 8,),
+            // TODO: add switcher to allow creating TimeColumn
             Container(
               padding: const EdgeInsets.all(24),
               decoration: BoxDecoration(
lib/components/export_warn_banner.dart
@@ -19,6 +19,8 @@ class ExportWarnBanner extends StatefulWidget {
   // TODO: consider attempting to import a file with that config and checking success.
   // This would allow fore more detailed error messages as well.
 
+  // TODO: warn about potential quality loss when using TimeColumn
+
   final ExportSettings exportSettings;
   final CsvExportSettings csvExportSettings;
   final ExportColumnsManager availableColumns;
lib/model/export_import/column.dart
@@ -215,8 +215,6 @@ class BuildInColumn extends ExportColumn {
   RowDataFieldType? get restoreAbleType => _formatter.restoreAbleType;
 }
 
-// TODO: add class for formattedTimestamp
-
 /// Class for storing data of user added columns.
 class UserColumn extends ExportColumn {
   /// Create a object that handles export behavior for data in a column.
@@ -232,10 +230,10 @@ class UserColumn extends ExportColumn {
   UserColumn.explicit(this.internalIdentifier, this.csvTitle, String formatPattern):
         formatter = ScriptedFormatter(formatPattern);
 
-  @override
   /// Unique identifier of userColumn.
   ///
   /// Is automatically prefixed with `userColumn.` to avoid name collisions with build-ins.
+  @override
   final String internalIdentifier;
 
   @override
@@ -260,6 +258,49 @@ class UserColumn extends ExportColumn {
   RowDataFieldType? get restoreAbleType => formatter.restoreAbleType;
 }
 
+class TimeColumn extends ExportColumn {
+  /// Create a formatter that converts between [String]s and [DateTime]s through a format pattern
+  ///
+  /// [internalIdentifier] is automatically prefixed with 'userColumn.' during object creation.
+  TimeColumn(this.csvTitle, this.formatPattern):
+        internalIdentifier = 'timeFormatter.$csvTitle';
+
+  /// UserColumn constructor that does not change the [internalIdentifier].
+  TimeColumn.explicit(this.internalIdentifier, this.csvTitle, this.formatPattern);
+
+  ScriptedTimeFormatter? _formatter;
+
+  @override
+  final String csvTitle;
+
+  @override
+  (RowDataFieldType, dynamic)? decode(String pattern) {
+    _formatter ??= ScriptedTimeFormatter(formatPattern);
+    return _formatter!.decode(pattern);
+  }
+
+  @override
+  String encode(BloodPressureRecord record) {
+    _formatter ??= ScriptedTimeFormatter(formatPattern);
+    return _formatter!.encode(record);
+  }
+
+  @override
+  final String formatPattern;
+
+  /// Unique identifier of userColumn.
+  ///
+  /// Is automatically prefixed with `timeFormatter.` to avoid name collisions with build-ins.
+  @override
+  final String internalIdentifier;
+
+  @override
+  RowDataFieldType? get restoreAbleType => RowDataFieldType.timestamp;
+
+  @override
+  String userTitle(AppLocalizations localizations) => csvTitle;
+
+}
 
 /// Interface for converters that allow formatting and provide metadata.
 sealed class ExportColumn implements Formatter {
lib/model/export_import/record_formatter.dart
@@ -7,7 +7,6 @@ import 'package:intl/intl.dart';
 /// Class to serialize and deserialize [BloodPressureRecord] values.
 abstract interface class Formatter {
   /// Pattern that a user can use to achieve the effect of [convertToCsvValue].
-  // TODO: consider making this implementer specific later in the development process
   String? get formatPattern;
 
   /// Creates a string representation of the record.
@@ -161,4 +160,38 @@ class ScriptedFormatter implements Formatter {
     return _restoreAbleType;
   }
 
+}
+
+/// Record formatter to format encode and decode formatted timestamps.
+///
+/// Where possible the direct timestamp should be used as this may reduce
+/// accuracy of timestamps.
+///
+/// This class is mainly a wrapper around [DateFormat].
+class ScriptedTimeFormatter implements Formatter {
+  /// Creates a new scripted time formatter from a format pattern.
+  ///
+  /// The pattern follows the ICU style like [DateFormat].
+  ScriptedTimeFormatter(String newPattern):
+    timeFormatter = DateFormat(newPattern);
+
+  final DateFormat timeFormatter;
+  
+  @override
+  (RowDataFieldType, dynamic)? decode(String pattern) {
+    try {
+      return (RowDataFieldType.timestamp, timeFormatter.parseLoose(pattern));
+    } on FormatException {
+      return null;
+    }
+  }
+
+  @override
+  String encode(BloodPressureRecord record) => timeFormatter.format(record.creationTime);
+
+  @override
+  String? get formatPattern => timeFormatter.pattern;
+
+  @override
+  RowDataFieldType? get restoreAbleType => RowDataFieldType.timestamp;
 }
\ No newline at end of file
lib/model/storage/export_columns_store.dart
@@ -91,6 +91,15 @@ class ExportColumnsManager extends ChangeNotifier { // TODO: separate ExportColu
         case BuildInColumn():
         case NativeColumn():
           assert(false, 'User is currently not able to create these columns.');
+          break;
+        case TimeColumn():
+          columns.add({
+            't': 1, // class type
+            'id': c.internalIdentifier,
+            'csvTitle': c.csvTitle,
+            'formatPattern': c.formatPattern
+          });
+          break;
       }
     }
     return jsonEncode({
@@ -107,6 +116,9 @@ class ExportColumnsManager extends ChangeNotifier { // TODO: separate ExportColu
         case 0:
           manager.addOrUpdate(UserColumn.explicit(c['id'], c['csvTitle'], c['formatString']));
           break;
+        case 1:
+          manager.addOrUpdate(TimeColumn.explicit(c['id'], c['csvTitle'], c['formatPattern']));
+          break;
         default:
           assert(false, 'Unexpected column type ${c['t']}.');
       }