Commit 2b607ff

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-11-24 15:55:19
update InputDialoge
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent a1822da
lib/components/dialoges/input_dialoge.dart
@@ -2,100 +2,82 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 
-// TODO: redo dialoges in flutter style
+/// Dialoge for prompting single value input from the user.
 class InputDialoge extends StatefulWidget {
-  final String hintText;
-  final String? initialValue;
-
-  /// Gets called when the user submits the text field or presses the submit button.
-  final void Function(String text) onSubmit;
-  final List<TextInputFormatter>? inputFormatters;
-  final TextInputType? keyboardType;
-
+  /// Creates an [AlertDialog] with an text input field.
+  ///
+  /// Pops the context after value submission with object of type [String?].
   const InputDialoge({super.key,
-    required this.hintText,
-    required this.onSubmit,
     this.inputFormatters,
     this.keyboardType,
+    this.hintText,
     this.initialValue});
 
+  /// Initial content of the input field.
+  final String? initialValue;
+
+  /// Supporting text describing the input field.
+  final String? hintText;
+
+  /// Optional input validation and formatting overrides.
+  final List<TextInputFormatter>? inputFormatters;
+
+  final TextInputType? keyboardType;
+
   @override
   State<InputDialoge> createState() => _InputDialogeState();
 }
 
 class _InputDialogeState extends State<InputDialoge> {
-  final formKey = GlobalKey<FormState>();
   final controller = TextEditingController();
-  final inputFocusNode = FocusNode();
+  final focusNode = FocusNode();
 
   @override
-  void dispose() {
-    controller.dispose();
-    super.dispose();
+  void initState() {
+    super.initState();
+    if (widget.initialValue != null) controller.text = widget.initialValue!;
+    focusNode.requestFocus();
   }
 
-
   @override
-  void initState() {
-    super.initState();
-    controller.text = widget.initialValue ?? '';
+  void dispose() {
+    controller.dispose();
+    focusNode.dispose();
+    super.dispose();
   }
 
   @override
   Widget build(BuildContext context) {
-    inputFocusNode.requestFocus();
+    final localizations = AppLocalizations.of(context)!;
     return AlertDialog(
-      content: TextFormField(
-        key: formKey,
-        focusNode: inputFocusNode,
+      content: TextField(
         controller: controller,
+        focusNode: focusNode,
         inputFormatters: widget.inputFormatters,
         keyboardType: widget.keyboardType,
         decoration: InputDecoration(
-          hintText: widget.hintText
+          hintText: widget.hintText,
+          labelText: widget.hintText
         ),
-        onFieldSubmitted: widget.onSubmit,
+        onSubmitted: _onSubmit,
       ),
       actions: [
         ElevatedButton(
-          onPressed: () {
-            widget.onSubmit(controller.text);
-          },
-          child: Text(AppLocalizations.of(context)!.btnConfirm)
-        )
+            onPressed: () => Navigator.of(context).pop(null),
+            child: Text(localizations.btnCancel)),
+        ElevatedButton(
+            onPressed: () => _onSubmit(controller.text),
+            child: Text(localizations.btnConfirm)),
       ],
     );
   }
-}
-
-typedef NumberInputResult = void Function(double result);
 
-class NumberInputDialoge extends StatelessWidget {
-  final String hintText;
-  final NumberInputResult onParsableSubmit;
-  final String? initialValue;
-
-  const NumberInputDialoge({
-    super.key,
-    required this.hintText,
-    required this.onParsableSubmit,
-    this.initialValue});
-
-  @override
-  Widget build(BuildContext context) {
-    return InputDialoge(
-      hintText: hintText,
-      inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'([0-9]+(\.([0-9]*))?)')),],
-      keyboardType: TextInputType.number,
-      initialValue: initialValue,
-      onSubmit: (text) {
-        double? value = double.tryParse(text);
-        value ??= int.tryParse(text)?.toDouble();
-        if (text.isEmpty || value == null) {
-          return;
-        }
-        onParsableSubmit(value);
-      }
-    );
+  void _onSubmit(String value) {
+    Navigator.of(context).pop(value);
   }
 }
