Commit 9a561ae

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-02-11 16:42:58
implement recursive column selection
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent cafb410
Changed files (3)
lib/components/dialoges/tree_selection_dialoge.dart
@@ -0,0 +1,116 @@
+
+import 'package:blood_pressure_app/components/dialoges/fullscreen_dialoge.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+/// Generic multilayered fullscreen dialoge for nesting string selections.
+class TreeSelectionDialoge extends StatefulWidget {
+  /// Create a multilayered string selection dialoge.
+  const TreeSelectionDialoge({super.key,
+    required this.buildTitle,
+    required this.buildOptions,
+    required this.bottomAppBars,
+    this.validator,
+  });
+
+  /// Builder for currently visible options.
+  ///
+  /// Should return currently visible options or close the dialoge. The
+  /// `madeSelections` parameter contains all selections the user already made
+  /// in the order in which they were made.
+  ///
+  /// **Tip:** use `madeSelections.length` to obtain the current depth.
+  ///
+  /// Guaranteed to be called after every selection.
+  final List<String> Function(List<String> madeSelections) buildOptions;
+
+  /// Builds a title that tells users what to this selection is about.
+  ///
+  /// Behaves like [buildOptions].
+  final String Function(List<String> madeSelections) buildTitle;
+
+  /// Validates selections and returns errors.
+  ///
+  /// When this function returns a string saving is not possible and the string
+  /// will be shown to the user. When this function returns null or is null the
+  /// selections will be returned popped to the underlying scope.
+  final String? Function(List<String> madeSelections)? validator;
+
+  /// Whether to move the app bar for saving and loading to the bottom of the
+  /// screen.
+  final bool bottomAppBars;
+
+  @override
+  State<TreeSelectionDialoge> createState() => _TreeSelectionDialogeState();
+}
+
+class _TreeSelectionDialogeState extends State<TreeSelectionDialoge> {
+  /// Selections the user already made.
+  final _selections = <String>[];
+
+  String? _error;
+
+  @override
+  Widget build(BuildContext context) {
+    final localizations = AppLocalizations.of(context)!;
+    final items = widget.buildOptions(_selections);
+    return PopScope(
+      canPop: _selections.isEmpty,
+      onPopInvoked: (didPop) {
+        if (!didPop) {
+          setState(_selections.removeLast);
+        }
+      },
+      child: FullscreenDialoge(
+        onActionButtonPressed: () {
+          setState(() {
+            _error = widget.validator?.call(_selections);
+          });
+          if (_error != null) {
+            return;
+          }
+          final selections = _selections.toList();
+          _selections.clear();
+          Navigator.pop(context, selections);
+        },
+        actionButtonText: localizations.btnSave,
+        bottomAppBar: widget.bottomAppBars,
+        body: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            Expanded(
+              child: ListView.builder(
+                shrinkWrap: true,
+                itemCount: items.length + 1,
+                itemBuilder: (context, idx) => (idx == 0)
+                    ? ListTile(
+                      title: Text(widget.buildTitle(_selections)),
+                      titleTextStyle: Theme.of(context).textTheme.headlineSmall,
+                    )
+                    : Padding(
+                        padding: const EdgeInsets.only(top: 10),
+                        child: ListTile(
+                          title: Text(items[idx-1]),
+                          onTap: () => setState(() {
+                            _selections.add(items[idx-1]);
+                          }),
+                          tileColor: Theme.of(context).cardColor,
+                          shape: RoundedRectangleBorder(
+                            borderRadius: BorderRadius.circular(10),
+                          ),
+                        ),
+                      ),
+              ),
+            ),
+            if (_error != null)
+              ListTile(
+                title: Text(_error!,),
+                textColor: Theme.of(context).colorScheme.error,
+                titleTextStyle: Theme.of(context).textTheme.labelLarge,
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+}
lib/screens/subsettings/foreign_db_import_screen.dart
@@ -1,4 +1,5 @@
 import 'package:blood_pressure_app/components/consistent_future_builder.dart';
+import 'package:blood_pressure_app/components/dialoges/tree_selection_dialoge.dart';
 import 'package:blood_pressure_app/model/export_import/import_field_type.dart';
 import 'package:collection/collection.dart';
 import 'package:flutter/material.dart';
@@ -18,91 +19,52 @@ class ForeignDBImportScreen extends StatefulWidget {
 }
 
 class _ForeignDBImportScreenState extends State<ForeignDBImportScreen> {
-
-  /// The name of the table that contains the data.
-  String? _selectedTableName;
-
-  /// The name of the selected column that contains the timestamps.
-  String? _activeTimeColumnName;
-
-  /// The name of the column selected before selecting a datatype.
-  ///
-  /// Once a datatype is selected, this is reset to null.
-  String? _lastSelectedColumnName;
-
   @override
-  Widget build(BuildContext context) => Scaffold(
-    appBar: AppBar(
-      title: (_selectedTableName == null)
-          ? const Text('Table')
-          : const Text('Time column'),
-    ),
-    body: ConsistentFutureBuilder(
-      future: _ColumnImportData.loadFromDB(widget.db),
-      onData: (BuildContext context, _ColumnImportData data) {
-        final localizations = AppLocalizations.of(context)!;
-
-        if (_selectedTableName == null) {
-          return _buildTableSelection(data);
-        }
-        if (_activeTimeColumnName == null) {
-          return _buildColumnSelection(data, (String columnName) => setState(() {
-            _activeTimeColumnName = columnName;
-          }),);
-        }
-
-        if (_lastSelectedColumnName == null) {
-          return _buildColumnSelection(data, (columnName) => setState(() {
-            _lastSelectedColumnName = columnName;
-          }),);
-        }
-        return _buildCardList(
-          RowDataFieldType.values.map((e) => e.localize(localizations)),
-          (columnName) {
-            setState(() {
-              // TODO: add to columns
-              _lastSelectedColumnName = null;
-            });
-          },
-        );
-
-        // TODO: add finalize button
-
-        
-      },
-    ),
-  );
-
-  Widget _buildTableSelection(_ColumnImportData data) =>
-      _buildCardList(data.tableNames, (tableName) => setState(() {
-        _selectedTableName = tableName;
-      }));
-
-  Widget _buildColumnSelection(
-      _ColumnImportData data, 
-      void Function(String columnName) onSelection,
-  ) => _buildCardList(data.columns[_selectedTableName]!, onSelection);
-
-  Widget _buildCardList(
-      Iterable<String> allOptions,
-      void Function(String columnName) onSelection,
-  ) => ListView(
-    children: [
-      for (final option in allOptions)
-        InkWell(
-          onTap: () => onSelection(option),
-          child: Card(
-            child: Padding(
-              padding: const EdgeInsets.all(14),
-              child: Text(option),
-            ),
-          ),
-        ),
-    ],
+  Widget build(BuildContext context) => ConsistentFutureBuilder(
+    future: _ColumnImportData.loadFromDB(widget.db),
+    onData: (BuildContext context, _ColumnImportData data) {
+      final localizations = AppLocalizations.of(context)!;
+      return TreeSelectionDialoge(
+        buildOptions: (selections) {
+          if (selections.isEmpty) {
+            return data.tableNames.toList();
+          }
+          if (selections.length == 1) {
+            // TODO: don't show tables without columns
+            return data.columns[selections[0]]!;
+          }
+
+          if ((selections.length % 2 == 0)) {
+            final columns = data.columns[selections[0]]!;
+            columns.remove(selections[1]);
+            return columns;
+          } else {
+            return RowDataFieldType.values
+                .whereNot((element) => element == RowDataFieldType.timestamp)
+                .map((e) => e.localize(localizations))
+                .toList();
+          }
+        },
+        validator: (todo) { // TODO
+          return 'The schnibledumps doesn\'t schwibble!';
+        },
+        buildTitle: (selections) {
+          if (selections.isEmpty) return 'Select table';
+          if (selections.length == 1) return 'Select time column';
+          if ((selections.length % 2 == 0)) {
+            return 'Select data column';
+          } else {
+            return 'Select column type';
+          }
+        },
+        bottomAppBars: true, // TODO
+      );
+      // TODO: perform import
+      // TODO: localize everything
+    },
   );
 }
 
-
 class _ColumnImportData {
   _ColumnImportData._create(this.columns);
   
pubspec.lock
@@ -329,18 +329,26 @@ packages:
     dependency: transitive
     description:
       name: leak_tracker
-      sha256: "04be76c4a4bb50f14904e64749237e541e7c7bcf7ec0b196907322ab5d2fc739"
+      sha256: f8cdf1383f5b4672a2693d875f1f239af6bd7e4a8925a17ef7219226db932624
       url: "https://pub.dev"
     source: hosted
-    version: "9.0.16"
+    version: "10.0.1"
+  leak_tracker_flutter_testing:
+    dependency: transitive
+    description:
+      name: leak_tracker_flutter_testing
+      sha256: a2055640bf5bc903475e4bbdb34e04f8bf698542bee41edec47d337a5939e1ae
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.3"
   leak_tracker_testing:
     dependency: transitive
     description:
       name: leak_tracker_testing
-      sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff
+      sha256: e62042d479c4c139dd774125ed4dfbde646b8f07ac228e3c1b57a3d91d6d9df4
       url: "https://pub.dev"
     source: hosted
-    version: "1.0.5"
+    version: "2.0.2"
   lints:
     dependency: transitive
     description:
@@ -369,10 +377,10 @@ packages:
     dependency: transitive
     description:
       name: matcher
-      sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
+      sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
       url: "https://pub.dev"
     source: hosted
-    version: "0.12.16"
+    version: "0.12.16+1"
   material_color_utilities:
     dependency: transitive
     description:
@@ -433,10 +441,10 @@ packages:
     dependency: "direct main"
     description:
       name: path
-      sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
       url: "https://pub.dev"
     source: hosted
-    version: "1.8.3"
+    version: "1.9.0"
   path_parsing:
     dependency: transitive
     description:
@@ -844,4 +852,4 @@ packages:
     version: "3.1.2"
 sdks:
   dart: ">=3.2.0 <4.0.0"
-  flutter: ">=3.16.0"
+  flutter: ">=3.18.0-18.0.pre.54"