Commit 387859b

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-03-15 15:47:08
implement no headline checkbox
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 5559a69
Changed files (4)
lib/components/dialoges/fullscreen_dialoge.dart
@@ -11,6 +11,7 @@ class FullscreenDialoge extends StatelessWidget {
     this.onActionButtonPressed,
     required this.bottomAppBar,
     this.closeIcon = Icons.close,
+    this.actions = const <Widget>[],
   });
 
   /// The primary content of the dialoge.
@@ -41,6 +42,13 @@ class FullscreenDialoge extends StatelessWidget {
   /// Setting this to false will let the app bar stay at the top.
   final bool bottomAppBar;
 
+  /// Secondary actions to display on the app bar.
+  ///
+  /// Positioned somewhere between close and primary action button.
+  ///
+  /// Recommended to be used with [CheckboxMenuButton].
+  final List<Widget> actions;
+
   @override
   Widget build(BuildContext context) => Scaffold(
     body: body,
@@ -59,6 +67,10 @@ class FullscreenDialoge extends StatelessWidget {
       onPressed: () => Navigator.pop(context, null),
       icon: Icon(closeIcon),
     ),
+    title: Row(
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: actions,
+    ),
     actions: [
       if (actionButtonText != null)
         TextButton(
lib/components/dialoges/import_preview_dialoge.dart
@@ -47,6 +47,9 @@ class _ImportPreviewDialogeState extends State<ImportPreviewDialoge> {
   /// Whether to limit shown rows to [_kRowLimit] for faster rendering.
   bool _limitRows = true;
 
+  /// Whether the CSV file has a title row that should be ignored.
+  bool _csvHasTitle = true;
+
   @override
   void initState() {
     super.initState();
@@ -82,13 +85,23 @@ class _ImportPreviewDialogeState extends State<ImportPreviewDialoge> {
 
   @override
   Widget build(BuildContext context) => FullscreenDialoge(
-    actionButtonText: AppLocalizations.of(context)!.import,
     bottomAppBar: widget.bottomAppBar,
+    actionButtonText: AppLocalizations.of(context)!.import,
     onActionButtonPressed: (_showingError) ? null : () {
       final result = _actor.attemptParse();
       if (result.hasError()) return;
       Navigator.pop<List<BloodPressureRecord>>(context, result.getOr((e) => null));
     },
+    actions: [
+      CheckboxMenuButton(
+        value: _actor.hasHeadline,
+        onChanged: (state) => setState(() {
+          _actor.hasHeadline = state ?? true;
+          _actor.attemptParse();
+        }),
+        child: Text(AppLocalizations.of(context)!.titleInCsv),
+      ),
+    ],
     body: SingleChildScrollView(
       child: SingleChildScrollView(
         scrollDirection: Axis.horizontal,
@@ -99,7 +112,7 @@ class _ImportPreviewDialogeState extends State<ImportPreviewDialoge> {
               Column(
                 crossAxisAlignment: CrossAxisAlignment.end,
                 children: [
-                  DropdownButton( // TODO: show original column name
+                  DropdownButton(
                     items: [
                       DropdownMenuItem(
                         child: Text(
@@ -122,7 +135,7 @@ class _ImportPreviewDialogeState extends State<ImportPreviewDialoge> {
                                   Text(parser.formatPattern!, style: Theme.of(context).textTheme.labelSmall,),
                               ],
                             ),
-                          ), // TODO: consider more info
+                          ),
                         ),
                     ],
                     value: _actor.columnParsers[_actor.columnNames[colIdx]],
@@ -136,7 +149,7 @@ class _ImportPreviewDialogeState extends State<ImportPreviewDialoge> {
                   const Divider(),
                   for (int rowIdx = 0; rowIdx < (_limitRows
                       ? min(_actor.dataLines.length, _kRowLimit)
-                      : _actor.dataLines.length); rowIdx++) // TODO rework if needed (parsed?)
+                      : _actor.dataLines.length); rowIdx++)
                     _buildCell(
                       rowIdx,
                       _actor.dataLines[rowIdx][colIdx],
@@ -190,8 +203,6 @@ class _ImportPreviewDialogeState extends State<ImportPreviewDialoge> {
   }
 }
 
-// TODO: add setting for row limiting to make errors actionable
-
 /// Shows a dialoge to preview import of a csv file
 Future<List<BloodPressureRecord>?> showImportPreview(
   BuildContext context,
lib/l10n/app_en.arb
@@ -502,5 +502,7 @@
   "dosis": "Dosis",
   "@dosis": {},
   "valueDistribution": "Value distribution",
-  "@valueDistribution": {}
+  "@valueDistribution": {},
+  "titleInCsv": "Title in CSV",
+  "@titleInCsv": {}
 }
lib/model/export_import/csv_record_parsing_actor.dart
@@ -1,4 +1,6 @@
 
+import 'dart:collection';
+
 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/record_parsing_result.dart';
@@ -8,21 +10,26 @@ class CsvRecordParsingActor {
   /// Create an intermediate object to manage a record parsing process.
   CsvRecordParsingActor(this._converter, String csvString) {
     final lines = _converter.getCsvLines(csvString);
-    _headline = lines.removeAt(0);
-    dataLines = lines;
-    _columnNames = _headline ?? [];
+    _firstLine = lines.removeAt(0);
+    _bodyLines = lines;
+    _columnNames = _firstLine ?? [];
     _columnParsers = _converter.getColumns(_columnNames);
   }
 
   final CsvConverter _converter;
 
   /// All lines without the first line.
-  late final List<List<String>> dataLines;
-
-  List<String>? _headline;
+  late final List<List<String>> _bodyLines;
+  
+  /// All lines containing data.
+  UnmodifiableListView<List<String>> get dataLines {
+    final lines = _bodyLines.toList();
+    if(!hasHeadline && _firstLine != null) lines.insert(0, _firstLine!);
+    return UnmodifiableListView(lines);
+  }
 
   /// The first line in the csv file.
-  List<String>? get headline => _headline;
+  List<String>? _firstLine;
 
   late List<String> _columnNames;
 
@@ -36,6 +43,9 @@ class CsvRecordParsingActor {
   /// There is no guarantee that every column in [columnNames] has a parser.
   Map<String, ExportColumn> get columnParsers => _columnParsers;
 
+  /// Whether the CSV file has a title row (first line) that contains no data.
+  bool hasHeadline = true;
+  
   /// Override a columns with a custom one.
   void changeColumnParser(String columnName, ExportColumn? parser) {
     assert(_columnNames.contains(columnName));
@@ -47,8 +57,7 @@ class CsvRecordParsingActor {
   }
 
   /// Try to parse the data with the current configuration.
-  RecordParsingResult attemptParse() =>
-    _converter.parseRecords(dataLines, columnNames, columnParsers, false);
+  RecordParsingResult attemptParse() {
+    return _converter.parseRecords(dataLines, columnNames, columnParsers, false);
+  }
 }
-
-// TODO: consider joining headline and columnNames OR support no headline.