diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 9569c13a6f..0dd44567b3 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -1591,21 +1591,26 @@ class _InputDecoratorState extends State with TickerProviderStat return themeData.textTheme.caption.copyWith(color: color).merge(decoration.errorStyle); } - double get _borderWeight { - if (decoration.isCollapsed || decoration.border == InputBorder.none || !decoration.enabled) - return 0.0; - return isFocused ? 2.0 : 1.0; - } - - Color _getBorderColor(ThemeData themeData) { - if (!decoration.enabled) { - if (decoration.filled == true && !decoration.border.isOutline) - return Colors.transparent; - return themeData.disabledColor; + InputBorder _getDefaultBorder(ThemeData themeData) { + Color borderColor; + if (decoration.enabled) { + borderColor = decoration.errorText == null + ? _getActiveColor(themeData) + : themeData.errorColor; + } else { + borderColor = (decoration.filled == true && decoration.border?.isOutline != true) + ? Colors.transparent + : themeData.disabledColor; } - return decoration.errorText == null - ? _getActiveColor(themeData) - : themeData.errorColor; + + double borderWeight; + if (decoration.isCollapsed || decoration?.border == InputBorder.none || !decoration.enabled) + borderWeight = 0.0; + else + borderWeight = isFocused ? 2.0 : 1.0; + + final InputBorder border = decoration.border ?? const UnderlineInputBorder(); + return border.copyWith(borderSide: new BorderSide(color: borderColor, width: borderWeight)); } @override @@ -1627,23 +1632,22 @@ class _InputDecoratorState extends State with TickerProviderStat ), ); - final InputBorder border = decoration.border.copyWith( - borderSide: new BorderSide( - color: _getBorderColor(themeData), - width: _borderWeight, - ), - ); + final bool isError = decoration.errorText != null; + InputBorder border; + if (!decoration.enabled) + border = isError ? decoration.errorBorder : decoration.disabledBorder; + else if (isFocused) + border = isError ? decoration.focusedErrorBorder : decoration.focusedBorder; + else + border = isError ? decoration.errorBorder : decoration.enabledBorder; + border ??= _getDefaultBorder(themeData); - final Widget container = border == null - ? new DecoratedBox( - decoration: new BoxDecoration(color: _getFillColor(themeData)) - ) - : new _BorderContainer( - border: border, - gap: _borderGap, - gapAnimation: _floatingLabelController.view, - fillColor: _getFillColor(themeData), - ); + final Widget container = new _BorderContainer( + border: border, + gap: _borderGap, + gapAnimation: _floatingLabelController.view, + fillColor: _getFillColor(themeData), + ); final TextStyle inlineLabelStyle = inlineStyle.merge(decoration.labelStyle); final Widget label = decoration.labelText == null ? null : new _Shaker( @@ -1759,7 +1763,7 @@ class _InputDecoratorState extends State with TickerProviderStat if (decoration.isCollapsed) { floatingLabelHeight = 0.0; contentPadding = decorationContentPadding ?? EdgeInsets.zero; - } else if (!decoration.border.isOutline) { + } else if (!border.isOutline) { // 4.0: the vertical gap between the inline elements and the floating label. floatingLabelHeight = 4.0 + 0.75 * inlineLabelStyle.fontSize; if (decoration.filled == true) { // filled == null same as filled == false @@ -1786,7 +1790,7 @@ class _InputDecoratorState extends State with TickerProviderStat isCollapsed: decoration.isCollapsed, floatingLabelHeight: floatingLabelHeight, floatingLabelProgress: _floatingLabelController.value, - border: decoration.border, + border: border, borderGap: _borderGap, icon: icon, input: widget.child, @@ -1856,6 +1860,11 @@ class InputDecoration { this.counterStyle, this.filled, this.fillColor, + this.errorBorder, + this.focusedBorder, + this.focusedErrorBorder, + this.disabledBorder, + this.enabledBorder, this.border, this.enabled = true, }) : assert(enabled != null), isCollapsed = false; @@ -1891,7 +1900,12 @@ class InputDecoration { suffixText = null, suffixStyle = null, counterText = null, - counterStyle = null; + counterStyle = null, + errorBorder = null, + focusedBorder = null, + focusedErrorBorder = null, + disabledBorder = null, + enabledBorder = null; /// An icon to show before the input field and outside of the decoration's /// container. @@ -2119,16 +2133,147 @@ class InputDecoration { /// [errorText], and [counterText]. final Color fillColor; - /// The border to draw around the decoration's container. + /// The border to display when the [InputDecorator] does not have the focus and + /// is showing an error. + /// + /// See also: + /// * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child + /// has the focus. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder errorBorder; + + /// The border to display when the [InputDecorator] has the focus and is not + /// showing an error. + /// + /// See also: + /// + /// * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child + /// has the focus. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder focusedBorder; + + /// The border to display when the [InputDecorator] has the focus and is + /// showing an error. + /// + /// See also: + /// + /// * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child + /// has the focus. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder focusedErrorBorder; + + /// The border to display when the [InputDecorator] is disabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder disabledBorder; + + /// The border to display when the [InputDecorator] is enabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + final InputBorder enabledBorder; + + /// The shape of the border to draw around the decoration's container. /// /// The decoration's container is the area which is filled if [isFilled] is /// true and bordered per the [border]. It's the area adjacent to - /// [decoration.icon] and above the widgets that contain [helperText], - /// [errorText], and [counterText]. + /// [InputDecoration.icon] and above the widgets that contain + /// [InputDecoration.helperText], [InputDecoration.errorText], and + /// [InputDecoration.counterText]. /// - /// The default value of this property is `const UnderlineInputBorder()`. + /// The border's bounds, i.e. the value of `border.getOuterPath()`, define + /// the area to be filled. + /// + /// This property is only used when the appropriate one of [errorBorder], + /// [focusedBorder], [focusedErrorBorder], [disabledBorder], or [enabledBorder] + /// is not specified. This border's [InputBorder.borderSide] property is + /// configured by the InputDecorator, depending on the values of + /// [InputDecoration.errorText], [InputDecoration.enabled], + /// [InputDecorator.isFocused and the current [Theme]. + /// + /// Typically one of [UnderlineInputBorder] or [OutlineInputBorder]. + /// If null, InputDecorator's default is `const UnderlineInputBorder()`. /// /// See also: + /// + /// * [InputBorder.none], which doesn't draw a border. /// * [UnderlineInputBorder], which draws a horizontal line at the /// bottom of the input decorator's container. /// * [OutlineInputBorder], an [InputDecorator] border which draws a @@ -2168,6 +2313,11 @@ class InputDecoration { TextStyle counterStyle, bool filled, Color fillColor, + InputBorder errorBorder, + InputBorder focusedBorder, + InputBorder focusedErrorBorder, + InputBorder disabledBorder, + InputBorder enabledBorder, InputBorder border, bool enabled, }) { @@ -2194,6 +2344,11 @@ class InputDecoration { counterStyle: counterStyle ?? this.counterStyle, filled: filled ?? this.filled, fillColor: fillColor ?? this.fillColor, + errorBorder: errorBorder ?? this.errorBorder, + focusedBorder: focusedBorder ?? this.focusedBorder, + focusedErrorBorder: focusedErrorBorder ?? this.focusedErrorBorder, + disabledBorder: disabledBorder ?? this.disabledBorder, + enabledBorder: enabledBorder ?? this.enabledBorder, border: border ?? this.border, enabled: enabled ?? this.enabled, ); @@ -2218,6 +2373,11 @@ class InputDecoration { counterStyle: counterStyle ?? theme.counterStyle, filled: filled ?? theme.filled, fillColor: fillColor ?? theme.fillColor, + errorBorder: errorBorder ?? theme.errorBorder, + focusedBorder: focusedBorder ?? theme.focusedBorder, + focusedErrorBorder: focusedErrorBorder ?? theme.focusedErrorBorder, + disabledBorder: disabledBorder ?? theme.disabledBorder, + enabledBorder: enabledBorder ?? theme.enabledBorder, border: border ?? theme.border, ); } @@ -2252,6 +2412,11 @@ class InputDecoration { && typedOther.counterStyle == counterStyle && typedOther.filled == filled && typedOther.fillColor == fillColor + && typedOther.errorBorder == errorBorder + && typedOther.focusedBorder == focusedBorder + && typedOther.focusedErrorBorder == focusedErrorBorder + && typedOther.disabledBorder == disabledBorder + && typedOther.enabledBorder == enabledBorder && typedOther.border == border && typedOther.enabled == enabled; } @@ -2265,12 +2430,12 @@ class InputDecoration { helperText, helperStyle, hintText, + hintStyle, + errorText, + errorStyle, + errorMaxLines, + isDense, hashValues( // Over 20 fields... - hintStyle, - errorText, - errorStyle, - errorMaxLines, - isDense, contentPadding, isCollapsed, prefixIcon, @@ -2283,6 +2448,11 @@ class InputDecoration { counterStyle, filled, fillColor, + errorBorder, + focusedBorder, + focusedErrorBorder, + disabledBorder, + enabledBorder, border, enabled, ), @@ -2332,6 +2502,16 @@ class InputDecoration { description.add('filled: true'); if (fillColor != null) description.add('fillColor: $fillColor'); + if (errorBorder != null) + description.add('errorBorder: $errorBorder'); + if (focusedBorder != null) + description.add('focusedBorder: $focusedBorder'); + if (focusedErrorBorder != null) + description.add('focusedErrorBorder: $focusedErrorBorder'); + if (disabledBorder != null) + description.add('disabledBorder: $disabledBorder'); + if (enabledBorder != null) + description.add('enabledBorder: $enabledBorder'); if (border != null) description.add('border: $border'); if (!enabled) @@ -2370,11 +2550,15 @@ class InputDecorationTheme extends Diagnosticable { this.counterStyle, this.filled = false, this.fillColor, - this.border = const UnderlineInputBorder(), + this.errorBorder, + this.focusedBorder, + this.focusedErrorBorder, + this.disabledBorder, + this.enabledBorder, + this.border, }) : assert(isDense != null), assert(isCollapsed != null), - assert(filled != null), - assert(border != null); + assert(filled != null); /// The style to use for [InputDecoration.labelText] when the label is /// above (i.e., vertically adjacent to) the input field. @@ -2476,7 +2660,124 @@ class InputDecorationTheme extends Diagnosticable { /// true and bordered per the [border]. final Color fillColor; - /// The border to draw around the decoration's container. + /// The border to display when the [InputDecorator] does not have the focus and + /// is showing an error. + /// + /// See also: + /// * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child + /// has the focus. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder errorBorder; + + /// The border to display when the [InputDecorator] has the focus and is not + /// showing an error. + /// + /// See also: + /// + /// * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child + /// has the focus. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder focusedBorder; + + /// The border to display when the [InputDecorator] has the focus and is + /// showing an error. + /// + /// See also: + /// + /// * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child + /// has the focus. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder focusedErrorBorder; + + /// The border to display when the [InputDecorator] is disabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [enabledBorder], displayed when [InputDecoration.enabled] is true + /// and [InputDecoration.errorText] is null. + final InputBorder disabledBorder; + + /// The border to display when the [InputDecorator] is enabled and is not + /// showing an error. + /// + /// See also: + /// + /// * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled. + /// * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null. + /// * [border], for a description of where the [InputDecorator] border appears. + /// * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal + /// line at the bottom of the input decorator's container. + /// * [OutlineInputBorder], an [InputDecorator] border which draws a + /// rounded rectangle around the input decorator's container. + /// * [InputBorder.none], which doesn't draw a border. + /// * [errorBorder], displayed when [InputDecorator.isFocused] is false + /// and [InputDecoration.errorText] is non-null. + /// * [focusedBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is null. + /// * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true + /// and [InputDecoration.errorText] is non-null. + /// * [disabledBorder], displayed when [InputDecoration.enabled] is false + /// and [InputDecoration.errorText] is null. + final InputBorder enabledBorder; + + /// The shape of the border to draw around the decoration's container. /// /// The decoration's container is the area which is filled if [isFilled] is /// true and bordered per the [border]. It's the area adjacent to @@ -2484,12 +2785,21 @@ class InputDecorationTheme extends Diagnosticable { /// [InputDecoration.helperText], [InputDecoration.errorText], and /// [InputDecoration.counterText]. /// - /// The default value of this property is `const UnderlineInputBorder()`. - /// - /// The border's bounds, i.e. the value of `border.getOuterPath()`, defines + /// The border's bounds, i.e. the value of `border.getOuterPath()`, define /// the area to be filled. /// + /// This property is only used when the appropriate one of [errorBorder], + /// [focusedBorder], [focusedErrorBorder], [disabledBorder], or [enabledBorder] + /// is not specified. This border's [InputBorder.borderSide] property is + /// configured by the InputDecorator, depending on the values of + /// [InputDecoration.errorText], [InputDecoration.enabled], + /// [InputDecorator.isFocused and the current [Theme]. + /// + /// Typically one of [UnderlineInputBorder] or [OutlineInputBorder]. + /// If null, InputDecorator's default is `const UnderlineInputBorder()`. + /// /// See also: + /// /// * [InputBorder.none], which doesn't draw a border. /// * [UnderlineInputBorder], which draws a horizontal line at the /// bottom of the input decorator's container. @@ -2514,6 +2824,11 @@ class InputDecorationTheme extends Diagnosticable { properties.add(new DiagnosticsProperty('counterStyle', counterStyle, defaultValue: defaultTheme.counterStyle)); properties.add(new DiagnosticsProperty('filled', filled, defaultValue: defaultTheme.filled)); properties.add(new DiagnosticsProperty('fillColor', fillColor, defaultValue: defaultTheme.fillColor)); + properties.add(new DiagnosticsProperty('errorBorder', errorBorder, defaultValue: defaultTheme.errorBorder)); + properties.add(new DiagnosticsProperty('focusedBorder', focusedBorder, defaultValue: defaultTheme.focusedErrorBorder)); + properties.add(new DiagnosticsProperty('focusedErrorborder', focusedErrorBorder, defaultValue: defaultTheme.focusedErrorBorder)); + properties.add(new DiagnosticsProperty('disabledBorder', disabledBorder, defaultValue: defaultTheme.disabledBorder)); + properties.add(new DiagnosticsProperty('enabledBorder', enabledBorder, defaultValue: defaultTheme.enabledBorder)); properties.add(new DiagnosticsProperty('border', border, defaultValue: defaultTheme.border)); } } diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index f22a8c8734..a8cbe02b26 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -62,7 +62,7 @@ double getBorderBottom(WidgetTester tester) { return box.size.height; } -BorderSide getBorderSide(WidgetTester tester) { +InputBorder getBorder(WidgetTester tester) { if (!tester.any(findBorderPainter())) return null; final CustomPaint customPaint = tester.widget(findBorderPainter()); @@ -70,7 +70,11 @@ BorderSide getBorderSide(WidgetTester tester) { final dynamic/*_InputBorderTween */ inputBorderTween = inputBorderPainter.border; final Animation animation = inputBorderPainter.borderAnimation; final dynamic/*_InputBorder */ border = inputBorderTween.evaluate(animation); - return border.borderSide; + return border; +} + +BorderSide getBorderSide(WidgetTester tester) { + return getBorder(tester)?.borderSide; } double getBorderWeight(WidgetTester tester) => getBorderSide(tester)?.width; @@ -1650,4 +1654,140 @@ void main() { contains('contentPadding: EdgeInsetsDirectional(5.0, 0.0, 0.0, 0.0)'), ); }); + + testWidgets('InputDecoration borders', (WidgetTester tester) async { + const InputBorder errorBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 1.5), + ); + const InputBorder focusedBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.green, width: 4.0), + ); + const InputBorder focusedErrorBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.teal, width: 5.0), + ); + const InputBorder disabledBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 0.0), + ); + const InputBorder enabledBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.blue, width: 2.5), + ); + + await tester.pumpWidget( + buildInputDecorator( + // isFocused: false (default) + decoration: const InputDecoration( + // errorText: null (default) + // enabled: true (default) + errorBorder: errorBorder, + focusedBorder: focusedBorder, + focusedErrorBorder: focusedErrorBorder, + disabledBorder: disabledBorder, + enabledBorder: enabledBorder, + ), + ), + ); + expect(getBorder(tester), enabledBorder); + + await tester.pumpWidget( + buildInputDecorator( + isFocused: true, + decoration: const InputDecoration( + // errorText: null (default) + // enabled: true (default) + errorBorder: errorBorder, + focusedBorder: focusedBorder, + focusedErrorBorder: focusedErrorBorder, + disabledBorder: disabledBorder, + enabledBorder: enabledBorder, + ), + ), + ); + await tester.pumpAndSettle(); // border changes are animated + expect(getBorder(tester), focusedBorder); + + await tester.pumpWidget( + buildInputDecorator( + isFocused: true, + decoration: const InputDecoration( + errorText: 'error', + // enabled: true (default) + errorBorder: errorBorder, + focusedBorder: focusedBorder, + focusedErrorBorder: focusedErrorBorder, + disabledBorder: disabledBorder, + enabledBorder: enabledBorder, + ), + ), + ); + await tester.pumpAndSettle(); // border changes are animated + expect(getBorder(tester), focusedErrorBorder); + + await tester.pumpWidget( + buildInputDecorator( + // isFocused: false (default) + decoration: const InputDecoration( + errorText: 'error', + // enabled: true (default) + errorBorder: errorBorder, + focusedBorder: focusedBorder, + focusedErrorBorder: focusedErrorBorder, + disabledBorder: disabledBorder, + enabledBorder: enabledBorder, + ), + ), + ); + await tester.pumpAndSettle(); // border changes are animated + expect(getBorder(tester), errorBorder); + + await tester.pumpWidget( + buildInputDecorator( + // isFocused: false (default) + decoration: const InputDecoration( + errorText: 'error', + enabled: false, + errorBorder: errorBorder, + focusedBorder: focusedBorder, + focusedErrorBorder: focusedErrorBorder, + disabledBorder: disabledBorder, + enabledBorder: enabledBorder, + ), + ), + ); + await tester.pumpAndSettle(); // border changes are animated + expect(getBorder(tester), errorBorder); + + await tester.pumpWidget( + buildInputDecorator( + // isFocused: false (default) + decoration: const InputDecoration( + // errorText: false (default) + enabled: false, + errorBorder: errorBorder, + focusedBorder: focusedBorder, + focusedErrorBorder: focusedErrorBorder, + disabledBorder: disabledBorder, + enabledBorder: enabledBorder, + ), + ), + ); + await tester.pumpAndSettle(); // border changes are animated + expect(getBorder(tester), disabledBorder); + + await tester.pumpWidget( + buildInputDecorator( + isFocused: true, + decoration: const InputDecoration( + // errorText: null (default) + enabled: false, + errorBorder: errorBorder, + focusedBorder: focusedBorder, + focusedErrorBorder: focusedErrorBorder, + disabledBorder: disabledBorder, + enabledBorder: enabledBorder, + ), + ), + ); + await tester.pumpAndSettle(); // border changes are animated + expect(getBorder(tester), disabledBorder); + }); }