Unverified Commit 7e3f1df3 authored by Victor Ohashi's avatar Victor Ohashi Committed by GitHub

fix: Search anchor box location when used on nested navigator (#127198)

This PR is to fix the position of `SearchAnchor` when used with nested navigator. This solution is based on what `DropdownButton` internally do, looking to the closest `Navigator` to proper calculate the where to render `SearchViewRoute`.

Fixes: https://github.com/flutter/flutter/issues/126435

<details>
<summary>Test case</summary>

```dart
void main() => runApp(const NestedSearchBarApp());

class NestedSearchBarApp extends StatefulWidget {
  const NestedSearchBarApp({super.key});

  @override
  State<NestedSearchBarApp> createState() => _NestedSearchBarAppState();
}

class _NestedSearchBarAppState extends State<NestedSearchBarApp> {
  final SearchController controller = SearchController();

  @override
  Widget build(BuildContext context) {
    final ThemeData themeData = ThemeData(useMaterial3: true);

    return MaterialApp(
      theme: themeData,
      builder: (BuildContext context, Widget? child) {
        return Scaffold(
          body: Row(
            children: <Widget>[
              NavigationRail(
                selectedIndex: 1,
                destinations: const <NavigationRailDestination>[
                  NavigationRailDestination(
                    icon: Icon(Icons.favorite_border),
                    selectedIcon: Icon(Icons.favorite),
                    label: Text('First'),
                  ),
                  NavigationRailDestination(
                    icon: Icon(Icons.bookmark_border),
                    selectedIcon: Icon(Icons.book),
                    label: Text('Second'),
                  ),
                ],
              ),
              const VerticalDivider(thickness: 1, width: 1),
              Expanded(child: child!)
            ],
          ),
        );
      },
      home: Scaffold(
        appBar: AppBar(title: const Text('Search Anchor Sample')),
        body: Column(
          children: <Widget>[
            SearchAnchor(
                searchController: controller,
                builder: (BuildContext context, SearchController controller) {
                  return IconButton(
                    icon: const Icon(Icons.search),
                    onPressed: () {
                      controller.openView();
                    },
                  );
                },
                suggestionsBuilder:
                    (BuildContext context, SearchController controller) {
                  return List<ListTile>.generate(5, (int index) {
                    final String item = 'item $index';
                    return ListTile(
                      title: Text(item),
                      onTap: () {
                        setState(() {
                          controller.closeView(item);
                        });
                      },
                    );
                  });
                }),
            Center(
              child: controller.text.isEmpty
                  ? const Text('No item selected')
                  : Text('Selected item: ${controller.value.text}'),
            ),
          ],
        ),
      ),
    );
  }
}
```
</details>

<details>
<summary>Before fix:</summary>

![Screenshot 2023-05-19 at 11 55 53](https://github.com/flutter/flutter/assets/38299943/c86747e5-6716-4e87-b3fd-ce7f0f943b92)
</details>

<details>
<summary>After fix:</summary>

![Screenshot 2023-05-19 at 11 53 30](https://github.com/flutter/flutter/assets/38299943/d790ee49-e047-485c-87f4-7254acbdddfa)
</details>
parent 7e1a0d40
......@@ -357,17 +357,6 @@ class _SearchAnchorState extends State<SearchAnchor> {
Navigator.of(context).pop();
}
Rect? getRect(GlobalKey key) {
final BuildContext? context = key.currentContext;
if (context != null) {
final RenderBox searchBarBox = context.findRenderObject()! as RenderBox;
final Size boxSize = searchBarBox.size;
final Offset boxLocation = searchBarBox.localToGlobal(Offset.zero);
return boxLocation & boxSize;
}
return null;
}
bool toggleVisibility() {
setState(() {
_anchorIsVisible = !_anchorIsVisible;
......@@ -468,7 +457,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
if (context != null) {
final RenderBox searchBarBox = context.findRenderObject()! as RenderBox;
final Size boxSize = searchBarBox.size;
final Offset boxLocation = searchBarBox.localToGlobal(Offset.zero);
final NavigatorState navigator = Navigator.of(context);
final Offset boxLocation = searchBarBox.localToGlobal(Offset.zero, ancestor: navigator.context.findRenderObject());
return boxLocation & boxSize;
}
return null;
......
......@@ -1679,6 +1679,43 @@ void main() {
// No exception.
});
testWidgets('Docked search should position itself correctly based on closest navigator', (WidgetTester tester) async {
const double rootSpacing = 100.0;
await tester.pumpWidget(MaterialApp(
builder: (BuildContext context, Widget? child) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(rootSpacing),
child: child,
),
);
},
home: Material(
child: SearchAnchor(
isFullScreen: false,
builder: (BuildContext context, SearchController controller) {
return IconButton(
icon: const Icon(Icons.search),
onPressed: () {
controller.openView();
},
);
},
suggestionsBuilder: (BuildContext context, SearchController controller) {
return <Widget>[];
},
),
),
));
await tester.tap(find.byIcon(Icons.search));
await tester.pumpAndSettle();
final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first);
expect(searchViewRect.topLeft, equals(const Offset(rootSpacing, rootSpacing)));
});
}
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
......
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