feat: reactive input field forms library (reactive form)
This commit is contained in:
parent
c9e784fb73
commit
094c1fd452
|
@ -4,10 +4,8 @@ import 'package:component_library/component_library.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'error_message_builder.dart';
|
||||
|
||||
class InputField<T> extends StatefulWidget {
|
||||
final String? label;
|
||||
final String label;
|
||||
final String placeholder;
|
||||
final Widget? suffixIcon;
|
||||
final Widget? suffix;
|
||||
|
@ -16,15 +14,16 @@ class InputField<T> extends StatefulWidget {
|
|||
final String? errorMessage;
|
||||
final bool obscureText;
|
||||
final TextInputType textInputType;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final bool isDisabled;
|
||||
final bool isTouched;
|
||||
final bool isError;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final void Function(String)? onChange;
|
||||
|
||||
const InputField({
|
||||
Key? key,
|
||||
required this.placeholder,
|
||||
this.label,
|
||||
required this.label,
|
||||
this.suffixIcon,
|
||||
this.suffix,
|
||||
this.prefix,
|
||||
|
@ -36,6 +35,7 @@ class InputField<T> extends StatefulWidget {
|
|||
required this.isDisabled,
|
||||
required this.isTouched,
|
||||
required this.isError,
|
||||
this.onChange,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -43,8 +43,15 @@ class InputField<T> extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _InputFieldState extends State<InputField> {
|
||||
final _controller = TextEditingController();
|
||||
bool _hasFocus = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = GolekTheme.of(context);
|
||||
|
@ -53,9 +60,9 @@ class _InputFieldState extends State<InputField> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.label != null && widget.label?.isEmpty == true) ...[
|
||||
if (widget.label.isEmpty == false) ...[
|
||||
Text(
|
||||
widget.label!,
|
||||
widget.label,
|
||||
style: theme.textStyle.copyWith(fontSize: FontSize.style14),
|
||||
),
|
||||
],
|
||||
|
@ -74,6 +81,14 @@ class _InputFieldState extends State<InputField> {
|
|||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
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,
|
||||
keyboardType: widget.textInputType,
|
||||
obscureText: widget.obscureText,
|
||||
|
@ -119,7 +134,7 @@ class _InputFieldState extends State<InputField> {
|
|||
),
|
||||
),
|
||||
ErrorMessageBuilder(
|
||||
inputLabel: widget.label ?? '',
|
||||
inputLabel: widget.label,
|
||||
messageError: widget.errorMessage ?? '',
|
||||
)
|
||||
],
|
||||
|
|
|
@ -324,6 +324,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
reactive_forms:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: reactive_forms
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "14.1.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -485,4 +492,4 @@ packages:
|
|||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.18.2 <3.0.0"
|
||||
flutter: ">=2.11.0-0.1.pre"
|
||||
flutter: ">=3.0.0"
|
||||
|
|
|
@ -324,6 +324,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
reactive_forms:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: reactive_forms
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "14.1.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -485,4 +492,4 @@ packages:
|
|||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.18.2 <3.0.0"
|
||||
flutter: ">=2.11.0-0.1.pre"
|
||||
flutter: ">=3.0.0"
|
||||
|
|
|
@ -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()}!');
|
||||
}
|
|
@ -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
|
|
@ -1,3 +1 @@
|
|||
int calculate() {
|
||||
return 6 * 7;
|
||||
}
|
||||
export 'src/reactive_input_field.dart';
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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."
|
||||
}
|
|
@ -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."
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -36,13 +43,34 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
url: "https://pub.dartlang.org"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -71,6 +99,23 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -99,6 +144,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -134,6 +186,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -169,6 +228,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -183,6 +263,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -211,6 +298,11 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -288,6 +380,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -316,6 +415,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -325,3 +431,4 @@ packages:
|
|||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.18.2 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
name: form_fields
|
||||
description: A sample command-line application.
|
||||
version: 1.0.0
|
||||
# homepage: https://www.example.com
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
|
||||
# dependencies:
|
||||
# path: ^1.8.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
reactive_forms: ^14.1.0
|
||||
component_library:
|
||||
path: ../component_library
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import 'package:form_fields/form_fields.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('calculate', () {
|
||||
expect(calculate(), 42);
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -358,7 +358,7 @@ packages:
|
|||
source: sdk
|
||||
version: "0.0.0"
|
||||
form_fields:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/form_fields"
|
||||
relative: true
|
||||
|
@ -560,6 +560,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
reactive_forms:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: reactive_forms
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "14.1.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -22,6 +22,8 @@ dependencies:
|
|||
path: packages/domain_models
|
||||
component_library:
|
||||
path: packages/component_library
|
||||
form_fields:
|
||||
path: packages/form_fields
|
||||
subscription_menu:
|
||||
path: packages/features/subscription_menu
|
||||
landing_menu:
|
||||
|
|
Loading…
Reference in New Issue