+
+/// Creates a dialoge for prompting a single user input.
+Future<String?> showInputDialoge(BuildContext context, {String? hintText, String? initialValue}) async =>
+  showDialog<String?>(context: context, builder: (context) =>
+      InputDialoge(hintText: hintText, initialValue: initialValue,));
\ No newline at end of file
lib/components/dialoges/oldinput_dialoge.dart
@@ -0,0 +1,103 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+// TODO: redo dialoges in flutter style
+@Deprecated('TODO: replace with new dialoge')
+class InputDialoge extends StatefulWidget {
+  final String hintText;
+  final String? initialValue;
+
+  /// Gets called when the user submits the text field or presses the submit button.
+  final void Function(String text) onSubmit;
+  final List<TextInputFormatter>? inputFormatters;
+  final TextInputType? keyboardType;
+
+  const InputDialoge({super.key,
+    required this.hintText,
+    required this.onSubmit,
+    this.inputFormatters,
+    this.keyboardType,
+    this.initialValue});
+
+  @override
+  State<InputDialoge> createState() => _InputDialogeState();
+}
+
+class _InputDialogeState extends State<InputDialoge> {
+  final formKey = GlobalKey<FormState>();
+  final controller = TextEditingController();
+  final inputFocusNode = FocusNode();
+
+  @override
+  void dispose() {
+    controller.dispose();
+    super.dispose();
+  }
+
+
+  @override
+  void initState() {
+    super.initState();
+    controller.text = widget.initialValue ?? '';
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    inputFocusNode.requestFocus();
+    return AlertDialog(
+      content: TextFormField(
+        key: formKey,
+        focusNode: inputFocusNode,
+        controller: controller,
+        inputFormatters: widget.inputFormatters,
+        keyboardType: widget.keyboardType,
+        decoration: InputDecoration(
+          hintText: widget.hintText
+        ),
+        onFieldSubmitted: widget.onSubmit,
+      ),
+      actions: [
+        ElevatedButton(
+          onPressed: () {
+            widget.onSubmit(controller.text);
+          },
+          child: Text(AppLocalizations.of(context)!.btnConfirm)
+        )
+      ],
+    );
+  }
+}
+
+typedef NumberInputResult = void Function(double result);
+
+@Deprecated('TODO: replace with new dialoge')
+class NumberInputDialoge extends StatelessWidget {
+  final String hintText;
+  final NumberInputResult onParsableSubmit;
+  final String? initialValue;
+
+  const NumberInputDialoge({
+    super.key,
+    required this.hintText,
+    required this.onParsableSubmit,
+    this.initialValue});
+
+  @override
+  Widget build(BuildContext context) {
+    return InputDialoge(
+      hintText: hintText,
+      inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'([0-9]+(\.([0-9]*))?)')),],
+      keyboardType: TextInputType.number,
+      initialValue: initialValue,
+      onSubmit: (text) {
+        double? value = double.tryParse(text);
+        value ??= int.tryParse(text)?.toDouble();
+        if (text.isEmpty || value == null) {
+          return;
+        }
+        onParsableSubmit(value);
+      }
+    );
+  }
+}
lib/components/settings/input_list_tile.dart
@@ -1,4 +1,4 @@
-import 'package:blood_pressure_app/components/dialoges/input_dialoge.dart';
+import 'package:blood_pressure_app/components/dialoges/oldinput_dialoge.dart';
 import 'package:flutter/material.dart';
 
 /// A list tile for exposing editable strings.
lib/components/settings/number_input_list_tile.dart
@@ -1,4 +1,4 @@
-import 'package:blood_pressure_app/components/dialoges/input_dialoge.dart';
+import 'package:blood_pressure_app/components/dialoges/oldinput_dialoge.dart';
 import 'package:flutter/material.dart';
 
 /// Widget for editing numbers in a list tile.
