InheritedTheme updates (#69050)
This commit is contained in:
parent
d50bfd5f66
commit
9fb1c521b1
@ -147,8 +147,7 @@ class MaterialBannerTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final MaterialBannerTheme? ancestorTheme = context.findAncestorWidgetOfExactType<MaterialBannerTheme>();
|
||||
return identical(this, ancestorTheme) ? child : MaterialBannerTheme(data: data, child: child);
|
||||
return MaterialBannerTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -417,7 +417,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
|
||||
class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
||||
_ModalBottomSheetRoute({
|
||||
this.builder,
|
||||
this.theme,
|
||||
required this.capturedThemes,
|
||||
this.barrierLabel,
|
||||
this.backgroundColor,
|
||||
this.elevation,
|
||||
@ -434,7 +434,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
||||
super(settings: settings);
|
||||
|
||||
final WidgetBuilder? builder;
|
||||
final ThemeData? theme;
|
||||
final CapturedThemes capturedThemes;
|
||||
final bool isScrollControlled;
|
||||
final Color? backgroundColor;
|
||||
final double? elevation;
|
||||
@ -470,13 +470,15 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
||||
|
||||
@override
|
||||
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
|
||||
// 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<T>(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
final BottomSheetThemeData sheetTheme = Theme.of(context)!.bottomSheetTheme;
|
||||
return _ModalBottomSheet<T>(
|
||||
route: this,
|
||||
backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor,
|
||||
elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation,
|
||||
@ -484,11 +486,11 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
||||
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<T?> showModalBottomSheet<T>({
|
||||
assert(debugCheckHasMediaQuery(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,
|
||||
theme: Theme.of(context, shadowThemeOnly: true),
|
||||
capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
|
||||
isScrollControlled: isScrollControlled,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
backgroundColor: backgroundColor,
|
||||
|
@ -241,8 +241,7 @@ class ButtonTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final ButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ButtonTheme>();
|
||||
return identical(this, ancestorTheme) ? child : ButtonTheme.fromButtonThemeData(data: data, child: child);
|
||||
return ButtonTheme.fromButtonThemeData(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -91,8 +91,7 @@ class ChipTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final ChipTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ChipTheme>();
|
||||
return identical(this, ancestorTheme) ? child : ChipTheme(data: data, child: child);
|
||||
return ChipTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -978,18 +978,12 @@ Future<T?> showDialog<T>({
|
||||
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<double> animation, Animation<double> 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);
|
||||
}
|
||||
|
@ -166,8 +166,7 @@ class DividerTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final DividerTheme? ancestorTheme = context.findAncestorWidgetOfExactType<DividerTheme>();
|
||||
return identical(this, ancestorTheme) ? child : DividerTheme(data: data, child: child);
|
||||
return DividerTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -402,7 +402,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
||||
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<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
||||
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<T> extends PopupRoute<_DropdownRouteResult<T>> {
|
||||
buttonRect: buttonRect,
|
||||
selectedIndex: selectedIndex,
|
||||
elevation: elevation,
|
||||
theme: theme,
|
||||
capturedThemes: capturedThemes,
|
||||
style: style,
|
||||
dropdownColor: dropdownColor,
|
||||
);
|
||||
@ -533,7 +533,7 @@ class _DropdownRoutePage<T> 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<T> 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<T> extends StatelessWidget {
|
||||
}
|
||||
|
||||
final TextDirection? textDirection = Directionality.of(context);
|
||||
Widget menu = _DropdownMenu<T>(
|
||||
final Widget menu = _DropdownMenu<T>(
|
||||
route: route,
|
||||
padding: padding.resolve(textDirection),
|
||||
buttonRect: buttonRect,
|
||||
@ -573,9 +573,6 @@ class _DropdownRoutePage<T> 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<T> extends StatelessWidget {
|
||||
route: route,
|
||||
textDirection: textDirection,
|
||||
),
|
||||
child: menu,
|
||||
child: capturedThemes.wrap(menu),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -1207,6 +1204,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
|
||||
)
|
||||
];
|
||||
|
||||
final NavigatorState navigator = Navigator.of(context)!;
|
||||
assert(_dropdownRoute == null);
|
||||
_dropdownRoute = _DropdownRoute<T>(
|
||||
items: menuItems,
|
||||
@ -1214,14 +1212,14 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> 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<void>((_DropdownRouteResult<T>? newValue) {
|
||||
navigator.push(_dropdownRoute!).then<void>((_DropdownRouteResult<T>? newValue) {
|
||||
_removeDropdownRoute();
|
||||
if (!mounted || newValue == null)
|
||||
return;
|
||||
|
@ -116,8 +116,7 @@ class ElevatedButtonTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final ElevatedButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ElevatedButtonTheme>();
|
||||
return identical(this, ancestorTheme) ? child : ElevatedButtonTheme(data: data, child: child);
|
||||
return ElevatedButtonTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -145,8 +145,7 @@ class ListTileTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final ListTileTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ListTileTheme>();
|
||||
return identical(this, ancestorTheme) ? child : ListTileTheme(
|
||||
return ListTileTheme(
|
||||
dense: dense,
|
||||
shape: shape,
|
||||
style: style,
|
||||
|
@ -207,8 +207,7 @@ class NavigationRailTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final NavigationRailTheme? ancestorTheme = context.findAncestorWidgetOfExactType<NavigationRailTheme>();
|
||||
return identical(this, ancestorTheme) ? child : NavigationRailTheme(data: data, child: child);
|
||||
return NavigationRailTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -116,8 +116,7 @@ class OutlinedButtonTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final OutlinedButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<OutlinedButtonTheme>();
|
||||
return identical(this, ancestorTheme) ? child : OutlinedButtonTheme(data: data, child: child);
|
||||
return OutlinedButtonTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -690,14 +690,11 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
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<Size?>.filled(items.length, null);
|
||||
|
||||
final RelativeRect position;
|
||||
@ -705,13 +702,10 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
final List<Size?> 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<double> createAnimation() {
|
||||
@ -745,16 +739,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
final Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
||||
|
||||
return SafeArea(
|
||||
child: Builder(
|
||||
@ -766,7 +751,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
selectedItemIndex,
|
||||
Directionality.of(context)!,
|
||||
),
|
||||
child: menu,
|
||||
child: capturedThemes.wrap(menu),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -838,14 +823,12 @@ Future<T?> showMenu<T>({
|
||||
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<T?> showMenu<T>({
|
||||
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,
|
||||
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<T> 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<T> 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<T> createState() => PopupMenuButtonState<T>();
|
||||
}
|
||||
@ -1094,7 +1068,6 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
position: position,
|
||||
shape: widget.shape ?? popupMenuTheme.shape,
|
||||
color: widget.color ?? popupMenuTheme.color,
|
||||
captureInheritedThemes: widget.captureInheritedThemes,
|
||||
)
|
||||
.then<void>((T? newValue) {
|
||||
if (!mounted)
|
||||
|
@ -152,8 +152,7 @@ class PopupMenuTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final PopupMenuTheme? ancestorTheme = context.findAncestorWidgetOfExactType<PopupMenuTheme>();
|
||||
return identical(this, ancestorTheme) ? child : PopupMenuTheme(data: data, child: child);
|
||||
return PopupMenuTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -117,8 +117,7 @@ class SliderTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final SliderTheme? ancestorTheme = context.findAncestorWidgetOfExactType<SliderTheme>();
|
||||
return identical(this, ancestorTheme) ? child : SliderTheme(data: data, child: child);
|
||||
return SliderTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -116,8 +116,7 @@ class TextButtonTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final TextButtonTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TextButtonTheme>();
|
||||
return identical(this, ancestorTheme) ? child : TextButtonTheme(data: data, child: child);
|
||||
return TextButtonTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -157,8 +157,7 @@ class TextSelectionTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final TextSelectionTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TextSelectionTheme>();
|
||||
return identical(this, ancestorTheme) ? child : TextSelectionTheme(data: data, child: child);
|
||||
return TextSelectionTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -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
|
||||
|
@ -385,8 +385,7 @@ class TimePickerTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final TimePickerTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TimePickerTheme>();
|
||||
return identical(this, ancestorTheme) ? child : TimePickerTheme(data: data, child: child);
|
||||
return TimePickerTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -271,8 +271,7 @@ class ToggleButtonsTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final ToggleButtonsTheme? ancestorTheme = context.findAncestorWidgetOfExactType<ToggleButtonsTheme>();
|
||||
return identical(this, ancestorTheme) ? child : ToggleButtonsTheme(data: data, child: child);
|
||||
return ToggleButtonsTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -244,8 +244,7 @@ class TooltipTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final TooltipTheme? ancestorTheme = context.findAncestorWidgetOfExactType<TooltipTheme>();
|
||||
return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child);
|
||||
return TooltipTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -79,8 +79,7 @@ class IconTheme extends InheritedTheme {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final IconTheme? iconTheme = context.findAncestorWidgetOfExactType<IconTheme>();
|
||||
return identical(this, iconTheme) ? child : IconTheme(data: data, child: child);
|
||||
return IconTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -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<TooltipTheme>());
|
||||
/// 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 <InheritedTheme>[]);
|
||||
}
|
||||
|
||||
final List<InheritedTheme> themes = <InheritedTheme>[];
|
||||
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) {
|
||||
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<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 {
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
final DefaultTextStyle? defaultTextStyle = context.findAncestorWidgetOfExactType<DefaultTextStyle>();
|
||||
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<DefaultTextHeightBehavior>();
|
||||
return identical(this, defaultTextHeightBehavior) ? child : DefaultTextHeightBehavior(
|
||||
return DefaultTextHeightBehavior(
|
||||
textHeightBehavior: textHeightBehavior,
|
||||
child: child,
|
||||
);
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user