feat: reactive input field forms library (reactive form)

This commit is contained in:
Golek 2022-12-05 10:55:14 +07:00
parent c9e784fb73
commit 094c1fd452
15 changed files with 295 additions and 28 deletions

View File

@ -4,10 +4,8 @@ import 'package:component_library/component_library.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'error_message_builder.dart';
class InputField<T> extends StatefulWidget { class InputField<T> extends StatefulWidget {
final String? label; final String label;
final String placeholder; final String placeholder;
final Widget? suffixIcon; final Widget? suffixIcon;
final Widget? suffix; final Widget? suffix;
@ -16,15 +14,16 @@ class InputField<T> extends StatefulWidget {
final String? errorMessage; final String? errorMessage;
final bool obscureText; final bool obscureText;
final TextInputType textInputType; final TextInputType textInputType;
final List<TextInputFormatter>? inputFormatters;
final bool isDisabled; final bool isDisabled;
final bool isTouched; final bool isTouched;
final bool isError; final bool isError;
final List<TextInputFormatter>? inputFormatters;
final void Function(String)? onChange;
const InputField({ const InputField({
Key? key, Key? key,
required this.placeholder, required this.placeholder,
this.label, required this.label,
this.suffixIcon, this.suffixIcon,
this.suffix, this.suffix,
this.prefix, this.prefix,
@ -36,6 +35,7 @@ class InputField<T> extends StatefulWidget {
required this.isDisabled, required this.isDisabled,
required this.isTouched, required this.isTouched,
required this.isError, required this.isError,
this.onChange,
}) : super(key: key); }) : super(key: key);
@override @override
@ -43,8 +43,15 @@ class InputField<T> extends StatefulWidget {
} }
class _InputFieldState extends State<InputField> { class _InputFieldState extends State<InputField> {
final _controller = TextEditingController();
bool _hasFocus = false; bool _hasFocus = false;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = GolekTheme.of(context); final theme = GolekTheme.of(context);
@ -53,9 +60,9 @@ class _InputFieldState extends State<InputField> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (widget.label != null && widget.label?.isEmpty == true) ...[ if (widget.label.isEmpty == false) ...[
Text( Text(
widget.label!, widget.label,
style: theme.textStyle.copyWith(fontSize: FontSize.style14), style: theme.textStyle.copyWith(fontSize: FontSize.style14),
), ),
], ],
@ -74,6 +81,14 @@ class _InputFieldState extends State<InputField> {
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
), ),
child: TextField( child: TextField(
controller: _controller,
onChanged: (value) {
_controller.text = value;
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
widget.onChange?.call(value);
},
inputFormatters: widget.inputFormatters, inputFormatters: widget.inputFormatters,
keyboardType: widget.textInputType, keyboardType: widget.textInputType,
obscureText: widget.obscureText, obscureText: widget.obscureText,
@ -119,7 +134,7 @@ class _InputFieldState extends State<InputField> {
), ),
), ),
ErrorMessageBuilder( ErrorMessageBuilder(
inputLabel: widget.label ?? '', inputLabel: widget.label,
messageError: widget.errorMessage ?? '', messageError: widget.errorMessage ?? '',
) )
], ],

View File

@ -324,6 +324,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
reactive_forms:
dependency: transitive
description:
name: reactive_forms
url: "https://pub.dartlang.org"
source: hosted
version: "14.1.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -485,4 +492,4 @@ packages:
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=2.18.2 <3.0.0" dart: ">=2.18.2 <3.0.0"
flutter: ">=2.11.0-0.1.pre" flutter: ">=3.0.0"

View File

@ -324,6 +324,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
reactive_forms:
dependency: transitive
description:
name: reactive_forms
url: "https://pub.dartlang.org"
source: hosted
version: "14.1.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -485,4 +492,4 @@ packages:
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=2.18.2 <3.0.0" dart: ">=2.18.2 <3.0.0"
flutter: ">=2.11.0-0.1.pre" flutter: ">=3.0.0"

View File

@ -1,5 +0,0 @@
import 'package:form_fields/form_fields.dart' as form_fields;
void main(List<String> arguments) {
print('Hello world: ${form_fields.calculate()}!');
}

View File

@ -0,0 +1,6 @@
arb-dir: lib/src/l10n
template-arb-file: messages_en.arb
output-localization-file: form_fields_localizations.dart
output-class: FormFieldsLocalizations
synthetic-package: false
nullable-getter: false

View File

@ -1,3 +1 @@
int calculate() { export 'src/reactive_input_field.dart';
return 6 * 7;
}

View File

@ -0,0 +1,26 @@
import 'package:reactive_forms/reactive_forms.dart';
class ErrorMessages {
static const messages = {
'required': '{_field_} harus diisi',
'email': 'Format email harus benar',
};
static String getUiErrorMessage({
required AbstractControl control,
required String label,
Map<String, String>? widgetCustomMessages,
}) {
Map<String, Object>? mergedErrorMessages = {
...?widgetCustomMessages,
...messages,
};
final message = mergedErrorMessages.entries.firstWhere(
(element) => element.key == control.errors.entries.first.key,
orElse: () => const MapEntry('', ''),
);
return message.value.toString().replaceFirst('{_field_}', label);
}
}

View File

@ -0,0 +1,13 @@
{
"downvoteIconButtonTooltip": "Downvote",
"upvoteIconButtonTooltip": "Upvote",
"searchBarHintText": "journey",
"searchBarLabelText": "Search",
"shareIconButtonTooltip": "Share",
"favoriteIconButtonTooltip": "Favorite",
"exceptionIndicatorGenericTitle": "Something went wrong",
"exceptionIndicatorTryAgainButton": "Try Again",
"exceptionIndicatorGenericMessage": "There has been an error.\nPlease, check your internet connection and try again later.",
"genericErrorSnackbarMessage": "There has been an error. Please, check your internet connection.",
"authenticationRequiredErrorSnackbarMessage": "You need to sign in before performing this action."
}

View File

@ -0,0 +1,13 @@
{
"downvoteIconButtonTooltip": "Downvote",
"upvoteIconButtonTooltip": "Upvote",
"searchBarHintText": "journey",
"searchBarLabelText": "Search",
"shareIconButtonTooltip": "Share",
"favoriteIconButtonTooltip": "Favorite",
"exceptionIndicatorGenericTitle": "Something went wrong",
"exceptionIndicatorTryAgainButton": "Try Again",
"exceptionIndicatorGenericMessage": "There has been an error.\nPlease, check your internet connection and try again later.",
"genericErrorSnackbarMessage": "There has been an error. Please, check your internet connection.",
"authenticationRequiredErrorSnackbarMessage": "You need to sign in before performing this action."
}

View File

@ -0,0 +1,69 @@
import 'package:component_library/component_library.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:form_fields/src/error_messages.dart';
import 'package:reactive_forms/reactive_forms.dart';
class ReactiveInputField<T> extends StatefulWidget {
const ReactiveInputField({
Key? key,
required this.name,
required this.label,
required this.placeholder,
required this.obscureText,
required this.textInputType,
this.suffixIcon,
this.suffix,
this.prefix,
this.prefixIcon,
this.inputFormatters,
this.customErrorMessages,
}) : super(key: key);
final String name;
final String label;
final String placeholder;
final Widget? suffixIcon;
final Widget? suffix;
final Widget? prefix;
final Widget? prefixIcon;
final bool obscureText;
final TextInputType textInputType;
final List<TextInputFormatter>? inputFormatters;
final Map<String, String>? customErrorMessages;
@override
State<ReactiveInputField> createState() => _ReactiveInputFieldState();
}
class _ReactiveInputFieldState<T> extends State<ReactiveInputField<T>> {
@override
Widget build(BuildContext context) {
return ReactiveValueListenableBuilder<T>(
formControlName: widget.name,
builder: (context, control, child) {
return InputField(
label: widget.label,
onChange: (value) {
control.value = value as T?;
},
placeholder: widget.placeholder,
textInputType: widget.textInputType,
obscureText: widget.obscureText,
errorMessage: ErrorMessages.getUiErrorMessage(
control: control,
label: widget.label,
),
inputFormatters: widget.inputFormatters,
prefix: widget.prefix,
prefixIcon: widget.prefixIcon,
suffix: widget.suffix,
suffixIcon: widget.suffixIcon,
isDisabled: control.disabled,
isTouched: control.touched,
isError: control.hasErrors,
);
},
);
}
}

View File

@ -29,6 +29,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.10.0" version: "2.10.0"
auto_size_text:
dependency: transitive
description:
name: auto_size_text
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -36,13 +43,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.17.0" version: "1.16.0"
component_library:
dependency: "direct main"
description:
path: "../component_library"
relative: true
source: path
version: "0.0.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -71,6 +99,23 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.4" version: "6.1.4"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -99,6 +144,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -134,6 +186,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.13" version: "0.12.13"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -169,6 +228,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.8.2"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.0"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -183,6 +263,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.3"
reactive_forms:
dependency: "direct main"
description:
name: reactive_forms
url: "https://pub.dartlang.org"
source: hosted
version: "14.1.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -211,6 +298,11 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_map_stack_trace: source_map_stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -288,6 +380,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@ -316,6 +415,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -325,3 +431,4 @@ packages:
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=2.18.2 <3.0.0" dart: ">=2.18.2 <3.0.0"
flutter: ">=3.0.0"

View File

@ -1,14 +1,19 @@
name: form_fields name: form_fields
description: A sample command-line application. description: A sample command-line application.
version: 1.0.0 version: 1.0.0
# homepage: https://www.example.com publish_to: none
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'
# dependencies: dependencies:
# path: ^1.8.0 flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
reactive_forms: ^14.1.0
component_library:
path: ../component_library
dev_dependencies: dev_dependencies:
lints: ^2.0.0 lints: ^2.0.0
test: ^1.16.0 test: ^1.16.0

View File

@ -1,8 +1,5 @@
import 'package:form_fields/form_fields.dart'; import 'package:form_fields/form_fields.dart';
import 'package:test/test.dart';
void main() { void main() {
test('calculate', () {
expect(calculate(), 42);
});
} }

View File

@ -358,7 +358,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
form_fields: form_fields:
dependency: transitive dependency: "direct main"
description: description:
path: "packages/form_fields" path: "packages/form_fields"
relative: true relative: true
@ -560,6 +560,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.1" version: "1.2.1"
reactive_forms:
dependency: transitive
description:
name: reactive_forms
url: "https://pub.dartlang.org"
source: hosted
version: "14.1.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:

View File

@ -22,6 +22,8 @@ dependencies:
path: packages/domain_models path: packages/domain_models
component_library: component_library:
path: packages/component_library path: packages/component_library
form_fields:
path: packages/form_fields
subscription_menu: subscription_menu:
path: packages/features/subscription_menu path: packages/features/subscription_menu
landing_menu: landing_menu: