diff --git a/packages/flutter/lib/src/material/banner_theme.dart b/packages/flutter/lib/src/material/banner_theme.dart index feab15d956..519298be86 100644 --- a/packages/flutter/lib/src/material/banner_theme.dart +++ b/packages/flutter/lib/src/material/banner_theme.dart @@ -147,8 +147,7 @@ class MaterialBannerTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final MaterialBannerTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : MaterialBannerTheme(data: data, child: child); + return MaterialBannerTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index 349a0b4e91..627a2f43db 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -417,7 +417,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { class _ModalBottomSheetRoute extends PopupRoute { _ModalBottomSheetRoute({ this.builder, - this.theme, + required this.capturedThemes, this.barrierLabel, this.backgroundColor, this.elevation, @@ -434,7 +434,7 @@ class _ModalBottomSheetRoute extends PopupRoute { super(settings: settings); final WidgetBuilder? builder; - final ThemeData? theme; + final CapturedThemes capturedThemes; final bool isScrollControlled; final Color? backgroundColor; final double? elevation; @@ -470,25 +470,27 @@ class _ModalBottomSheetRoute extends PopupRoute { @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { - final BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context)!.bottomSheetTheme; // By definition, the bottom sheet is aligned to the bottom of the page // and isn't exposed to the top padding of the MediaQuery. - Widget bottomSheet = MediaQuery.removePadding( + final Widget bottomSheet = MediaQuery.removePadding( context: context, removeTop: true, - child: _ModalBottomSheet( - route: this, - backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor, - elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation, - shape: shape, - clipBehavior: clipBehavior, - isScrollControlled: isScrollControlled, - enableDrag: enableDrag, + child: Builder( + builder: (BuildContext context) { + final BottomSheetThemeData sheetTheme = Theme.of(context)!.bottomSheetTheme; + return _ModalBottomSheet( + route: this, + backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor, + elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation, + shape: shape, + clipBehavior: clipBehavior, + isScrollControlled: isScrollControlled, + enableDrag: enableDrag, + ); + }, ), ); - if (theme != null) - bottomSheet = Theme(data: theme!, child: bottomSheet); - return bottomSheet; + return capturedThemes.wrap(bottomSheet); } } @@ -667,9 +669,10 @@ Future showModalBottomSheet({ assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMaterialLocalizations(context)); - return Navigator.of(context, rootNavigator: useRootNavigator)!.push(_ModalBottomSheetRoute( + final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator)!; + return navigator.push(_ModalBottomSheetRoute( builder: builder, - theme: Theme.of(context, shadowThemeOnly: true), + capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), isScrollControlled: isScrollControlled, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, backgroundColor: backgroundColor, diff --git a/packages/flutter/lib/src/material/button_theme.dart b/packages/flutter/lib/src/material/button_theme.dart index 1b360f3967..99334ca420 100644 --- a/packages/flutter/lib/src/material/button_theme.dart +++ b/packages/flutter/lib/src/material/button_theme.dart @@ -241,8 +241,7 @@ class ButtonTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final ButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : ButtonTheme.fromButtonThemeData(data: data, child: child); + return ButtonTheme.fromButtonThemeData(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/chip_theme.dart b/packages/flutter/lib/src/material/chip_theme.dart index 5a37c79768..a2b407a4cb 100644 --- a/packages/flutter/lib/src/material/chip_theme.dart +++ b/packages/flutter/lib/src/material/chip_theme.dart @@ -91,8 +91,7 @@ class ChipTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final ChipTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : ChipTheme(data: data, child: child); + return ChipTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index fab542de23..1571ab7a14 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -978,18 +978,12 @@ Future showDialog({ assert(useRootNavigator != null); assert(debugCheckHasMaterialLocalizations(context)); - final ThemeData? theme = Theme.of(context, shadowThemeOnly: true); + final CapturedThemes themes = InheritedTheme.capture(from: context, to: Navigator.of(context, rootNavigator: useRootNavigator)!.context); return showGeneralDialog( context: context, pageBuilder: (BuildContext buildContext, Animation animation, Animation secondaryAnimation) { final Widget pageChild = child ?? Builder(builder: builder!); - Widget dialog = Builder( - builder: (BuildContext context) { - return theme != null - ? Theme(data: theme, child: pageChild) - : pageChild; - } - ); + Widget dialog = themes.wrap(pageChild); if (useSafeArea) { dialog = SafeArea(child: dialog); } diff --git a/packages/flutter/lib/src/material/divider_theme.dart b/packages/flutter/lib/src/material/divider_theme.dart index 4c4835727e..24298e56d9 100644 --- a/packages/flutter/lib/src/material/divider_theme.dart +++ b/packages/flutter/lib/src/material/divider_theme.dart @@ -166,8 +166,7 @@ class DividerTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final DividerTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : DividerTheme(data: data, child: child); + return DividerTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index 34a37acf7d..a9b259d4ff 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -402,7 +402,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { required this.buttonRect, required this.selectedIndex, this.elevation = 8, - this.theme, + required this.capturedThemes, required this.style, this.barrierLabel, this.itemHeight, @@ -415,7 +415,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { final Rect buttonRect; final int selectedIndex; final int elevation; - final ThemeData? theme; + final CapturedThemes capturedThemes; final TextStyle style; final double? itemHeight; final Color? dropdownColor; @@ -447,7 +447,7 @@ class _DropdownRoute extends PopupRoute<_DropdownRouteResult> { buttonRect: buttonRect, selectedIndex: selectedIndex, elevation: elevation, - theme: theme, + capturedThemes: capturedThemes, style: style, dropdownColor: dropdownColor, ); @@ -533,7 +533,7 @@ class _DropdownRoutePage extends StatelessWidget { required this.buttonRect, required this.selectedIndex, this.elevation = 8, - this.theme, + required this.capturedThemes, this.style, required this.dropdownColor, }) : super(key: key); @@ -545,7 +545,7 @@ class _DropdownRoutePage extends StatelessWidget { final Rect buttonRect; final int selectedIndex; final int elevation; - final ThemeData? theme; + final CapturedThemes capturedThemes; final TextStyle? style; final Color? dropdownColor; @@ -565,7 +565,7 @@ class _DropdownRoutePage extends StatelessWidget { } final TextDirection? textDirection = Directionality.of(context); - Widget menu = _DropdownMenu( + final Widget menu = _DropdownMenu( route: route, padding: padding.resolve(textDirection), buttonRect: buttonRect, @@ -573,9 +573,6 @@ class _DropdownRoutePage extends StatelessWidget { dropdownColor: dropdownColor, ); - if (theme != null) - menu = Theme(data: theme!, child: menu); - return MediaQuery.removePadding( context: context, removeTop: true, @@ -590,7 +587,7 @@ class _DropdownRoutePage extends StatelessWidget { route: route, textDirection: textDirection, ), - child: menu, + child: capturedThemes.wrap(menu), ); }, ), @@ -1207,6 +1204,7 @@ class _DropdownButtonState extends State> with WidgetsBindi ) ]; + final NavigatorState navigator = Navigator.of(context)!; assert(_dropdownRoute == null); _dropdownRoute = _DropdownRoute( items: menuItems, @@ -1214,14 +1212,14 @@ class _DropdownButtonState extends State> with WidgetsBindi padding: _kMenuItemPadding.resolve(textDirection), selectedIndex: _selectedIndex ?? 0, elevation: widget.elevation, - theme: Theme.of(context, shadowThemeOnly: true), + capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), style: _textStyle!, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, itemHeight: widget.itemHeight, dropdownColor: widget.dropdownColor, ); - Navigator.push(context, _dropdownRoute!).then((_DropdownRouteResult? newValue) { + navigator.push(_dropdownRoute!).then((_DropdownRouteResult? newValue) { _removeDropdownRoute(); if (!mounted || newValue == null) return; diff --git a/packages/flutter/lib/src/material/elevated_button_theme.dart b/packages/flutter/lib/src/material/elevated_button_theme.dart index dbeb87ba44..cc27e82729 100644 --- a/packages/flutter/lib/src/material/elevated_button_theme.dart +++ b/packages/flutter/lib/src/material/elevated_button_theme.dart @@ -116,8 +116,7 @@ class ElevatedButtonTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final ElevatedButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : ElevatedButtonTheme(data: data, child: child); + return ElevatedButtonTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index e52a95886c..f320060bad 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -145,8 +145,7 @@ class ListTileTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final ListTileTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : ListTileTheme( + return ListTileTheme( dense: dense, shape: shape, style: style, diff --git a/packages/flutter/lib/src/material/navigation_rail_theme.dart b/packages/flutter/lib/src/material/navigation_rail_theme.dart index 372982fc8f..8c6f51aa5d 100644 --- a/packages/flutter/lib/src/material/navigation_rail_theme.dart +++ b/packages/flutter/lib/src/material/navigation_rail_theme.dart @@ -207,8 +207,7 @@ class NavigationRailTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final NavigationRailTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : NavigationRailTheme(data: data, child: child); + return NavigationRailTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/outlined_button_theme.dart b/packages/flutter/lib/src/material/outlined_button_theme.dart index 1c5fcd568c..46b3bcd2bd 100644 --- a/packages/flutter/lib/src/material/outlined_button_theme.dart +++ b/packages/flutter/lib/src/material/outlined_button_theme.dart @@ -116,8 +116,7 @@ class OutlinedButtonTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final OutlinedButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : OutlinedButtonTheme(data: data, child: child); + return OutlinedButtonTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 69d6f5b703..42e9b1f801 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -690,14 +690,11 @@ class _PopupMenuRoute extends PopupRoute { required this.items, this.initialValue, this.elevation, - this.theme, - required this.popupMenuTheme, required this.barrierLabel, this.semanticLabel, this.shape, this.color, - required this.showMenuContext, - required this.captureInheritedThemes, + required this.capturedThemes, }) : itemSizes = List.filled(items.length, null); final RelativeRect position; @@ -705,13 +702,10 @@ class _PopupMenuRoute extends PopupRoute { final List itemSizes; final T? initialValue; final double? elevation; - final ThemeData? theme; final String? semanticLabel; final ShapeBorder? shape; final Color? color; - final PopupMenuThemeData popupMenuTheme; - final BuildContext showMenuContext; - final bool captureInheritedThemes; + final CapturedThemes capturedThemes; @override Animation createAnimation() { @@ -745,16 +739,7 @@ class _PopupMenuRoute extends PopupRoute { } } - Widget menu = _PopupMenu(route: this, semanticLabel: semanticLabel); - if (captureInheritedThemes) { - menu = InheritedTheme.captureAll(showMenuContext, menu); - } else { - // For the sake of backwards compatibility. An (unlikely) app that relied - // on having menus only inherit from the material Theme could set - // captureInheritedThemes to false and get the original behavior. - if (theme != null) - menu = Theme(data: theme!, child: menu); - } + final Widget menu = _PopupMenu(route: this, semanticLabel: semanticLabel); return SafeArea( child: Builder( @@ -766,7 +751,7 @@ class _PopupMenuRoute extends PopupRoute { selectedItemIndex, Directionality.of(context)!, ), - child: menu, + child: capturedThemes.wrap(menu), ); }, ), @@ -838,14 +823,12 @@ Future showMenu({ String? semanticLabel, ShapeBorder? shape, Color? color, - bool captureInheritedThemes = true, bool useRootNavigator = false, }) { assert(context != null); assert(position != null); assert(useRootNavigator != null); assert(items != null && items.isNotEmpty); - assert(captureInheritedThemes != null); assert(debugCheckHasMaterialLocalizations(context)); switch (Theme.of(context)!.platform) { @@ -859,19 +842,17 @@ Future showMenu({ semanticLabel ??= MaterialLocalizations.of(context).popupMenuLabel; } - return Navigator.of(context, rootNavigator: useRootNavigator)!.push(_PopupMenuRoute( + final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator)!; + return navigator.push(_PopupMenuRoute( position: position, items: items, initialValue: initialValue, elevation: elevation, semanticLabel: semanticLabel, - theme: Theme.of(context, shadowThemeOnly: true), - popupMenuTheme: PopupMenuTheme.of(context), barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, shape: shape, color: color, - showMenuContext: context, - captureInheritedThemes: captureInheritedThemes, + capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), )); } @@ -964,11 +945,9 @@ class PopupMenuButton extends StatefulWidget { this.enabled = true, this.shape, this.color, - this.captureInheritedThemes = true, }) : assert(itemBuilder != null), assert(offset != null), assert(enabled != null), - assert(captureInheritedThemes != null), assert(!(child != null && icon != null), 'You can only pass [child] or [icon], not both.'), super(key: key); @@ -1050,11 +1029,6 @@ class PopupMenuButton extends StatefulWidget { /// Theme.of(context).cardColor is used. final Color? color; - /// If true (the default) then the menu will be wrapped with copies - /// of the [InheritedTheme]s, like [Theme] and [PopupMenuTheme], which - /// are defined above the [BuildContext] where the menu is shown. - final bool captureInheritedThemes; - @override PopupMenuButtonState createState() => PopupMenuButtonState(); } @@ -1094,7 +1068,6 @@ class PopupMenuButtonState extends State> { position: position, shape: widget.shape ?? popupMenuTheme.shape, color: widget.color ?? popupMenuTheme.color, - captureInheritedThemes: widget.captureInheritedThemes, ) .then((T? newValue) { if (!mounted) diff --git a/packages/flutter/lib/src/material/popup_menu_theme.dart b/packages/flutter/lib/src/material/popup_menu_theme.dart index efbf567f8f..8df038ffba 100644 --- a/packages/flutter/lib/src/material/popup_menu_theme.dart +++ b/packages/flutter/lib/src/material/popup_menu_theme.dart @@ -152,8 +152,7 @@ class PopupMenuTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final PopupMenuTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : PopupMenuTheme(data: data, child: child); + return PopupMenuTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart index 7d7a279502..af3ef94ca8 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -117,8 +117,7 @@ class SliderTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SliderTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : SliderTheme(data: data, child: child); + return SliderTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/text_button_theme.dart b/packages/flutter/lib/src/material/text_button_theme.dart index 29a873ef48..49893a8d00 100644 --- a/packages/flutter/lib/src/material/text_button_theme.dart +++ b/packages/flutter/lib/src/material/text_button_theme.dart @@ -116,8 +116,7 @@ class TextButtonTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final TextButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : TextButtonTheme(data: data, child: child); + return TextButtonTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/text_selection_theme.dart b/packages/flutter/lib/src/material/text_selection_theme.dart index 6b67848d18..0361a2fed4 100644 --- a/packages/flutter/lib/src/material/text_selection_theme.dart +++ b/packages/flutter/lib/src/material/text_selection_theme.dart @@ -157,8 +157,7 @@ class TextSelectionTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final TextSelectionTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : TextSelectionTheme(data: data, child: child); + return TextSelectionTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/theme.dart b/packages/flutter/lib/src/material/theme.dart index c363e71606..c5a918af96 100644 --- a/packages/flutter/lib/src/material/theme.dart +++ b/packages/flutter/lib/src/material/theme.dart @@ -176,8 +176,7 @@ class _InheritedTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final _InheritedTheme? ancestorTheme = context.findAncestorWidgetOfExactType<_InheritedTheme>(); - return identical(this, ancestorTheme) ? child : Theme(data: theme.data, child: child); + return Theme(data: theme.data, child: child); } @override diff --git a/packages/flutter/lib/src/material/time_picker_theme.dart b/packages/flutter/lib/src/material/time_picker_theme.dart index 7137a5fb3f..c8bd19741e 100644 --- a/packages/flutter/lib/src/material/time_picker_theme.dart +++ b/packages/flutter/lib/src/material/time_picker_theme.dart @@ -385,8 +385,7 @@ class TimePickerTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final TimePickerTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : TimePickerTheme(data: data, child: child); + return TimePickerTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/toggle_buttons_theme.dart b/packages/flutter/lib/src/material/toggle_buttons_theme.dart index 74daa91772..bd51807e87 100644 --- a/packages/flutter/lib/src/material/toggle_buttons_theme.dart +++ b/packages/flutter/lib/src/material/toggle_buttons_theme.dart @@ -271,8 +271,7 @@ class ToggleButtonsTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final ToggleButtonsTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : ToggleButtonsTheme(data: data, child: child); + return ToggleButtonsTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/material/tooltip_theme.dart b/packages/flutter/lib/src/material/tooltip_theme.dart index 8b69636bda..f50b9d704c 100644 --- a/packages/flutter/lib/src/material/tooltip_theme.dart +++ b/packages/flutter/lib/src/material/tooltip_theme.dart @@ -244,8 +244,7 @@ class TooltipTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final TooltipTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); - return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child); + return TooltipTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/widgets/icon_theme.dart b/packages/flutter/lib/src/widgets/icon_theme.dart index 2b8e280420..1da6d19e67 100644 --- a/packages/flutter/lib/src/widgets/icon_theme.dart +++ b/packages/flutter/lib/src/widgets/icon_theme.dart @@ -79,8 +79,7 @@ class IconTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final IconTheme? iconTheme = context.findAncestorWidgetOfExactType(); - return identical(this, iconTheme) ? child : IconTheme(data: data, child: child); + return IconTheme(data: data, child: child); } @override diff --git a/packages/flutter/lib/src/widgets/inherited_theme.dart b/packages/flutter/lib/src/widgets/inherited_theme.dart index 2eab84493b..7571c08b7a 100644 --- a/packages/flutter/lib/src/widgets/inherited_theme.dart +++ b/packages/flutter/lib/src/widgets/inherited_theme.dart @@ -9,23 +9,21 @@ import 'framework.dart'; /// An [InheritedWidget] that defines visual properties like colors /// and text styles, which the [child]'s subtree depends on. /// -/// The [wrap] method is used by [captureAll] to construct a widget -/// that will wrap a child in all of the inherited themes which -/// are present in a build context but are not present in the -/// context that the returned widget is eventually built in. +/// The [wrap] method is used by [captureAll] and [CapturedThemes.wrap] to +/// construct a widget that will wrap a child in all of the inherited themes +/// which are present in a specified part of the widget tree. /// -/// A widget that's shown in a different context from the one it's -/// built in, like the contents of a new route or an overlay, will -/// be able to depend on inherited widget ancestors of the context -/// it's built in. +/// A widget that's shown in a different context from the one it's built in, +/// like the contents of a new route or an overlay, will be able to see the +/// ancestor inherited themes of the context it was built in. /// /// {@tool dartpad --template=freeform} -/// This example demonstrates how `InheritedTheme.captureAll()` can be used +/// This example demonstrates how `InheritedTheme.capture()` can be used /// to wrap the contents of a new route with the inherited themes that -/// are present when the route is built - but are not present when route +/// are present when the route was built - but are not present when route /// is actually shown. /// -/// If the same code is run without `InheritedTheme.captureAll(), the +/// If the same code is run without `InheritedTheme.capture(), the /// new route's Text widget will inherit the "something must be wrong" /// fallback text style, rather than the default text style defined in MyApp. /// @@ -43,19 +41,25 @@ import 'framework.dart'; /// class MyAppBody extends StatelessWidget { /// @override /// Widget build(BuildContext context) { +/// final NavigatorState navigator = Navigator.of(context); +/// // This InheritedTheme.capture() saves references to themes that are +/// // found above the context provided to this widget's build method +/// // excluding themes are are found above the navigator. Those themes do +/// // not have to be captured, because they will already be visible from +/// // the new route pushed onto said navigator. +/// // Themes are captured outside of the route's builder because when the +/// // builder executes, the context may not be valid anymore. +/// final CapturedThemes themes = InheritedTheme.capture(from: context, to: navigator.context); /// return GestureDetector( /// onTap: () { /// Navigator.of(context).push( /// MaterialPageRoute( /// builder: (BuildContext _) { -/// // InheritedTheme.captureAll() saves references to themes that -/// // are found above the context provided to this widget's build -/// // method, notably the DefaultTextStyle defined in MyApp. The -/// // context passed to the MaterialPageRoute's builder is not used, -/// // because its ancestors are above MyApp's home. -/// return InheritedTheme.captureAll(context, Container( +/// // Wrap the actual child of the route in the previously +/// // captured themes. +/// return themes.wrap(Container( /// alignment: Alignment.center, -/// color: Theme.of(context).colorScheme.surface, +/// color: Colors.white, /// child: Text('Hello World'), /// )); /// }, @@ -95,27 +99,72 @@ abstract class InheritedTheme extends InheritedWidget { /// Return a copy of this inherited theme with the specified [child]. /// - /// If the identical inherited theme is already visible from [context] then - /// just return the [child]. - /// /// This implementation for [TooltipTheme] is typical: + /// /// ```dart /// Widget wrap(BuildContext context, Widget child) { - /// final TooltipTheme ancestorTheme = context.findAncestorWidgetOfExactType()); - /// return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child); + /// return TooltipTheme(data: data, child: child); /// } /// ``` Widget wrap(BuildContext context, Widget child); - /// Returns a widget that will [wrap] child in all of the inherited themes - /// which are visible from [context]. - static Widget captureAll(BuildContext context, Widget child) { + /// Returns a widget that will [wrap] `child` in all of the inherited themes + /// which are present between `context` and the specified `to` + /// [BuildContext]. + /// + /// The `to` context must be an ancestor of `context`. If `to` is not + /// specified, all inherited themes up to the root of the widget tree are + /// captured. + /// + /// After calling this method, the themes present between `context` and `to` + /// are frozen for the provided `child`. If the themes (or their theme data) + /// change in the original subtree, those changes will not be visible to + /// the wrapped `child` - unless this method is called again to re-wrap the + /// child. + static Widget captureAll(BuildContext context, Widget child, {BuildContext? to}) { assert(child != null); assert(context != null); + return capture(from: context, to: to).wrap(child); + } + + /// Returns a [CapturedThemes] object that includes all the [InheritedTheme]s + /// between the given `from` and `to` [BuildContext]s. + /// + /// The `to` context must be an ancestor of the `from` context. If `to` is + /// null, all ancestor inherited themes of `from` up to the root of the + /// widget tree are captured. + /// + /// After calling this method, the themes present between `from` and `to` are + /// frozen in the returned [CapturedThemes] object. If the themes (or their + /// theme data) change in the original subtree, those changes will not be + /// applied to the themes captured in the [CapturedThemes] object - unless + /// this method is called again to re-capture the updated themes. + /// + /// To wrap a [Widget] in the captured themes, call [CapturedThemes.wrap]. + static CapturedThemes capture({ required BuildContext from, required BuildContext? to }) { + assert(from != null); + + if (from == to) { + // Nothing to capture. + return CapturedThemes._(const []); + } + final List themes = []; final Set themeTypes = {}; - context.visitAncestorElements((Element ancestor) { + late bool debugDidFindAncestor; + assert(() { + debugDidFindAncestor = to == null; + return true; + }()); + from.visitAncestorElements((Element ancestor) { + if (ancestor == to) { + assert(() { + debugDidFindAncestor = true; + return true; + }()); + return false; + } if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) { final InheritedTheme theme = ancestor.widget as InheritedTheme; final Type themeType = theme.runtimeType; @@ -130,7 +179,23 @@ abstract class InheritedTheme extends InheritedWidget { return true; }); - return _CaptureAll(themes: themes, child: child); + assert(debugDidFindAncestor, 'The provided `to` context must be an ancestor of the `from` context.'); + return CapturedThemes._(themes); + } +} + +/// Stores a list of captured [InheritedTheme]s that can be wrapped around a +/// child [Widget]. +/// +/// Used as return type by [InheritedTheme.capture]. +class CapturedThemes { + CapturedThemes._(this._themes); + + final List _themes; + + /// Wraps a `child` [Widget] in the [InheritedTheme]s captured in this object. + Widget wrap(Widget child) { + return _CaptureAll(themes: _themes, child: child); } } diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index 64f4828595..41925be7ff 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -181,8 +181,7 @@ class DefaultTextStyle extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final DefaultTextStyle? defaultTextStyle = context.findAncestorWidgetOfExactType(); - return identical(this, defaultTextStyle) ? child : DefaultTextStyle( + return DefaultTextStyle( style: style, textAlign: textAlign, softWrap: softWrap, @@ -266,8 +265,7 @@ class DefaultTextHeightBehavior extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final DefaultTextHeightBehavior? defaultTextHeightBehavior = context.findAncestorWidgetOfExactType(); - return identical(this, defaultTextHeightBehavior) ? child : DefaultTextHeightBehavior( + return DefaultTextHeightBehavior( textHeightBehavior: textHeightBehavior, child: child, ); diff --git a/packages/flutter/test/material/inherited_theme_test.dart b/packages/flutter/test/material/inherited_theme_test.dart index bc1a328aec..7c9190ec3b 100644 --- a/packages/flutter/test/material/inherited_theme_test.dart +++ b/packages/flutter/test/material/inherited_theme_test.dart @@ -96,7 +96,6 @@ void main() { testWidgets('PopupMenuTheme.wrap()', (WidgetTester tester) async { const double menuFontSize = 24; const Color menuTextColor = Color(0xFF0000FF); - bool captureInheritedThemes = true; Widget buildFrame() { return MaterialApp( @@ -112,7 +111,6 @@ void main() { // The appearance of the menu items' text is defined by the // PopupMenuTheme defined above. Popup menus use // InheritedTheme.captureAll() by default. - captureInheritedThemes: captureInheritedThemes, child: const Text('show popupmenu'), onSelected: (int result) { }, itemBuilder: (BuildContext context) { @@ -146,17 +144,6 @@ void main() { // Dismiss the menu await tester.tap(find.text('One')); await tester.pumpAndSettle(); // menu route animation - - // Defeat the default support for capturing the PopupMenuTheme. - captureInheritedThemes = false; - await tester.pumpWidget(buildFrame()); - - await tester.tap(find.text('show popupmenu')); - await tester.pumpAndSettle(); // menu route animation - expect(itemTextStyle('One').fontSize, isNot(menuFontSize)); - expect(itemTextStyle('One').color, isNot(menuTextColor)); - expect(itemTextStyle('Two').fontSize, isNot(menuFontSize)); - expect(itemTextStyle('Two').color, isNot(menuTextColor)); }); testWidgets('BannerTheme.wrap()', (WidgetTester tester) async {