Unverified Commit 9fb1c521 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

InheritedTheme updates (#69050)

parent d50bfd5f
...@@ -147,8 +147,7 @@ class MaterialBannerTheme extends InheritedTheme { ...@@ -147,8 +147,7 @@ class MaterialBannerTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final MaterialBannerTheme? ancestorTheme = context.findAncestorWidgetOfExactType<MaterialBannerTheme>(); return MaterialBannerTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : MaterialBannerTheme(data: data, child: child);
} }
@override @override
......
...@@ -417,7 +417,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { ...@@ -417,7 +417,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
class _ModalBottomSheetRoute<T> extends PopupRoute<T> { class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
_ModalBottomSheetRoute({ _ModalBottomSheetRoute({
this.builder, this.builder,
this.theme, required this.capturedThemes,
this.barrierLabel, this.barrierLabel,
this.backgroundColor, this.backgroundColor,
this.elevation, this.elevation,
...@@ -434,7 +434,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> { ...@@ -434,7 +434,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
super(settings: settings); super(settings: settings);
final WidgetBuilder? builder; final WidgetBuilder? builder;
final ThemeData? theme; final CapturedThemes capturedThemes;
final bool isScrollControlled; final bool isScrollControlled;
final Color? backgroundColor; final Color? backgroundColor;
final double? elevation; final double? elevation;
...@@ -470,13 +470,15 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> { ...@@ -470,13 +470,15 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
@override @override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context)!.bottomSheetTheme;
// By definition, the bottom sheet is aligned to the bottom of the page // By definition, the bottom sheet is aligned to the bottom of the page
// and isn't exposed to the top padding of the MediaQuery. // and isn't exposed to the top padding of the MediaQuery.
Widget bottomSheet = MediaQuery.removePadding( final Widget bottomSheet = MediaQuery.removePadding(
context: context, context: context,
removeTop: true, removeTop: true,
child: _ModalBottomSheet<T>( child: Builder(
builder: (BuildContext context) {
final BottomSheetThemeData sheetTheme = Theme.of(context)!.bottomSheetTheme;
return _ModalBottomSheet<T>(
route: this, route: this,
backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor, backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor,
elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation, elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation,
...@@ -484,11 +486,11 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> { ...@@ -484,11 +486,11 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
clipBehavior: clipBehavior, clipBehavior: clipBehavior,
isScrollControlled: isScrollControlled, isScrollControlled: isScrollControlled,
enableDrag: enableDrag, enableDrag: enableDrag,
);
},
), ),
); );
if (theme != null) return capturedThemes.wrap(bottomSheet);
bottomSheet = Theme(data: theme!, child: bottomSheet);
return bottomSheet;
} }
} }
...@@ -667,9 +669,10 @@ Future<T?> showModalBottomSheet<T>({ ...@@ -667,9 +669,10 @@ Future<T?> showModalBottomSheet<T>({
assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
return Navigator.of(context, rootNavigator: useRootNavigator)!.push(_ModalBottomSheetRoute<T>( final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator)!;
return navigator.push(_ModalBottomSheetRoute<T>(
builder: builder, builder: builder,
theme: Theme.of(context, shadowThemeOnly: true), capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
isScrollControlled: isScrollControlled, isScrollControlled: isScrollControlled,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
......
...@@ -241,8 +241,7 @@ class ButtonTheme extends InheritedTheme { ...@@ -241,8 +241,7 @@ class ButtonTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final ButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ButtonTheme>(); return ButtonTheme.fromButtonThemeData(data: data, child: child);
return identical(this, ancestorTheme) ? child : ButtonTheme.fromButtonThemeData(data: data, child: child);
} }
@override @override
......
...@@ -91,8 +91,7 @@ class ChipTheme extends InheritedTheme { ...@@ -91,8 +91,7 @@ class ChipTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final ChipTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ChipTheme>(); return ChipTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : ChipTheme(data: data, child: child);
} }
@override @override
......
...@@ -978,18 +978,12 @@ Future<T?> showDialog<T>({ ...@@ -978,18 +978,12 @@ Future<T?> showDialog<T>({
assert(useRootNavigator != null); assert(useRootNavigator != null);
assert(debugCheckHasMaterialLocalizations(context)); 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( return showGeneralDialog(
context: context, context: context,
pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) { pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget pageChild = child ?? Builder(builder: builder!); final Widget pageChild = child ?? Builder(builder: builder!);
Widget dialog = Builder( Widget dialog = themes.wrap(pageChild);
builder: (BuildContext context) {
return theme != null
? Theme(data: theme, child: pageChild)
: pageChild;
}
);
if (useSafeArea) { if (useSafeArea) {
dialog = SafeArea(child: dialog); dialog = SafeArea(child: dialog);
} }
......
...@@ -166,8 +166,7 @@ class DividerTheme extends InheritedTheme { ...@@ -166,8 +166,7 @@ class DividerTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final DividerTheme? ancestorTheme = context.findAncestorWidgetOfExactType<DividerTheme>(); return DividerTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : DividerTheme(data: data, child: child);
} }
@override @override
......
...@@ -402,7 +402,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> { ...@@ -402,7 +402,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
required this.buttonRect, required this.buttonRect,
required this.selectedIndex, required this.selectedIndex,
this.elevation = 8, this.elevation = 8,
this.theme, required this.capturedThemes,
required this.style, required this.style,
this.barrierLabel, this.barrierLabel,
this.itemHeight, this.itemHeight,
...@@ -415,7 +415,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> { ...@@ -415,7 +415,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
final Rect buttonRect; final Rect buttonRect;
final int selectedIndex; final int selectedIndex;
final int elevation; final int elevation;
final ThemeData? theme; final CapturedThemes capturedThemes;
final TextStyle style; final TextStyle style;
final double? itemHeight; final double? itemHeight;
final Color? dropdownColor; final Color? dropdownColor;
...@@ -447,7 +447,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> { ...@@ -447,7 +447,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
buttonRect: buttonRect, buttonRect: buttonRect,
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
elevation: elevation, elevation: elevation,
theme: theme, capturedThemes: capturedThemes,
style: style, style: style,
dropdownColor: dropdownColor, dropdownColor: dropdownColor,
); );
...@@ -533,7 +533,7 @@ class _DropdownRoutePage<T> extends StatelessWidget { ...@@ -533,7 +533,7 @@ class _DropdownRoutePage<T> extends StatelessWidget {
required this.buttonRect, required this.buttonRect,
required this.selectedIndex, required this.selectedIndex,
this.elevation = 8, this.elevation = 8,
this.theme, required this.capturedThemes,
this.style, this.style,
required this.dropdownColor, required this.dropdownColor,
}) : super(key: key); }) : super(key: key);
...@@ -545,7 +545,7 @@ class _DropdownRoutePage<T> extends StatelessWidget { ...@@ -545,7 +545,7 @@ class _DropdownRoutePage<T> extends StatelessWidget {
final Rect buttonRect; final Rect buttonRect;
final int selectedIndex; final int selectedIndex;
final int elevation; final int elevation;
final ThemeData? theme; final CapturedThemes capturedThemes;
final TextStyle? style; final TextStyle? style;
final Color? dropdownColor; final Color? dropdownColor;
...@@ -565,7 +565,7 @@ class _DropdownRoutePage<T> extends StatelessWidget { ...@@ -565,7 +565,7 @@ class _DropdownRoutePage<T> extends StatelessWidget {
} }
final TextDirection? textDirection = Directionality.of(context); final TextDirection? textDirection = Directionality.of(context);
Widget menu = _DropdownMenu<T>( final Widget menu = _DropdownMenu<T>(
route: route, route: route,
padding: padding.resolve(textDirection), padding: padding.resolve(textDirection),
buttonRect: buttonRect, buttonRect: buttonRect,
...@@ -573,9 +573,6 @@ class _DropdownRoutePage<T> extends StatelessWidget { ...@@ -573,9 +573,6 @@ class _DropdownRoutePage<T> extends StatelessWidget {
dropdownColor: dropdownColor, dropdownColor: dropdownColor,
); );
if (theme != null)
menu = Theme(data: theme!, child: menu);
return MediaQuery.removePadding( return MediaQuery.removePadding(
context: context, context: context,
removeTop: true, removeTop: true,
...@@ -590,7 +587,7 @@ class _DropdownRoutePage<T> extends StatelessWidget { ...@@ -590,7 +587,7 @@ class _DropdownRoutePage<T> extends StatelessWidget {
route: route, route: route,
textDirection: textDirection, textDirection: textDirection,
), ),
child: menu, child: capturedThemes.wrap(menu),
); );
}, },
), ),
...@@ -1207,6 +1204,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi ...@@ -1207,6 +1204,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
) )
]; ];
final NavigatorState navigator = Navigator.of(context)!;
assert(_dropdownRoute == null); assert(_dropdownRoute == null);
_dropdownRoute = _DropdownRoute<T>( _dropdownRoute = _DropdownRoute<T>(
items: menuItems, items: menuItems,
...@@ -1214,14 +1212,14 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi ...@@ -1214,14 +1212,14 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
padding: _kMenuItemPadding.resolve(textDirection), padding: _kMenuItemPadding.resolve(textDirection),
selectedIndex: _selectedIndex ?? 0, selectedIndex: _selectedIndex ?? 0,
elevation: widget.elevation, elevation: widget.elevation,
theme: Theme.of(context, shadowThemeOnly: true), capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
style: _textStyle!, style: _textStyle!,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
itemHeight: widget.itemHeight, itemHeight: widget.itemHeight,
dropdownColor: widget.dropdownColor, dropdownColor: widget.dropdownColor,
); );
Navigator.push(context, _dropdownRoute!).then<void>((_DropdownRouteResult<T>? newValue) { navigator.push(_dropdownRoute!).then<void>((_DropdownRouteResult<T>? newValue) {
_removeDropdownRoute(); _removeDropdownRoute();
if (!mounted || newValue == null) if (!mounted || newValue == null)
return; return;
......
...@@ -116,8 +116,7 @@ class ElevatedButtonTheme extends InheritedTheme { ...@@ -116,8 +116,7 @@ class ElevatedButtonTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final ElevatedButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ElevatedButtonTheme>(); return ElevatedButtonTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : ElevatedButtonTheme(data: data, child: child);
} }
@override @override
......
...@@ -145,8 +145,7 @@ class ListTileTheme extends InheritedTheme { ...@@ -145,8 +145,7 @@ class ListTileTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final ListTileTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ListTileTheme>(); return ListTileTheme(
return identical(this, ancestorTheme) ? child : ListTileTheme(
dense: dense, dense: dense,
shape: shape, shape: shape,
style: style, style: style,
......
...@@ -207,8 +207,7 @@ class NavigationRailTheme extends InheritedTheme { ...@@ -207,8 +207,7 @@ class NavigationRailTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final NavigationRailTheme? ancestorTheme = context.findAncestorWidgetOfExactType<NavigationRailTheme>(); return NavigationRailTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : NavigationRailTheme(data: data, child: child);
} }
@override @override
......
...@@ -116,8 +116,7 @@ class OutlinedButtonTheme extends InheritedTheme { ...@@ -116,8 +116,7 @@ class OutlinedButtonTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final OutlinedButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<OutlinedButtonTheme>(); return OutlinedButtonTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : OutlinedButtonTheme(data: data, child: child);
} }
@override @override
......
...@@ -690,14 +690,11 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -690,14 +690,11 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
required this.items, required this.items,
this.initialValue, this.initialValue,
this.elevation, this.elevation,
this.theme,
required this.popupMenuTheme,
required this.barrierLabel, required this.barrierLabel,
this.semanticLabel, this.semanticLabel,
this.shape, this.shape,
this.color, this.color,
required this.showMenuContext, required this.capturedThemes,
required this.captureInheritedThemes,
}) : itemSizes = List<Size?>.filled(items.length, null); }) : itemSizes = List<Size?>.filled(items.length, null);
final RelativeRect position; final RelativeRect position;
...@@ -705,13 +702,10 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -705,13 +702,10 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
final List<Size?> itemSizes; final List<Size?> itemSizes;
final T? initialValue; final T? initialValue;
final double? elevation; final double? elevation;
final ThemeData? theme;
final String? semanticLabel; final String? semanticLabel;
final ShapeBorder? shape; final ShapeBorder? shape;
final Color? color; final Color? color;
final PopupMenuThemeData popupMenuTheme; final CapturedThemes capturedThemes;
final BuildContext showMenuContext;
final bool captureInheritedThemes;
@override @override
Animation<double> createAnimation() { Animation<double> createAnimation() {
...@@ -745,16 +739,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -745,16 +739,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
} }
} }
Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel); final Widget menu = _PopupMenu<T>(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);
}
return SafeArea( return SafeArea(
child: Builder( child: Builder(
...@@ -766,7 +751,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -766,7 +751,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
selectedItemIndex, selectedItemIndex,
Directionality.of(context)!, Directionality.of(context)!,
), ),
child: menu, child: capturedThemes.wrap(menu),
); );
}, },
), ),
...@@ -838,14 +823,12 @@ Future<T?> showMenu<T>({ ...@@ -838,14 +823,12 @@ Future<T?> showMenu<T>({
String? semanticLabel, String? semanticLabel,
ShapeBorder? shape, ShapeBorder? shape,
Color? color, Color? color,
bool captureInheritedThemes = true,
bool useRootNavigator = false, bool useRootNavigator = false,
}) { }) {
assert(context != null); assert(context != null);
assert(position != null); assert(position != null);
assert(useRootNavigator != null); assert(useRootNavigator != null);
assert(items != null && items.isNotEmpty); assert(items != null && items.isNotEmpty);
assert(captureInheritedThemes != null);
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
switch (Theme.of(context)!.platform) { switch (Theme.of(context)!.platform) {
...@@ -859,19 +842,17 @@ Future<T?> showMenu<T>({ ...@@ -859,19 +842,17 @@ Future<T?> showMenu<T>({
semanticLabel ??= MaterialLocalizations.of(context).popupMenuLabel; semanticLabel ??= MaterialLocalizations.of(context).popupMenuLabel;
} }
return Navigator.of(context, rootNavigator: useRootNavigator)!.push(_PopupMenuRoute<T>( final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator)!;
return navigator.push(_PopupMenuRoute<T>(
position: position, position: position,
items: items, items: items,
initialValue: initialValue, initialValue: initialValue,
elevation: elevation, elevation: elevation,
semanticLabel: semanticLabel, semanticLabel: semanticLabel,
theme: Theme.of(context, shadowThemeOnly: true),
popupMenuTheme: PopupMenuTheme.of(context),
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
shape: shape, shape: shape,
color: color, color: color,
showMenuContext: context, capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
captureInheritedThemes: captureInheritedThemes,
)); ));
} }
...@@ -964,11 +945,9 @@ class PopupMenuButton<T> extends StatefulWidget { ...@@ -964,11 +945,9 @@ class PopupMenuButton<T> extends StatefulWidget {
this.enabled = true, this.enabled = true,
this.shape, this.shape,
this.color, this.color,
this.captureInheritedThemes = true,
}) : assert(itemBuilder != null), }) : assert(itemBuilder != null),
assert(offset != null), assert(offset != null),
assert(enabled != null), assert(enabled != null),
assert(captureInheritedThemes != null),
assert(!(child != null && icon != null), assert(!(child != null && icon != null),
'You can only pass [child] or [icon], not both.'), 'You can only pass [child] or [icon], not both.'),
super(key: key); super(key: key);
...@@ -1050,11 +1029,6 @@ class PopupMenuButton<T> extends StatefulWidget { ...@@ -1050,11 +1029,6 @@ class PopupMenuButton<T> extends StatefulWidget {
/// Theme.of(context).cardColor is used. /// Theme.of(context).cardColor is used.
final Color? color; 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 @override
PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>(); PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>();
} }
...@@ -1094,7 +1068,6 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> { ...@@ -1094,7 +1068,6 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
position: position, position: position,
shape: widget.shape ?? popupMenuTheme.shape, shape: widget.shape ?? popupMenuTheme.shape,
color: widget.color ?? popupMenuTheme.color, color: widget.color ?? popupMenuTheme.color,
captureInheritedThemes: widget.captureInheritedThemes,
) )
.then<void>((T? newValue) { .then<void>((T? newValue) {
if (!mounted) if (!mounted)
......
...@@ -152,8 +152,7 @@ class PopupMenuTheme extends InheritedTheme { ...@@ -152,8 +152,7 @@ class PopupMenuTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final PopupMenuTheme? ancestorTheme = context.findAncestorWidgetOfExactType<PopupMenuTheme>(); return PopupMenuTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : PopupMenuTheme(data: data, child: child);
} }
@override @override
......
...@@ -117,8 +117,7 @@ class SliderTheme extends InheritedTheme { ...@@ -117,8 +117,7 @@ class SliderTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final SliderTheme? ancestorTheme = context.findAncestorWidgetOfExactType<SliderTheme>(); return SliderTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : SliderTheme(data: data, child: child);
} }
@override @override
......
...@@ -116,8 +116,7 @@ class TextButtonTheme extends InheritedTheme { ...@@ -116,8 +116,7 @@ class TextButtonTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final TextButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TextButtonTheme>(); return TextButtonTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : TextButtonTheme(data: data, child: child);
} }
@override @override
......
...@@ -157,8 +157,7 @@ class TextSelectionTheme extends InheritedTheme { ...@@ -157,8 +157,7 @@ class TextSelectionTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final TextSelectionTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TextSelectionTheme>(); return TextSelectionTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : TextSelectionTheme(data: data, child: child);
} }
@override @override
......
...@@ -176,8 +176,7 @@ class _InheritedTheme extends InheritedTheme { ...@@ -176,8 +176,7 @@ class _InheritedTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final _InheritedTheme? ancestorTheme = context.findAncestorWidgetOfExactType<_InheritedTheme>(); return Theme(data: theme.data, child: child);
return identical(this, ancestorTheme) ? child : Theme(data: theme.data, child: child);
} }
@override @override
......
...@@ -385,8 +385,7 @@ class TimePickerTheme extends InheritedTheme { ...@@ -385,8 +385,7 @@ class TimePickerTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final TimePickerTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TimePickerTheme>(); return TimePickerTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : TimePickerTheme(data: data, child: child);
} }
@override @override
......
...@@ -271,8 +271,7 @@ class ToggleButtonsTheme extends InheritedTheme { ...@@ -271,8 +271,7 @@ class ToggleButtonsTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final ToggleButtonsTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ToggleButtonsTheme>(); return ToggleButtonsTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : ToggleButtonsTheme(data: data, child: child);
} }
@override @override
......
...@@ -244,8 +244,7 @@ class TooltipTheme extends InheritedTheme { ...@@ -244,8 +244,7 @@ class TooltipTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final TooltipTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TooltipTheme>(); return TooltipTheme(data: data, child: child);
return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child);
} }
@override @override
......
...@@ -79,8 +79,7 @@ class IconTheme extends InheritedTheme { ...@@ -79,8 +79,7 @@ class IconTheme extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final IconTheme? iconTheme = context.findAncestorWidgetOfExactType<IconTheme>(); return IconTheme(data: data, child: child);
return identical(this, iconTheme) ? child : IconTheme(data: data, child: child);
} }
@override @override
......
...@@ -9,23 +9,21 @@ import 'framework.dart'; ...@@ -9,23 +9,21 @@ import 'framework.dart';
/// An [InheritedWidget] that defines visual properties like colors /// An [InheritedWidget] that defines visual properties like colors
/// and text styles, which the [child]'s subtree depends on. /// and text styles, which the [child]'s subtree depends on.
/// ///
/// The [wrap] method is used by [captureAll] to construct a widget /// The [wrap] method is used by [captureAll] and [CapturedThemes.wrap] to
/// that will wrap a child in all of the inherited themes which /// construct a widget that will wrap a child in all of the inherited themes
/// are present in a build context but are not present in the /// which are present in a specified part of the widget tree.
/// context that the returned widget is eventually built in.
/// ///
/// A widget that's shown in a different context from the one it's /// A widget that's shown in a different context from the one it's built in,
/// built in, like the contents of a new route or an overlay, will /// like the contents of a new route or an overlay, will be able to see the
/// be able to depend on inherited widget ancestors of the context /// ancestor inherited themes of the context it was built in.
/// it's built in.
/// ///
/// {@tool dartpad --template=freeform} /// {@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 /// 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. /// 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" /// new route's Text widget will inherit the "something must be wrong"
/// fallback text style, rather than the default text style defined in MyApp. /// fallback text style, rather than the default text style defined in MyApp.
/// ///
...@@ -43,19 +41,25 @@ import 'framework.dart'; ...@@ -43,19 +41,25 @@ import 'framework.dart';
/// class MyAppBody extends StatelessWidget { /// class MyAppBody extends StatelessWidget {
/// @override /// @override
/// Widget build(BuildContext context) { /// 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( /// return GestureDetector(
/// onTap: () { /// onTap: () {
/// Navigator.of(context).push( /// Navigator.of(context).push(
/// MaterialPageRoute( /// MaterialPageRoute(
/// builder: (BuildContext _) { /// builder: (BuildContext _) {
/// // InheritedTheme.captureAll() saves references to themes that /// // Wrap the actual child of the route in the previously
/// // are found above the context provided to this widget's build /// // captured themes.
/// // method, notably the DefaultTextStyle defined in MyApp. The /// return themes.wrap(Container(
/// // context passed to the MaterialPageRoute's builder is not used,
/// // because its ancestors are above MyApp's home.
/// return InheritedTheme.captureAll(context, Container(
/// alignment: Alignment.center, /// alignment: Alignment.center,
/// color: Theme.of(context).colorScheme.surface, /// color: Colors.white,
/// child: Text('Hello World'), /// child: Text('Hello World'),
/// )); /// ));
/// }, /// },
...@@ -95,27 +99,72 @@ abstract class InheritedTheme extends InheritedWidget { ...@@ -95,27 +99,72 @@ abstract class InheritedTheme extends InheritedWidget {
/// Return a copy of this inherited theme with the specified [child]. /// 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: /// This implementation for [TooltipTheme] is typical:
///
/// ```dart /// ```dart
/// Widget wrap(BuildContext context, Widget child) { /// Widget wrap(BuildContext context, Widget child) {
/// final TooltipTheme ancestorTheme = context.findAncestorWidgetOfExactType<TooltipTheme>()); /// return TooltipTheme(data: data, child: child);
/// return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child);
/// } /// }
/// ``` /// ```
Widget wrap(BuildContext context, Widget child); Widget wrap(BuildContext context, Widget child);
/// Returns a widget that will [wrap] child in all of the inherited themes /// Returns a widget that will [wrap] `child` in all of the inherited themes
/// which are visible from [context]. /// which are present between `context` and the specified `to`
static Widget captureAll(BuildContext context, Widget child) { /// [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(child != null);
assert(context != 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 <InheritedTheme>[]);
}
final List<InheritedTheme> themes = <InheritedTheme>[]; final List<InheritedTheme> themes = <InheritedTheme>[];
final Set<Type> themeTypes = <Type>{}; final Set<Type> themeTypes = <Type>{};
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) { if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) {
final InheritedTheme theme = ancestor.widget as InheritedTheme; final InheritedTheme theme = ancestor.widget as InheritedTheme;
final Type themeType = theme.runtimeType; final Type themeType = theme.runtimeType;
...@@ -130,7 +179,23 @@ abstract class InheritedTheme extends InheritedWidget { ...@@ -130,7 +179,23 @@ abstract class InheritedTheme extends InheritedWidget {
return true; 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<InheritedTheme> _themes;
/// Wraps a `child` [Widget] in the [InheritedTheme]s captured in this object.
Widget wrap(Widget child) {
return _CaptureAll(themes: _themes, child: child);
} }
} }
......
...@@ -181,8 +181,7 @@ class DefaultTextStyle extends InheritedTheme { ...@@ -181,8 +181,7 @@ class DefaultTextStyle extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final DefaultTextStyle? defaultTextStyle = context.findAncestorWidgetOfExactType<DefaultTextStyle>(); return DefaultTextStyle(
return identical(this, defaultTextStyle) ? child : DefaultTextStyle(
style: style, style: style,
textAlign: textAlign, textAlign: textAlign,
softWrap: softWrap, softWrap: softWrap,
...@@ -266,8 +265,7 @@ class DefaultTextHeightBehavior extends InheritedTheme { ...@@ -266,8 +265,7 @@ class DefaultTextHeightBehavior extends InheritedTheme {
@override @override
Widget wrap(BuildContext context, Widget child) { Widget wrap(BuildContext context, Widget child) {
final DefaultTextHeightBehavior? defaultTextHeightBehavior = context.findAncestorWidgetOfExactType<DefaultTextHeightBehavior>(); return DefaultTextHeightBehavior(
return identical(this, defaultTextHeightBehavior) ? child : DefaultTextHeightBehavior(
textHeightBehavior: textHeightBehavior, textHeightBehavior: textHeightBehavior,
child: child, child: child,
); );
......
...@@ -96,7 +96,6 @@ void main() { ...@@ -96,7 +96,6 @@ void main() {
testWidgets('PopupMenuTheme.wrap()', (WidgetTester tester) async { testWidgets('PopupMenuTheme.wrap()', (WidgetTester tester) async {
const double menuFontSize = 24; const double menuFontSize = 24;
const Color menuTextColor = Color(0xFF0000FF); const Color menuTextColor = Color(0xFF0000FF);
bool captureInheritedThemes = true;
Widget buildFrame() { Widget buildFrame() {
return MaterialApp( return MaterialApp(
...@@ -112,7 +111,6 @@ void main() { ...@@ -112,7 +111,6 @@ void main() {
// The appearance of the menu items' text is defined by the // The appearance of the menu items' text is defined by the
// PopupMenuTheme defined above. Popup menus use // PopupMenuTheme defined above. Popup menus use
// InheritedTheme.captureAll() by default. // InheritedTheme.captureAll() by default.
captureInheritedThemes: captureInheritedThemes,
child: const Text('show popupmenu'), child: const Text('show popupmenu'),
onSelected: (int result) { }, onSelected: (int result) { },
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
...@@ -146,17 +144,6 @@ void main() { ...@@ -146,17 +144,6 @@ void main() {
// Dismiss the menu // Dismiss the menu
await tester.tap(find.text('One')); await tester.tap(find.text('One'));
await tester.pumpAndSettle(); // menu route animation 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 { testWidgets('BannerTheme.wrap()', (WidgetTester tester) 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