Unverified Commit 433bca5e authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Fix `SearchAnchor`'s search view isn't updated when the theme changes &...

Fix `SearchAnchor`'s search view isn't updated when the theme changes & widgets inside the search view do not inherit local themes (#132749)

fixes [SearchAnchor (search view) UI glitch on platform brightness changes](https://github.com/flutter/flutter/issues/131835)
fixes [Search view widgets cannot inherit local themes](https://github.com/flutter/flutter/issues/132741) 

### Description

- This fixes an issue where the `SearchAnchor`'s search view isn't updated when the platform brightness changes.
- Fixes an issue where widgets inside the search view cannot use local themes

### Actual Results

`SearchAnchor` currently passed both global and local themes on the search view popup pushing and it uses anchor. button's context to look up the theme.

![search_view drawio (1)](https://github.com/flutter/flutter/assets/48603081/b5317fb1-ee73-461c-a119-f2a1e29f5909)

As a result, when the platform changes and the search view is rebuilt, it cannot use the updated theme.

https://github.com/flutter/flutter/assets/48603081/2f1ebe74-e7d5-4ef3-b97c-a741c3d68964

### Expected Results

Similar to `PopupMenuButton`, the theme should be located in the search view so that when the platform brightness is updated and the search view is rebuilt it can use the updated theme.

![search_view drawio](https://github.com/flutter/flutter/assets/48603081/4e48c0cb-a558-4de6-9865-5f51981a343f)

https://github.com/flutter/flutter/assets/48603081/d8d85982-c661-4cac-83e8-0488b1d93daf

However, the search view's context cannot access local themes so I added support for `InheritedTheme`, which fixes the local. theme issue for both the search view and widgets inside the search view.

### When using local themes for the `SearchAnchor`'s search view and widgets inside the view.

### Before

![Screenshot 2023-08-17 at 15 54 02](https://github.com/flutter/flutter/assets/48603081/dec18ba3-9f01-4706-987a-eb2fd4afb180)

### After
![Screenshot 2023-08-17 at 15 55 15](https://github.com/flutter/flutter/assets/48603081/13f2797a-7f70-43b5-bc56-7971cf76a61d)
parent d3c45f14
...@@ -342,7 +342,8 @@ class _SearchAnchorState extends State<SearchAnchor> { ...@@ -342,7 +342,8 @@ class _SearchAnchorState extends State<SearchAnchor> {
} }
void _openView() { void _openView() {
Navigator.of(context).push(_SearchViewRoute( final NavigatorState navigator = Navigator.of(context);
navigator.push(_SearchViewRoute(
viewLeading: widget.viewLeading, viewLeading: widget.viewLeading,
viewTrailing: widget.viewTrailing, viewTrailing: widget.viewTrailing,
viewHintText: widget.viewHintText, viewHintText: widget.viewHintText,
...@@ -363,6 +364,7 @@ class _SearchAnchorState extends State<SearchAnchor> { ...@@ -363,6 +364,7 @@ class _SearchAnchorState extends State<SearchAnchor> {
searchController: _searchController, searchController: _searchController,
suggestionsBuilder: widget.suggestionsBuilder, suggestionsBuilder: widget.suggestionsBuilder,
textCapitalization: widget.textCapitalization, textCapitalization: widget.textCapitalization,
capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
)); ));
} }
...@@ -433,6 +435,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -433,6 +435,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
required this.anchorKey, required this.anchorKey,
required this.searchController, required this.searchController,
required this.suggestionsBuilder, required this.suggestionsBuilder,
required this.capturedThemes,
}); });
final ValueGetter<bool>? toggleVisibility; final ValueGetter<bool>? toggleVisibility;
...@@ -455,6 +458,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -455,6 +458,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
final GlobalKey anchorKey; final GlobalKey anchorKey;
final SearchController searchController; final SearchController searchController;
final SuggestionsBuilder suggestionsBuilder; final SuggestionsBuilder suggestionsBuilder;
final CapturedThemes capturedThemes;
@override @override
Color? get barrierColor => Colors.transparent; Color? get barrierColor => Colors.transparent;
...@@ -467,7 +471,6 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -467,7 +471,6 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
late final SearchViewThemeData viewDefaults; late final SearchViewThemeData viewDefaults;
late final SearchViewThemeData viewTheme; late final SearchViewThemeData viewTheme;
late final DividerThemeData dividerTheme;
final RectTween _rectTween = RectTween(); final RectTween _rectTween = RectTween();
Rect? getRect() { Rect? getRect() {
...@@ -502,7 +505,6 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -502,7 +505,6 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
void updateViewConfig(BuildContext context) { void updateViewConfig(BuildContext context) {
viewDefaults = _SearchViewDefaultsM3(context, isFullScreen: showFullScreenView); viewDefaults = _SearchViewDefaultsM3(context, isFullScreen: showFullScreenView);
viewTheme = SearchViewTheme.of(context); viewTheme = SearchViewTheme.of(context);
dividerTheme = DividerTheme.of(context);
} }
void updateTweens(BuildContext context) { void updateTweens(BuildContext context) {
...@@ -576,30 +578,29 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { ...@@ -576,30 +578,29 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
curve: _kViewFadeOnInterval, curve: _kViewFadeOnInterval,
reverseCurve: _kViewFadeOnInterval.flipped, reverseCurve: _kViewFadeOnInterval.flipped,
), ),
child: _ViewContent( child: capturedThemes.wrap(
viewLeading: viewLeading, _ViewContent(
viewTrailing: viewTrailing, viewLeading: viewLeading,
viewHintText: viewHintText, viewTrailing: viewTrailing,
viewBackgroundColor: viewBackgroundColor, viewHintText: viewHintText,
viewElevation: viewElevation, viewBackgroundColor: viewBackgroundColor,
viewSurfaceTintColor: viewSurfaceTintColor, viewElevation: viewElevation,
viewSide: viewSide, viewSurfaceTintColor: viewSurfaceTintColor,
viewShape: viewShape, viewSide: viewSide,
viewHeaderTextStyle: viewHeaderTextStyle, viewShape: viewShape,
viewHeaderHintStyle: viewHeaderHintStyle, viewHeaderTextStyle: viewHeaderTextStyle,
dividerColor: dividerColor, viewHeaderHintStyle: viewHeaderHintStyle,
showFullScreenView: showFullScreenView, dividerColor: dividerColor,
animation: curvedAnimation, showFullScreenView: showFullScreenView,
topPadding: topPadding, animation: curvedAnimation,
viewMaxWidth: _rectTween.end!.width, topPadding: topPadding,
viewRect: viewRect, viewMaxWidth: _rectTween.end!.width,
viewDefaults: viewDefaults, viewRect: viewRect,
viewTheme: viewTheme, viewBuilder: viewBuilder,
dividerTheme: dividerTheme, searchController: searchController,
viewBuilder: viewBuilder, suggestionsBuilder: suggestionsBuilder,
searchController: searchController, textCapitalization: textCapitalization,
suggestionsBuilder: suggestionsBuilder, ),
textCapitalization: textCapitalization,
), ),
); );
} }
...@@ -631,9 +632,6 @@ class _ViewContent extends StatefulWidget { ...@@ -631,9 +632,6 @@ class _ViewContent extends StatefulWidget {
required this.animation, required this.animation,
required this.viewMaxWidth, required this.viewMaxWidth,
required this.viewRect, required this.viewRect,
required this.viewDefaults,
required this.viewTheme,
required this.dividerTheme,
required this.searchController, required this.searchController,
required this.suggestionsBuilder, required this.suggestionsBuilder,
}); });
...@@ -656,9 +654,6 @@ class _ViewContent extends StatefulWidget { ...@@ -656,9 +654,6 @@ class _ViewContent extends StatefulWidget {
final Animation<double> animation; final Animation<double> animation;
final double viewMaxWidth; final double viewMaxWidth;
final Rect viewRect; final Rect viewRect;
final SearchViewThemeData viewDefaults;
final SearchViewThemeData viewTheme;
final DividerThemeData dividerTheme;
final SearchController searchController; final SearchController searchController;
final SuggestionsBuilder suggestionsBuilder; final SuggestionsBuilder suggestionsBuilder;
...@@ -747,39 +742,43 @@ class _ViewContentState extends State<_ViewContent> { ...@@ -747,39 +742,43 @@ class _ViewContentState extends State<_ViewContent> {
), ),
]; ];
final SearchViewThemeData viewDefaults = _SearchViewDefaultsM3(context, isFullScreen: widget.showFullScreenView);
final SearchViewThemeData viewTheme = SearchViewTheme.of(context);
final DividerThemeData dividerTheme = DividerTheme.of(context);
final Color effectiveBackgroundColor = widget.viewBackgroundColor final Color effectiveBackgroundColor = widget.viewBackgroundColor
?? widget.viewTheme.backgroundColor ?? viewTheme.backgroundColor
?? widget.viewDefaults.backgroundColor!; ?? viewDefaults.backgroundColor!;
final Color effectiveSurfaceTint = widget.viewSurfaceTintColor final Color effectiveSurfaceTint = widget.viewSurfaceTintColor
?? widget.viewTheme.surfaceTintColor ?? viewTheme.surfaceTintColor
?? widget.viewDefaults.surfaceTintColor!; ?? viewDefaults.surfaceTintColor!;
final double effectiveElevation = widget.viewElevation final double effectiveElevation = widget.viewElevation
?? widget.viewTheme.elevation ?? viewTheme.elevation
?? widget.viewDefaults.elevation!; ?? viewDefaults.elevation!;
final BorderSide? effectiveSide = widget.viewSide final BorderSide? effectiveSide = widget.viewSide
?? widget.viewTheme.side ?? viewTheme.side
?? widget.viewDefaults.side; ?? viewDefaults.side;
OutlinedBorder effectiveShape = widget.viewShape OutlinedBorder effectiveShape = widget.viewShape
?? widget.viewTheme.shape ?? viewTheme.shape
?? widget.viewDefaults.shape!; ?? viewDefaults.shape!;
if (effectiveSide != null) { if (effectiveSide != null) {
effectiveShape = effectiveShape.copyWith(side: effectiveSide); effectiveShape = effectiveShape.copyWith(side: effectiveSide);
} }
final Color effectiveDividerColor = widget.dividerColor final Color effectiveDividerColor = widget.dividerColor
?? widget.viewTheme.dividerColor ?? viewTheme.dividerColor
?? widget.dividerTheme.color ?? dividerTheme.color
?? widget.viewDefaults.dividerColor!; ?? viewDefaults.dividerColor!;
final TextStyle? effectiveTextStyle = widget.viewHeaderTextStyle final TextStyle? effectiveTextStyle = widget.viewHeaderTextStyle
?? widget.viewTheme.headerTextStyle ?? viewTheme.headerTextStyle
?? widget.viewDefaults.headerTextStyle; ?? viewDefaults.headerTextStyle;
final TextStyle? effectiveHintStyle = widget.viewHeaderHintStyle final TextStyle? effectiveHintStyle = widget.viewHeaderHintStyle
?? widget.viewTheme.headerHintStyle ?? viewTheme.headerHintStyle
?? widget.viewHeaderTextStyle ?? widget.viewHeaderTextStyle
?? widget.viewTheme.headerTextStyle ?? viewTheme.headerTextStyle
?? widget.viewDefaults.headerHintStyle; ?? viewDefaults.headerHintStyle;
final Widget viewDivider = DividerTheme( final Widget viewDivider = DividerTheme(
data: widget.dividerTheme.copyWith(color: effectiveDividerColor), data: dividerTheme.copyWith(color: effectiveDividerColor),
child: const Divider(height: 1), child: const Divider(height: 1),
); );
......
...@@ -187,7 +187,7 @@ class SearchViewThemeData with Diagnosticable { ...@@ -187,7 +187,7 @@ class SearchViewThemeData with Diagnosticable {
/// ///
/// * [SearchViewThemeData], which describes the actual configuration of a search view /// * [SearchViewThemeData], which describes the actual configuration of a search view
/// theme. /// theme.
class SearchViewTheme extends InheritedWidget { class SearchViewTheme extends InheritedTheme {
/// Creates a const theme that controls the configurations for the search view /// Creates a const theme that controls the configurations for the search view
/// created by the [SearchAnchor] widget. /// created by the [SearchAnchor] widget.
const SearchViewTheme({ const SearchViewTheme({
...@@ -212,6 +212,11 @@ class SearchViewTheme extends InheritedWidget { ...@@ -212,6 +212,11 @@ class SearchViewTheme extends InheritedWidget {
return searchViewTheme?.data ?? Theme.of(context).searchViewTheme; return searchViewTheme?.data ?? Theme.of(context).searchViewTheme;
} }
@override
Widget wrap(BuildContext context, Widget child) {
return SearchViewTheme(data: data, child: child);
}
@override @override
bool updateShouldNotify(SearchViewTheme oldWidget) => data != oldWidget.data; bool updateShouldNotify(SearchViewTheme oldWidget) => data != oldWidget.data;
} }
...@@ -2055,6 +2055,117 @@ void main() { ...@@ -2055,6 +2055,117 @@ void main() {
expect(inputText.style.color, theme.colorScheme.onSurface); expect(inputText.style.color, theme.colorScheme.onSurface);
}); });
}); });
testWidgets('SearchAnchor view respects theme brightness', (WidgetTester tester) async {
Widget buildSearchAnchor(ThemeData theme) {
return MaterialApp(
theme: theme,
home: Center(
child: Material(
child: SearchAnchor(
builder: (BuildContext context, SearchController controller) {
return IconButton(
icon: const Icon(Icons.ac_unit),
onPressed: () {
controller.openView();
},
);
},
suggestionsBuilder: (BuildContext context, SearchController controller) {
return <Widget>[];
},
),
),
),
);
}
ThemeData theme = ThemeData(brightness: Brightness.light);
await tester.pumpWidget(buildSearchAnchor(theme));
// Open the search view.
await tester.tap(find.widgetWithIcon(IconButton, Icons.ac_unit));
await tester.pumpAndSettle();
// Test the search view background color.
Material material = getSearchViewMaterial(tester);
expect(material.color, theme.colorScheme.surface);
// Change the theme brightness.
theme = ThemeData(brightness: Brightness.dark);
await tester.pumpWidget(buildSearchAnchor(theme));
await tester.pumpAndSettle();
// Test the search view background color.
material = getSearchViewMaterial(tester);
expect(material.color, theme.colorScheme.surface);
});
testWidgets('Search view widgets can inherit local themes', (WidgetTester tester) async {
final ThemeData globalTheme = ThemeData(colorSchemeSeed: Colors.red);
final ThemeData localTheme = ThemeData(
colorSchemeSeed: Colors.green,
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(
backgroundColor: const Color(0xffffff00)
),
),
cardTheme: const CardTheme(color: Color(0xff00ffff)),
);
Widget buildSearchAnchor() {
return MaterialApp(
theme: globalTheme,
home: Center(
child: Builder(
builder: (BuildContext context) {
return Theme(
data: localTheme,
child: Material(
child: SearchAnchor.bar(
suggestionsBuilder: (BuildContext context, SearchController controller) {
return <Widget>[
Card(
child: ListTile(
onTap: () {},
title: const Text('Item 1'),
),
),
];
},
),
),
);
}
),
),
);
}
await tester.pumpWidget(buildSearchAnchor());
// Open the search view.
await tester.tap(find.byType(SearchBar));
await tester.pumpAndSettle();
// Test the search view background color.
final Material searchViewMaterial = getSearchViewMaterial(tester);
expect(searchViewMaterial.color, localTheme.colorScheme.surface);
// Test the search view icons background color.
final Material iconButtonMaterial = tester.widget<Material>(find.descendant(
of: find.byType(IconButton),
matching: find.byType(Material),
).first);
expect(find.byWidget(iconButtonMaterial), findsOneWidget);
expect(iconButtonMaterial.color, localTheme.iconButtonTheme.style?.backgroundColor?.resolve(<MaterialState>{}));
// Test the suggestion card color.
final Material suggestionMaterial = tester.widget<Material>(find.descendant(
of: find.byType(Card),
matching: find.byType(Material),
).first);
expect(suggestionMaterial.color, localTheme.cardTheme.color);
});
} }
Future<void> checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async { Future<void> checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async {
......
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