Refactor route focus node creation (#147390)

## Description

This fixes an issue in the creation of the `FocusScope` in a route:  the route should be creating the `FocusScope` widget it has with `withExternalFocusNode`, since it is modifying the node attributes in a builder. 

Also modified some `AnimatedBuilder`s to be `ListenableBuilder`s, since they're not using animations (no functionality change there, since the implementation of the two is identical).

## Related Issues
 - #147256
 - Fixes #146844

## Tests
 - Updated example test.
This commit is contained in:
Greg Spencer 2024-04-26 10:17:19 -07:00 committed by GitHub
parent 973d3a009b
commit d274a2126f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 11 additions and 11 deletions

View File

@ -10,7 +10,7 @@ void main() {
testWidgets('Tapping FAB increments counter', (WidgetTester tester) async {
await tester.pumpWidget(const example.ListenableBuilderExample());
String getCount() => (tester.widget(find.descendant(of: find.byType(ListenableBuilder), matching: find.byType(Text))) as Text).data!;
String getCount() => (tester.widget(find.descendant(of: find.byType(ListenableBuilder).last, matching: find.byType(Text))) as Text).data!;
expect(find.text('Current counter value:'), findsOneWidget);
expect(find.text('0'), findsOneWidget);

View File

@ -1022,6 +1022,8 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
@override
Widget build(BuildContext context) {
// Only top most route can participate in focus traversal.
focusScopeNode.skipTraversal = !widget.route.isCurrent;
return AnimatedBuilder(
animation: widget.route.restorationScopeId,
builder: (BuildContext context, Widget? child) {
@ -1048,24 +1050,22 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
},
child: PrimaryScrollController(
controller: primaryScrollController,
child: FocusScope(
node: focusScopeNode, // immutable
// Only top most route can participate in focus traversal.
skipTraversal: !widget.route.isCurrent,
child: FocusScope.withExternalFocusNode(
focusScopeNode: focusScopeNode, // immutable
child: RepaintBoundary(
child: AnimatedBuilder(
animation: _listenable, // immutable
child: ListenableBuilder(
listenable: _listenable, // immutable
builder: (BuildContext context, Widget? child) {
return widget.route.buildTransitions(
context,
widget.route.animation!,
widget.route.secondaryAnimation!,
// This additional AnimatedBuilder is include because if the
// This additional ListenableBuilder is include because if the
// value of the userGestureInProgressNotifier changes, it's
// only necessary to rebuild the IgnorePointer widget and set
// the focus node's ability to focus.
AnimatedBuilder(
animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
ListenableBuilder(
listenable: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
builder: (BuildContext context, Widget? child) {
final bool ignoreEvents = _shouldIgnoreFocusRequest;
focusScopeNode.canRequestFocus = !ignoreEvents;

View File

@ -1736,7 +1736,7 @@ void main() {
semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}));
testWidgets('focus traverse correct when pop multiple page simultaneously', (WidgetTester tester) async {
testWidgets('focus traversal is correct when popping multiple pages simultaneously', (WidgetTester tester) async {
// Regression test: https://github.com/flutter/flutter/issues/48903
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
await tester.pumpWidget(MaterialApp(