Unverified Commit 64b0cfda authored by Bjarte Bore's avatar Bjarte Bore Committed by GitHub

Added support for MaterialState to InputDecorator (#91762)

parent 06e75e94
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for InputDecoration
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextFormField(
initialValue: 'abc',
decoration: InputDecoration(
prefixIcon: const Icon(Icons.person),
prefixIconColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return Colors.green;
} if (states.contains(MaterialState.error)) {
return Colors.red;
}
return Colors.grey;
}),
),
);
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for InputDecoration
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);
return Theme(
data: themeData.copyWith(
inputDecorationTheme: themeData.inputDecorationTheme.copyWith(
prefixIconColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return Colors.green;
} if (states.contains(MaterialState.error)) {
return Colors.red;
}
return Colors.grey;
}),
)
),
child: TextFormField(
initialValue: 'abc',
decoration: const InputDecoration(
prefixIcon: Icon(Icons.person),
),
),
);
}
}
...@@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart'; ...@@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'input_border.dart'; import 'input_border.dart';
import 'material_state.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
...@@ -2081,7 +2082,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2081,7 +2082,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
if (decoration!.filled != true) // filled == null same as filled == false 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 MaterialStateProperty.resolveAs(decoration!.fillColor!, materialState);
// dark theme: 10% white (enabled), 5% white (disabled) // dark theme: 10% white (enabled), 5% white (disabled)
// light theme: 4% black (enabled), 2% black (disabled) // light theme: 4% black (enabled), 2% black (disabled)
...@@ -2104,16 +2105,33 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2104,16 +2105,33 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
return decoration!.hoverColor ?? themeData.inputDecorationTheme.hoverColor ?? themeData.hoverColor; return decoration!.hoverColor ?? themeData.inputDecorationTheme.hoverColor ?? themeData.hoverColor;
} }
Color _getDefaultIconColor(ThemeData themeData) { Color _getIconColor(ThemeData themeData) {
if (!decoration!.enabled && !isFocused) Color _resolveIconColor(Set<MaterialState> states) {
return themeData.disabledColor; if (states.contains(MaterialState.disabled) && !states.contains(MaterialState.focused))
return themeData.disabledColor;
switch (themeData.brightness) { if (states.contains(MaterialState.focused))
case Brightness.dark: return themeData.colorScheme.primary;
return Colors.white70;
case Brightness.light: switch (themeData.brightness) {
return Colors.black45; case Brightness.dark:
return Colors.white70;
case Brightness.light:
return Colors.black45;
}
} }
return MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.iconColor, materialState)
?? MaterialStateProperty.resolveWith(_resolveIconColor).resolve(materialState);
}
Color _getPrefixIconColor(ThemeData themeData) {
return MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.prefixIconColor, materialState)
?? _getIconColor(themeData);
}
Color _getSuffixIconColor(ThemeData themeData) {
return MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.suffixIconColor, materialState)
?? _getIconColor(themeData);
} }
// True if the label will be shown and the hint will not. // True if the label will be shown and the hint will not.
...@@ -2129,34 +2147,68 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2129,34 +2147,68 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
// If the label is a floating placeholder, it's always shown. // If the label is a floating placeholder, it's always shown.
bool get _shouldShowLabel => _hasInlineLabel || _floatingLabelEnabled; bool get _shouldShowLabel => _hasInlineLabel || _floatingLabelEnabled;
// The base style for the inline label or hint when they're displayed "inline", // The base style for the inline label when they're displayed "inline",
// i.e. when they appear in place of the empty text field. // i.e. when they appear in place of the empty text field.
TextStyle _getInlineStyle(ThemeData themeData) { TextStyle _getInlineLabelStyle(ThemeData themeData) {
return themeData.textTheme.subtitle1!.merge(widget.baseStyle) final TextStyle defaultStyle = TextStyle(
.copyWith(color: decoration!.enabled ? themeData.hintColor : themeData.disabledColor); color: decoration!.enabled ? themeData.hintColor : themeData.disabledColor,
);
final TextStyle? style = MaterialStateProperty.resolveAs(decoration!.labelStyle, materialState)
?? MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.labelStyle, materialState);
return themeData.textTheme.subtitle1!
.merge(widget.baseStyle)
.merge(defaultStyle)
.merge(style)
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Setting TextStyle.height to 1 ensures that the label's height will equal
// its font size.
.copyWith(height: themeData.fixTextFieldOutlineLabel ? 1 : null);
}
// The base style for the inline hint when they're displayed "inline",
// i.e. when they appear in place of the empty text field.
TextStyle _getInlineHintStyle(ThemeData themeData) {
final TextStyle defaultStyle = TextStyle(
color: decoration!.enabled ? themeData.hintColor : themeData.disabledColor,
);
final TextStyle? style = MaterialStateProperty.resolveAs(decoration!.hintStyle, materialState)
?? MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.hintStyle, materialState);
return themeData.textTheme.subtitle1!
.merge(widget.baseStyle)
.merge(defaultStyle)
.merge(style);
} }
TextStyle _getFloatingLabelStyle(ThemeData themeData) { TextStyle _getFloatingLabelStyle(ThemeData themeData) {
final Color color = decoration!.errorText != null TextStyle getFallbackTextStyle() {
? decoration!.errorStyle?.color ?? themeData.errorColor final Color color = decoration!.errorText != null
: _getActiveColor(themeData); ? decoration!.errorStyle?.color ?? themeData.errorColor
final TextStyle style = themeData.textTheme.subtitle1!.merge(widget.baseStyle); : _getActiveColor(themeData);
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Setting TextStyle.height to 1 ensures that the label's height will equal return TextStyle(color: decoration!.enabled ? color : themeData.disabledColor)
// its font size.
return themeData.fixTextFieldOutlineLabel
? style
.copyWith(height: 1, color: decoration!.enabled ? color : themeData.disabledColor)
.merge(decoration!.floatingLabelStyle ?? decoration!.labelStyle)
: style
.copyWith(color: decoration!.enabled ? color : themeData.disabledColor)
.merge(decoration!.floatingLabelStyle ?? decoration!.labelStyle); .merge(decoration!.floatingLabelStyle ?? decoration!.labelStyle);
}
final TextStyle? style = MaterialStateProperty.resolveAs(decoration!.floatingLabelStyle, materialState)
?? MaterialStateProperty.resolveAs(themeData.inputDecorationTheme.floatingLabelStyle, materialState);
return themeData.textTheme.subtitle1!
.merge(widget.baseStyle)
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Setting TextStyle.height to 1 ensures that the label's height will equal
// its font size.
.copyWith(height: themeData.fixTextFieldOutlineLabel ? 1 : null)
.merge(getFallbackTextStyle())
.merge(style);
} }
TextStyle _getHelperStyle(ThemeData themeData) { TextStyle _getHelperStyle(ThemeData themeData) {
final Color color = decoration!.enabled ? themeData.hintColor : Colors.transparent; final Color color = decoration!.enabled ? themeData.hintColor : Colors.transparent;
return themeData.textTheme.caption!.copyWith(color: color).merge(decoration!.helperStyle); return themeData.textTheme.caption!.copyWith(color: color).merge(MaterialStateProperty.resolveAs(decoration!.helperStyle, materialState));
} }
TextStyle _getErrorStyle(ThemeData themeData) { TextStyle _getErrorStyle(ThemeData themeData) {
...@@ -2164,9 +2216,25 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2164,9 +2216,25 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
return themeData.textTheme.caption!.copyWith(color: color).merge(decoration!.errorStyle); return themeData.textTheme.caption!.copyWith(color: color).merge(decoration!.errorStyle);
} }
Set<MaterialState> get materialState {
return <MaterialState>{
if (!decoration!.enabled) MaterialState.disabled,
if (isFocused) MaterialState.focused,
if (isHovering) MaterialState.hovered,
if (decoration!.errorText != null) MaterialState.error,
};
}
InputBorder _getDefaultBorder(ThemeData themeData) { InputBorder _getDefaultBorder(ThemeData themeData) {
if (decoration!.border?.borderSide == BorderSide.none) { final InputBorder border = MaterialStateProperty.resolveAs(decoration!.border, materialState)
return decoration!.border!; ?? const UnderlineInputBorder();
if (decoration!.border is MaterialStateProperty<InputBorder>) {
return border;
}
if (border.borderSide == BorderSide.none) {
return border;
} }
final Color borderColor; final Color borderColor;
...@@ -2186,17 +2254,16 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2186,17 +2254,16 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
else else
borderWeight = isFocused ? 2.0 : 1.0; borderWeight = isFocused ? 2.0 : 1.0;
final InputBorder border = decoration!.border ?? const UnderlineInputBorder();
return border.copyWith(borderSide: BorderSide(color: borderColor, width: borderWeight)); return border.copyWith(borderSide: BorderSide(color: borderColor, width: borderWeight));
} }
@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 = _getInlineStyle(themeData); final TextStyle labelStyle = _getInlineLabelStyle(themeData);
final TextBaseline textBaseline = inlineStyle.textBaseline!; final TextBaseline textBaseline = labelStyle.textBaseline!;
final TextStyle hintStyle = inlineStyle.merge(decoration!.hintStyle); final TextStyle hintStyle = _getInlineHintStyle(themeData);
final Widget? hint = decoration!.hintText == null ? null : AnimatedOpacity( final Widget? hint = decoration!.hintText == null ? null : AnimatedOpacity(
opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0, opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0,
duration: _kTransitionDuration, duration: _kTransitionDuration,
...@@ -2231,12 +2298,6 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2231,12 +2298,6 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
isHovering: isHovering, isHovering: isHovering,
); );
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Setting TextStyle.height to 1 ensures that the label's height will equal
// its font size.
final TextStyle inlineLabelStyle = themeData.fixTextFieldOutlineLabel
? inlineStyle.merge(decoration!.labelStyle).copyWith(height: 1)
: inlineStyle.merge(decoration!.labelStyle);
final Widget? label = decoration!.labelText == null && decoration!.label == null ? null : _Shaker( final Widget? label = decoration!.labelText == null && decoration!.label == null ? null : _Shaker(
animation: _shakingLabelController.view, animation: _shakingLabelController.view,
child: AnimatedOpacity( child: AnimatedOpacity(
...@@ -2248,7 +2309,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2248,7 +2309,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
curve: _kTransitionCurve, curve: _kTransitionCurve,
style: widget._labelShouldWithdraw style: widget._labelShouldWithdraw
? _getFloatingLabelStyle(themeData) ? _getFloatingLabelStyle(themeData)
: inlineLabelStyle, : labelStyle,
child: decoration!.label ?? Text( child: decoration!.label ?? Text(
decoration!.labelText!, decoration!.labelText!,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
...@@ -2262,7 +2323,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2262,7 +2323,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
_AffixText( _AffixText(
labelIsFloating: widget._labelShouldWithdraw, labelIsFloating: widget._labelShouldWithdraw,
text: decoration!.prefixText, text: decoration!.prefixText,
style: decoration!.prefixStyle ?? hintStyle, style: MaterialStateProperty.resolveAs(decoration!.prefixStyle, materialState) ?? hintStyle,
child: decoration!.prefix, child: decoration!.prefix,
); );
...@@ -2270,21 +2331,20 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2270,21 +2331,20 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
_AffixText( _AffixText(
labelIsFloating: widget._labelShouldWithdraw, labelIsFloating: widget._labelShouldWithdraw,
text: decoration!.suffixText, text: decoration!.suffixText,
style: decoration!.suffixStyle ?? hintStyle, style: MaterialStateProperty.resolveAs(decoration!.suffixStyle, materialState) ?? hintStyle,
child: decoration!.suffix, child: decoration!.suffix,
); );
final Color activeColor = _getActiveColor(themeData);
final bool decorationIsDense = decoration!.isDense == true; // isDense == null, same as false final bool decorationIsDense = decoration!.isDense == true; // isDense == null, same as false
final double iconSize = decorationIsDense ? 18.0 : 24.0; final double iconSize = decorationIsDense ? 18.0 : 24.0;
final Color iconColor = isFocused ? activeColor : _getDefaultIconColor(themeData);
final Widget? icon = decoration!.icon == null ? null : final Widget? icon = decoration!.icon == null ? null :
Padding( Padding(
padding: const EdgeInsetsDirectional.only(end: 16.0), padding: const EdgeInsetsDirectional.only(end: 16.0),
child: IconTheme.merge( child: IconTheme.merge(
data: IconThemeData( data: IconThemeData(
color: iconColor, color: _getIconColor(themeData),
size: iconSize, size: iconSize,
), ),
child: decoration!.icon!, child: decoration!.icon!,
...@@ -2304,7 +2364,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2304,7 +2364,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
), ),
child: IconTheme.merge( child: IconTheme.merge(
data: IconThemeData( data: IconThemeData(
color: iconColor, color: _getPrefixIconColor(themeData),
size: iconSize, size: iconSize,
), ),
child: decoration!.prefixIcon!, child: decoration!.prefixIcon!,
...@@ -2325,7 +2385,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2325,7 +2385,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
), ),
child: IconTheme.merge( child: IconTheme.merge(
data: IconThemeData( data: IconThemeData(
color: iconColor, color: _getSuffixIconColor(themeData),
size: iconSize, size: iconSize,
), ),
child: decoration!.suffixIcon!, child: decoration!.suffixIcon!,
...@@ -2352,7 +2412,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2352,7 +2412,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
liveRegion: isFocused, liveRegion: isFocused,
child: Text( child: Text(
decoration!.counterText!, decoration!.counterText!,
style: _getHelperStyle(themeData).merge(decoration!.counterStyle), style: _getHelperStyle(themeData).merge(MaterialStateProperty.resolveAs(decoration!.counterStyle, materialState)),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
semanticsLabel: decoration!.semanticCounterText, semanticsLabel: decoration!.semanticCounterText,
), ),
...@@ -2371,7 +2431,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2371,7 +2431,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
contentPadding = decorationContentPadding ?? EdgeInsets.zero; contentPadding = decorationContentPadding ?? EdgeInsets.zero;
} else if (!border.isOutline) { } else if (!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 * inlineLabelStyle.fontSize!) * MediaQuery.textScaleFactorOf(context); floatingLabelHeight = (4.0 + 0.75 * labelStyle.fontSize!) * MediaQuery.textScaleFactorOf(context);
if (decoration!.filled == true) { // filled == null same as filled == false if (decoration!.filled == true) { // filled == null same as filled == false
contentPadding = decorationContentPadding ?? (decorationIsDense contentPadding = decorationContentPadding ?? (decorationIsDense
? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0) ? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0)
...@@ -2482,6 +2542,22 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -2482,6 +2542,22 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
/// ** See code in examples/api/lib/material/input_decorator/input_decoration.3.dart ** /// ** See code in examples/api/lib/material/input_decorator/input_decoration.3.dart **
/// {@end-tool} /// {@end-tool}
/// ///
/// {@tool dartpad --template=stateless_widget_scaffold}
/// This sample shows how to style a `TextField` with a prefixIcon that changes color
/// based on the `MaterialState`. The color defaults to gray, be blue while focused
/// and red if in an error state.
///
/// ** See code in examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart **
/// {@end-tool}
///
/// {@tool dartpad --template=stateless_widget_scaffold}
/// This sample shows how to style a `TextField` with a prefixIcon that changes color
/// based on the `MaterialState` through the use of `ThemeData`. The color defaults
/// to gray, be blue while focused and red if in an error state.
///
/// ** See code in examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart **
/// {@end-tool}
///
/// See also: /// See also:
/// ///
/// * [TextField], which is a text input widget that uses an /// * [TextField], which is a text input widget that uses an
...@@ -2507,6 +2583,7 @@ class InputDecoration { ...@@ -2507,6 +2583,7 @@ class InputDecoration {
/// Similarly, only one of [suffix] and [suffixText] can be specified. /// Similarly, only one of [suffix] and [suffixText] can be specified.
const InputDecoration({ const InputDecoration({
this.icon, this.icon,
this.iconColor,
this.label, this.label,
this.labelText, this.labelText,
this.labelStyle, this.labelStyle,
...@@ -2530,10 +2607,12 @@ class InputDecoration { ...@@ -2530,10 +2607,12 @@ class InputDecoration {
this.prefix, this.prefix,
this.prefixText, this.prefixText,
this.prefixStyle, this.prefixStyle,
this.prefixIconColor,
this.suffixIcon, this.suffixIcon,
this.suffix, this.suffix,
this.suffixText, this.suffixText,
this.suffixStyle, this.suffixStyle,
this.suffixIconColor,
this.suffixIconConstraints, this.suffixIconConstraints,
this.counter, this.counter,
this.counterText, this.counterText,
...@@ -2575,6 +2654,7 @@ class InputDecoration { ...@@ -2575,6 +2654,7 @@ class InputDecoration {
this.enabled = true, this.enabled = true,
}) : assert(enabled != null), }) : assert(enabled != null),
icon = null, icon = null,
iconColor = null,
label = null, label = null,
labelText = null, labelText = null,
labelStyle = null, labelStyle = null,
...@@ -2593,11 +2673,13 @@ class InputDecoration { ...@@ -2593,11 +2673,13 @@ class InputDecoration {
prefix = null, prefix = null,
prefixText = null, prefixText = null,
prefixStyle = null, prefixStyle = null,
prefixIconColor = null,
prefixIconConstraints = null, prefixIconConstraints = null,
suffix = null, suffix = null,
suffixIcon = null, suffixIcon = null,
suffixText = null, suffixText = null,
suffixStyle = null, suffixStyle = null,
suffixIconColor = null,
suffixIconConstraints = null, suffixIconConstraints = null,
counter = null, counter = null,
counterText = null, counterText = null,
...@@ -2628,6 +2710,13 @@ class InputDecoration { ...@@ -2628,6 +2710,13 @@ class InputDecoration {
/// See [Icon], [ImageIcon]. /// See [Icon], [ImageIcon].
final Widget? icon; final Widget? icon;
/// The color of the [icon].
///
/// If [iconColor] is a [MaterialStateColor], then the effective
/// color can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
final Color? iconColor;
/// Optional widget that describes the input field. /// Optional widget that describes the input field.
/// ///
/// {@template flutter.material.inputDecoration.label} /// {@template flutter.material.inputDecoration.label}
...@@ -2662,6 +2751,10 @@ class InputDecoration { ...@@ -2662,6 +2751,10 @@ class InputDecoration {
/// The style to use for the [labelText] when the label is on top of the /// The style to use for the [labelText] when the label is on top of the
/// input field. /// input field.
/// ///
/// If [labelStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// When the [labelText] is above (i.e., vertically adjacent to) the input /// When the [labelText] is above (i.e., vertically adjacent to) the input
/// field, the text uses the [floatingLabelStyle] instead. /// field, the text uses the [floatingLabelStyle] instead.
/// ///
...@@ -2672,6 +2765,10 @@ class InputDecoration { ...@@ -2672,6 +2765,10 @@ class InputDecoration {
/// The style to use for the [labelText] when the label is above (i.e., /// The style to use for the [labelText] when the label is above (i.e.,
/// vertically adjacent to) the input field. /// vertically adjacent to) the input field.
/// ///
/// If [floatingLabelStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to [labelStyle]. /// If null, defaults to [labelStyle].
final TextStyle? floatingLabelStyle; final TextStyle? floatingLabelStyle;
...@@ -2684,6 +2781,10 @@ class InputDecoration { ...@@ -2684,6 +2781,10 @@ class InputDecoration {
final String? helperText; final String? helperText;
/// The style to use for the [helperText]. /// The style to use for the [helperText].
///
/// If [helperStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
final TextStyle? helperStyle; final TextStyle? helperStyle;
/// The maximum number of lines the [helperText] can occupy. /// The maximum number of lines the [helperText] can occupy.
...@@ -2709,6 +2810,10 @@ class InputDecoration { ...@@ -2709,6 +2810,10 @@ class InputDecoration {
/// The style to use for the [hintText]. /// The style to use for the [hintText].
/// ///
/// If [hintStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// Also used for the [labelText] when the [labelText] is displayed on /// 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 /// top of the input field (i.e., at the same location on the screen where
/// text may be entered in the [InputDecorator.child]). /// text may be entered in the [InputDecorator.child]).
...@@ -2904,6 +3009,10 @@ class InputDecoration { ...@@ -2904,6 +3009,10 @@ class InputDecoration {
/// The style to use for the [prefixText]. /// The style to use for the [prefixText].
/// ///
/// If [prefixStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [hintStyle]. /// If null, defaults to the [hintStyle].
/// ///
/// See also: /// See also:
...@@ -2911,6 +3020,15 @@ class InputDecoration { ...@@ -2911,6 +3020,15 @@ class InputDecoration {
/// * [suffixStyle], the equivalent but on the trailing edge. /// * [suffixStyle], the equivalent but on the trailing edge.
final TextStyle? prefixStyle; final TextStyle? prefixStyle;
/// Optional color of the prefixIcon
///
/// Defaults to [iconColor]
///
/// If [prefixIconColor] is a [MaterialStateColor], then the effective
/// color can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
final Color? prefixIconColor;
/// An icon that appears after the editable part of the text field and /// An icon that appears after the editable part of the text field and
/// after the [suffix] or [suffixText], within the decoration's container. /// after the [suffix] or [suffixText], within the decoration's container.
/// ///
...@@ -2977,6 +3095,10 @@ class InputDecoration { ...@@ -2977,6 +3095,10 @@ class InputDecoration {
/// The style to use for the [suffixText]. /// The style to use for the [suffixText].
/// ///
/// If [suffixStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [hintStyle]. /// If null, defaults to the [hintStyle].
/// ///
/// See also: /// See also:
...@@ -2984,6 +3106,15 @@ class InputDecoration { ...@@ -2984,6 +3106,15 @@ class InputDecoration {
/// * [prefixStyle], the equivalent but on the leading edge. /// * [prefixStyle], the equivalent but on the leading edge.
final TextStyle? suffixStyle; final TextStyle? suffixStyle;
/// Optional color of the suffixIcon
///
/// Defaults to [iconColor]
///
/// If [suffixIconColor] is a [MaterialStateColor], then the effective
/// color can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
final Color? suffixIconColor;
/// The constraints for the suffix icon. /// The constraints for the suffix icon.
/// ///
/// This can be used to modify the [BoxConstraints] surrounding [suffixIcon]. /// This can be used to modify the [BoxConstraints] surrounding [suffixIcon].
...@@ -3028,6 +3159,10 @@ class InputDecoration { ...@@ -3028,6 +3159,10 @@ class InputDecoration {
/// The style to use for the [counterText]. /// The style to use for the [counterText].
/// ///
/// If [counterStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [helperStyle]. /// If null, defaults to the [helperStyle].
final TextStyle? counterStyle; final TextStyle? counterStyle;
...@@ -3204,11 +3339,15 @@ class InputDecoration { ...@@ -3204,11 +3339,15 @@ class InputDecoration {
/// The shape of the border to draw around the decoration's container. /// The shape of the border to draw around the decoration's container.
/// ///
/// This border's [InputBorder.borderSide], i.e. the border's color and width, /// If [border] is a [MaterialStateUnderlineInputBorder]
/// will be overridden to reflect the input decorator's state. Only the /// or [MaterialStateOutlineInputBorder], then the effective border can depend on
/// border's shape is used. If custom [BorderSide] values are desired for /// the [MaterialState.focused] state, i.e. if the [TextField] is focused or not.
/// a given state, all four borders – [errorBorder], [focusedBorder], ///
/// [enabledBorder], [disabledBorder] – must be set. /// If [border] derives from [InputBorder] the border's [InputBorder.borderSide],
/// i.e. the border's color and width, will be overridden to reflect the input
/// decorator's state. Only the border's shape is used. If custom [BorderSide]
/// values are desired for a given state, all four borders – [errorBorder],
/// [focusedBorder], [enabledBorder], [disabledBorder] – must be set.
/// ///
/// The decoration's container is the area which is filled if [filled] is /// The decoration's container is the area which is filled if [filled] is
/// true and bordered per the [border]. It's the area adjacent to /// true and bordered per the [border]. It's the area adjacent to
...@@ -3276,6 +3415,7 @@ class InputDecoration { ...@@ -3276,6 +3415,7 @@ class InputDecoration {
/// by the new values. /// by the new values.
InputDecoration copyWith({ InputDecoration copyWith({
Widget? icon, Widget? icon,
Color? iconColor,
Widget? label, Widget? label,
String? labelText, String? labelText,
TextStyle? labelStyle, TextStyle? labelStyle,
...@@ -3299,10 +3439,12 @@ class InputDecoration { ...@@ -3299,10 +3439,12 @@ class InputDecoration {
String? prefixText, String? prefixText,
BoxConstraints? prefixIconConstraints, BoxConstraints? prefixIconConstraints,
TextStyle? prefixStyle, TextStyle? prefixStyle,
Color? prefixIconColor,
Widget? suffixIcon, Widget? suffixIcon,
Widget? suffix, Widget? suffix,
String? suffixText, String? suffixText,
TextStyle? suffixStyle, TextStyle? suffixStyle,
Color? suffixIconColor,
BoxConstraints? suffixIconConstraints, BoxConstraints? suffixIconConstraints,
Widget? counter, Widget? counter,
String? counterText, String? counterText,
...@@ -3324,6 +3466,7 @@ class InputDecoration { ...@@ -3324,6 +3466,7 @@ class InputDecoration {
}) { }) {
return InputDecoration( return InputDecoration(
icon: icon ?? this.icon, icon: icon ?? this.icon,
iconColor: iconColor ?? this.iconColor,
label: label ?? this.label, label: label ?? this.label,
labelText: labelText ?? this.labelText, labelText: labelText ?? this.labelText,
labelStyle: labelStyle ?? this.labelStyle, labelStyle: labelStyle ?? this.labelStyle,
...@@ -3346,11 +3489,13 @@ class InputDecoration { ...@@ -3346,11 +3489,13 @@ class InputDecoration {
prefix: prefix ?? this.prefix, prefix: prefix ?? this.prefix,
prefixText: prefixText ?? this.prefixText, prefixText: prefixText ?? this.prefixText,
prefixStyle: prefixStyle ?? this.prefixStyle, prefixStyle: prefixStyle ?? this.prefixStyle,
prefixIconColor: prefixIconColor ?? this.prefixIconColor,
prefixIconConstraints: prefixIconConstraints ?? this.prefixIconConstraints, prefixIconConstraints: prefixIconConstraints ?? this.prefixIconConstraints,
suffixIcon: suffixIcon ?? this.suffixIcon, suffixIcon: suffixIcon ?? this.suffixIcon,
suffix: suffix ?? this.suffix, suffix: suffix ?? this.suffix,
suffixText: suffixText ?? this.suffixText, suffixText: suffixText ?? this.suffixText,
suffixStyle: suffixStyle ?? this.suffixStyle, suffixStyle: suffixStyle ?? this.suffixStyle,
suffixIconColor: suffixIconColor ?? this.suffixIconColor,
suffixIconConstraints: suffixIconConstraints ?? this.suffixIconConstraints, suffixIconConstraints: suffixIconConstraints ?? this.suffixIconConstraints,
counter: counter ?? this.counter, counter: counter ?? this.counter,
counterText: counterText ?? this.counterText, counterText: counterText ?? this.counterText,
...@@ -3416,6 +3561,7 @@ class InputDecoration { ...@@ -3416,6 +3561,7 @@ class InputDecoration {
return false; return false;
return other is InputDecoration return other is InputDecoration
&& other.icon == icon && other.icon == icon
&& other.iconColor == iconColor
&& other.label == label && other.label == label
&& other.labelText == labelText && other.labelText == labelText
&& other.labelStyle == labelStyle && other.labelStyle == labelStyle
...@@ -3435,11 +3581,13 @@ class InputDecoration { ...@@ -3435,11 +3581,13 @@ class InputDecoration {
&& other.contentPadding == contentPadding && other.contentPadding == contentPadding
&& other.isCollapsed == isCollapsed && other.isCollapsed == isCollapsed
&& other.prefixIcon == prefixIcon && other.prefixIcon == prefixIcon
&& other.prefixIconColor == prefixIconColor
&& other.prefix == prefix && other.prefix == prefix
&& other.prefixText == prefixText && other.prefixText == prefixText
&& other.prefixStyle == prefixStyle && other.prefixStyle == prefixStyle
&& other.prefixIconConstraints == prefixIconConstraints && other.prefixIconConstraints == prefixIconConstraints
&& other.suffixIcon == suffixIcon && other.suffixIcon == suffixIcon
&& other.suffixIconColor == suffixIconColor
&& other.suffix == suffix && other.suffix == suffix
&& other.suffixText == suffixText && other.suffixText == suffixText
&& other.suffixStyle == suffixStyle && other.suffixStyle == suffixStyle
...@@ -3467,6 +3615,7 @@ class InputDecoration { ...@@ -3467,6 +3615,7 @@ class InputDecoration {
int get hashCode { int get hashCode {
final List<Object?> values = <Object?>[ final List<Object?> values = <Object?>[
icon, icon,
iconColor,
label, label,
labelText, labelText,
floatingLabelStyle, floatingLabelStyle,
...@@ -3492,11 +3641,13 @@ class InputDecoration { ...@@ -3492,11 +3641,13 @@ class InputDecoration {
border, border,
enabled, enabled,
prefixIcon, prefixIcon,
prefixIconColor,
prefix, prefix,
prefixText, prefixText,
prefixStyle, prefixStyle,
prefixIconConstraints, prefixIconConstraints,
suffixIcon, suffixIcon,
suffixIconColor,
suffix, suffix,
suffixText, suffixText,
suffixStyle, suffixStyle,
...@@ -3522,6 +3673,7 @@ class InputDecoration { ...@@ -3522,6 +3673,7 @@ class InputDecoration {
String toString() { String toString() {
final List<String> description = <String>[ final List<String> description = <String>[
if (icon != null) 'icon: $icon', if (icon != null) 'icon: $icon',
if (iconColor != null) 'iconColor: $iconColor',
if (label != null) 'label: $label', if (label != null) 'label: $label',
if (labelText != null) 'labelText: "$labelText"', if (labelText != null) 'labelText: "$labelText"',
if (floatingLabelStyle != null) 'floatingLabelStyle: "$floatingLabelStyle"', if (floatingLabelStyle != null) 'floatingLabelStyle: "$floatingLabelStyle"',
...@@ -3537,11 +3689,13 @@ class InputDecoration { ...@@ -3537,11 +3689,13 @@ class InputDecoration {
if (contentPadding != null) 'contentPadding: $contentPadding', if (contentPadding != null) 'contentPadding: $contentPadding',
if (isCollapsed) 'isCollapsed: $isCollapsed', if (isCollapsed) 'isCollapsed: $isCollapsed',
if (prefixIcon != null) 'prefixIcon: $prefixIcon', if (prefixIcon != null) 'prefixIcon: $prefixIcon',
if (prefixIconColor != null) 'prefixIconColor: $prefixIconColor',
if (prefix != null) 'prefix: $prefix', if (prefix != null) 'prefix: $prefix',
if (prefixText != null) 'prefixText: $prefixText', if (prefixText != null) 'prefixText: $prefixText',
if (prefixStyle != null) 'prefixStyle: $prefixStyle', if (prefixStyle != null) 'prefixStyle: $prefixStyle',
if (prefixIconConstraints != null) 'prefixIconConstraints: $prefixIconConstraints', if (prefixIconConstraints != null) 'prefixIconConstraints: $prefixIconConstraints',
if (suffixIcon != null) 'suffixIcon: $suffixIcon', if (suffixIcon != null) 'suffixIcon: $suffixIcon',
if (suffixIconColor != null) 'suffixIconColor: $suffixIconColor',
if (suffix != null) 'suffix: $suffix', if (suffix != null) 'suffix: $suffix',
if (suffixText != null) 'suffixText: $suffixText', if (suffixText != null) 'suffixText: $suffixText',
if (suffixStyle != null) 'suffixStyle: $suffixStyle', if (suffixStyle != null) 'suffixStyle: $suffixStyle',
...@@ -3596,8 +3750,11 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3596,8 +3750,11 @@ class InputDecorationTheme with Diagnosticable {
this.isDense = false, this.isDense = false,
this.contentPadding, this.contentPadding,
this.isCollapsed = false, this.isCollapsed = false,
this.iconColor,
this.prefixStyle, this.prefixStyle,
this.prefixIconColor,
this.suffixStyle, this.suffixStyle,
this.suffixIconColor,
this.counterStyle, this.counterStyle,
this.filled = false, this.filled = false,
this.fillColor, this.fillColor,
...@@ -3622,6 +3779,10 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3622,6 +3779,10 @@ class InputDecorationTheme with Diagnosticable {
/// When the [InputDecoration.labelText] is floating above the input field, /// When the [InputDecoration.labelText] is floating above the input field,
/// the text uses the [floatingLabelStyle] instead. /// the text uses the [floatingLabelStyle] instead.
/// ///
/// If [labelStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to a value derived from the base [TextStyle] for the /// If null, defaults to a value derived from the base [TextStyle] for the
/// input field and the current [Theme]. /// input field and the current [Theme].
final TextStyle? labelStyle; final TextStyle? labelStyle;
...@@ -3632,10 +3793,18 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3632,10 +3793,18 @@ class InputDecorationTheme with Diagnosticable {
/// When the [InputDecoration.labelText] is on top of the input field, the /// When the [InputDecoration.labelText] is on top of the input field, the
/// text uses the [labelStyle] instead. /// text uses the [labelStyle] instead.
/// ///
/// If [floatingLabelStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to [labelStyle]. /// If null, defaults to [labelStyle].
final TextStyle? floatingLabelStyle; final TextStyle? floatingLabelStyle;
/// The style to use for [InputDecoration.helperText]. /// The style to use for [InputDecoration.helperText].
///
/// If [helperStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
final TextStyle? helperStyle; final TextStyle? helperStyle;
/// The maximum number of lines the [InputDecoration.helperText] can occupy. /// The maximum number of lines the [InputDecoration.helperText] can occupy.
...@@ -3653,6 +3822,10 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3653,6 +3822,10 @@ class InputDecorationTheme with Diagnosticable {
/// The style to use for the [InputDecoration.hintText]. /// The style to use for the [InputDecoration.hintText].
/// ///
/// If [hintStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// Also used for the [InputDecoration.labelText] when the /// Also used for the [InputDecoration.labelText] when the
/// [InputDecoration.labelText] is displayed on top of the input field (i.e., /// [InputDecoration.labelText] is displayed on top of the input field (i.e.,
/// at the same location on the screen where text may be entered in the input /// at the same location on the screen where text may be entered in the input
...@@ -3712,18 +3885,57 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3712,18 +3885,57 @@ class InputDecorationTheme with Diagnosticable {
/// [InputDecoration.errorText], or an [InputDecoration.icon]. /// [InputDecoration.errorText], or an [InputDecoration.icon].
final bool isCollapsed; final bool isCollapsed;
/// The Color to use for the [InputDecoration.icon].
///
/// If [iconColor] is a [MaterialStateColor], then the effective
/// color can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [ColorScheme.primary].
final Color? iconColor;
/// The style to use for the [InputDecoration.prefixText]. /// The style to use for the [InputDecoration.prefixText].
/// ///
/// If [prefixStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [hintStyle]. /// If null, defaults to the [hintStyle].
final TextStyle? prefixStyle; final TextStyle? prefixStyle;
/// The Color to use for the [InputDecoration.prefixIcon].
///
/// If [prefixIconColor] is a [MaterialStateColor], then the effective
/// color can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [ColorScheme.primary].
final Color? prefixIconColor;
/// The style to use for the [InputDecoration.suffixText]. /// The style to use for the [InputDecoration.suffixText].
/// ///
/// If [suffixStyle] is a [MaterialStateTextStyle], then the effective
/// color can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [hintStyle]. /// If null, defaults to the [hintStyle].
final TextStyle? suffixStyle; final TextStyle? suffixStyle;
/// The Color to use for the [InputDecoration.suffixIcon].
///
/// If [suffixIconColor] is a [MaterialStateColor], then the effective
/// color can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [ColorScheme.primary].
final Color? suffixIconColor;
/// The style to use for the [InputDecoration.counterText]. /// The style to use for the [InputDecoration.counterText].
/// ///
/// If [counterStyle] is a [MaterialStateTextStyle], then the effective
/// text style can depend on the [MaterialState.focused] state, i.e.
/// if the [TextField] is focused or not.
///
/// If null, defaults to the [helperStyle]. /// If null, defaults to the [helperStyle].
final TextStyle? counterStyle; final TextStyle? counterStyle;
...@@ -3890,6 +4102,10 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3890,6 +4102,10 @@ class InputDecorationTheme with Diagnosticable {
/// The shape of the border to draw around the decoration's container. /// The shape of the border to draw around the decoration's container.
/// ///
/// If [border] is a [MaterialStateUnderlineInputBorder]
/// or [MaterialStateOutlineInputBorder], then the effective border can depend on
/// the [MaterialState.focused] state, i.e. if the [TextField] is focused or not.
///
/// The decoration's container is the area which is filled if [filled] is /// The decoration's container is the area which is filled if [filled] is
/// true and bordered per the [border]. It's the area adjacent to /// true and bordered per the [border]. It's the area adjacent to
/// [InputDecoration.icon] and above the widgets that contain /// [InputDecoration.icon] and above the widgets that contain
...@@ -3954,8 +4170,11 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3954,8 +4170,11 @@ class InputDecorationTheme with Diagnosticable {
bool? isDense, bool? isDense,
EdgeInsetsGeometry? contentPadding, EdgeInsetsGeometry? contentPadding,
bool? isCollapsed, bool? isCollapsed,
Color? iconColor,
TextStyle? prefixStyle, TextStyle? prefixStyle,
Color? prefixIconColor,
TextStyle? suffixStyle, TextStyle? suffixStyle,
Color? suffixIconColor,
TextStyle? counterStyle, TextStyle? counterStyle,
bool? filled, bool? filled,
Color? fillColor, Color? fillColor,
...@@ -3981,9 +4200,12 @@ class InputDecorationTheme with Diagnosticable { ...@@ -3981,9 +4200,12 @@ class InputDecorationTheme with Diagnosticable {
floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior, floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior,
isDense: isDense ?? this.isDense, isDense: isDense ?? this.isDense,
contentPadding: contentPadding ?? this.contentPadding, contentPadding: contentPadding ?? this.contentPadding,
iconColor: iconColor,
isCollapsed: isCollapsed ?? this.isCollapsed, isCollapsed: isCollapsed ?? this.isCollapsed,
prefixStyle: prefixStyle ?? this.prefixStyle, prefixStyle: prefixStyle ?? this.prefixStyle,
prefixIconColor: prefixIconColor ?? this.prefixIconColor,
suffixStyle: suffixStyle ?? this.suffixStyle, suffixStyle: suffixStyle ?? this.suffixStyle,
suffixIconColor: suffixIconColor ?? this.suffixIconColor,
counterStyle: counterStyle ?? this.counterStyle, counterStyle: counterStyle ?? this.counterStyle,
filled: filled ?? this.filled, filled: filled ?? this.filled,
fillColor: fillColor ?? this.fillColor, fillColor: fillColor ?? this.fillColor,
...@@ -4014,8 +4236,11 @@ class InputDecorationTheme with Diagnosticable { ...@@ -4014,8 +4236,11 @@ class InputDecorationTheme with Diagnosticable {
isDense, isDense,
contentPadding, contentPadding,
isCollapsed, isCollapsed,
iconColor,
prefixStyle, prefixStyle,
prefixIconColor,
suffixStyle, suffixStyle,
suffixIconColor,
counterStyle, counterStyle,
filled, filled,
fillColor, fillColor,
...@@ -4033,7 +4258,7 @@ class InputDecorationTheme with Diagnosticable { ...@@ -4033,7 +4258,7 @@ class InputDecorationTheme with Diagnosticable {
} }
@override @override
bool operator==(Object other) { bool operator ==(Object other) {
if (identical(this, other)) if (identical(this, other))
return true; return true;
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType)
...@@ -4049,8 +4274,11 @@ class InputDecorationTheme with Diagnosticable { ...@@ -4049,8 +4274,11 @@ class InputDecorationTheme with Diagnosticable {
&& other.isDense == isDense && other.isDense == isDense
&& other.contentPadding == contentPadding && other.contentPadding == contentPadding
&& other.isCollapsed == isCollapsed && other.isCollapsed == isCollapsed
&& other.iconColor == iconColor
&& other.prefixStyle == prefixStyle && other.prefixStyle == prefixStyle
&& other.prefixIconColor == prefixIconColor
&& other.suffixStyle == suffixStyle && other.suffixStyle == suffixStyle
&& other.suffixIconColor == suffixIconColor
&& other.counterStyle == counterStyle && other.counterStyle == counterStyle
&& other.floatingLabelBehavior == floatingLabelBehavior && other.floatingLabelBehavior == floatingLabelBehavior
&& other.filled == filled && other.filled == filled
...@@ -4083,7 +4311,10 @@ class InputDecorationTheme with Diagnosticable { ...@@ -4083,7 +4311,10 @@ class InputDecorationTheme with Diagnosticable {
properties.add(DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense)); properties.add(DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: defaultTheme.contentPadding)); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: defaultTheme.contentPadding));
properties.add(DiagnosticsProperty<bool>('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed)); properties.add(DiagnosticsProperty<bool>('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed));
properties.add(DiagnosticsProperty<Color>('iconColor', iconColor, defaultValue: defaultTheme.iconColor));
properties.add(DiagnosticsProperty<Color>('prefixIconColor', prefixIconColor, defaultValue: defaultTheme.prefixIconColor));
properties.add(DiagnosticsProperty<TextStyle>('prefixStyle', prefixStyle, defaultValue: defaultTheme.prefixStyle)); properties.add(DiagnosticsProperty<TextStyle>('prefixStyle', prefixStyle, defaultValue: defaultTheme.prefixStyle));
properties.add(DiagnosticsProperty<Color>('suffixIconColor', suffixIconColor, defaultValue: defaultTheme.suffixIconColor));
properties.add(DiagnosticsProperty<TextStyle>('suffixStyle', suffixStyle, defaultValue: defaultTheme.suffixStyle)); properties.add(DiagnosticsProperty<TextStyle>('suffixStyle', suffixStyle, defaultValue: defaultTheme.suffixStyle));
properties.add(DiagnosticsProperty<TextStyle>('counterStyle', counterStyle, defaultValue: defaultTheme.counterStyle)); properties.add(DiagnosticsProperty<TextStyle>('counterStyle', counterStyle, defaultValue: defaultTheme.counterStyle));
properties.add(DiagnosticsProperty<bool>('filled', filled, defaultValue: defaultTheme.filled)); properties.add(DiagnosticsProperty<bool>('filled', filled, defaultValue: defaultTheme.filled));
......
...@@ -6,6 +6,8 @@ import 'package:flutter/foundation.dart'; ...@@ -6,6 +6,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'input_border.dart';
/// Interactive states that some of the Material widgets can take on when /// Interactive states that some of the Material widgets can take on when
/// receiving input from the user. /// receiving input from the user.
/// ///
...@@ -96,6 +98,9 @@ typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states); ...@@ -96,6 +98,9 @@ typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states);
/// [Set] of [MaterialState]s, like [MaterialState.pressed], /// [Set] of [MaterialState]s, like [MaterialState.pressed],
/// [MaterialState.focused] and [MaterialState.hovered]. /// [MaterialState.focused] and [MaterialState.hovered].
/// ///
/// [MaterialStateColor] should only be used with widgets that document
/// their support, like [TimePickerThemeData.dayPeriodColor].
///
/// To use a [MaterialStateColor], you can either: /// To use a [MaterialStateColor], you can either:
/// 1. Create a subclass of [MaterialStateColor] and implement the abstract `resolve` method. /// 1. Create a subclass of [MaterialStateColor] and implement the abstract `resolve` method.
/// 2. Use [MaterialStateColor.resolveWith] and pass in a callback that /// 2. Use [MaterialStateColor.resolveWith] and pass in a callback that
...@@ -110,11 +115,6 @@ typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states); ...@@ -110,11 +115,6 @@ typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states);
/// to provide a `defaultValue` to the super constructor, so that we can know /// to provide a `defaultValue` to the super constructor, so that we can know
/// at compile-time what its default color is. /// at compile-time what its default color is.
/// ///
/// This class enables existing widget implementations with [Color]
/// properties to be extended to also effectively support `MaterialStateProperty<Color>`
/// property values. [MaterialStateColor] should only be used with widgets that document
/// their support, like [TimePickerThemeData.dayPeriodColor].
///
/// {@tool snippet} /// {@tool snippet}
/// ///
/// This example defines a `MaterialStateColor` with a const constructor. /// This example defines a `MaterialStateColor` with a const constructor.
...@@ -391,6 +391,196 @@ abstract class MaterialStateOutlinedBorder extends OutlinedBorder implements Mat ...@@ -391,6 +391,196 @@ abstract class MaterialStateOutlinedBorder extends OutlinedBorder implements Mat
OutlinedBorder? resolve(Set<MaterialState> states); OutlinedBorder? resolve(Set<MaterialState> states);
} }
/// Defines a [TextStyle] that is also a [MaterialStateProperty].
///
/// This class exists to enable widgets with [TextStyle] valued properties
/// to also accept [MaterialStateProperty<TextStyle>] values. A material
/// state text style property represents a text style which depends on
/// a widget's "interactive state". This state is represented as a
/// [Set] of [MaterialState]s, like [MaterialState.pressed],
/// [MaterialState.focused] and [MaterialState.hovered].
///
/// [MaterialStateTextStyle] should only be used with widgets that document
/// their support, like [InputDecoration.labelStyle].
///
/// To use a [MaterialStateTextStyle], you can either:
/// 1. Create a subclass of [MaterialStateTextStyle] and implement the abstract `resolve` method.
/// 2. Use [MaterialStateTextStyle.resolveWith] and pass in a callback that
/// will be used to resolve the color in the given states.
///
/// If a [MaterialStateTextStyle] is used for a property or a parameter that doesn't
/// support resolving [MaterialStateProperty<TextStyle>]s, then its default color
/// value will be used for all states.
///
/// To define a `const` [MaterialStateTextStyle], you'll need to extend
/// [MaterialStateTextStyle] and override its [resolve] method. You'll also need
/// to provide a `defaultValue` to the super constructor, so that we can know
/// at compile-time what its default color is.
abstract class MaterialStateTextStyle extends TextStyle implements MaterialStateProperty<TextStyle> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const MaterialStateTextStyle();
/// Creates a [MaterialStateTextStyle] from a [MaterialPropertyResolver<TextStyle>]
/// callback function.
///
/// If used as a regular text style, the style resolved in the default state (the
/// empty set of states) will be used.
///
/// The given callback parameter must return a non-null text style in the default
/// state.
static MaterialStateTextStyle resolveWith(MaterialPropertyResolver<TextStyle> callback) =>
_MaterialStateTextStyle(callback);
/// Returns a [TextStyle] that's to be used when a Material component is in the
/// specified state.
@override
TextStyle resolve(Set<MaterialState> states);
}
/// A [MaterialStateTextStyle] created from a [MaterialPropertyResolver<TextStyle>]
/// callback alone.
///
/// If used as a regular text style, the style resolved in the default state will
/// be used.
///
/// Used by [MaterialStateTextStyle.resolveWith].
class _MaterialStateTextStyle extends MaterialStateTextStyle {
const _MaterialStateTextStyle(this._resolve);
final MaterialPropertyResolver<TextStyle> _resolve;
@override
TextStyle resolve(Set<MaterialState> states) => _resolve(states);
}
/// Defines a [OutlineInputBorder] that is also a [MaterialStateProperty].
///
/// This class exists to enable widgets with [OutlineInputBorder] valued properties
/// to also accept [MaterialStateProperty<OutlineInputBorder>] values. A material
/// state input border property represents a text style which depends on
/// a widget's "interactive state". This state is represented as a
/// [Set] of [MaterialState]s, like [MaterialState.pressed],
/// [MaterialState.focused] and [MaterialState.hovered].
///
/// [MaterialStateOutlineInputBorder] should only be used with widgets that document
/// their support, like [InputDecoration.border].
///
/// To use a [MaterialStateOutlineInputBorder], you can either:
/// 1. Create a subclass of [MaterialStateOutlineInputBorder] and implement the abstract `resolve` method.
/// 2. Use [MaterialStateOutlineInputBorder.resolveWith] and pass in a callback that
/// will be used to resolve the color in the given states.
///
/// If a [MaterialStateOutlineInputBorder] is used for a property or a parameter that doesn't
/// support resolving [MaterialStateProperty<OutlineInputBorder>]s, then its default color
/// value will be used for all states.
///
/// To define a `const` [MaterialStateOutlineInputBorder], you'll need to extend
/// [MaterialStateOutlineInputBorder] and override its [resolve] method. You'll also need
/// to provide a `defaultValue` to the super constructor, so that we can know
/// at compile-time what its default color is.
abstract class MaterialStateOutlineInputBorder extends OutlineInputBorder implements MaterialStateProperty<InputBorder> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const MaterialStateOutlineInputBorder();
/// Creates a [MaterialStateOutlineInputBorder] from a [MaterialPropertyResolver<InputBorder>]
/// callback function.
///
/// If used as a regular input border, the border resolved in the default state (the
/// empty set of states) will be used.
///
/// The given callback parameter must return a non-null text style in the default
/// state.
static MaterialStateOutlineInputBorder resolveWith(MaterialPropertyResolver<InputBorder> callback) =>
_MaterialStateOutlineInputBorder(callback);
/// Returns a [InputBorder] that's to be used when a Material component is in the
/// specified state.
@override
InputBorder resolve(Set<MaterialState> states);
}
/// A [MaterialStateOutlineInputBorder] created from a [MaterialPropertyResolver<OutlineInputBorder>]
/// callback alone.
///
/// If used as a regular input border, the border resolved in the default state will
/// be used.
///
/// Used by [MaterialStateTextStyle.resolveWith].
class _MaterialStateOutlineInputBorder extends MaterialStateOutlineInputBorder {
const _MaterialStateOutlineInputBorder(this._resolve);
final MaterialPropertyResolver<InputBorder> _resolve;
@override
InputBorder resolve(Set<MaterialState> states) => _resolve(states);
}
/// Defines a [UnderlineInputBorder] that is also a [MaterialStateProperty].
///
/// This class exists to enable widgets with [UnderlineInputBorder] valued properties
/// to also accept [MaterialStateProperty<UnderlineInputBorder>] values. A material
/// state input border property represents a text style which depends on
/// a widget's "interactive state". This state is represented as a
/// [Set] of [MaterialState]s, like [MaterialState.pressed],
/// [MaterialState.focused] and [MaterialState.hovered].
///
/// [MaterialStateUnderlineInputBorder] should only be used with widgets that document
/// their support, like [InputDecoration.border].
///
/// To use a [MaterialStateUnderlineInputBorder], you can either:
/// 1. Create a subclass of [MaterialStateUnderlineInputBorder] and implement the abstract `resolve` method.
/// 2. Use [MaterialStateUnderlineInputBorder.resolveWith] and pass in a callback that
/// will be used to resolve the color in the given states.
///
/// If a [MaterialStateUnderlineInputBorder] is used for a property or a parameter that doesn't
/// support resolving [MaterialStateProperty<UnderlineInputBorder>]s, then its default color
/// value will be used for all states.
///
/// To define a `const` [MaterialStateUnderlineInputBorder], you'll need to extend
/// [MaterialStateUnderlineInputBorder] and override its [resolve] method. You'll also need
/// to provide a `defaultValue` to the super constructor, so that we can know
/// at compile-time what its default color is.
abstract class MaterialStateUnderlineInputBorder extends UnderlineInputBorder implements MaterialStateProperty<InputBorder> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const MaterialStateUnderlineInputBorder();
/// Creates a [MaterialStateUnderlineInputBorder] from a [MaterialPropertyResolver<InputBorder>]
/// callback function.
///
/// If used as a regular ionput bortder, the border resolved in the default state (the
/// empty set of states) will be used.
///
/// The given callback parameter must return a non-null text style in the default
/// state.
static MaterialStateUnderlineInputBorder resolveWith(MaterialPropertyResolver<InputBorder> callback) =>
_MaterialStateUnderlineInputBorder(callback);
/// Returns a [InputBorder] that's to be used when a Material component is in the
/// specified state.
@override
InputBorder resolve(Set<MaterialState> states);
}
/// A [MaterialStateUnderlineInputBorder] created from a [MaterialPropertyResolver<UnderlineInputBorder>]
/// callback alone.
///
/// If used as a regular input border, the border resolved in the default state will
/// be used.
///
/// Used by [MaterialStateTextStyle.resolveWith].
class _MaterialStateUnderlineInputBorder extends MaterialStateUnderlineInputBorder {
const _MaterialStateUnderlineInputBorder(this._resolve);
final MaterialPropertyResolver<InputBorder> _resolve;
@override
InputBorder resolve(Set<MaterialState> states) => _resolve(states);
}
/// Interface for classes that [resolve] to a value of type `T` based /// Interface for classes that [resolve] to a value of type `T` based
/// on a widget's interactive "state", which is defined as a set /// on a widget's interactive "state", which is defined as a set
/// of [MaterialState]s. /// of [MaterialState]s.
......
...@@ -3642,6 +3642,110 @@ void main() { ...@@ -3642,6 +3642,110 @@ void main() {
expect(decoration.constraints, const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40)); expect(decoration.constraints, const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40));
}); });
testWidgets('InputDecorationTheme.inputDecoration with MaterialState', (WidgetTester tester) async {
final MaterialStateTextStyle themeStyle = MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
return const TextStyle(color: Colors.green);
});
final MaterialStateTextStyle decorationStyle = MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
return const TextStyle(color: Colors.blue);
});
// InputDecorationTheme arguments define InputDecoration properties.
InputDecoration decoration = const InputDecoration().applyDefaults(
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,
focusColor: Colors.blue,
border: InputBorder.none,
alignLabelWithHint: true,
constraints: const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40),
),
);
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);
expect(decoration.alignLabelWithHint, true);
expect(decoration.constraints, const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40));
// InputDecoration (baseDecoration) defines InputDecoration properties
final MaterialStateOutlineInputBorder border = MaterialStateOutlineInputBorder.resolveWith((Set<MaterialState> states) {
return const OutlineInputBorder();
});
decoration = 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: border,
alignLabelWithHint: false,
constraints: const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40),
).applyDefaults(
InputDecorationTheme(
labelStyle: themeStyle,
helperStyle: themeStyle,
helperMaxLines: 5,
hintStyle: themeStyle,
errorStyle: themeStyle,
errorMaxLines: 4,
isDense: true,
contentPadding: const EdgeInsets.all(1.0),
prefixStyle: themeStyle,
suffixStyle: themeStyle,
counterStyle: themeStyle,
filled: true,
fillColor: Colors.red,
focusColor: Colors.blue,
border: InputBorder.none,
alignLabelWithHint: true,
constraints: const BoxConstraints(minWidth: 40, maxWidth: 30, minHeight: 20, maxHeight: 10),
),
);
expect(decoration.labelStyle, decorationStyle);
expect(decoration.helperStyle, decorationStyle);
expect(decoration.helperMaxLines, 5);
expect(decoration.hintStyle, decorationStyle);
expect(decoration.errorStyle, decorationStyle);
expect(decoration.errorMaxLines, 4);
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, isA<MaterialStateOutlineInputBorder>());
expect(decoration.alignLabelWithHint, false);
expect(decoration.constraints, const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40));
});
testWidgets('InputDecorator OutlineInputBorder fillColor is clipped by border', (WidgetTester tester) async { testWidgets('InputDecorator OutlineInputBorder fillColor is clipped by border', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/15742 // This is a regression test for https://github.com/flutter/flutter/issues/15742
......
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