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.
This commit is contained in:
parent
b6b417c250
commit
8d837062a4
@ -302,6 +302,7 @@ class _HelperError extends StatefulWidget {
|
|||||||
this.helperText,
|
this.helperText,
|
||||||
this.helperStyle,
|
this.helperStyle,
|
||||||
this.helperMaxLines,
|
this.helperMaxLines,
|
||||||
|
this.error,
|
||||||
this.errorText,
|
this.errorText,
|
||||||
this.errorStyle,
|
this.errorStyle,
|
||||||
this.errorMaxLines,
|
this.errorMaxLines,
|
||||||
@ -311,6 +312,7 @@ class _HelperError extends StatefulWidget {
|
|||||||
final String? helperText;
|
final String? helperText;
|
||||||
final TextStyle? helperStyle;
|
final TextStyle? helperStyle;
|
||||||
final int? helperMaxLines;
|
final int? helperMaxLines;
|
||||||
|
final Widget? error;
|
||||||
final String? errorText;
|
final String? errorText;
|
||||||
final TextStyle? errorStyle;
|
final TextStyle? errorStyle;
|
||||||
final int? errorMaxLines;
|
final int? errorMaxLines;
|
||||||
@ -328,6 +330,8 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
|
|||||||
Widget? _helper;
|
Widget? _helper;
|
||||||
Widget? _error;
|
Widget? _error;
|
||||||
|
|
||||||
|
bool get _hasError => widget.errorText != null || widget.error != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -335,7 +339,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
|
|||||||
duration: _kTransitionDuration,
|
duration: _kTransitionDuration,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
if (widget.errorText != null) {
|
if (_hasError) {
|
||||||
_error = _buildError();
|
_error = _buildError();
|
||||||
_controller.value = 1.0;
|
_controller.value = 1.0;
|
||||||
} else if (widget.helperText != null) {
|
} else if (widget.helperText != null) {
|
||||||
@ -360,16 +364,19 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
|
|||||||
void didUpdateWidget(_HelperError old) {
|
void didUpdateWidget(_HelperError old) {
|
||||||
super.didUpdateWidget(old);
|
super.didUpdateWidget(old);
|
||||||
|
|
||||||
|
final Widget? newError = widget.error;
|
||||||
final String? newErrorText = widget.errorText;
|
final String? newErrorText = widget.errorText;
|
||||||
final String? newHelperText = widget.helperText;
|
final String? newHelperText = widget.helperText;
|
||||||
|
final Widget? oldError = old.error;
|
||||||
final String? oldErrorText = old.errorText;
|
final String? oldErrorText = old.errorText;
|
||||||
final String? oldHelperText = old.helperText;
|
final String? oldHelperText = old.helperText;
|
||||||
|
|
||||||
|
final bool errorStateChanged = (newError != null) != (oldError != null);
|
||||||
final bool errorTextStateChanged = (newErrorText != null) != (oldErrorText != null);
|
final bool errorTextStateChanged = (newErrorText != null) != (oldErrorText != null);
|
||||||
final bool helperTextStateChanged = newErrorText == null && (newHelperText != null) != (oldHelperText != null);
|
final bool helperTextStateChanged = newErrorText == null && (newHelperText != null) != (oldHelperText != null);
|
||||||
|
|
||||||
if (errorTextStateChanged || helperTextStateChanged) {
|
if (errorStateChanged || errorTextStateChanged || helperTextStateChanged) {
|
||||||
if (newErrorText != null) {
|
if (newError != null || newErrorText != null) {
|
||||||
_error = _buildError();
|
_error = _buildError();
|
||||||
_controller.forward();
|
_controller.forward();
|
||||||
} else if (newHelperText != null) {
|
} else if (newHelperText != null) {
|
||||||
@ -399,7 +406,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildError() {
|
Widget _buildError() {
|
||||||
assert(widget.errorText != null);
|
assert(widget.error != null || widget.errorText != null);
|
||||||
return Semantics(
|
return Semantics(
|
||||||
container: true,
|
container: true,
|
||||||
child: FadeTransition(
|
child: FadeTransition(
|
||||||
@ -409,7 +416,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
|
|||||||
begin: const Offset(0.0, -0.25),
|
begin: const Offset(0.0, -0.25),
|
||||||
end: Offset.zero,
|
end: Offset.zero,
|
||||||
).evaluate(_controller.view),
|
).evaluate(_controller.view),
|
||||||
child: Text(
|
child: widget.error ?? Text(
|
||||||
widget.errorText!,
|
widget.errorText!,
|
||||||
style: widget.errorStyle,
|
style: widget.errorStyle,
|
||||||
textAlign: widget.textAlign,
|
textAlign: widget.textAlign,
|
||||||
@ -435,7 +442,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
|
|||||||
|
|
||||||
if (_controller.isCompleted) {
|
if (_controller.isCompleted) {
|
||||||
_helper = null;
|
_helper = null;
|
||||||
if (widget.errorText != null) {
|
if (_hasError) {
|
||||||
return _error = _buildError();
|
return _error = _buildError();
|
||||||
} else {
|
} else {
|
||||||
_error = null;
|
_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();
|
return _buildError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,7 +458,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta
|
|||||||
return _buildHelper();
|
return _buildHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.errorText != null) {
|
if (_hasError) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FadeTransition(
|
FadeTransition(
|
||||||
@ -2365,6 +2372,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
|||||||
helperText: decoration.helperText,
|
helperText: decoration.helperText,
|
||||||
helperStyle: _getHelperStyle(themeData, defaults),
|
helperStyle: _getHelperStyle(themeData, defaults),
|
||||||
helperMaxLines: decoration.helperMaxLines,
|
helperMaxLines: decoration.helperMaxLines,
|
||||||
|
error: decoration.error,
|
||||||
errorText: decoration.errorText,
|
errorText: decoration.errorText,
|
||||||
errorStyle: _getErrorStyle(themeData, defaults),
|
errorStyle: _getErrorStyle(themeData, defaults),
|
||||||
errorMaxLines: decoration.errorMaxLines,
|
errorMaxLines: decoration.errorMaxLines,
|
||||||
@ -2562,6 +2570,7 @@ class InputDecoration {
|
|||||||
this.hintStyle,
|
this.hintStyle,
|
||||||
this.hintTextDirection,
|
this.hintTextDirection,
|
||||||
this.hintMaxLines,
|
this.hintMaxLines,
|
||||||
|
this.error,
|
||||||
this.errorText,
|
this.errorText,
|
||||||
this.errorStyle,
|
this.errorStyle,
|
||||||
this.errorMaxLines,
|
this.errorMaxLines,
|
||||||
@ -2601,7 +2610,8 @@ class InputDecoration {
|
|||||||
this.constraints,
|
this.constraints,
|
||||||
}) : assert(!(label != null && labelText != null), 'Declaring both label and labelText is not supported.'),
|
}) : 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(!(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.
|
/// Defines an [InputDecorator] that is the same size as the input field.
|
||||||
///
|
///
|
||||||
@ -2630,6 +2640,7 @@ class InputDecoration {
|
|||||||
helperStyle = null,
|
helperStyle = null,
|
||||||
helperMaxLines = null,
|
helperMaxLines = null,
|
||||||
hintMaxLines = null,
|
hintMaxLines = null,
|
||||||
|
error = null,
|
||||||
errorText = null,
|
errorText = null,
|
||||||
errorStyle = null,
|
errorStyle = null,
|
||||||
errorMaxLines = null,
|
errorMaxLines = null,
|
||||||
@ -2842,6 +2853,13 @@ class InputDecoration {
|
|||||||
/// used to handle the overflow when it is limited to single line.
|
/// used to handle the overflow when it is limited to single line.
|
||||||
final int? hintMaxLines;
|
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.
|
/// Text that appears below the [InputDecorator.child] and the border.
|
||||||
///
|
///
|
||||||
/// If non-null, the border's color animates to red and the [helperText] is
|
/// 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
|
/// In a [TextFormField], this is overridden by the value returned from
|
||||||
/// [TextFormField.validator], if that is not null.
|
/// [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;
|
final String? errorText;
|
||||||
|
|
||||||
/// {@template flutter.material.inputDecoration.errorStyle}
|
/// {@template flutter.material.inputDecoration.errorStyle}
|
||||||
@ -3485,6 +3507,7 @@ class InputDecoration {
|
|||||||
TextStyle? hintStyle,
|
TextStyle? hintStyle,
|
||||||
TextDirection? hintTextDirection,
|
TextDirection? hintTextDirection,
|
||||||
int? hintMaxLines,
|
int? hintMaxLines,
|
||||||
|
Widget? error,
|
||||||
String? errorText,
|
String? errorText,
|
||||||
TextStyle? errorStyle,
|
TextStyle? errorStyle,
|
||||||
int? errorMaxLines,
|
int? errorMaxLines,
|
||||||
@ -3537,6 +3560,7 @@ class InputDecoration {
|
|||||||
hintStyle: hintStyle ?? this.hintStyle,
|
hintStyle: hintStyle ?? this.hintStyle,
|
||||||
hintTextDirection: hintTextDirection ?? this.hintTextDirection,
|
hintTextDirection: hintTextDirection ?? this.hintTextDirection,
|
||||||
hintMaxLines: hintMaxLines ?? this.hintMaxLines,
|
hintMaxLines: hintMaxLines ?? this.hintMaxLines,
|
||||||
|
error: error ?? this.error,
|
||||||
errorText: errorText ?? this.errorText,
|
errorText: errorText ?? this.errorText,
|
||||||
errorStyle: errorStyle ?? this.errorStyle,
|
errorStyle: errorStyle ?? this.errorStyle,
|
||||||
errorMaxLines: errorMaxLines ?? this.errorMaxLines,
|
errorMaxLines: errorMaxLines ?? this.errorMaxLines,
|
||||||
@ -3639,6 +3663,7 @@ class InputDecoration {
|
|||||||
&& other.hintStyle == hintStyle
|
&& other.hintStyle == hintStyle
|
||||||
&& other.hintTextDirection == hintTextDirection
|
&& other.hintTextDirection == hintTextDirection
|
||||||
&& other.hintMaxLines == hintMaxLines
|
&& other.hintMaxLines == hintMaxLines
|
||||||
|
&& other.error == error
|
||||||
&& other.errorText == errorText
|
&& other.errorText == errorText
|
||||||
&& other.errorStyle == errorStyle
|
&& other.errorStyle == errorStyle
|
||||||
&& other.errorMaxLines == errorMaxLines
|
&& other.errorMaxLines == errorMaxLines
|
||||||
@ -3694,6 +3719,7 @@ class InputDecoration {
|
|||||||
hintStyle,
|
hintStyle,
|
||||||
hintTextDirection,
|
hintTextDirection,
|
||||||
hintMaxLines,
|
hintMaxLines,
|
||||||
|
error,
|
||||||
errorText,
|
errorText,
|
||||||
errorStyle,
|
errorStyle,
|
||||||
errorMaxLines,
|
errorMaxLines,
|
||||||
@ -3747,6 +3773,7 @@ class InputDecoration {
|
|||||||
if (helperMaxLines != null) 'helperMaxLines: "$helperMaxLines"',
|
if (helperMaxLines != null) 'helperMaxLines: "$helperMaxLines"',
|
||||||
if (hintText != null) 'hintText: "$hintText"',
|
if (hintText != null) 'hintText: "$hintText"',
|
||||||
if (hintMaxLines != null) 'hintMaxLines: "$hintMaxLines"',
|
if (hintMaxLines != null) 'hintMaxLines: "$hintMaxLines"',
|
||||||
|
if (error != null) 'error: "$error"',
|
||||||
if (errorText != null) 'errorText: "$errorText"',
|
if (errorText != null) 'errorText: "$errorText"',
|
||||||
if (errorStyle != null) 'errorStyle: "$errorStyle"',
|
if (errorStyle != null) 'errorStyle: "$errorStyle"',
|
||||||
if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"',
|
if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"',
|
||||||
|
@ -1643,6 +1643,47 @@ void main() {
|
|||||||
expect(tester.getBottomLeft(find.text(kHelper1)), const Offset(12.0, 76.0));
|
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 {
|
testWidgets('InputDecorator prefix/suffix texts', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
buildInputDecorator(
|
buildInputDecorator(
|
||||||
@ -5217,7 +5258,6 @@ void main() {
|
|||||||
useMaterial3: useMaterial3,
|
useMaterial3: useMaterial3,
|
||||||
// isFocused: false (default)
|
// isFocused: false (default)
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
// errorText: false (default)
|
|
||||||
enabled: false,
|
enabled: false,
|
||||||
errorBorder: errorBorder,
|
errorBorder: errorBorder,
|
||||||
focusedBorder: focusedBorder,
|
focusedBorder: focusedBorder,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user