Unverified Commit 24530b80 authored by chunhtai's avatar chunhtai Committed by GitHub

Fixes ability to call nextFocus() on a node to focus its descendant (#136773)

fixes https://github.com/flutter/flutter/issues/134854
parent f847346d
......@@ -305,7 +305,7 @@ abstract class FocusTraversalPolicy with Diagnosticable {
final FocusScopeNode scope = currentNode.nearestScope!;
FocusNode? candidate = scope.focusedChild;
if (ignoreCurrentFocus || candidate == null && scope.descendants.isNotEmpty) {
final Iterable<FocusNode> sorted = _sortAllDescendants(scope, currentNode);
final Iterable<FocusNode> sorted = _sortAllDescendants(scope, currentNode).where((FocusNode node) => _canRequestTraversalFocus(node));
if (sorted.isEmpty) {
candidate = null;
} else {
......@@ -404,13 +404,17 @@ abstract class FocusTraversalPolicy with Diagnosticable {
@protected
Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode);
static bool _canRequestTraversalFocus(FocusNode node) {
return node.canRequestFocus && !node.skipTraversal;
}
static Iterable<FocusNode> _getDescendantsWithoutExpandingScope(FocusNode node) {
final List<FocusNode> result = <FocusNode>[];
for (final FocusNode child in node.children) {
result.add(child);
if (child is! FocusScopeNode) {
result.addAll(_getDescendantsWithoutExpandingScope(child));
}
result.add(child);
}
return result;
}
......@@ -488,7 +492,7 @@ abstract class FocusTraversalPolicy with Diagnosticable {
// They were left in above because they were needed to find their members
// during sorting.
sortedDescendants.removeWhere((FocusNode node) {
return node != currentNode && (!node.canRequestFocus || node.skipTraversal);
return node != currentNode && !_canRequestTraversalFocus(node);
});
// Sanity check to make sure that the algorithm above doesn't diverge from
......@@ -496,7 +500,7 @@ abstract class FocusTraversalPolicy with Diagnosticable {
// finds.
assert((){
final Set<FocusNode> difference = sortedDescendants.toSet().difference(scope.traversalDescendants.toSet());
if (currentNode.skipTraversal || !currentNode.canRequestFocus) {
if (!_canRequestTraversalFocus(currentNode)) {
// The scope.traversalDescendants will not contain currentNode if it
// skips traversal or not focusable.
assert(
......
......@@ -96,6 +96,113 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgetsWithLeakTracking('focus traversal should work case 1', (WidgetTester tester) async {
final FocusNode outer1 = FocusNode(debugLabel: 'outer1', skipTraversal: true);
final FocusNode outer2 = FocusNode(debugLabel: 'outer2', skipTraversal: true);
final FocusNode inner1 = FocusNode(debugLabel: 'inner1', );
final FocusNode inner2 = FocusNode(debugLabel: 'inner2', );
addTearDown(() {
outer1.dispose();
outer2.dispose();
inner1.dispose();
inner2.dispose();
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: FocusTraversalGroup(
child: Row(
children: <Widget>[
FocusScope(
child: Focus(
focusNode: outer1,
child: Focus(
focusNode: inner1,
child: const SizedBox(width: 10, height: 10),
),
),
),
FocusScope(
child: Focus(
focusNode: outer2,
// Add a padding to ensure both Focus widgets have different
// sizes.
child: Padding(
padding: const EdgeInsets.all(5),
child: Focus(
focusNode: inner2,
child: const SizedBox(width: 10, height: 10),
),
),
),
),
],
),
),
),
);
expect(FocusManager.instance.primaryFocus, isNull);
inner1.requestFocus();
await tester.pump();
expect(FocusManager.instance.primaryFocus, inner1);
outer2.nextFocus();
await tester.pump();
expect(FocusManager.instance.primaryFocus, inner2);
});
testWidgetsWithLeakTracking('focus traversal should work case 2', (WidgetTester tester) async {
final FocusNode outer1 = FocusNode(debugLabel: 'outer1', skipTraversal: true);
final FocusNode outer2 = FocusNode(debugLabel: 'outer2', skipTraversal: true);
final FocusNode inner1 = FocusNode(debugLabel: 'inner1', );
final FocusNode inner2 = FocusNode(debugLabel: 'inner2', );
addTearDown(() {
outer1.dispose();
outer2.dispose();
inner1.dispose();
inner2.dispose();
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: FocusTraversalGroup(
child: Row(
children: <Widget>[
FocusScope(
child: Focus(
focusNode: outer1,
child: Focus(
focusNode: inner1,
child: const SizedBox(width: 10, height: 10),
),
),
),
FocusScope(
child: Focus(
focusNode: outer2,
child: Focus(
focusNode: inner2,
child: const SizedBox(width: 10, height: 10),
),
),
),
],
),
),
),
);
expect(FocusManager.instance.primaryFocus, isNull);
inner1.requestFocus();
await tester.pump();
expect(FocusManager.instance.primaryFocus, inner1);
outer2.nextFocus();
await tester.pump();
expect(FocusManager.instance.primaryFocus, inner2);
});
testWidgetsWithLeakTracking('Move focus to next node.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
......@@ -716,8 +823,13 @@ void main() {
final bool didFindNode = node1.nextFocus();
await tester.pump();
expect(didFindNode, isTrue);
expect(node1.hasPrimaryFocus, isFalse);
expect(node2.hasPrimaryFocus, isTrue);
if (canRequestFocus) {
expect(node1.hasPrimaryFocus, isTrue);
expect(node2.hasPrimaryFocus, isFalse);
} else {
expect(node1.hasPrimaryFocus, isFalse);
expect(node2.hasPrimaryFocus, isTrue);
}
}
});
......
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