lib/screens/subsettings/graph_markings.dart
@@ -1,5 +1,5 @@
 import 'package:blood_pressure_app/components/color_picker.dart';
-import 'package:blood_pressure_app/components/dialoges/input_dialoge.dart';
+import 'package:blood_pressure_app/components/dialoges/oldinput_dialoge.dart';
 import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
 import 'package:blood_pressure_app/model/storage/settings_store.dart';
 import 'package:flutter/material.dart';
lib/screens/settings.dart
@@ -2,7 +2,7 @@ import 'dart:io';
 
 import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/components/dialoges/enter_timeformat.dart';
-import 'package:blood_pressure_app/components/dialoges/input_dialoge.dart';
+import 'package:blood_pressure_app/components/dialoges/oldinput_dialoge.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/iso_lang_names.dart';
test/ui/components/settings/input_list_tile_test.dart
@@ -1,4 +1,4 @@
-import 'package:blood_pressure_app/components/dialoges/input_dialoge.dart';
+import 'package:blood_pressure_app/components/dialoges/oldinput_dialoge.dart';
 import 'package:blood_pressure_app/components/settings/input_list_tile.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
test/ui/components/settings/number_input_list_tile_test.dart
@@ -1,4 +1,4 @@
-import 'package:blood_pressure_app/components/dialoges/input_dialoge.dart';
+import 'package:blood_pressure_app/components/dialoges/oldinput_dialoge.dart';
 import 'package:blood_pressure_app/components/settings/number_input_list_tile.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
test/ui/components/input_dialoge_test.dart
@@ -0,0 +1,103 @@
+import 'package:blood_pressure_app/components/dialoges/input_dialoge.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('InputDialoge', () {
+    testWidgets('should initialize without errors', (widgetTester) async {
+      await widgetTester.pumpWidget(const MaterialApp(
+          localizationsDelegates: [AppLocalizations.delegate,],
+          locale: Locale('en'),
+          home: InputDialoge()
+      ));
+      expect(widgetTester.takeException(), isNull);
+      await widgetTester.pumpWidget(const MaterialApp(
+          localizationsDelegates: [AppLocalizations.delegate,], locale: Locale('en'),
+          home: InputDialoge(
+            hintText: 'test hint',
+            initialValue: 'initial text',
+          )
+      ));
+      expect(widgetTester.takeException(), isNull);
+      expect(find.byType(InputDialoge), findsOneWidget);
+    });
+    testWidgets('should show prefilled text', (widgetTester) async {
+      await widgetTester.pumpWidget(const MaterialApp(
+          localizationsDelegates: [AppLocalizations.delegate,], locale: Locale('en'),
+          home: InputDialoge(
+            hintText: 'test hint',
+            initialValue: 'initial text',
+          )
+      ));
+      expect(find.text('initial text'), findsOneWidget);
+      expect(find.text('test hint'), findsNWidgets(2));
+    });
+  });
+  group('showInputDialoge', () {
+    testWidgets('should start with input focused', (widgetTester) async {
+      await widgetTester.pumpWidget(MaterialApp(
+          localizationsDelegates: const [AppLocalizations.delegate,], locale: const Locale('en'),
+          home: Builder(builder: (BuildContext context) => TextButton(onPressed:
+              () => showInputDialoge(context, initialValue: 'testval'), child: const Text('X')))
+      ));
+      await widgetTester.tap(find.text('X'));
+      await widgetTester.pumpAndSettle();
+
+      expect(find.byType(InputDialoge), findsOneWidget);
+      final primaryFocus = FocusManager.instance.primaryFocus;
+      expect(primaryFocus?.context?.widget, isNotNull);
+      final focusedTextField = find.ancestor(
+        of: find.byWidget(primaryFocus!.context!.widget),
+        matching: find.byType(TextField),
+      );
+      expect(find.descendant(of: focusedTextField, matching: find.text('testval')), findsOneWidget);
+    });
+    testWidgets('should allow entering a value', (widgetTester) async {
+      String? result = 'init';
+      await widgetTester.pumpWidget(MaterialApp(
+          localizationsDelegates: const [AppLocalizations.delegate,], locale: const Locale('en'),
+          home: Builder(builder: (BuildContext context) => TextButton(onPressed:
+              () async {
+                result = await showInputDialoge(context);
+              }, child: const Text('X')))
+      ));
+      final localizations = await AppLocalizations.delegate.load(const Locale('en'));
+      await widgetTester.tap(find.text('X'));
+      await widgetTester.pumpAndSettle();
+
+      expect(find.byType(InputDialoge), findsOneWidget);
+      expect(find.byType(TextField), findsOneWidget);
+
+      await widgetTester.enterText(find.byType(TextField), 'inputted text');
+      expect(find.text(localizations.btnConfirm), findsOneWidget);
+      await widgetTester.tap(find.text(localizations.btnConfirm));
+      await widgetTester.pumpAndSettle();
+
+      expect(result, 'inputted text');
+    });
+    testWidgets('should not return value on cancel', (widgetTester) async {
+      String? result = 'init';
+      await widgetTester.pumpWidget(MaterialApp(
+          localizationsDelegates: const [AppLocalizations.delegate,], locale: const Locale('en'),
+          home: Builder(builder: (BuildContext context) => TextButton(onPressed:
+              () async {
+            result = await showInputDialoge(context, initialValue: 'test');
+          }, child: const Text('X')))
+      ));
+      final localizations = await AppLocalizations.delegate.load(const Locale('en'));
+      await widgetTester.tap(find.text('X'));
+      await widgetTester.pumpAndSettle();
+
+      expect(find.byType(InputDialoge), findsOneWidget);
+      expect(find.byType(TextField), findsOneWidget);
+
+      await widgetTester.enterText(find.byType(TextField), 'inputted text');
+      expect(find.text(localizations.btnCancel), findsOneWidget);
+      await widgetTester.tap(find.text(localizations.btnCancel));
+      await widgetTester.pumpAndSettle();
+
+      expect(result, null);
+    });
+  });
+}
\ No newline at end of file
pubspec.lock
@@ -125,10 +125,10 @@ packages:
     dependency: "direct main"
     description:
       name: collection
