Unverified Commit cccfe96e authored by EricEnslen's avatar EricEnslen Committed by GitHub

Allow ListTiles to be autofocused (#54229)

Adds an "autofocus" param to ListTile and its Checkbox, Radio and Switch variants, and passes the given value through to the wrapped InkWell, Switch, etc.

This is important for scenarios like a settings screen, where the first interactable element on a page may be a SwitchListTile, for example.
parent 627cca79
......@@ -266,11 +266,13 @@ class CheckboxListTile extends StatelessWidget {
this.secondary,
this.selected = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.autofocus = false,
}) : assert(value != null),
assert(isThreeLine != null),
assert(!isThreeLine || subtitle != null),
assert(selected != null),
assert(controlAffinity != null),
assert(autofocus != null),
super(key: key);
/// Whether this checkbox is checked.
......@@ -351,6 +353,9 @@ class CheckboxListTile extends StatelessWidget {
/// Where to place the control relative to the text.
final ListTileControlAffinity controlAffinity;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
@override
Widget build(BuildContext context) {
final Widget control = Checkbox(
......@@ -359,6 +364,7 @@ class CheckboxListTile extends StatelessWidget {
activeColor: activeColor,
checkColor: checkColor,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
autofocus: autofocus,
);
Widget leading, trailing;
switch (controlAffinity) {
......@@ -385,6 +391,7 @@ class CheckboxListTile extends StatelessWidget {
enabled: onChanged != null,
onTap: onChanged != null ? () { onChanged(!value); } : null,
selected: selected,
autofocus: autofocus,
),
),
);
......
......@@ -638,9 +638,11 @@ class ListTile extends StatelessWidget {
this.onTap,
this.onLongPress,
this.selected = false,
this.autofocus = false,
}) : assert(isThreeLine != null),
assert(enabled != null),
assert(selected != null),
assert(autofocus != null),
assert(!isThreeLine || subtitle != null),
super(key: key);
......@@ -724,6 +726,9 @@ class ListTile extends StatelessWidget {
/// can be overridden with a [ListTileTheme].
final bool selected;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// Add a one pixel border in between each tile. If color isn't specified the
/// [ThemeData.dividerColor] of the context's [Theme] is used.
///
......@@ -883,6 +888,7 @@ class ListTile extends StatelessWidget {
onTap: enabled ? onTap : null,
onLongPress: enabled ? onLongPress : null,
canRequestFocus: enabled,
autofocus: autofocus,
child: Semantics(
selected: selected,
enabled: enabled,
......
......@@ -318,12 +318,14 @@ class RadioListTile<T> extends StatelessWidget {
this.secondary,
this.selected = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.autofocus = false,
}) : assert(toggleable != null),
assert(isThreeLine != null),
assert(!isThreeLine || subtitle != null),
assert(selected != null),
assert(controlAffinity != null),
assert(autofocus != null),
super(key: key);
/// The value represented by this radio button.
......@@ -464,6 +466,9 @@ class RadioListTile<T> extends StatelessWidget {
/// Where to place the control relative to the text.
final ListTileControlAffinity controlAffinity;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// Whether this radio button is checked.
///
/// To control this value, set [value] and [groupValue] appropriately.
......@@ -478,6 +483,7 @@ class RadioListTile<T> extends StatelessWidget {
toggleable: toggleable,
activeColor: activeColor,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
autofocus: autofocus,
);
Widget leading, trailing;
switch (controlAffinity) {
......@@ -512,6 +518,7 @@ class RadioListTile<T> extends StatelessWidget {
}
} : null,
selected: selected,
autofocus: autofocus,
),
),
);
......
......@@ -272,11 +272,13 @@ class SwitchListTile extends StatelessWidget {
this.contentPadding,
this.secondary,
this.selected = false,
this.autofocus = false,
}) : _switchListTileType = _SwitchListTileType.material,
assert(value != null),
assert(isThreeLine != null),
assert(!isThreeLine || subtitle != null),
assert(selected != null),
assert(autofocus != null),
super(key: key);
/// Creates the wrapped switch with [Switch.adaptive].
......@@ -304,11 +306,13 @@ class SwitchListTile extends StatelessWidget {
this.contentPadding,
this.secondary,
this.selected = false,
this.autofocus = false,
}) : _switchListTileType = _SwitchListTileType.adaptive,
assert(value != null),
assert(isThreeLine != null),
assert(!isThreeLine || subtitle != null),
assert(selected != null),
assert(autofocus != null),
super(key: key);
/// Whether this switch is checked.
......@@ -419,6 +423,9 @@ class SwitchListTile extends StatelessWidget {
/// Normally, this property is left to its default value, false.
final bool selected;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// If adaptive, creates the switch with [Switch.adaptive].
final _SwitchListTileType _switchListTileType;
......@@ -437,6 +444,7 @@ class SwitchListTile extends StatelessWidget {
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
inactiveThumbColor: inactiveThumbColor,
autofocus: autofocus,
);
break;
......@@ -451,6 +459,7 @@ class SwitchListTile extends StatelessWidget {
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
inactiveThumbColor: inactiveThumbColor,
autofocus: autofocus,
);
}
return MergeSemantics(
......@@ -467,6 +476,7 @@ class SwitchListTile extends StatelessWidget {
enabled: onChanged != null,
onTap: onChanged != null ? () { onChanged(!value); } : null,
selected: selected,
autofocus: autofocus,
),
),
);
......
......@@ -82,4 +82,37 @@ void main() {
await tester.pumpAndSettle();
expect(getCheckboxListTileRenderer(), paints..rrect(color: const Color(0xFFFFFFFF))); // paints's color is 0xFFFFFFFF (params)
});
testWidgets('CheckboxListTile can autofocus unless disabled.', (WidgetTester tester) async {
final GlobalKey childKey = GlobalKey();
await tester.pumpWidget(
wrap(
child: CheckboxListTile(
value: true,
onChanged: (_) {},
title: Text('Hello', key: childKey),
autofocus: true,
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue);
await tester.pumpWidget(
wrap(
child: CheckboxListTile(
value: true,
onChanged: null,
title: Text('Hello', key: childKey),
autofocus: true,
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse);
});
}
......@@ -1137,6 +1137,7 @@ void main() {
expect(tester.getRect(find.byType(Placeholder).at(0)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 16.0, 24.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), const Rect.fromLTWH(800.0 - 16.0 - 24.0, 88.0 + 16.0, 24.0, 56.0));
});
testWidgets('ListTile only accepts focus when enabled', (WidgetTester tester) async {
final GlobalKey childKey = GlobalKey();
......@@ -1184,4 +1185,50 @@ void main() {
expect(tester.binding.focusManager.primaryFocus, isNot(equals(tileNode)));
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse);
});
testWidgets('ListTile can autofocus unless disabled.', (WidgetTester tester) async {
final GlobalKey childKey = GlobalKey();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: <Widget>[
ListTile(
title: Text('A', key: childKey),
dense: true,
enabled: true,
autofocus: true,
onTap: () {},
),
],
),
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: <Widget>[
ListTile(
title: Text('A', key: childKey),
dense: true,
enabled: false,
autofocus: true,
onTap: () {},
),
],
),
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse);
});
}
......@@ -572,4 +572,38 @@ void main() {
semantics.dispose();
SystemChannels.accessibility.setMockMessageHandler(null);
});
testWidgets('RadioListTile can autofocus unless disabled.', (WidgetTester tester) async {
final GlobalKey childKey = GlobalKey();
await tester.pumpWidget(
wrap(
child: RadioListTile<int>(
value: 1,
groupValue: 2,
onChanged: (_) {},
title: Text('Title', key: childKey),
autofocus: true,
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue);
await tester.pumpWidget(
wrap(
child: RadioListTile<int>(
value: 1,
groupValue: 2,
onChanged: null,
title: Text('Title', key: childKey),
autofocus: true,
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse);
});
}
......@@ -254,4 +254,48 @@ void main() {
expect(tester.getTopLeft(find.byType(Switch)).dx, 20.0); // contentPadding.end = 20
expect(tester.getTopRight(find.text('L')).dx, 790.0); // 800 - contentPadding.start
});
testWidgets('SwitchListTile can autofocus unless disabled.', (WidgetTester tester) async {
final GlobalKey childKey = GlobalKey();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: <Widget>[
SwitchListTile(
value: true,
onChanged: (_) {},
title: Text('A', key: childKey),
autofocus: true,
),
],
),
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isTrue);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: <Widget>[
SwitchListTile(
value: true,
onChanged: null,
title: Text('A', key: childKey),
autofocus: true,
),
],
),
),
),
);
await tester.pump();
expect(Focus.of(childKey.currentContext, nullOk: true).hasPrimaryFocus, isFalse);
});
}
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