Unverified Commit 8d837062 authored by Bruno Leroux's avatar Bruno Leroux Committed by GitHub

Add InputDecorator.error to allow error message customization (#129275)

## Description

This PR is a fork of https://github.com/flutter/flutter/pull/118610 with some changes (mainly applying @justinmc comments).

~~This can be used by KishanBusa8 to update https://github.com/flutter/flutter/pull/118610 or can become a non WIP PR if KishanBusa8 does not respond or can not work on the update.~~

## Related Issue

fixes https://github.com/flutter/flutter/issues/11068

## Tests

Adds 3 tests.
parent b6b417c2
......@@ -302,6 +302,7 @@ class _HelperError extends StatefulWidget {
this.helperText,
this.helperStyle,
this.helperMaxLines,
this.error,
this.errorText,
this.errorStyle,
this.errorMaxLines,
......@@ -311,6 +312,7 @@ class _HelperError extends StatefulWidget {
final String? helperText;
final TextStyle? helperStyle;
final int? helperMaxLines;
final Widget? error;
final String? errorText;
final TextStyle? errorStyle;
final int? errorMaxLines;
......@@ -328,6 +330,8 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
Widget? _helper;
Widget? _error;
bool get _hasError => widget.errorText != null || widget.error != null;
@override
void initState() {
super.initState();
......@@ -335,7 +339,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
duration: _kTransitionDuration,
vsync: this,
);
if (widget.errorText != null) {
if (_hasError) {
_error = _buildError();
_controller.value = 1.0;
} else if (widget.helperText != null) {
......@@ -360,16 +364,19 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
void didUpdateWidget(_HelperError old) {
super.didUpdateWidget(old);
final Widget? newError = widget.error;
final String? newErrorText = widget.errorText;
final String? newHelperText = widget.helperText;
final Widget? oldError = old.error;
final String? oldErrorText = old.errorText;
final String? oldHelperText = old.helperText;
final bool errorStateChanged = (newError != null) != (oldError != null);
final bool errorTextStateChanged = (newErrorText != null) != (oldErrorText != null);
final bool helperTextStateChanged = newErrorText == null && (newHelperText != null) != (oldHelperText != null);
if (errorTextStateChanged || helperTextStateChanged) {
if (newErrorText != null) {
if (errorStateChanged || errorTextStateChanged || helperTextStateChanged) {
if (newError != null || newErrorText != null) {
_error = _buildError();
_controller.forward();
} else if (newHelperText != null) {
......@@ -399,7 +406,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
}
Widget _buildError() {
assert(widget.errorText != null);
assert(widget.error != null || widget.errorText != null);
return Semantics(
container: true,
child: FadeTransition(
......@@ -409,7 +416,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
begin: const Offset(0.0, -0.25),
end: Offset.zero,
).evaluate(_controller.view),
child: Text(
child: widget.error ?? Text(
widget.errorText!,
style: widget.errorStyle,
textAlign: widget.textAlign,
......@@ -435,7 +442,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
if (_controller.isCompleted) {
_helper = null;
if (widget.errorText != null) {
if (_hasError) {
return _error = _buildError();
} else {
_error = null;
......@@ -443,7 +450,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
}
}
if (_helper == null && widget.errorText != null) {
if (_helper == null && _hasError) {
return _buildError();
}
......@@ -451,7 +458,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
return _buildHelper();
}
if (widget.errorText != null) {
if (_hasError) {
return Stack(
children: <Widget>[
FadeTransition(
......@@ -2365,6 +2372,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
helperText: decoration.helperText,
helperStyle: _getHelperStyle(themeData, defaults),
helperMaxLines: decoration.helperMaxLines,
error: decoration.error,
errorText: decoration.errorText,
errorStyle: _getErrorStyle(themeData, defaults),
errorMaxLines: decoration.errorMaxLines,
......@@ -2562,6 +2570,7 @@ class InputDecoration {
this.hintStyle,
this.hintTextDirection,
this.hintMaxLines,
this.error,
this.errorText,
this.errorStyle,
this.errorMaxLines,
......@@ -2601,7 +2610,8 @@ class InputDecoration {
this.constraints,
}) : assert(!(label != null && labelText != null), 'Declaring both label and labelText is not supported.'),
assert(!(prefix != null && prefixText != null), 'Declaring both prefix and prefixText is not supported.'),
assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not supported.');
assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not supported.'),
assert(!(error != null && errorText != null), 'Declaring both error and errorText is not supported.');
/// Defines an [InputDecorator] that is the same size as the input field.
///
......@@ -2630,6 +2640,7 @@ class InputDecoration {
helperStyle = null,
helperMaxLines = null,
hintMaxLines = null,
error = null,
errorText = null,
errorStyle = null,
errorMaxLines = null,
......@@ -2842,6 +2853,13 @@ class InputDecoration {
/// used to handle the overflow when it is limited to single line.
final int? hintMaxLines;
/// Optional widget that appears below the [InputDecorator.child] and the border.
///
/// If non-null, the border's color animates to red and the [helperText] is not shown.
///
/// Only one of [error] and [errorText] can be specified.
final Widget? error;
/// Text that appears below the [InputDecorator.child] and the border.
///
/// If non-null, the border's color animates to red and the [helperText] is
......@@ -2849,6 +2867,10 @@ class InputDecoration {
///
/// In a [TextFormField], this is overridden by the value returned from
/// [TextFormField.validator], if that is not null.
///
/// If a more elaborate error is required, consider using [error] instead.
///
/// Only one of [error] and [errorText] can be specified.
final String? errorText;
/// {@template flutter.material.inputDecoration.errorStyle}
......@@ -3485,6 +3507,7 @@ class InputDecoration {
TextStyle? hintStyle,
TextDirection? hintTextDirection,
int? hintMaxLines,
Widget? error,
String? errorText,
TextStyle? errorStyle,
int? errorMaxLines,
......@@ -3537,6 +3560,7 @@ class InputDecoration {
hintStyle: hintStyle ?? this.hintStyle,
hintTextDirection: hintTextDirection ?? this.hintTextDirection,
hintMaxLines: hintMaxLines ?? this.hintMaxLines,
error: error ?? this.error,
errorText: errorText ?? this.errorText,
errorStyle: errorStyle ?? this.errorStyle,
errorMaxLines: errorMaxLines ?? this.errorMaxLines,
......@@ -3639,6 +3663,7 @@ class InputDecoration {
&& other.hintStyle == hintStyle
&& other.hintTextDirection == hintTextDirection
&& other.hintMaxLines == hintMaxLines
&& other.error == error
&& other.errorText == errorText
&& other.errorStyle == errorStyle
&& other.errorMaxLines == errorMaxLines
......@@ -3694,6 +3719,7 @@ class InputDecoration {
hintStyle,
hintTextDirection,
hintMaxLines,
error,
errorText,
errorStyle,
errorMaxLines,
......@@ -3747,6 +3773,7 @@ class InputDecoration {
if (helperMaxLines != null) 'helperMaxLines: "$helperMaxLines"',
if (hintText != null) 'hintText: "$hintText"',
if (hintMaxLines != null) 'hintMaxLines: "$hintMaxLines"',
if (error != null) 'error: "$error"',
if (errorText != null) 'errorText: "$errorText"',
if (errorStyle != null) 'errorStyle: "$errorStyle"',
if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"',
......
......@@ -1643,6 +1643,47 @@ void main() {
expect(tester.getBottomLeft(find.text(kHelper1)), const Offset(12.0, 76.0));
});
testWidgets('InputDecorator shows error text', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
decoration: const InputDecoration(
errorText: 'errorText',
),
),
);
expect(find.text('errorText'), findsOneWidget);
});
testWidgets('InputDecorator shows error widget', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
decoration: const InputDecoration(
error: Text('error', style: TextStyle(fontSize: 20.0)),
),
),
);
expect(find.text('error'), findsOneWidget);
});
testWidgets('InputDecorator throws when error text and error widget are provided', (WidgetTester tester) async {
expect(
() {
buildInputDecorator(
useMaterial3: useMaterial3,
decoration: InputDecoration(
errorText: 'errorText',
error: const Text('error', style: TextStyle(fontSize: 20.0)),
),
);
},
throwsAssertionError,
);
});
testWidgets('InputDecorator prefix/suffix texts', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
......@@ -5217,7 +5258,6 @@ void main() {
useMaterial3: useMaterial3,
// isFocused: false (default)
decoration: const InputDecoration(
// errorText: false (default)
enabled: false,
errorBorder: errorBorder,
focusedBorder: focusedBorder,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment