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.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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user