-      sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
+      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
       url: "https://pub.dev"
     source: hosted
-    version: "1.17.2"
+    version: "1.18.0"
   convert:
     dependency: transitive
     description:
@@ -369,10 +369,10 @@ packages:
     dependency: transitive
     description:
       name: meta
-      sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
+      sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
       url: "https://pub.dev"
     source: hosted
-    version: "1.9.1"
+    version: "1.10.0"
   mockito:
     dependency: "direct dev"
     description:
@@ -638,18 +638,18 @@ packages:
     dependency: transitive
     description:
       name: stack_trace
-      sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+      sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
       url: "https://pub.dev"
     source: hosted
-    version: "1.11.0"
+    version: "1.11.1"
   stream_channel:
     dependency: transitive
     description:
       name: stream_channel
-      sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+      sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
       url: "https://pub.dev"
     source: hosted
-    version: "2.1.1"
+    version: "2.1.2"
   string_scanner:
     dependency: transitive
     description:
@@ -678,10 +678,10 @@ packages:
     dependency: transitive
     description:
       name: test_api
-      sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
+      sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
       url: "https://pub.dev"
     source: hosted
-    version: "0.6.0"
+    version: "0.6.1"
   typed_data:
     dependency: transitive
     description:
@@ -774,10 +774,10 @@ packages:
     dependency: transitive
     description:
       name: web
-      sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
+      sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
       url: "https://pub.dev"
     source: hosted
-    version: "0.1.4-beta"
+    version: "0.3.0"
   win32:
     dependency: transitive
     description:
@@ -811,5 +811,5 @@ packages:
     source: hosted
     version: "3.1.2"
 sdks:
-  dart: ">=3.1.0 <4.0.0"
+  dart: ">=3.2.0-194.0.dev <4.0.0"
   flutter: ">=3.13.0"