Unverified Commit 8ee26efb authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Wire in focusNode, focusColor, autofocus, and dropdownColor to DropdownButtonFormField (#54706)

parent 45c52508
......@@ -799,6 +799,8 @@ class DropdownButton<T> extends StatefulWidget {
/// defaults, so do not need to be specified). The boolean [isDense] and
/// [isExpanded] arguments must not be null.
///
/// The [autofocus] argument must not be null.
///
/// The [dropdownColor] argument specifies the background color of the
/// dropdown when it is open. If it is null, the current theme's
/// [ThemeData.canvasColor] will be used instead.
......@@ -825,6 +827,8 @@ class DropdownButton<T> extends StatefulWidget {
this.focusNode,
this.autofocus = false,
this.dropdownColor,
// When adding new arguments, consider adding similar arguments to
// DropdownButtonFormField.
}) : assert(items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
......@@ -1426,25 +1430,26 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
}
}
/// A convenience widget that wraps a [DropdownButton] in a [FormField].
/// A convenience widget that makes a [DropdownButton] into a [FormField].
class DropdownButtonFormField<T> extends FormField<T> {
/// Creates a [DropdownButton] widget wrapped in an [InputDecorator] and
/// [FormField].
/// Creates a [DropdownButton] widget that is a [FormField], wrapped in an
/// [InputDecorator].
///
/// For a description of the `onSaved`, `validator`, or `autovalidate`
/// parameters, see [FormField]. For the rest (other than [decoration]), see
/// [DropdownButton].
///
/// The [DropdownButton] [items] parameters must not be null.
/// The `items`, `elevation`, `iconSize`, `isDense`, `isExpanded`,
/// `autofocus`, and `decoration` parameters must not be null.
DropdownButtonFormField({
Key key,
T value,
@required List<DropdownMenuItem<T>> items,
DropdownButtonBuilder selectedItemBuilder,
T value,
Widget hint,
Widget disabledHint,
@required this.onChanged,
VoidCallback onTap,
this.decoration = const InputDecoration(),
FormFieldSetter<T> onSaved,
FormFieldValidator<T> validator,
bool autovalidate = false,
Widget disabledHint,
int elevation = 8,
TextStyle style,
Widget icon,
......@@ -1454,6 +1459,14 @@ class DropdownButtonFormField<T> extends FormField<T> {
bool isDense = true,
bool isExpanded = false,
double itemHeight,
Color focusColor,
FocusNode focusNode,
bool autofocus = false,
Color dropdownColor,
InputDecoration decoration,
FormFieldSetter<T> onSaved,
FormFieldValidator<T> validator,
bool autovalidate = false,
}) : assert(items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
......@@ -1463,12 +1476,13 @@ class DropdownButtonFormField<T> extends FormField<T> {
'Either zero or 2 or more [DropdownMenuItem]s were detected '
'with the same value',
),
assert(decoration != null),
assert(elevation != null),
assert(iconSize != null),
assert(isDense != null),
assert(isExpanded != null),
assert(itemHeight == null || itemHeight > 0),
assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
assert(autofocus != null),
decoration = decoration ?? InputDecoration(focusColor: focusColor),
super(
key: key,
onSaved: onSaved,
......@@ -1477,32 +1491,46 @@ class DropdownButtonFormField<T> extends FormField<T> {
autovalidate: autovalidate,
builder: (FormFieldState<T> field) {
final _DropdownButtonFormFieldState<T> state = field as _DropdownButtonFormFieldState<T>;
final InputDecoration effectiveDecoration = decoration.applyDefaults(
final InputDecoration decorationArg = decoration ?? InputDecoration(focusColor: focusColor);
final InputDecoration effectiveDecoration = decorationArg.applyDefaults(
Theme.of(field.context).inputDecorationTheme,
);
return InputDecorator(
decoration: effectiveDecoration.copyWith(errorText: field.errorText),
isEmpty: state.value == null,
child: DropdownButtonHideUnderline(
child: DropdownButton<T>(
value: state.value,
items: items,
selectedItemBuilder: selectedItemBuilder,
hint: hint,
onChanged: onChanged == null ? null : state.didChange,
onTap: onTap,
disabledHint: disabledHint,
elevation: elevation,
style: style,
icon: icon,
iconDisabledColor: iconDisabledColor,
iconEnabledColor: iconEnabledColor,
iconSize: iconSize,
isDense: isDense,
isExpanded: isExpanded,
itemHeight: itemHeight,
),
),
// An unfocusable Focus widget so that this widget can detect if its
// descendants have focus or not.
return Focus(
canRequestFocus: false,
skipTraversal: true,
child: Builder(builder: (BuildContext context) {
return InputDecorator(
decoration: effectiveDecoration.copyWith(errorText: field.errorText),
isEmpty: state.value == null,
isFocused: Focus.of(context).hasFocus,
child: DropdownButtonHideUnderline(
child: DropdownButton<T>(
items: items,
selectedItemBuilder: selectedItemBuilder,
value: state.value,
hint: hint,
disabledHint: disabledHint,
onChanged: onChanged == null ? null : state.didChange,
onTap: onTap,
elevation: elevation,
style: style,
icon: icon,
iconDisabledColor: iconDisabledColor,
iconEnabledColor: iconEnabledColor,
iconSize: iconSize,
isDense: isDense,
isExpanded: isExpanded,
itemHeight: itemHeight,
focusColor: focusColor,
focusNode: focusNode,
autofocus: autofocus,
dropdownColor: dropdownColor,
),
),
);
}),
);
},
);
......@@ -1512,11 +1540,11 @@ class DropdownButtonFormField<T> extends FormField<T> {
/// The decoration to show around the dropdown button form field.
///
/// By default, draws a horizontal line under the dropdown button field but can be
/// configured to show an icon, label, hint text, and error text.
/// By default, draws a horizontal line under the dropdown button field but
/// can be configured to show an icon, label, hint text, and error text.
///
/// Specify null to remove the decoration entirely (including the
/// extra padding introduced by the decoration to save space for the labels).
/// If not specified, an [InputDecorator] with the `focusColor` set to the
/// supplied `focusColor` (if any) will be used.
final InputDecoration decoration;
@override
......
......@@ -30,6 +30,92 @@ Finder _iconRichText(Key iconKey) {
);
}
Widget buildDropdown({
bool isFormField,
Key buttonKey,
String value = 'two',
ValueChanged<String> onChanged,
VoidCallback onTap,
Widget icon,
Color iconDisabledColor,
Color iconEnabledColor,
double iconSize = 24.0,
bool isDense = false,
bool isExpanded = false,
Widget hint,
Widget disabledHint,
Widget underline,
List<String> items = menuItems,
List<Widget> Function(BuildContext) selectedItemBuilder,
double itemHeight = kMinInteractiveDimension,
Alignment alignment = Alignment.center,
TextDirection textDirection = TextDirection.ltr,
Size mediaSize,
FocusNode focusNode,
bool autofocus = false,
Color focusColor,
Color dropdownColor,
}) {
final List<DropdownMenuItem<String>> listItems = items == null
? null
: items.map<DropdownMenuItem<String>>((String item) {
return DropdownMenuItem<String>(
key: ValueKey<String>(item),
value: item,
child: Text(item, key: ValueKey<String>(item + 'Text')),
);
}).toList();
if (isFormField) {
return Form(
child: DropdownButtonFormField<String>(
key: buttonKey,
value: value,
hint: hint,
disabledHint: disabledHint,
onChanged: onChanged,
onTap: onTap,
icon: icon,
iconSize: iconSize,
iconDisabledColor: iconDisabledColor,
iconEnabledColor: iconEnabledColor,
isDense: isDense,
isExpanded: isExpanded,
// No underline attribute
focusNode: focusNode,
autofocus: autofocus,
focusColor: focusColor,
dropdownColor: dropdownColor,
items: listItems,
selectedItemBuilder: selectedItemBuilder,
itemHeight: itemHeight,
),
);
}
return DropdownButton<String>(
key: buttonKey,
value: value,
hint: hint,
disabledHint: disabledHint,
onChanged: onChanged,
onTap: onTap,
icon: icon,
iconSize: iconSize,
iconDisabledColor: iconDisabledColor,
iconEnabledColor: iconEnabledColor,
isDense: isDense,
isExpanded: isExpanded,
underline: underline,
focusNode: focusNode,
autofocus: autofocus,
focusColor: focusColor,
dropdownColor: dropdownColor,
items: listItems,
selectedItemBuilder: selectedItemBuilder,
itemHeight: itemHeight,
);
}
Widget buildFrame({
Key buttonKey,
String value = 'two',
......@@ -54,6 +140,7 @@ Widget buildFrame({
bool autofocus = false,
Color focusColor,
Color dropdownColor,
bool isFormField = false,
}) {
return TestApp(
textDirection: textDirection,
......@@ -62,8 +149,9 @@ Widget buildFrame({
child: Align(
alignment: alignment,
child: RepaintBoundary(
child: DropdownButton<String>(
key: buttonKey,
child: buildDropdown(
isFormField: isFormField,
buttonKey: buttonKey,
value: value,
hint: hint,
disabledHint: disabledHint,
......@@ -80,16 +168,9 @@ Widget buildFrame({
autofocus: autofocus,
focusColor: focusColor,
dropdownColor: dropdownColor,
items: items == null ? null : items.map<DropdownMenuItem<String>>((String item) {
return DropdownMenuItem<String>(
key: ValueKey<String>(item),
value: item,
child: Text(item, key: ValueKey<String>(item + 'Text')),
);
}).toList(),
items: items,
selectedItemBuilder: selectedItemBuilder,
itemHeight: itemHeight,
),
itemHeight: itemHeight,),
),
),
),
......@@ -176,22 +257,36 @@ void verifyPaintedShadow(Finder customPaint, int elevation) {
);
}
Future<void> checkDropdownColor(WidgetTester tester, {Color color}) async {
Future<void> checkDropdownColor(WidgetTester tester, {Color color, bool isFormField = false }) async {
const String text = 'foo';
await tester.pumpWidget(
MaterialApp(
home: Material(
child: DropdownButton<String>(
dropdownColor: color,
value: text,
items: const <DropdownMenuItem<String>>[
DropdownMenuItem<String>(
value: text,
child: Text(text),
),
],
onChanged: (_) { },
),
child: isFormField
? Form(
child: DropdownButtonFormField<String>(
dropdownColor: color,
value: text,
items: const <DropdownMenuItem<String>>[
DropdownMenuItem<String>(
value: text,
child: Text(text),
),
],
onChanged: (_) {},
),
)
: DropdownButton<String>(
dropdownColor: color,
value: text,
items: const <DropdownMenuItem<String>>[
DropdownMenuItem<String>(
value: text,
child: Text(text),
),
],
onChanged: (_) {},
),
),
),
);
......@@ -1815,10 +1910,14 @@ void main() {
await checkDropdownColor(tester);
});
testWidgets('DropdownButton uses dropdownColor when expanded when given', (WidgetTester tester) async {
testWidgets('DropdownButton uses dropdownColor when expanded', (WidgetTester tester) async {
await checkDropdownColor(tester, color: const Color.fromRGBO(120, 220, 70, 0.8));
});
testWidgets('DropdownButtonFormField uses dropdownColor when expanded', (WidgetTester tester) async {
await checkDropdownColor(tester, color: const Color.fromRGBO(120, 220, 70, 0.8), isFormField: true);
});
testWidgets('DropdownButton hint displays properly when selectedItemBuilder is defined', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/42340
final List<String> items = <String>['1', '2', '3'];
......@@ -2100,6 +2199,20 @@ void main() {
expect(buttonFinder, paints ..rrect(rrect: const RRect.fromLTRBXY(0.0, 0.0, 104.0, 48.0, 4.0, 4.0), color: const Color(0xff00ff00)));
});
testWidgets('DropdownButtonFormField can be focused, and has focusColor', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final UniqueKey buttonKey = UniqueKey();
final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButtonFormField');
await tester.pumpWidget(buildFrame(isFormField: true, buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, autofocus: true));
await tester.pump(); // Pump a frame for autofocus to take effect.
expect(focusNode.hasPrimaryFocus, isTrue);
final Finder buttonFinder = find.descendant(of: find.byKey(buttonKey), matching: find.byType(InputDecorator));
expect(buttonFinder, paints ..rrect(rrect: const RRect.fromLTRBXY(0.0, 12.0, 800.0, 60.0, 4.0, 4.0), color: const Color(0x1f000000)));
await tester.pumpWidget(buildFrame(isFormField: true, buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, focusColor: const Color(0xff00ff00)));
expect(buttonFinder, paints ..rrect(rrect: const RRect.fromLTRBXY(0.0, 12.0, 800.0, 60.0, 4.0, 4.0), color: const Color(0xff00ff00)));
});
testWidgets("DropdownButton won't be focused if not enabled", (WidgetTester tester) async {
final UniqueKey buttonKey = UniqueKey();
final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton');
......
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