Unverified Commit 8e978076 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Support for disabling TextField, TextFormField (#16027)

parent 89570732
...@@ -138,8 +138,10 @@ class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> { ...@@ -138,8 +138,10 @@ class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
children: <Widget>[ children: <Widget>[
new TextField( new TextField(
enabled: true,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Event name', labelText: 'Event name',
border: const OutlineInputBorder(),
), ),
style: Theme.of(context).textTheme.display1, style: Theme.of(context).textTheme.display1,
), ),
......
...@@ -249,17 +249,21 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> { ...@@ -249,17 +249,21 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
fieldKey: _passwordFieldKey, fieldKey: _passwordFieldKey,
helperText: 'No more than 8 characters.', helperText: 'No more than 8 characters.',
labelText: 'Password *', labelText: 'Password *',
onSaved: (String value) { person.password = value; }, onFieldSubmitted: (String value) {
setState(() {
person.password = value;
});
},
), ),
const SizedBox(height: 24.0), const SizedBox(height: 24.0),
new TextFormField( new TextFormField(
enabled: person.password != null && person.password.isNotEmpty,
decoration: const InputDecoration( decoration: const InputDecoration(
border: const UnderlineInputBorder(), border: const UnderlineInputBorder(),
filled: true, filled: true,
labelText: 'Re-type password', labelText: 'Re-type password',
), ),
maxLength: 8, maxLength: 8,
onFieldSubmitted: (String value) { person.password = value; },
obscureText: true, obscureText: true,
validator: _validatePassword, validator: _validatePassword,
), ),
......
...@@ -1459,6 +1459,9 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1459,6 +1459,9 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
} }
Color _getDefaultIconColor(ThemeData themeData) { Color _getDefaultIconColor(ThemeData themeData) {
if (!decoration.enabled)
return themeData.disabledColor;
switch (themeData.brightness) { switch (themeData.brightness) {
case Brightness.dark: case Brightness.dark:
return Colors.white70; return Colors.white70;
...@@ -1478,7 +1481,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1478,7 +1481,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
// 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 _getInlineStyle(ThemeData themeData) {
return themeData.textTheme.subhead.merge(widget.baseStyle) return themeData.textTheme.subhead.merge(widget.baseStyle)
.copyWith(color: themeData.hintColor); .copyWith(color: decoration.enabled ? themeData.hintColor : themeData.disabledColor);
} }
TextStyle _getFloatingLabelStyle(ThemeData themeData) { TextStyle _getFloatingLabelStyle(ThemeData themeData) {
...@@ -1487,16 +1490,18 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1487,16 +1490,18 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
: _getActiveColor(themeData); : _getActiveColor(themeData);
final TextStyle style = themeData.textTheme.subhead.merge(widget.baseStyle); final TextStyle style = themeData.textTheme.subhead.merge(widget.baseStyle);
return style return style
.copyWith(color: color) .copyWith(color: decoration.enabled ? color : themeData.disabledColor)
.merge(decoration.labelStyle); .merge(decoration.labelStyle);
} }
TextStyle _getHelperStyle(ThemeData themeData) { TextStyle _getHelperStyle(ThemeData themeData) {
return themeData.textTheme.caption.copyWith(color: themeData.hintColor).merge(decoration.helperStyle); final Color color = decoration.enabled ? themeData.hintColor : Colors.transparent;
return themeData.textTheme.caption.copyWith(color: color).merge(decoration.helperStyle);
} }
TextStyle _getErrorStyle(ThemeData themeData) { TextStyle _getErrorStyle(ThemeData themeData) {
return themeData.textTheme.caption.copyWith(color: themeData.errorColor).merge(decoration.errorStyle); final Color color = decoration.enabled ? themeData.errorColor : Colors.transparent;
return themeData.textTheme.caption.copyWith(color: color).merge(decoration.errorStyle);
} }
double get _borderWeight { double get _borderWeight {
...@@ -1506,6 +1511,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1506,6 +1511,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
} }
Color _getBorderColor(ThemeData themeData) { Color _getBorderColor(ThemeData themeData) {
if (!decoration.enabled) {
if (decoration.filled == true && !decoration.border.isOutline)
return Colors.transparent;
return themeData.disabledColor;
}
return decoration.errorText == null return decoration.errorText == null
? _getActiveColor(themeData) ? _getActiveColor(themeData)
: themeData.errorColor; : themeData.errorColor;
......
...@@ -111,6 +111,7 @@ class TextField extends StatefulWidget { ...@@ -111,6 +111,7 @@ class TextField extends StatefulWidget {
this.onChanged, this.onChanged,
this.onSubmitted, this.onSubmitted,
this.inputFormatters, this.inputFormatters,
this.enabled,
}) : assert(keyboardType != null), }) : assert(keyboardType != null),
assert(textAlign != null), assert(textAlign != null),
assert(autofocus != null), assert(autofocus != null),
...@@ -137,7 +138,7 @@ class TextField extends StatefulWidget { ...@@ -137,7 +138,7 @@ class TextField extends StatefulWidget {
/// By default, draws a horizontal line under the text field but can be /// By default, draws a horizontal line under the text field but can be
/// configured to show an icon, label, hint text, and error text. /// configured to show an icon, label, hint text, and error text.
/// ///
/// Set this field to null to remove the decoration entirely (including the /// Specify null to remove the decoration entirely (including the
/// extra padding introduced by the decoration to save space for the labels). /// extra padding introduced by the decoration to save space for the labels).
final InputDecoration decoration; final InputDecoration decoration;
...@@ -261,6 +262,13 @@ class TextField extends StatefulWidget { ...@@ -261,6 +262,13 @@ class TextField extends StatefulWidget {
/// Formatters are run in the provided order when the text input changes. /// Formatters are run in the provided order when the text input changes.
final List<TextInputFormatter> inputFormatters; final List<TextInputFormatter> inputFormatters;
/// If false the textfield is "disabled": it ignores taps and its
/// [decoration] is rendered in grey.
///
/// If non-null this property overrides the [decoration]'s
/// [Decoration.enabled] property.
final bool enabled;
@override @override
_TextFieldState createState() => new _TextFieldState(); _TextFieldState createState() => new _TextFieldState();
...@@ -299,7 +307,10 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -299,7 +307,10 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
InputDecoration _getEffectiveDecoration() { InputDecoration _getEffectiveDecoration() {
final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration()) final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration())
.applyDefaults(Theme.of(context).inputDecorationTheme); .applyDefaults(Theme.of(context).inputDecorationTheme)
.copyWith(
enabled: widget.enabled,
);
if (!needsCounter) if (!needsCounter)
return effectiveDecoration; return effectiveDecoration;
...@@ -495,14 +506,17 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -495,14 +506,17 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
_controller.selection = new TextSelection.collapsed(offset: _controller.text.length); _controller.selection = new TextSelection.collapsed(offset: _controller.text.length);
_requestKeyboard(); _requestKeyboard();
}, },
child: new GestureDetector( child: new IgnorePointer(
behavior: HitTestBehavior.translucent, ignoring: !(widget.enabled ?? widget.decoration?.enabled ?? true),
onTapDown: _handleTapDown, child: new GestureDetector(
onTap: _handleTap, behavior: HitTestBehavior.translucent,
onTapCancel: _handleTapCancel, onTapDown: _handleTapDown,
onLongPress: _handleLongPress, onTap: _handleTap,
excludeFromSemantics: true, onTapCancel: _handleTapCancel,
child: child, onLongPress: _handleLongPress,
excludeFromSemantics: true,
child: child,
),
), ),
); );
} }
......
...@@ -67,6 +67,7 @@ class TextFormField extends FormField<String> { ...@@ -67,6 +67,7 @@ class TextFormField extends FormField<String> {
FormFieldSetter<String> onSaved, FormFieldSetter<String> onSaved,
FormFieldValidator<String> validator, FormFieldValidator<String> validator,
List<TextInputFormatter> inputFormatters, List<TextInputFormatter> inputFormatters,
bool enabled,
}) : assert(initialValue == null || controller == null), }) : assert(initialValue == null || controller == null),
assert(keyboardType != null), assert(keyboardType != null),
assert(textAlign != null), assert(textAlign != null),
...@@ -101,6 +102,7 @@ class TextFormField extends FormField<String> { ...@@ -101,6 +102,7 @@ class TextFormField extends FormField<String> {
onChanged: field.didChange, onChanged: field.didChange,
onSubmitted: onFieldSubmitted, onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
enabled: enabled,
); );
}, },
); );
......
...@@ -60,17 +60,21 @@ double getBorderBottom(WidgetTester tester) { ...@@ -60,17 +60,21 @@ double getBorderBottom(WidgetTester tester) {
return box.size.height; return box.size.height;
} }
double getBorderWeight(WidgetTester tester) { BorderSide getBorderSide(WidgetTester tester) {
if (!tester.any(findBorderPainter())) if (!tester.any(findBorderPainter()))
return 0.0; return null;
final CustomPaint customPaint = tester.widget(findBorderPainter()); final CustomPaint customPaint = tester.widget(findBorderPainter());
final dynamic/* _InputBorderPainter */ inputBorderPainter = customPaint.foregroundPainter; final dynamic/* _InputBorderPainter */ inputBorderPainter = customPaint.foregroundPainter;
final dynamic/*_InputBorderTween */ inputBorderTween = inputBorderPainter.border; final dynamic/*_InputBorderTween */ inputBorderTween = inputBorderPainter.border;
final Animation<double> animation = inputBorderPainter.borderAnimation; final Animation<double> animation = inputBorderPainter.borderAnimation;
final dynamic/*_InputBorder */ border = inputBorderTween.evaluate(animation); final dynamic/*_InputBorder */ border = inputBorderTween.evaluate(animation);
return border.borderSide.width; return border.borderSide;
} }
double getBorderWeight(WidgetTester tester) => getBorderSide(tester)?.width;
Color getBorderColor(WidgetTester tester) => getBorderSide(tester)?.color;
double getHintOpacity(WidgetTester tester) { double getHintOpacity(WidgetTester tester) {
final Opacity opacityWidget = tester.widget<Opacity>( final Opacity opacityWidget = tester.widget<Opacity>(
find.ancestor( find.ancestor(
...@@ -190,7 +194,8 @@ void main() { ...@@ -190,7 +194,8 @@ void main() {
expect(getBorderBottom(tester), 56.0); expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 2.0); expect(getBorderWeight(tester), 2.0);
// enabled: false causes the border to disappear // enabled: false produces a hairline border if filled: false (the default)
// The widget's size and layout is the same as for enabled: true.
await tester.pumpWidget( await tester.pumpWidget(
buildInputDecorator( buildInputDecorator(
isEmpty: true, isEmpty: true,
...@@ -208,6 +213,27 @@ void main() { ...@@ -208,6 +213,27 @@ void main() {
expect(tester.getTopLeft(find.text('label')).dy, 20.0); expect(tester.getTopLeft(find.text('label')).dy, 20.0);
expect(tester.getBottomLeft(find.text('label')).dy, 36.0); expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
expect(getBorderWeight(tester), 0.0); expect(getBorderWeight(tester), 0.0);
// enabled: false produces a transparent border if filled: true.
// The widget's size and layout is the same as for enabled: true.
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
isFocused: false,
decoration: const InputDecoration(
labelText: 'label',
enabled: false,
filled: true,
),
),
);
await tester.pumpAndSettle();
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopLeft(find.text('text')).dy, 28.0);
expect(tester.getBottomLeft(find.text('text')).dy, 44.0);
expect(tester.getTopLeft(find.text('label')).dy, 20.0);
expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
expect(getBorderColor(tester), Colors.transparent);
}); });
// Overall height for this InputDecorator is 40.0dps // Overall height for this InputDecorator is 40.0dps
......
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