Unverified Commit 677f218e authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added InputDecorationTheme (#14177)

parent 38ce59f0
...@@ -28,6 +28,13 @@ import 'package:flutter/widgets.dart'; ...@@ -28,6 +28,13 @@ import 'package:flutter/widgets.dart';
/// rounded rectangle around the input decorator's container. /// rounded rectangle around the input decorator's container.
/// * [InputDecoration], which is used to configure an [InputDecorator]. /// * [InputDecoration], which is used to configure an [InputDecorator].
abstract class InputBorder extends ShapeBorder { abstract class InputBorder extends ShapeBorder {
/// No input border.
///
/// Use this value with [InputDecoration.border] to specify that no border
/// should be drawn. The [InputDecoration.collapsed] constructor sets
/// its border to this value.
static const InputBorder none = const _NoInputBorder();
/// Creates a border for an [InputDecorator]. /// Creates a border for an [InputDecorator].
/// ///
/// The [borderSide] parameter must not be null. Applications typically do /// The [borderSide] parameter must not be null. Applications typically do
...@@ -72,6 +79,43 @@ abstract class InputBorder extends ShapeBorder { ...@@ -72,6 +79,43 @@ abstract class InputBorder extends ShapeBorder {
}); });
} }
// Used to create the InputBorder.none singleton.
class _NoInputBorder extends InputBorder {
const _NoInputBorder() : super(borderSide: BorderSide.none);
@override
_NoInputBorder copyWith({ BorderSide borderSide }) => const _NoInputBorder();
@override
bool get isOutline => false;
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
@override
_NoInputBorder scale(double t) => const _NoInputBorder();
@override
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
return new Path()..addRect(rect);
}
@override
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
return new Path()..addRect(rect);
}
@override
void paint(Canvas canvas, Rect rect, {
double gapStart,
double gapExtent: 0.0,
double gapPercentage: 0.0,
TextDirection textDirection,
}) {
// Do not paint.
}
}
/// Draws a horizontal line at the bottom of an [InputDecorator]'s container. /// Draws a horizontal line at the bottom of an [InputDecorator]'s container.
/// ///
/// The input decorator's "container" is the optionally filled area above the /// The input decorator's "container" is the optionally filled area above the
......
...@@ -402,6 +402,7 @@ enum _DecorationSlot { ...@@ -402,6 +402,7 @@ enum _DecorationSlot {
class _Decoration { class _Decoration {
const _Decoration({ const _Decoration({
@required this.contentPadding, @required this.contentPadding,
@required this.isCollapsed,
@required this.floatingLabelHeight, @required this.floatingLabelHeight,
@required this.floatingLabelProgress, @required this.floatingLabelProgress,
this.border, this.border,
...@@ -418,10 +419,12 @@ class _Decoration { ...@@ -418,10 +419,12 @@ class _Decoration {
this.counter, this.counter,
this.container, this.container,
}) : assert(contentPadding != null), }) : assert(contentPadding != null),
assert(isCollapsed != null),
assert(floatingLabelHeight != null), assert(floatingLabelHeight != null),
assert(floatingLabelProgress != null); assert(floatingLabelProgress != null);
final EdgeInsets contentPadding; final EdgeInsets contentPadding;
final bool isCollapsed;
final double floatingLabelHeight; final double floatingLabelHeight;
final double floatingLabelProgress; final double floatingLabelProgress;
final InputBorder border; final InputBorder border;
...@@ -895,9 +898,9 @@ class _RenderDecoration extends RenderBox { ...@@ -895,9 +898,9 @@ class _RenderDecoration extends RenderBox {
final double right = overallWidth - contentPadding.right; final double right = overallWidth - contentPadding.right;
height = layout.containerHeight; height = layout.containerHeight;
baseline = decoration.border == null || decoration.border.isOutline baseline = decoration.isCollapsed || !decoration.border.isOutline
? layout.outlineBaseline ? layout.inputBaseline
: layout.inputBaseline; : layout.outlineBaseline;
if (icon != null) { if (icon != null) {
final double x = textDirection == TextDirection.rtl ? overallWidth - icon.size.width : 0.0; final double x = textDirection == TextDirection.rtl ? overallWidth - icon.size.width : 0.0;
...@@ -1250,7 +1253,7 @@ class InputDecorator extends StatefulWidget { ...@@ -1250,7 +1253,7 @@ class InputDecorator extends StatefulWidget {
/// The [isFocused] and [isEmpty] arguments must not be null. /// The [isFocused] and [isEmpty] arguments must not be null.
const InputDecorator({ const InputDecorator({
Key key, Key key,
@required this.decoration, this.decoration,
this.baseStyle, this.baseStyle,
this.textAlign, this.textAlign,
this.isFocused: false, this.isFocused: false,
...@@ -1261,12 +1264,17 @@ class InputDecorator extends StatefulWidget { ...@@ -1261,12 +1264,17 @@ class InputDecorator extends StatefulWidget {
super(key: key); super(key: key);
/// The text and styles to use when decorating the child. /// The text and styles to use when decorating the child.
///
/// If null, `const InputDecoration()` is used. Null [InputDecoration]
/// properties are initialized with the corresponding values from
/// [ThemeData.inputDecorationTheme].
final InputDecoration decoration; final InputDecoration decoration;
/// The style on which to base the label, hint, counter, and error styles /// The style on which to base the label, hint, counter, and error styles
/// if the [decoration] does not provide explicit styles. /// if the [decoration] does not provide explicit styles.
/// ///
/// If null, defaults to a text style from the current [Theme]. /// If null, `baseStyle` defaults to the `subhead` style from the
/// current [Theme], see [ThemeData.textTheme].
final TextStyle baseStyle; final TextStyle baseStyle;
/// How the text in the decoration should be aligned horizontally. /// How the text in the decoration should be aligned horizontally.
...@@ -1341,6 +1349,12 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1341,6 +1349,12 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
); );
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
_effectiveDecoration = null;
}
@override @override
void dispose() { void dispose() {
_floatingLabelController.dispose(); _floatingLabelController.dispose();
...@@ -1354,7 +1368,14 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1354,7 +1368,14 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
}); });
} }
InputDecoration get decoration => widget.decoration; InputDecoration _effectiveDecoration;
InputDecoration get decoration {
_effectiveDecoration ??= widget.decoration.applyDefaults(
Theme.of(context).inputDecorationTheme
);
return _effectiveDecoration;
}
TextAlign get textAlign => widget.textAlign; TextAlign get textAlign => widget.textAlign;
bool get isFocused => widget.isFocused; bool get isFocused => widget.isFocused;
bool get isEmpty => widget.isEmpty; bool get isEmpty => widget.isEmpty;
...@@ -1362,6 +1383,9 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1362,6 +1383,9 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
@override @override
void didUpdateWidget(InputDecorator old) { void didUpdateWidget(InputDecorator old) {
super.didUpdateWidget(old); super.didUpdateWidget(old);
if (widget.decoration != old.decoration)
_effectiveDecoration = null;
if (widget._labelIsFloating != old._labelIsFloating) { if (widget._labelIsFloating != old._labelIsFloating) {
if (widget._labelIsFloating) if (widget._labelIsFloating)
_floatingLabelController.forward(); _floatingLabelController.forward();
...@@ -1392,7 +1416,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1392,7 +1416,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
} }
Color _getFillColor(ThemeData themeData) { Color _getFillColor(ThemeData themeData) {
if (!decoration.filled) if (decoration.filled != true) // filled == null same as filled == false
return Colors.transparent; return Colors.transparent;
if (decoration.fillColor != null) if (decoration.fillColor != null)
return decoration.fillColor; return decoration.fillColor;
...@@ -1418,12 +1442,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1418,12 +1442,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
// then the label appears where the hint would. // then the label appears where the hint would.
bool get _hasInlineLabel => !isFocused && isEmpty && decoration.labelText != null; bool get _hasInlineLabel => !isFocused && isEmpty && decoration.labelText != null;
// The style for the inline label or hint when they're displayed "inline", i.e. // The base style for the inline label or hint when they're displayed "inline",
// when they appear in place of the empty text field. // i.e. when they appear in place of the empty text field.
TextStyle _getInlineLabelStyle(ThemeData themeData) { TextStyle _getInlineStyle(ThemeData themeData) {
return themeData.textTheme.subhead.merge(widget.baseStyle) return themeData.textTheme.subhead.merge(widget.baseStyle)
.copyWith(color: themeData.hintColor) .copyWith(color: themeData.hintColor);
.merge(decoration.hintStyle);
} }
TextStyle _getFloatingLabelStyle(ThemeData themeData) { TextStyle _getFloatingLabelStyle(ThemeData themeData) {
...@@ -1445,7 +1468,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1445,7 +1468,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
} }
double get _borderWeight { double get _borderWeight {
if (decoration.isCollapsed || decoration.border == null || !decoration.enabled) if (decoration.isCollapsed || decoration.border == InputBorder.none || !decoration.enabled)
return 0.0; return 0.0;
return isFocused ? 2.0 : 1.0; return isFocused ? 2.0 : 1.0;
} }
...@@ -1459,21 +1482,22 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1459,21 +1482,22 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final TextStyle inlineStyle = _getInlineLabelStyle(themeData); final TextStyle inlineStyle = _getInlineStyle(themeData);
final TextStyle hintStyle = inlineStyle.merge(decoration.hintStyle);
final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity( final Widget hint = decoration.hintText == null ? null : new AnimatedOpacity(
opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0, opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0,
duration: _kTransitionDuration, duration: _kTransitionDuration,
curve: _kTransitionCurve, curve: _kTransitionCurve,
child: new Text( child: new Text(
decoration.hintText, decoration.hintText,
style: inlineStyle, style: hintStyle,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: textAlign, textAlign: textAlign,
), ),
); );
final InputBorder border = decoration.border == null ? null : decoration.border.copyWith( final InputBorder border = decoration.border.copyWith(
borderSide: new BorderSide( borderSide: new BorderSide(
color: _getBorderColor(themeData), color: _getBorderColor(themeData),
width: _borderWeight, width: _borderWeight,
...@@ -1490,6 +1514,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1490,6 +1514,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
child: containerFill, child: containerFill,
); );
final TextStyle inlineLabelStyle = inlineStyle.merge(decoration.labelStyle);
final Widget label = decoration.labelText == null ? null : new _Shaker( final Widget label = decoration.labelText == null ? null : new _Shaker(
animation: _shakingLabelController.view, animation: _shakingLabelController.view,
child: new AnimatedDefaultTextStyle( child: new AnimatedDefaultTextStyle(
...@@ -1497,7 +1522,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1497,7 +1522,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
curve: _kTransitionCurve, curve: _kTransitionCurve,
style: widget._labelIsFloating style: widget._labelIsFloating
? _getFloatingLabelStyle(themeData) ? _getFloatingLabelStyle(themeData)
: _getInlineLabelStyle(themeData), : inlineLabelStyle,
child: new Text( child: new Text(
decoration.labelText, decoration.labelText,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
...@@ -1513,7 +1538,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1513,7 +1538,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
opacity: widget._labelIsFloating ? 1.0 : 0.0, opacity: widget._labelIsFloating ? 1.0 : 0.0,
child: new Text( child: new Text(
decoration.prefixText, decoration.prefixText,
style: decoration.prefixStyle ?? inlineStyle style: decoration.prefixStyle ?? hintStyle
), ),
); );
...@@ -1524,12 +1549,13 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1524,12 +1549,13 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
opacity: widget._labelIsFloating ? 1.0 : 0.0, opacity: widget._labelIsFloating ? 1.0 : 0.0,
child: new Text( child: new Text(
decoration.suffixText, decoration.suffixText,
style: decoration.suffixStyle ?? inlineStyle style: decoration.suffixStyle ?? hintStyle
), ),
); );
final Color activeColor = _getActiveColor(themeData); final Color activeColor = _getActiveColor(themeData);
final double iconSize = decoration.isDense ? 18.0 : 24.0; final bool decorationIsDense = decoration.isDense == true; // isDense == null, same as false
final double iconSize = decorationIsDense ? 18.0 : 24.0;
final Color iconColor = isFocused ? activeColor : Colors.black45; final Color iconColor = isFocused ? activeColor : Colors.black45;
final Widget icon = decoration.icon == null ? null : final Widget icon = decoration.icon == null ? null :
...@@ -1583,24 +1609,24 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1583,24 +1609,24 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
if (decoration.isCollapsed) { if (decoration.isCollapsed) {
floatingLabelHeight = 0.0; floatingLabelHeight = 0.0;
contentPadding = decoration.contentPadding ?? EdgeInsets.zero; contentPadding = decoration.contentPadding ?? EdgeInsets.zero;
} else if (decoration.border == null || !decoration.border.isOutline) { } else if (!decoration.border.isOutline) {
// 4.0: the vertical gap between the inline elements and the floating label. // 4.0: the vertical gap between the inline elements and the floating label.
floatingLabelHeight = 4.0 + 0.75 * inlineStyle.fontSize; floatingLabelHeight = 4.0 + 0.75 * inlineLabelStyle.fontSize;
if (decoration.filled) { if (decoration.filled == true) { // filled == null same as filled == false
contentPadding = decoration.contentPadding ?? (decoration.isDense contentPadding = decoration.contentPadding ?? (decorationIsDense
? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0) ? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0)
: const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0)); : const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0));
} else { } else {
// Not left or right padding for underline borders that aren't filled // Not left or right padding for underline borders that aren't filled
// is a small concession to backwards compatibility. This eliminates // is a small concession to backwards compatibility. This eliminates
// the most noticeable layout change introduced by #13734. // the most noticeable layout change introduced by #13734.
contentPadding = decoration.contentPadding ?? (decoration.isDense contentPadding = decoration.contentPadding ?? (decorationIsDense
? const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0) ? const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0)
: const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 12.0)); : const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 12.0));
} }
} else { } else {
floatingLabelHeight = 0.0; floatingLabelHeight = 0.0;
contentPadding = decoration.contentPadding ?? (decoration.isDense contentPadding = decoration.contentPadding ?? (decorationIsDense
? const EdgeInsets.fromLTRB(12.0, 20.0, 12.0, 12.0) ? const EdgeInsets.fromLTRB(12.0, 20.0, 12.0, 12.0)
: const EdgeInsets.fromLTRB(12.0, 24.0, 12.0, 16.0)); : const EdgeInsets.fromLTRB(12.0, 24.0, 12.0, 16.0));
} }
...@@ -1608,6 +1634,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1608,6 +1634,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
return new _Decorator( return new _Decorator(
decoration: new _Decoration( decoration: new _Decoration(
contentPadding: contentPadding, contentPadding: contentPadding,
isCollapsed: decoration.isCollapsed,
floatingLabelHeight: floatingLabelHeight, floatingLabelHeight: floatingLabelHeight,
floatingLabelProgress: _floatingLabelController.value, floatingLabelProgress: _floatingLabelController.value,
border: decoration.border, border: decoration.border,
...@@ -1648,8 +1675,12 @@ class InputDecoration { ...@@ -1648,8 +1675,12 @@ class InputDecoration {
/// Creates a bundle of the border, labels, icons, and styles used to /// Creates a bundle of the border, labels, icons, and styles used to
/// decorate a Material Design text field. /// decorate a Material Design text field.
/// ///
/// The [isDense], [filled], and [enabled] arguments must not /// Unless specified by [ThemeData.inputDecorationTheme],
/// be null. /// [InputDecorator] defaults [isDense] to true, and [filled] to false,
/// Similarly, the default border is an instance of [UnderlineInputBorder].
/// If [border] is [InputBorder.none] then no border is drawn.
///
/// The [enabled] argument must not be null.
const InputDecoration({ const InputDecoration({
this.icon, this.icon,
this.labelText, this.labelText,
...@@ -1660,7 +1691,7 @@ class InputDecoration { ...@@ -1660,7 +1691,7 @@ class InputDecoration {
this.hintStyle, this.hintStyle,
this.errorText, this.errorText,
this.errorStyle, this.errorStyle,
this.isDense: false, this.isDense,
this.contentPadding, this.contentPadding,
this.prefixIcon, this.prefixIcon,
this.prefixText, this.prefixText,
...@@ -1670,18 +1701,15 @@ class InputDecoration { ...@@ -1670,18 +1701,15 @@ class InputDecoration {
this.suffixStyle, this.suffixStyle,
this.counterText, this.counterText,
this.counterStyle, this.counterStyle,
this.filled: false, this.filled,
this.fillColor, this.fillColor,
this.border: const UnderlineInputBorder(), this.border,
this.enabled: true, this.enabled: true,
}) : assert(isDense != null), }) : assert(enabled != null), isCollapsed = false;
assert(filled != null),
assert(enabled != null),
isCollapsed = false;
/// Defines an [InputDecorator] that is the same size as the input field. /// Defines an [InputDecorator] that is the same size as the input field.
/// ///
/// This type of input decoration only includes the border. /// This type of input decoration does not include a border by default.
/// ///
/// Sets the [isCollapsed] property to true. /// Sets the [isCollapsed] property to true.
const InputDecoration.collapsed({ const InputDecoration.collapsed({
...@@ -1689,10 +1717,9 @@ class InputDecoration { ...@@ -1689,10 +1717,9 @@ class InputDecoration {
this.hintStyle, this.hintStyle,
this.filled: false, this.filled: false,
this.fillColor, this.fillColor,
this.border: const UnderlineInputBorder(), this.border: InputBorder.none,
this.enabled: true, this.enabled: true,
}) : assert(filled != null), }) : assert(enabled != null),
assert(enabled != null),
icon = null, icon = null,
labelText = null, labelText = null,
labelStyle = null, labelStyle = null,
...@@ -1993,6 +2020,28 @@ class InputDecoration { ...@@ -1993,6 +2020,28 @@ class InputDecoration {
); );
} }
/// Used by widgets like [TextField] and [InputDecorator] to create a new
/// [InputDecoration] with default values taken from the [theme].
///
/// Only null valued properties from this [InputDecoration] are replaced
/// by the corresponding values from [theme].
InputDecoration applyDefaults(InputDecorationTheme theme) {
return copyWith(
labelStyle: labelStyle ?? theme.labelStyle,
helperStyle: helperStyle ?? theme.helperStyle,
hintStyle: hintStyle ?? theme.hintStyle,
errorStyle: errorStyle ?? theme.errorStyle,
isDense: isDense ?? theme.isDense,
contentPadding: contentPadding ?? theme.contentPadding,
prefixStyle: prefixStyle ?? theme.prefixStyle,
suffixStyle: suffixStyle ?? theme.suffixStyle,
counterStyle: counterStyle ?? theme.counterStyle,
filled: filled ?? theme.filled,
fillColor: fillColor ?? theme.fillColor,
border: border ?? theme.border,
);
}
@override @override
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
if (identical(this, other)) if (identical(this, other))
...@@ -2071,7 +2120,7 @@ class InputDecoration { ...@@ -2071,7 +2120,7 @@ class InputDecoration {
description.add('hintText: "$hintText"'); description.add('hintText: "$hintText"');
if (errorText != null) if (errorText != null)
description.add('errorText: "$errorText"'); description.add('errorText: "$errorText"');
if (isDense) if (isDense ?? false)
description.add('isDense: $isDense'); description.add('isDense: $isDense');
if (contentPadding != null) if (contentPadding != null)
description.add('contentPadding: $contentPadding'); description.add('contentPadding: $contentPadding');
...@@ -2093,7 +2142,7 @@ class InputDecoration { ...@@ -2093,7 +2142,7 @@ class InputDecoration {
description.add('counterText: $counterText'); description.add('counterText: $counterText');
if (counterStyle != null) if (counterStyle != null)
description.add('counterStyle: $counterStyle'); description.add('counterStyle: $counterStyle');
if (filled) if (filled == true) // filled == null same as filled == false
description.add('filled: true'); description.add('filled: true');
if (fillColor != null) if (fillColor != null)
description.add('fillColor: $fillColor'); description.add('fillColor: $fillColor');
...@@ -2104,3 +2153,151 @@ class InputDecoration { ...@@ -2104,3 +2153,151 @@ class InputDecoration {
return 'InputDecoration(${description.join(', ')})'; return 'InputDecoration(${description.join(', ')})';
} }
} }
/// Defines the default appearance of [InputDecorator]s.
///
/// This class is used to define the value of [ThemeData.inputDecorationTheme].
/// The [InputDecorator], [TextField], and [TextFormField] widgets use
/// the current input decoration theme to initialize null [InputDecoration]
/// properties.
///
/// The [InputDecoration.applyDefaults] method is used to combine a input
/// decoration theme with an [InputDecoration] object.
@immutable
class InputDecorationTheme {
/// Creates a value for [ThemeData.inputDecorationTheme] that
/// defines default values for [InputDecorator].
///
/// The values of [isDense], [isCollapsed], [isFilled], and [border] must
/// not be null.
const InputDecorationTheme({
this.labelStyle,
this.helperStyle,
this.hintStyle,
this.errorStyle,
this.isDense: false,
this.contentPadding,
this.isCollapsed: false,
this.prefixStyle,
this.suffixStyle,
this.counterStyle,
this.filled: false,
this.fillColor,
this.border: const UnderlineInputBorder(),
}) : assert(isDense != null),
assert(isCollapsed != null),
assert(filled != null),
assert(border != null);
/// The style to use for [InputDecoration.labelText] when the label is
/// above (i.e., vertically adjacent to) the input field.
///
/// When the [labelText] is on top of the input field, the text uses the
/// [hintStyle] instead.
///
/// If null, defaults to a value derived from the base [TextStyle] for the
/// input field and the current [Theme].
final TextStyle labelStyle;
/// The style to use for [InputDecoration.helperText].
final TextStyle helperStyle;
/// The style to use for the [InputDecoration.hintText].
///
/// Also used for the [labelText] when the [labelText] is displayed on
/// top of the input field (i.e., at the same location on the screen where
/// text my be entered in the input field).
///
/// If null, defaults to a value derived from the base [TextStyle] for the
/// input field and the current [Theme].
final TextStyle hintStyle;
/// The style to use for the [InputDecoration.errorText].
///
/// If null, defaults of a value derived from the base [TextStyle] for the
/// input field and the current [Theme].
final TextStyle errorStyle;
/// Whether the input decorator's child is part of a dense form (i.e., uses
/// less vertical space).
///
/// Defaults to false.
final bool isDense;
/// The padding for the input decoration's container.
///
/// The decoration's container is the area which is filled if
/// [InputDecoration.isFilled] is true and bordered per the [border].
/// It's the area adjacent to [InputDecoration.icon] and above the
/// [InputDecoration.icon] and above the widgets that contain
/// [InputDecoration.helperText], [InputDecoration.errorText], and
/// [InputDecoration.counterText].
///
/// By default the `contentPadding` reflects [isDense] and the type of the
/// [border]. If [isCollapsed] is true then `contentPadding` is
/// [EdgeInsets.zero].
final EdgeInsets contentPadding;
/// Whether the decoration is the same size as the input field.
///
/// A collapsed decoration cannot have [InputDecoration.labelText],
/// [InputDecoration.errorText], or an [InputDecoration.icon].
final bool isCollapsed;
/// The style to use for the [InputDecoration.prefixText].
///
/// If null, defaults to the [hintStyle].
final TextStyle prefixStyle;
/// The style to use for the [InputDecoration.suffixText].
///
/// If null, defaults to the [hintStyle].
final TextStyle suffixStyle;
/// The style to use for the [InputDecoration.counterText].
///
/// If null, defaults to the [helperStyle].
final TextStyle counterStyle;
/// If true the decoration's container is filled with [fillColor].
///
/// Typically this field set to true if [border] is
/// [const UnderlineInputBorder()].
///
/// The decoration's container is the area, defined by the border's
/// [InputBorder.getOuterPath], which is filled if [isFilled] is
/// true and bordered per the [border].
///
/// This property is false by default.
final bool filled;
/// The color to fill the decoration's container with, if [filled] is true.
///
/// By default the fillColor is based on the current [Theme].
///
/// The decoration's container is the area, defined by the border's
/// [InputBorder.getOuterPath], which is filled if [isFilled] is
/// true and bordered per the [border].
final Color fillColor;
/// 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
/// [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()`, defines
/// the area to be filled.
///
/// 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
/// rounded rectangle around the input decorator's container.
final InputBorder border;
}
...@@ -296,10 +296,12 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -296,10 +296,12 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
&& widget.decoration.counterText == null; && widget.decoration.counterText == null;
InputDecoration _getEffectiveDecoration() { InputDecoration _getEffectiveDecoration() {
final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration())
.applyDefaults(Theme.of(context).inputDecorationTheme);
if (!needsCounter) if (!needsCounter)
return widget.decoration; return effectiveDecoration;
final InputDecoration effectiveDecoration = widget?.decoration ?? const InputDecoration();
final String counterText = '${_effectiveController.value.text.runes.length} / ${widget.maxLength}'; final String counterText = '${_effectiveController.value.text.runes.length} / ${widget.maxLength}';
if (_effectiveController.value.text.runes.length > widget.maxLength) { if (_effectiveController.value.text.runes.length > widget.maxLength) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
......
...@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart'; ...@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
import 'input_decorator.dart'; import 'input_decorator.dart';
import 'text_field.dart'; import 'text_field.dart';
import 'theme.dart';
/// A [FormField] that contains a [TextField]. /// A [FormField] that contains a [TextField].
/// ///
...@@ -71,10 +72,12 @@ class TextFormField extends FormField<String> { ...@@ -71,10 +72,12 @@ class TextFormField extends FormField<String> {
validator: validator, validator: validator,
builder: (FormFieldState<String> field) { builder: (FormFieldState<String> field) {
final _TextFormFieldState state = field; final _TextFormFieldState state = field;
final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
.applyDefaults(Theme.of(field.context).inputDecorationTheme);
return new TextField( return new TextField(
controller: state._effectiveController, controller: state._effectiveController,
focusNode: focusNode, focusNode: focusNode,
decoration: decoration.copyWith(errorText: field.errorText), decoration: effectiveDecoration.copyWith(errorText: field.errorText),
keyboardType: keyboardType, keyboardType: keyboardType,
style: style, style: style,
textAlign: textAlign, textAlign: textAlign,
......
...@@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'ink_splash.dart'; import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory; import 'ink_well.dart' show InteractiveInkFeatureFactory;
import 'input_decorator.dart';
import 'typography.dart'; import 'typography.dart';
/// Describes the contrast needs of a color. /// Describes the contrast needs of a color.
...@@ -101,6 +102,7 @@ class ThemeData { ...@@ -101,6 +102,7 @@ class ThemeData {
TextTheme textTheme, TextTheme textTheme,
TextTheme primaryTextTheme, TextTheme primaryTextTheme,
TextTheme accentTextTheme, TextTheme accentTextTheme,
InputDecorationTheme inputDecorationTheme,
IconThemeData iconTheme, IconThemeData iconTheme,
IconThemeData primaryIconTheme, IconThemeData primaryIconTheme,
IconThemeData accentIconTheme, IconThemeData accentIconTheme,
...@@ -135,6 +137,7 @@ class ThemeData { ...@@ -135,6 +137,7 @@ class ThemeData {
indicatorColor ??= accentColor == primaryColor ? Colors.white : accentColor; indicatorColor ??= accentColor == primaryColor ? Colors.white : accentColor;
hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000); hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000);
errorColor ??= Colors.red[700]; errorColor ??= Colors.red[700];
inputDecorationTheme ??= const InputDecorationTheme();
iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black); iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black); primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black); accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
...@@ -176,6 +179,7 @@ class ThemeData { ...@@ -176,6 +179,7 @@ class ThemeData {
textTheme: textTheme, textTheme: textTheme,
primaryTextTheme: primaryTextTheme, primaryTextTheme: primaryTextTheme,
accentTextTheme: accentTextTheme, accentTextTheme: accentTextTheme,
inputDecorationTheme: inputDecorationTheme,
iconTheme: iconTheme, iconTheme: iconTheme,
primaryIconTheme: primaryIconTheme, primaryIconTheme: primaryIconTheme,
accentIconTheme: accentIconTheme, accentIconTheme: accentIconTheme,
...@@ -217,6 +221,7 @@ class ThemeData { ...@@ -217,6 +221,7 @@ class ThemeData {
@required this.textTheme, @required this.textTheme,
@required this.primaryTextTheme, @required this.primaryTextTheme,
@required this.accentTextTheme, @required this.accentTextTheme,
@required this.inputDecorationTheme,
@required this.iconTheme, @required this.iconTheme,
@required this.primaryIconTheme, @required this.primaryIconTheme,
@required this.accentIconTheme, @required this.accentIconTheme,
...@@ -248,6 +253,7 @@ class ThemeData { ...@@ -248,6 +253,7 @@ class ThemeData {
assert(textTheme != null), assert(textTheme != null),
assert(primaryTextTheme != null), assert(primaryTextTheme != null),
assert(accentTextTheme != null), assert(accentTextTheme != null),
assert(inputDecorationTheme != null),
assert(iconTheme != null), assert(iconTheme != null),
assert(primaryIconTheme != null), assert(primaryIconTheme != null),
assert(accentIconTheme != null), assert(accentIconTheme != null),
...@@ -388,6 +394,12 @@ class ThemeData { ...@@ -388,6 +394,12 @@ class ThemeData {
/// A text theme that contrasts with the accent color. /// A text theme that contrasts with the accent color.
final TextTheme accentTextTheme; final TextTheme accentTextTheme;
/// The default [InputDecoration] values for [InputDecorator], [TextField],
/// and [TextFormField] are based on this theme.
///
/// See [InputDecoration.applyDefaults].
final InputDecorationTheme inputDecorationTheme;
/// An icon theme that contrasts with the card and canvas colors. /// An icon theme that contrasts with the card and canvas colors.
final IconThemeData iconTheme; final IconThemeData iconTheme;
...@@ -431,6 +443,7 @@ class ThemeData { ...@@ -431,6 +443,7 @@ class ThemeData {
TextTheme textTheme, TextTheme textTheme,
TextTheme primaryTextTheme, TextTheme primaryTextTheme,
TextTheme accentTextTheme, TextTheme accentTextTheme,
InputDecorationTheme inputDecorationTheme,
IconThemeData iconTheme, IconThemeData iconTheme,
IconThemeData primaryIconTheme, IconThemeData primaryIconTheme,
IconThemeData accentIconTheme, IconThemeData accentIconTheme,
...@@ -464,6 +477,7 @@ class ThemeData { ...@@ -464,6 +477,7 @@ class ThemeData {
textTheme: textTheme ?? this.textTheme, textTheme: textTheme ?? this.textTheme,
primaryTextTheme: primaryTextTheme ?? this.primaryTextTheme, primaryTextTheme: primaryTextTheme ?? this.primaryTextTheme,
accentTextTheme: accentTextTheme ?? this.accentTextTheme, accentTextTheme: accentTextTheme ?? this.accentTextTheme,
inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
iconTheme: iconTheme ?? this.iconTheme, iconTheme: iconTheme ?? this.iconTheme,
primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
accentIconTheme: accentIconTheme ?? this.accentIconTheme, accentIconTheme: accentIconTheme ?? this.accentIconTheme,
...@@ -582,6 +596,7 @@ class ThemeData { ...@@ -582,6 +596,7 @@ class ThemeData {
textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t), textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t), primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t),
accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t), accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t),
inputDecorationTheme: t < 0.5 ? a.inputDecorationTheme : b.inputDecorationTheme,
iconTheme: IconThemeData.lerp(a.iconTheme, b.iconTheme, t), iconTheme: IconThemeData.lerp(a.iconTheme, b.iconTheme, t),
primaryIconTheme: IconThemeData.lerp(a.primaryIconTheme, b.primaryIconTheme, t), primaryIconTheme: IconThemeData.lerp(a.primaryIconTheme, b.primaryIconTheme, t),
accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t), accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
...@@ -621,6 +636,7 @@ class ThemeData { ...@@ -621,6 +636,7 @@ class ThemeData {
(otherData.textTheme == textTheme) && (otherData.textTheme == textTheme) &&
(otherData.primaryTextTheme == primaryTextTheme) && (otherData.primaryTextTheme == primaryTextTheme) &&
(otherData.accentTextTheme == accentTextTheme) && (otherData.accentTextTheme == accentTextTheme) &&
(otherData.inputDecorationTheme == inputDecorationTheme) &&
(otherData.iconTheme == iconTheme) && (otherData.iconTheme == iconTheme) &&
(otherData.primaryIconTheme == primaryIconTheme) && (otherData.primaryIconTheme == primaryIconTheme) &&
(otherData.accentIconTheme == accentIconTheme) && (otherData.accentIconTheme == accentIconTheme) &&
...@@ -659,6 +675,7 @@ class ThemeData { ...@@ -659,6 +675,7 @@ class ThemeData {
primaryTextTheme, primaryTextTheme,
accentTextTheme, accentTextTheme,
iconTheme, iconTheme,
inputDecorationTheme,
primaryIconTheme, primaryIconTheme,
accentIconTheme, accentIconTheme,
platform, platform,
......
...@@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart';
Widget buildInputDecorator({ Widget buildInputDecorator({
InputDecoration decoration: const InputDecoration(), InputDecoration decoration: const InputDecoration(),
InputDecorationTheme inputDecorationTheme,
TextDirection textDirection: TextDirection.ltr, TextDirection textDirection: TextDirection.ltr,
bool isEmpty: false, bool isEmpty: false,
bool isFocused: false, bool isFocused: false,
...@@ -19,6 +20,12 @@ Widget buildInputDecorator({ ...@@ -19,6 +20,12 @@ Widget buildInputDecorator({
}) { }) {
return new MaterialApp( return new MaterialApp(
home: new Material( home: new Material(
child: new Builder(
builder: (BuildContext context) {
return new Theme(
data: Theme.of(context).copyWith(
inputDecorationTheme: inputDecorationTheme,
),
child: new Align( child: new Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: new Directionality( child: new Directionality(
...@@ -32,6 +39,9 @@ Widget buildInputDecorator({ ...@@ -32,6 +39,9 @@ Widget buildInputDecorator({
), ),
), ),
), ),
);
},
),
), ),
); );
} }
...@@ -394,14 +404,14 @@ void main() { ...@@ -394,14 +404,14 @@ void main() {
expect(getBorderWeight(tester), 2.0); expect(getBorderWeight(tester), 2.0);
}); });
testWidgets('InputDecorator with null border', (WidgetTester tester) async { testWidgets('InputDecorator with no input border', (WidgetTester tester) async {
// Label is visible, hint is not (opacity 0.0). // Label is visible, hint is not (opacity 0.0).
await tester.pumpWidget( await tester.pumpWidget(
buildInputDecorator( buildInputDecorator(
isEmpty: true, isEmpty: true,
// isFocused: false (default) // isFocused: false (default)
decoration: const InputDecoration( decoration: const InputDecoration(
border: null, border: InputBorder.none,
), ),
), ),
); );
...@@ -898,6 +908,131 @@ void main() { ...@@ -898,6 +908,131 @@ void main() {
expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0)); expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0));
}); });
testWidgets('InputDecorationTheme outline border', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true, // label appears, vertically centered
// isFocused: false (default)
inputDecorationTheme: const InputDecorationTheme(
border: const OutlineInputBorder(),
),
decoration: const InputDecoration(
labelText: 'label',
),
),
);
// Overall height for this InputDecorator is 56dps. Layout is:
// 20 - top padding
// 16 - label (ahem font size 16dps)
// 20 - bottom padding
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('InputDecorationTheme outline border, dense layout', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true, // label appears, vertically centered
// isFocused: false (default)
inputDecorationTheme: const InputDecorationTheme(
border: const OutlineInputBorder(),
isDense: true,
),
decoration: const InputDecoration(
labelText: 'label',
hintText: 'hint',
),
),
);
// Overall height for this InputDecorator is 56dps. Layout is:
// 16 - top padding
// 16 - label (ahem font size 16dps)
// 16 - bottom padding
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0));
expect(tester.getTopLeft(find.text('label')).dy, 16.0);
expect(tester.getBottomLeft(find.text('label')).dy, 32.0);
expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('InputDecorationTheme style overrides', (WidgetTester tester) async {
const TextStyle style16 = const TextStyle(fontFamily: 'Ahem', fontSize: 16.0);
final TextStyle labelStyle = style16.merge(const TextStyle(color: Colors.red));
final TextStyle hintStyle = style16.merge(const TextStyle(color: Colors.green));
final TextStyle prefixStyle = style16.merge(const TextStyle(color: Colors.blue));
final TextStyle suffixStyle = style16.merge(const TextStyle(color: Colors.purple));
const TextStyle style12 = const TextStyle(fontFamily: 'Ahem', fontSize: 12.0);
final TextStyle helperStyle = style12.merge(const TextStyle(color: Colors.orange));
final TextStyle counterStyle = style12.merge(const TextStyle(color: Colors.orange));
// This test also verifies that the default InputDecorator provides a
// "small concession to backwards compatibility" by not padding on
// the left and right. If filled is true or an outline border is
// provided then the horizontal padding is included.
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true, // label appears, vertically centered
// isFocused: false (default)
inputDecorationTheme: new InputDecorationTheme(
labelStyle: labelStyle,
hintStyle: hintStyle,
prefixStyle: prefixStyle,
suffixStyle: suffixStyle,
helperStyle: helperStyle,
counterStyle: counterStyle,
// filled: false (default) - don't pad by left/right 12dps
),
decoration: const InputDecoration(
labelText: 'label',
hintText: 'hint',
prefixText: 'prefix',
suffixText: 'suffix',
helperText: 'helper',
counterText: 'counter',
),
),
);
// Overall height for this InputDecorator is 76dps. Layout is:
// 12 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - prefix/hint/input/suffix text (ahem font size 16dps)
// 12 - bottom padding
// 8 - below the border padding
// 12 - help/error/counter text (ahem font size 12dps)
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 76.0));
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
expect(tester.getTopLeft(find.text('helper')), const Offset(0.0, 64.0));
expect(tester.getTopRight(find.text('counter')), const Offset(800.0, 64.0));
// Verify that the styles were passed along
expect(tester.widget<Text>(find.text('prefix')).style.color, prefixStyle.color);
expect(tester.widget<Text>(find.text('suffix')).style.color, suffixStyle.color);
expect(tester.widget<Text>(find.text('helper')).style.color, helperStyle.color);
expect(tester.widget<Text>(find.text('counter')).style.color, counterStyle.color);
TextStyle getLabelStyle() {
return tester.firstWidget<AnimatedDefaultTextStyle>(
find.ancestor(
of: find.text('label'),
matching: find.byType(AnimatedDefaultTextStyle)
)
).style;
}
expect(getLabelStyle().color, labelStyle.color);
});
testWidgets('InputDecorator.toString()', (WidgetTester tester) async { testWidgets('InputDecorator.toString()', (WidgetTester tester) async {
final Widget child = const InputDecorator( final Widget child = const InputDecorator(
key: const Key('key'), key: const Key('key'),
...@@ -910,11 +1045,11 @@ void main() { ...@@ -910,11 +1045,11 @@ void main() {
); );
expect( expect(
child.toString(), child.toString(),
"InputDecorator-[<'key'>](decoration: InputDecoration(border: UnderlineInputBorder()), baseStyle: TextStyle(<all styles inherited>), isFocused: false, isEmpty: false)", "InputDecorator-[<'key'>](decoration: InputDecoration(), baseStyle: TextStyle(<all styles inherited>), isFocused: false, isEmpty: false)",
); );
}); });
testWidgets('InputDecorator with null border and label', (WidgetTester tester) async { testWidgets('InputDecorator with empty border and label', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/14165 // Regression test for https://github.com/flutter/flutter/issues/14165
await tester.pumpWidget( await tester.pumpWidget(
buildInputDecorator( buildInputDecorator(
...@@ -922,7 +1057,7 @@ void main() { ...@@ -922,7 +1057,7 @@ void main() {
// isFocused: false (default) // isFocused: false (default)
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'label', labelText: 'label',
border: null, border: InputBorder.none,
), ),
), ),
); );
...@@ -932,4 +1067,84 @@ void main() { ...@@ -932,4 +1067,84 @@ void main() {
expect(tester.getTopLeft(find.text('label')).dy, 12.0); expect(tester.getTopLeft(find.text('label')).dy, 12.0);
expect(tester.getBottomLeft(find.text('label')).dy, 24.0); expect(tester.getBottomLeft(find.text('label')).dy, 24.0);
}); });
testWidgets('InputDecorationTheme.inputDecoration', (WidgetTester tester) async {
const TextStyle themeStyle = const TextStyle(color: Colors.green);
const TextStyle decorationStyle = const TextStyle(color: Colors.blue);
// InputDecorationTheme arguments define InputDecoration properties.
InputDecoration decoration = const InputDecoration().applyDefaults(
const InputDecorationTheme(
labelStyle: themeStyle,
helperStyle: themeStyle,
hintStyle: themeStyle,
errorStyle: themeStyle,
isDense: true,
contentPadding: const EdgeInsets.all(1.0),
prefixStyle: themeStyle,
suffixStyle: themeStyle,
counterStyle: themeStyle,
filled: true,
fillColor: Colors.red,
border: InputBorder.none,
)
);
expect(decoration.labelStyle, themeStyle);
expect(decoration.helperStyle, themeStyle);
expect(decoration.hintStyle, themeStyle);
expect(decoration.errorStyle, themeStyle);
expect(decoration.isDense, true);
expect(decoration.contentPadding, const EdgeInsets.all(1.0));
expect(decoration.prefixStyle, themeStyle);
expect(decoration.suffixStyle, themeStyle);
expect(decoration.counterStyle, themeStyle);
expect(decoration.filled, true);
expect(decoration.fillColor, Colors.red);
expect(decoration.border, InputBorder.none);
// InputDecoration (baseDecoration) defines InputDecoration properties
decoration = const InputDecoration(
labelStyle: decorationStyle,
helperStyle: decorationStyle,
hintStyle: decorationStyle,
errorStyle: decorationStyle,
isDense: false,
contentPadding: const EdgeInsets.all(4.0),
prefixStyle: decorationStyle,
suffixStyle: decorationStyle,
counterStyle: decorationStyle,
filled: false,
fillColor: Colors.blue,
border: const OutlineInputBorder(),
).applyDefaults(
const InputDecorationTheme(
labelStyle: themeStyle,
helperStyle: themeStyle,
hintStyle: themeStyle,
errorStyle: themeStyle,
isDense: true,
contentPadding: const EdgeInsets.all(1.0),
prefixStyle: themeStyle,
suffixStyle: themeStyle,
counterStyle: themeStyle,
filled: true,
fillColor: Colors.red,
border: InputBorder.none,
),
);
expect(decoration.labelStyle, decorationStyle);
expect(decoration.helperStyle, decorationStyle);
expect(decoration.hintStyle, decorationStyle);
expect(decoration.errorStyle, decorationStyle);
expect(decoration.isDense, false);
expect(decoration.contentPadding, const EdgeInsets.all(4.0));
expect(decoration.prefixStyle, decorationStyle);
expect(decoration.suffixStyle, decorationStyle);
expect(decoration.counterStyle, decorationStyle);
expect(decoration.filled, false);
expect(decoration.fillColor, Colors.blue);
expect(decoration.border, const OutlineInputBorder());
});
} }
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