Commit 2b607ff
Changed files (10)
lib
components
screens
subsettings
test
ui
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"