Added InheritedTheme (#38583)
This commit is contained in:
parent
4277f364ff
commit
2e01eef5aa
@ -116,7 +116,7 @@ class MaterialBannerThemeData extends Diagnosticable {
|
|||||||
///
|
///
|
||||||
/// Values specified here are used for [MaterialBanner] properties that are not
|
/// Values specified here are used for [MaterialBanner] properties that are not
|
||||||
/// given an explicit non-null value.
|
/// given an explicit non-null value.
|
||||||
class MaterialBannerTheme extends InheritedWidget {
|
class MaterialBannerTheme extends InheritedTheme {
|
||||||
/// Creates a banner theme that controls the configurations for
|
/// Creates a banner theme that controls the configurations for
|
||||||
/// [MaterialBanner]s in its widget subtree.
|
/// [MaterialBanner]s in its widget subtree.
|
||||||
const MaterialBannerTheme({
|
const MaterialBannerTheme({
|
||||||
@ -144,6 +144,12 @@ class MaterialBannerTheme extends InheritedWidget {
|
|||||||
return popupMenuTheme?.data ?? Theme.of(context).bannerTheme;
|
return popupMenuTheme?.data ?? Theme.of(context).bannerTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final MaterialBannerTheme ancestorTheme = context.ancestorWidgetOfExactType(MaterialBannerTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : MaterialBannerTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(MaterialBannerTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(MaterialBannerTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ enum ButtonBarLayoutBehavior {
|
|||||||
/// based on the ambient button theme.
|
/// based on the ambient button theme.
|
||||||
/// * [RawMaterialButton], which can be used to configure a button that doesn't
|
/// * [RawMaterialButton], which can be used to configure a button that doesn't
|
||||||
/// depend on any inherited themes.
|
/// depend on any inherited themes.
|
||||||
class ButtonTheme extends InheritedWidget {
|
class ButtonTheme extends InheritedTheme {
|
||||||
/// Creates a button theme.
|
/// Creates a button theme.
|
||||||
///
|
///
|
||||||
/// The [textTheme], [minWidth], [height], and [colorScheme] arguments
|
/// The [textTheme], [minWidth], [height], and [colorScheme] arguments
|
||||||
@ -230,6 +230,12 @@ class ButtonTheme extends InheritedWidget {
|
|||||||
return buttonTheme;
|
return buttonTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final ButtonTheme ancestorTheme = context.ancestorWidgetOfExactType(ButtonTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : ButtonTheme.fromButtonThemeData(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(ButtonTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(ButtonTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ import 'theme_data.dart';
|
|||||||
/// theme.
|
/// theme.
|
||||||
/// * [ThemeData], which describes the overall theme information for the
|
/// * [ThemeData], which describes the overall theme information for the
|
||||||
/// application.
|
/// application.
|
||||||
class ChipTheme extends InheritedWidget {
|
class ChipTheme extends InheritedTheme {
|
||||||
/// Applies the given theme [data] to [child].
|
/// Applies the given theme [data] to [child].
|
||||||
///
|
///
|
||||||
/// The [data] and [child] arguments must not be null.
|
/// The [data] and [child] arguments must not be null.
|
||||||
@ -89,6 +89,12 @@ class ChipTheme extends InheritedWidget {
|
|||||||
return inheritedTheme?.data ?? Theme.of(context).chipTheme;
|
return inheritedTheme?.data ?? Theme.of(context).chipTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final ChipTheme ancestorTheme = context.ancestorWidgetOfExactType(ChipTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : ChipTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(ChipTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(ChipTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ class DividerThemeData extends Diagnosticable {
|
|||||||
/// An inherited widget that defines the configuration for
|
/// An inherited widget that defines the configuration for
|
||||||
/// [Divider]s, [VerticalDividers]s, dividers between [ListTile]s, and dividers
|
/// [Divider]s, [VerticalDividers]s, dividers between [ListTile]s, and dividers
|
||||||
/// between rows in [DataTable]s in this widget's subtree.
|
/// between rows in [DataTable]s in this widget's subtree.
|
||||||
class DividerTheme extends InheritedWidget {
|
class DividerTheme extends InheritedTheme {
|
||||||
/// Creates a divider theme that controls the configurations for
|
/// Creates a divider theme that controls the configurations for
|
||||||
/// [Divider]s, [VerticalDividers]s, dividers between [ListTile]s, and dividers
|
/// [Divider]s, [VerticalDividers]s, dividers between [ListTile]s, and dividers
|
||||||
/// between rows in [DataTable]s in its widget subtree.
|
/// between rows in [DataTable]s in its widget subtree.
|
||||||
@ -163,6 +163,12 @@ class DividerTheme extends InheritedWidget {
|
|||||||
return dividerTheme?.data ?? Theme.of(context).dividerTheme;
|
return dividerTheme?.data ?? Theme.of(context).dividerTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final DividerTheme ancestorTheme = context.ancestorWidgetOfExactType(DividerTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : DividerTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(DividerTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(DividerTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ enum ListTileStyle {
|
|||||||
///
|
///
|
||||||
/// The [Drawer] widget specifies a tile theme for its children which sets
|
/// The [Drawer] widget specifies a tile theme for its children which sets
|
||||||
/// [style] to [ListTileStyle.drawer].
|
/// [style] to [ListTileStyle.drawer].
|
||||||
class ListTileTheme extends InheritedWidget {
|
class ListTileTheme extends InheritedTheme {
|
||||||
/// Creates a list tile theme that controls the color and style parameters for
|
/// Creates a list tile theme that controls the color and style parameters for
|
||||||
/// [ListTile]s.
|
/// [ListTile]s.
|
||||||
const ListTileTheme({
|
const ListTileTheme({
|
||||||
@ -115,6 +115,20 @@ class ListTileTheme extends InheritedWidget {
|
|||||||
return result ?? const ListTileTheme();
|
return result ?? const ListTileTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final ListTileTheme ancestorTheme = context.ancestorWidgetOfExactType(ListTileTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : ListTileTheme(
|
||||||
|
dense: dense,
|
||||||
|
style: style,
|
||||||
|
selectedColor: selectedColor,
|
||||||
|
iconColor: iconColor,
|
||||||
|
textColor: textColor,
|
||||||
|
contentPadding: contentPadding,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(ListTileTheme oldWidget) {
|
bool updateShouldNotify(ListTileTheme oldWidget) {
|
||||||
return dense != oldWidget.dense
|
return dense != oldWidget.dense
|
||||||
|
@ -264,7 +264,7 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
|
|||||||
duration: kThemeChangeDuration,
|
duration: kThemeChangeDuration,
|
||||||
child: Baseline(
|
child: Baseline(
|
||||||
baseline: widget.height - _kBaselineOffsetFromBottom,
|
baseline: widget.height - _kBaselineOffsetFromBottom,
|
||||||
baselineType: style.textBaseline,
|
baselineType: style.textBaseline ?? TextBaseline.alphabetic,
|
||||||
child: buildChild(),
|
child: buildChild(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -608,6 +608,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||||||
this.semanticLabel,
|
this.semanticLabel,
|
||||||
this.shape,
|
this.shape,
|
||||||
this.color,
|
this.color,
|
||||||
|
this.showMenuContext,
|
||||||
|
this.captureInheritedThemes,
|
||||||
});
|
});
|
||||||
|
|
||||||
final RelativeRect position;
|
final RelativeRect position;
|
||||||
@ -619,6 +621,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||||||
final ShapeBorder shape;
|
final ShapeBorder shape;
|
||||||
final Color color;
|
final Color color;
|
||||||
final PopupMenuThemeData popupMenuTheme;
|
final PopupMenuThemeData popupMenuTheme;
|
||||||
|
final BuildContext showMenuContext;
|
||||||
|
final bool captureInheritedThemes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Animation<double> createAnimation() {
|
Animation<double> createAnimation() {
|
||||||
@ -656,10 +660,15 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
||||||
if (popupMenuTheme != null)
|
if (captureInheritedThemes) {
|
||||||
menu = PopupMenuTheme(data: PopupMenuThemeData(textStyle: popupMenuTheme.textStyle), child: menu);
|
menu = InheritedTheme.captureAll(showMenuContext, menu);
|
||||||
if (theme != null)
|
} else {
|
||||||
menu = Theme(data: theme, child: menu);
|
// 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 behvaior.
|
||||||
|
if (theme != null)
|
||||||
|
menu = Theme(data: theme, child: menu);
|
||||||
|
}
|
||||||
|
|
||||||
return MediaQuery.removePadding(
|
return MediaQuery.removePadding(
|
||||||
context: context,
|
context: context,
|
||||||
@ -743,10 +752,12 @@ Future<T> showMenu<T>({
|
|||||||
String semanticLabel,
|
String semanticLabel,
|
||||||
ShapeBorder shape,
|
ShapeBorder shape,
|
||||||
Color color,
|
Color color,
|
||||||
|
bool captureInheritedThemes = true,
|
||||||
}) {
|
}) {
|
||||||
assert(context != null);
|
assert(context != null);
|
||||||
assert(position != null);
|
assert(position != null);
|
||||||
assert(items != null && items.isNotEmpty);
|
assert(items != null && items.isNotEmpty);
|
||||||
|
assert(captureInheritedThemes != null);
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
|
||||||
String label = semanticLabel;
|
String label = semanticLabel;
|
||||||
@ -770,6 +781,8 @@ Future<T> showMenu<T>({
|
|||||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
shape: shape,
|
shape: shape,
|
||||||
color: color,
|
color: color,
|
||||||
|
showMenuContext: context,
|
||||||
|
captureInheritedThemes: captureInheritedThemes,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -862,9 +875,11 @@ 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)), // fails if passed both parameters
|
assert(!(child != null && icon != null)), // fails if passed both parameters
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@ -943,6 +958,11 @@ 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 [InheritedThemes], 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>();
|
||||||
}
|
}
|
||||||
@ -970,6 +990,7 @@ 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)
|
||||||
|
@ -121,7 +121,7 @@ class PopupMenuThemeData extends Diagnosticable {
|
|||||||
///
|
///
|
||||||
/// Values specified here are used for popup menu properties that are not
|
/// Values specified here are used for popup menu properties that are not
|
||||||
/// given an explicit non-null value.
|
/// given an explicit non-null value.
|
||||||
class PopupMenuTheme extends InheritedWidget {
|
class PopupMenuTheme extends InheritedTheme {
|
||||||
/// Creates a popup menu theme that controls the configurations for
|
/// Creates a popup menu theme that controls the configurations for
|
||||||
/// popup menus in its widget subtree.
|
/// popup menus in its widget subtree.
|
||||||
///
|
///
|
||||||
@ -149,6 +149,12 @@ class PopupMenuTheme extends InheritedWidget {
|
|||||||
return popupMenuTheme?.data ?? Theme.of(context).popupMenuTheme;
|
return popupMenuTheme?.data ?? Theme.of(context).popupMenuTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final PopupMenuTheme ancestorTheme = context.ancestorWidgetOfExactType(PopupMenuTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : PopupMenuTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(PopupMenuTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(PopupMenuTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ import 'theme_data.dart';
|
|||||||
/// {@macro flutter.material.slider.seeAlso.rangeSliderValueIndicatorShape}
|
/// {@macro flutter.material.slider.seeAlso.rangeSliderValueIndicatorShape}
|
||||||
/// {@macro flutter.material.slider.seeAlso.rangeSliderTrackShape}
|
/// {@macro flutter.material.slider.seeAlso.rangeSliderTrackShape}
|
||||||
/// {@macro flutter.material.slider.seeAlso.rangeSliderTickMarkShape}
|
/// {@macro flutter.material.slider.seeAlso.rangeSliderTickMarkShape}
|
||||||
class SliderTheme extends InheritedWidget {
|
class SliderTheme extends InheritedTheme {
|
||||||
/// Applies the given theme [data] to [child].
|
/// Applies the given theme [data] to [child].
|
||||||
///
|
///
|
||||||
/// The [data] and [child] arguments must not be null.
|
/// The [data] and [child] arguments must not be null.
|
||||||
@ -189,6 +189,12 @@ class SliderTheme extends InheritedWidget {
|
|||||||
return inheritedTheme != null ? inheritedTheme.data : Theme.of(context).sliderTheme;
|
return inheritedTheme != null ? inheritedTheme.data : Theme.of(context).sliderTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final SliderTheme ancestorTheme = context.ancestorWidgetOfExactType(SliderTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : SliderTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(SliderTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(SliderTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
@ -2928,4 +2934,3 @@ class RangeLabels {
|
|||||||
return '$runtimeType($start, $end)';
|
return '$runtimeType($start, $end)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ class Theme extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InheritedTheme extends InheritedWidget {
|
class _InheritedTheme extends InheritedTheme {
|
||||||
const _InheritedTheme({
|
const _InheritedTheme({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.theme,
|
@required this.theme,
|
||||||
@ -174,6 +174,12 @@ class _InheritedTheme extends InheritedWidget {
|
|||||||
|
|
||||||
final Theme theme;
|
final Theme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final _InheritedTheme ancestorTheme = context.ancestorWidgetOfExactType(_InheritedTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : Theme(data: theme.data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
|
bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
|
||||||
}
|
}
|
||||||
|
@ -227,7 +227,7 @@ class ToggleButtonsThemeData extends Diagnosticable {
|
|||||||
///
|
///
|
||||||
/// Values specified here are used for [ToggleButtons] properties that are not
|
/// Values specified here are used for [ToggleButtons] properties that are not
|
||||||
/// given an explicit non-null value.
|
/// given an explicit non-null value.
|
||||||
class ToggleButtonsTheme extends InheritedWidget {
|
class ToggleButtonsTheme extends InheritedTheme {
|
||||||
/// Creates a toggle buttons theme that controls the color and border
|
/// Creates a toggle buttons theme that controls the color and border
|
||||||
/// parameters for [ToggleButtons].
|
/// parameters for [ToggleButtons].
|
||||||
///
|
///
|
||||||
@ -256,6 +256,12 @@ class ToggleButtonsTheme extends InheritedWidget {
|
|||||||
return toggleButtonsTheme?.data ?? Theme.of(context).toggleButtonsTheme;
|
return toggleButtonsTheme?.data ?? Theme.of(context).toggleButtonsTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final ToggleButtonsTheme ancestorTheme = context.ancestorWidgetOfExactType(ToggleButtonsTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : ToggleButtonsTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(ToggleButtonsTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(ToggleButtonsTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,7 @@ class TooltipThemeData extends Diagnosticable {
|
|||||||
/// ),
|
/// ),
|
||||||
/// ```
|
/// ```
|
||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
class TooltipTheme extends InheritedWidget {
|
class TooltipTheme extends InheritedTheme {
|
||||||
/// Creates a tooltip theme that controls the configurations for
|
/// Creates a tooltip theme that controls the configurations for
|
||||||
/// [Tooltip].
|
/// [Tooltip].
|
||||||
///
|
///
|
||||||
@ -241,6 +241,12 @@ class TooltipTheme extends InheritedWidget {
|
|||||||
return tooltipTheme?.data ?? Theme.of(context).tooltipTheme;
|
return tooltipTheme?.data ?? Theme.of(context).tooltipTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final TooltipTheme ancestorTheme = context.ancestorWidgetOfExactType(TooltipTheme);
|
||||||
|
return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(TooltipTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(TooltipTheme oldWidget) => data != oldWidget.data;
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,12 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'icon_theme_data.dart';
|
import 'icon_theme_data.dart';
|
||||||
|
import 'inherited_theme.dart';
|
||||||
|
|
||||||
/// Controls the default color, opacity, and size of icons in a widget subtree.
|
/// Controls the default color, opacity, and size of icons in a widget subtree.
|
||||||
///
|
///
|
||||||
/// The icon theme is honored by [Icon] and [ImageIcon] widgets.
|
/// The icon theme is honored by [Icon] and [ImageIcon] widgets.
|
||||||
class IconTheme extends InheritedWidget {
|
class IconTheme extends InheritedTheme {
|
||||||
/// Creates an icon theme that controls the color, opacity, and size of
|
/// Creates an icon theme that controls the color, opacity, and size of
|
||||||
/// descendant widgets.
|
/// descendant widgets.
|
||||||
///
|
///
|
||||||
@ -70,6 +71,12 @@ class IconTheme extends InheritedWidget {
|
|||||||
@override
|
@override
|
||||||
bool updateShouldNotify(IconTheme oldWidget) => data != oldWidget.data;
|
bool updateShouldNotify(IconTheme oldWidget) => data != oldWidget.data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final IconTheme iconTheme = context.ancestorWidgetOfExactType(IconTheme);
|
||||||
|
return identical(this, iconTheme) ? child : IconTheme(data: data, child: child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
|
146
packages/flutter/lib/src/widgets/inherited_theme.dart
Normal file
146
packages/flutter/lib/src/widgets/inherited_theme.dart
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// {@tool snippet --template=freeform}
|
||||||
|
/// This example demonstrates how `InheritedTheme.captureAll()` 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
|
||||||
|
/// is actually shown.
|
||||||
|
///
|
||||||
|
/// If the same code is run without `InheritedTheme.captureAll(), 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.
|
||||||
|
///
|
||||||
|
/// ```dart imports
|
||||||
|
/// import 'package:flutter/material.dart';
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```dart main
|
||||||
|
/// void main() {
|
||||||
|
/// runApp(MyApp());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// class MyAppBody extends StatelessWidget {
|
||||||
|
/// @override
|
||||||
|
/// Widget build(BuildContext 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(
|
||||||
|
/// alignment: Alignment.center,
|
||||||
|
/// color: Theme.of(context).colorScheme.surface,
|
||||||
|
/// child: Text('Hello World'),
|
||||||
|
/// ));
|
||||||
|
/// },
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// },
|
||||||
|
/// child: Center(child: Text('Tap Here')),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// class MyApp extends StatelessWidget {
|
||||||
|
/// @override
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// return MaterialApp(
|
||||||
|
/// home: Scaffold(
|
||||||
|
/// // Override the DefaultTextStyle defined by the Scaffold.
|
||||||
|
/// // Descendant widgets will inherit this big blue text style.
|
||||||
|
/// body: DefaultTextStyle(
|
||||||
|
/// style: TextStyle(fontSize: 48, color: Colors.blue),
|
||||||
|
/// child: MyAppBody(),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// {@end-tool}
|
||||||
|
abstract class InheritedTheme extends InheritedWidget {
|
||||||
|
/// Abstract const constructor. This constructor enables subclasses to provide
|
||||||
|
/// const constructors so that they can be used in const expressions.
|
||||||
|
|
||||||
|
const InheritedTheme({
|
||||||
|
Key key,
|
||||||
|
@required Widget child,
|
||||||
|
}) : super(key: key, child: 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:
|
||||||
|
/// ```dart
|
||||||
|
/// Widget wrap(BuildContext context, Widget child) {
|
||||||
|
/// final TooltipTheme ancestorTheme = context.ancestorWidgetOfExactType(TooltipTheme);
|
||||||
|
/// return identical(this, ancestorTheme) ? child : 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) {
|
||||||
|
assert(child != null);
|
||||||
|
assert(context != null);
|
||||||
|
|
||||||
|
final List<InheritedTheme> themes = <InheritedTheme>[];
|
||||||
|
context.visitAncestorElements((Element ancestor) {
|
||||||
|
if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) {
|
||||||
|
final InheritedTheme theme = ancestor.widget;
|
||||||
|
themes.add(theme);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return _CaptureAll(themes: themes, child: child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CaptureAll extends StatelessWidget {
|
||||||
|
const _CaptureAll({
|
||||||
|
Key key,
|
||||||
|
@required this.themes,
|
||||||
|
@required this.child
|
||||||
|
}) : assert(themes != null), assert(child != null), super(key: key);
|
||||||
|
|
||||||
|
final List<InheritedTheme> themes;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget wrappedChild = child;
|
||||||
|
for (InheritedTheme theme in themes)
|
||||||
|
wrappedChild = theme.wrap(context, wrappedChild);
|
||||||
|
return wrappedChild;
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'package:flutter/painting.dart';
|
|||||||
|
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
|
import 'inherited_theme.dart';
|
||||||
import 'media_query.dart';
|
import 'media_query.dart';
|
||||||
|
|
||||||
// Examples can assume:
|
// Examples can assume:
|
||||||
@ -20,7 +21,7 @@ import 'media_query.dart';
|
|||||||
/// smoothly over a given duration.
|
/// smoothly over a given duration.
|
||||||
/// * [DefaultTextStyleTransition], which takes a provided [Animation] to
|
/// * [DefaultTextStyleTransition], which takes a provided [Animation] to
|
||||||
/// animate changes in text style smoothly over time.
|
/// animate changes in text style smoothly over time.
|
||||||
class DefaultTextStyle extends InheritedWidget {
|
class DefaultTextStyle extends InheritedTheme {
|
||||||
/// Creates a default text style for the given subtree.
|
/// Creates a default text style for the given subtree.
|
||||||
///
|
///
|
||||||
/// Consider using [DefaultTextStyle.merge] to inherit styling information
|
/// Consider using [DefaultTextStyle.merge] to inherit styling information
|
||||||
@ -161,6 +162,20 @@ class DefaultTextStyle extends InheritedWidget {
|
|||||||
textWidthBasis != oldWidget.textWidthBasis;
|
textWidthBasis != oldWidget.textWidthBasis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget wrap(BuildContext context, Widget child) {
|
||||||
|
final DefaultTextStyle defaultTextStyle = context.ancestorWidgetOfExactType(DefaultTextStyle);
|
||||||
|
return identical(this, defaultTextStyle) ? child : DefaultTextStyle(
|
||||||
|
style: style,
|
||||||
|
textAlign: textAlign,
|
||||||
|
softWrap: softWrap,
|
||||||
|
overflow: overflow,
|
||||||
|
maxLines: maxLines,
|
||||||
|
textWidthBasis: textWidthBasis,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
|
@ -52,6 +52,7 @@ export 'src/widgets/image_icon.dart';
|
|||||||
export 'src/widgets/implicit_animations.dart';
|
export 'src/widgets/implicit_animations.dart';
|
||||||
export 'src/widgets/inherited_model.dart';
|
export 'src/widgets/inherited_model.dart';
|
||||||
export 'src/widgets/inherited_notifier.dart';
|
export 'src/widgets/inherited_notifier.dart';
|
||||||
|
export 'src/widgets/inherited_theme.dart';
|
||||||
export 'src/widgets/layout_builder.dart';
|
export 'src/widgets/layout_builder.dart';
|
||||||
export 'src/widgets/list_wheel_scroll_view.dart';
|
export 'src/widgets/list_wheel_scroll_view.dart';
|
||||||
export 'src/widgets/localizations.dart';
|
export 'src/widgets/localizations.dart';
|
||||||
|
723
packages/flutter/test/material/inherited_theme_test.dart
Normal file
723
packages/flutter/test/material/inherited_theme_test.dart
Normal file
@ -0,0 +1,723 @@
|
|||||||
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../rendering/mock_canvas.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Theme.wrap()', (WidgetTester tester) async {
|
||||||
|
const Color primaryColor = Color(0xFF00FF00);
|
||||||
|
final Key primaryContainerKey = UniqueKey();
|
||||||
|
|
||||||
|
// Effectively the same as a StatelessWidget subclass.
|
||||||
|
final Widget primaryBox = Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
key: primaryContainerKey,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Builder( // Introduce a context so the app's Theme is visible.
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
navigatorContext = context;
|
||||||
|
return Theme(
|
||||||
|
data: Theme.of(context).copyWith(primaryColor: primaryColor),
|
||||||
|
child: Builder( // Introduce a context so the shadow Theme is visible to captureAll().
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push unwrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// The primaryBox will see the default Theme when built.
|
||||||
|
builder: (BuildContext _) => primaryBox,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push wrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// Capture the shadow Theme.
|
||||||
|
builder: (BuildContext _) => InheritedTheme.captureAll(context, primaryBox),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color containerColor() {
|
||||||
|
final BoxDecoration decoration = tester.widget<Container>(find.byKey(primaryContainerKey)).decoration;
|
||||||
|
return decoration.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
// Show the route which contains primaryBox which was wrapped with
|
||||||
|
// InheritedTheme.captureAll().
|
||||||
|
await tester.tap(find.text('push wrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(containerColor(), primaryColor);
|
||||||
|
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
|
||||||
|
// Show the route which contains primaryBox
|
||||||
|
await tester.tap(find.text('push unwrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(containerColor(), isNot(primaryColor));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('PopupMenuTheme.wrap()', (WidgetTester tester) async {
|
||||||
|
const double menuFontSize = 24;
|
||||||
|
const Color menuTextColor = Color(0xFF0000FF);
|
||||||
|
bool captureInheritedThemes = true;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: PopupMenuTheme(
|
||||||
|
data: const PopupMenuThemeData(
|
||||||
|
// The menu route's elevation, shape, and color are defined by the
|
||||||
|
// current context, so they're not affected by ThemeData.captureAll().
|
||||||
|
textStyle: TextStyle(fontSize: menuFontSize, color: menuTextColor),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: PopupMenuButton<int>(
|
||||||
|
// 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) {
|
||||||
|
return const <PopupMenuEntry<int>>[
|
||||||
|
PopupMenuItem<int>(value: 1, child: Text('One')),
|
||||||
|
PopupMenuItem<int>(value: 2, child: Text('Two')),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle itemTextStyle(String text) {
|
||||||
|
return tester.widget<RichText>(
|
||||||
|
find.descendant(of: find.text(text), matching: find.byType(RichText)),
|
||||||
|
).text.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
await tester.tap(find.text('show popupmenu'));
|
||||||
|
await tester.pumpAndSettle(); // menu route animation
|
||||||
|
expect(itemTextStyle('One').fontSize, menuFontSize);
|
||||||
|
expect(itemTextStyle('One').color, menuTextColor);
|
||||||
|
expect(itemTextStyle('Two').fontSize, menuFontSize);
|
||||||
|
expect(itemTextStyle('Two').color, menuTextColor);
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
const Color bannerBackgroundColor = Color(0xFF0000FF);
|
||||||
|
const double bannerFontSize = 48;
|
||||||
|
const Color bannerTextColor = Color(0xFF00FF00);
|
||||||
|
|
||||||
|
final Widget banner = MaterialBanner(
|
||||||
|
content: const Text('hello'),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
child: const Text('action'),
|
||||||
|
onPressed: () { },
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: MaterialBannerTheme(
|
||||||
|
data: const MaterialBannerThemeData(
|
||||||
|
backgroundColor: bannerBackgroundColor,
|
||||||
|
contentTextStyle: TextStyle(fontSize: bannerFontSize, color: bannerTextColor),
|
||||||
|
),
|
||||||
|
child: Builder( // Introduce a context so the shadow BannerTheme is visible to captureAll().
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
navigatorContext = context;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push unwrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// The Banner will see the default BannerTheme when built.
|
||||||
|
builder: (BuildContext _) => banner,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push wrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// Capture the shadow BannerTheme.
|
||||||
|
builder: (BuildContext _) => InheritedTheme.captureAll(context, banner),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color bannerColor() {
|
||||||
|
final BoxDecoration decoration = tester.widget<Container>(
|
||||||
|
find.descendant(of: find.byType(MaterialBanner), matching: find.byType(Container)).first,
|
||||||
|
).decoration;
|
||||||
|
return decoration.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle getTextStyle(String text) {
|
||||||
|
return tester.widget<RichText>(
|
||||||
|
find.descendant(
|
||||||
|
of: find.text(text),
|
||||||
|
matching: find.byType(RichText),
|
||||||
|
),
|
||||||
|
).text.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
// Show the route which contains the banner.
|
||||||
|
await tester.tap(find.text('push wrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(bannerColor(), bannerBackgroundColor);
|
||||||
|
expect(getTextStyle('hello').fontSize, bannerFontSize);
|
||||||
|
expect(getTextStyle('hello').color, bannerTextColor);
|
||||||
|
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
|
||||||
|
await tester.tap(find.text('push unwrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(bannerColor(), isNot(bannerBackgroundColor));
|
||||||
|
expect(getTextStyle('hello').fontSize, isNot(bannerFontSize));
|
||||||
|
expect(getTextStyle('hello').color, isNot(bannerTextColor));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('DividerTheme.wrap()', (WidgetTester tester) async {
|
||||||
|
const Color dividerColor = Color(0xFF0000FF);
|
||||||
|
const double dividerSpace = 13;
|
||||||
|
const double dividerThickness = 7;
|
||||||
|
const Widget divider = Center(child: Divider());
|
||||||
|
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: DividerTheme(
|
||||||
|
data: const DividerThemeData(
|
||||||
|
color: dividerColor,
|
||||||
|
space: dividerSpace,
|
||||||
|
thickness: dividerThickness,
|
||||||
|
),
|
||||||
|
child: Builder( // Introduce a context so the shadow DividerTheme is visible to captureAll().
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
navigatorContext = context;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push unwrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// The Banner will see the default BannerTheme when built.
|
||||||
|
builder: (BuildContext _) => divider,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push wrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// Capture the shadow BannerTheme.
|
||||||
|
builder: (BuildContext _) => InheritedTheme.captureAll(context, divider),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderSide dividerBorder() {
|
||||||
|
final BoxDecoration decoration = tester.widget<Container>(
|
||||||
|
find.descendant(of: find.byType(Divider), matching: find.byType(Container)).first,
|
||||||
|
).decoration;
|
||||||
|
return decoration.border.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
// Show a route which contains a divider.
|
||||||
|
await tester.tap(find.text('push wrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(tester.getSize(find.byType(Divider)).height, dividerSpace);
|
||||||
|
expect(dividerBorder().color, dividerColor);
|
||||||
|
expect(dividerBorder().width, dividerThickness);
|
||||||
|
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
|
||||||
|
await tester.tap(find.text('push unwrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(tester.getSize(find.byType(Divider)).height, isNot(dividerSpace));
|
||||||
|
expect(dividerBorder().color, isNot(dividerColor));
|
||||||
|
expect(dividerBorder().width, isNot(dividerThickness));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ListTileTheme.wrap()', (WidgetTester tester) async {
|
||||||
|
const Color tileSelectedColor = Color(0xFF00FF00);
|
||||||
|
const Color tileIconColor = Color(0xFF0000FF);
|
||||||
|
const Color tileTextColor = Color(0xFFFF0000);
|
||||||
|
|
||||||
|
final Key selectedIconKey = UniqueKey();
|
||||||
|
final Key unselectedIconKey = UniqueKey();
|
||||||
|
|
||||||
|
final Widget listTiles = Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.computer, key: selectedIconKey),
|
||||||
|
title: const Text('selected'),
|
||||||
|
enabled: true,
|
||||||
|
selected: true,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.add, key: unselectedIconKey),
|
||||||
|
title: const Text('unselected'),
|
||||||
|
enabled: true,
|
||||||
|
selected: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: ListTileTheme(
|
||||||
|
selectedColor: tileSelectedColor,
|
||||||
|
textColor: tileTextColor,
|
||||||
|
iconColor: tileIconColor,
|
||||||
|
child: Builder( // Introduce a context so the shadow ListTileTheme is visible to captureAll().
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
navigatorContext = context;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push unwrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// The Banner will see the default BannerTheme when built.
|
||||||
|
builder: (BuildContext _) => listTiles,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push wrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// Capture the shadow BannerTheme.
|
||||||
|
builder: (BuildContext _) => InheritedTheme.captureAll(context, listTiles),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle getTextStyle(String text) {
|
||||||
|
return tester.widget<RichText>(
|
||||||
|
find.descendant(of: find.text(text), matching: find.byType(RichText)),
|
||||||
|
).text.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle getIconStyle(Key key) {
|
||||||
|
return tester.widget<RichText>(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byKey(key),
|
||||||
|
matching: find.byType(RichText),
|
||||||
|
),
|
||||||
|
).text.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
// Show a route which contains listTiles.
|
||||||
|
await tester.tap(find.text('push wrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(getTextStyle('unselected').color, tileTextColor);
|
||||||
|
expect(getTextStyle('selected').color, tileSelectedColor);
|
||||||
|
expect(getIconStyle(selectedIconKey).color, tileSelectedColor);
|
||||||
|
expect(getIconStyle(unselectedIconKey).color, tileIconColor);
|
||||||
|
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
|
||||||
|
await tester.tap(find.text('push unwrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(getTextStyle('unselected').color, isNot(tileTextColor));
|
||||||
|
expect(getTextStyle('selected').color, isNot(tileSelectedColor));
|
||||||
|
expect(getIconStyle(selectedIconKey).color, isNot(tileSelectedColor));
|
||||||
|
expect(getIconStyle(unselectedIconKey).color, isNot(tileIconColor));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SliderTheme.wrap()', (WidgetTester tester) async {
|
||||||
|
const Color activeTrackColor = Color(0xFF00FF00);
|
||||||
|
const Color inactiveTrackColor = Color(0xFF0000FF);
|
||||||
|
const Color thumbColor = Color(0xFFFF0000);
|
||||||
|
|
||||||
|
final Widget slider = Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Slider(
|
||||||
|
value: 0.5,
|
||||||
|
onChanged: (double value) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: SliderTheme(
|
||||||
|
data: const SliderThemeData(
|
||||||
|
activeTrackColor: activeTrackColor,
|
||||||
|
inactiveTrackColor: inactiveTrackColor,
|
||||||
|
thumbColor: thumbColor,
|
||||||
|
),
|
||||||
|
child: Builder( // Introduce a context so the shadow SliderTheme is visible to captureAll().
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
navigatorContext = context;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push unwrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// The slider will see the default SliderTheme when built.
|
||||||
|
builder: (BuildContext _) => slider,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push wrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// Capture the shadow SliderTheme.
|
||||||
|
builder: (BuildContext _) => InheritedTheme.captureAll(context, slider),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
// Show a route which contains listTiles.
|
||||||
|
await tester.tap(find.text('push wrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
|
||||||
|
expect(sliderBox, paints..rect(color: activeTrackColor)..rect(color: inactiveTrackColor));
|
||||||
|
expect(sliderBox, paints..circle(color: thumbColor));
|
||||||
|
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
|
||||||
|
await tester.tap(find.text('push unwrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
|
||||||
|
expect(sliderBox, isNot(paints..rect(color: activeTrackColor)..rect(color: inactiveTrackColor)));
|
||||||
|
expect(sliderBox, isNot(paints..circle(color: thumbColor)));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ToggleButtonsTheme.wrap()', (WidgetTester tester) async {
|
||||||
|
const Color buttonColor = Color(0xFF00FF00);
|
||||||
|
const Color selectedButtonColor = Color(0xFFFF0000);
|
||||||
|
|
||||||
|
final Widget toggleButtons = Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: ToggleButtons(
|
||||||
|
children: const <Widget>[
|
||||||
|
Text('selected'),
|
||||||
|
Text('unselected'),
|
||||||
|
],
|
||||||
|
isSelected: const <bool>[true, false],
|
||||||
|
onPressed: (int index) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: ToggleButtonsTheme(
|
||||||
|
data: const ToggleButtonsThemeData(
|
||||||
|
color: buttonColor,
|
||||||
|
selectedColor: selectedButtonColor,
|
||||||
|
),
|
||||||
|
child: Builder( // Introduce a context so the shadow ToggleButtonsTheme is visible to captureAll().
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
navigatorContext = context;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push unwrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// The slider will see the default ToggleButtonsTheme when built.
|
||||||
|
builder: (BuildContext _) => toggleButtons,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push wrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// Capture the shadow toggleButtons.
|
||||||
|
builder: (BuildContext _) => InheritedTheme.captureAll(context, toggleButtons),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getTextColor(String text) {
|
||||||
|
return tester.widget<RichText>(
|
||||||
|
find.descendant(of: find.text(text), matching: find.byType(RichText)),
|
||||||
|
).text.style.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
// Show a route which contains toggleButtons.
|
||||||
|
await tester.tap(find.text('push wrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(getTextColor('selected'), selectedButtonColor);
|
||||||
|
expect(getTextColor('unselected'), buttonColor);
|
||||||
|
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
|
||||||
|
await tester.tap(find.text('push unwrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(getTextColor('selected'), isNot(selectedButtonColor));
|
||||||
|
expect(getTextColor('unselected'), isNot(buttonColor));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ButtonTheme.wrap()', (WidgetTester tester) async {
|
||||||
|
const Color buttonColor = Color(0xFF00FF00);
|
||||||
|
const Color disabledButtonColor = Color(0xFFFF0000);
|
||||||
|
|
||||||
|
final Widget buttons = Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
const RaisedButton(child: Text('disabled'), onPressed: null),
|
||||||
|
RaisedButton(child: const Text('enabled'), onPressed: () { }),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: ButtonTheme.fromButtonThemeData(
|
||||||
|
data: const ButtonThemeData(
|
||||||
|
buttonColor: buttonColor,
|
||||||
|
disabledColor: disabledButtonColor,
|
||||||
|
),
|
||||||
|
child: Builder( // Introduce a context so the shadow ButtonTheme is visible to captureAll().
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
navigatorContext = context;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push unwrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// The slider will see the default ButtonTheme when built.
|
||||||
|
builder: (BuildContext _) => buttons,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: const Text('push wrapped'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push<void>(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
// Capture the shadow toggleButtons.
|
||||||
|
builder: (BuildContext _) => InheritedTheme.captureAll(context, buttons),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getButtonColor(String text) {
|
||||||
|
return tester.widget<Material>(
|
||||||
|
find.descendant(
|
||||||
|
of: find.widgetWithText(RawMaterialButton, text),
|
||||||
|
matching: find.byType(Material),
|
||||||
|
)
|
||||||
|
).color;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
|
||||||
|
// Show a route which contains toggleButtons.
|
||||||
|
await tester.tap(find.text('push wrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(getButtonColor('disabled'), disabledButtonColor);
|
||||||
|
expect(getButtonColor('enabled'), buttonColor);
|
||||||
|
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
|
||||||
|
await tester.tap(find.text('push unwrapped'));
|
||||||
|
await tester.pumpAndSettle(); // route animation
|
||||||
|
expect(getButtonColor('disabled'), isNot(disabledButtonColor));
|
||||||
|
expect(getButtonColor('enabled'), isNot(buttonColor));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
149
packages/flutter/test/widgets/inherited_theme_test.dart
Normal file
149
packages/flutter/test/widgets/inherited_theme_test.dart
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
class TestRoute extends PageRouteBuilder<void> {
|
||||||
|
TestRoute(Widget child) : super(
|
||||||
|
pageBuilder: (BuildContext _, Animation<double> __, Animation<double> ___) => child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class IconTextBox extends StatelessWidget {
|
||||||
|
const IconTextBox(this.text);
|
||||||
|
final String text;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[const Icon(IconData(0x41, fontFamily: 'Roboto')), Text(text)],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('InheritedTheme.captureAll()', (WidgetTester tester) async {
|
||||||
|
const double fontSize = 32;
|
||||||
|
const double iconSize = 48;
|
||||||
|
const Color textColor = Color(0xFF00FF00);
|
||||||
|
const Color iconColor = Color(0xFF0000FF);
|
||||||
|
bool useCaptureAll = false;
|
||||||
|
BuildContext navigatorContext;
|
||||||
|
|
||||||
|
Widget buildFrame() {
|
||||||
|
return WidgetsApp(
|
||||||
|
color: const Color(0xFFFFFFFF),
|
||||||
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
|
return TestRoute(
|
||||||
|
// The outer DefaultTextStyle and IconTheme widgets must have
|
||||||
|
// no effect on the test because InheritedTheme.captureAll()
|
||||||
|
// is required to only save the closest InheritedTheme ancestors.
|
||||||
|
DefaultTextStyle(
|
||||||
|
style: const TextStyle(fontSize: iconSize, color: iconColor),
|
||||||
|
child: IconTheme(
|
||||||
|
data: const IconThemeData(size: fontSize, color: textColor),
|
||||||
|
// The inner DefaultTextStyle and IconTheme widgets define
|
||||||
|
// InheritedThemes that captureAll() will wrap() around
|
||||||
|
// TestRoute's IconTextBox child.
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: const TextStyle(fontSize: fontSize, color: textColor),
|
||||||
|
child: IconTheme(
|
||||||
|
data: const IconThemeData(size: iconSize, color: iconColor),
|
||||||
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
navigatorContext = context;
|
||||||
|
Navigator.of(context).push(
|
||||||
|
TestRoute(
|
||||||
|
useCaptureAll
|
||||||
|
? InheritedTheme.captureAll(context, const IconTextBox('Hello'))
|
||||||
|
: const IconTextBox('Hello')
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const IconTextBox('Tap'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle getIconStyle() {
|
||||||
|
return tester.widget<RichText>(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byType(Icon),
|
||||||
|
matching: find.byType(RichText),
|
||||||
|
),
|
||||||
|
).text.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle getTextStyle(String text) {
|
||||||
|
return tester.widget<RichText>(
|
||||||
|
find.descendant(
|
||||||
|
of: find.text(text),
|
||||||
|
matching: find.byType(RichText),
|
||||||
|
),
|
||||||
|
).text.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
useCaptureAll = false;
|
||||||
|
await tester.pumpWidget(buildFrame());
|
||||||
|
expect(find.text('Tap'), findsOneWidget);
|
||||||
|
expect(find.text('Hello'), findsNothing);
|
||||||
|
expect(getTextStyle('Tap').color, textColor);
|
||||||
|
expect(getTextStyle('Tap').fontSize, fontSize);
|
||||||
|
expect(getIconStyle().color, iconColor);
|
||||||
|
expect(getIconStyle().fontSize, iconSize);
|
||||||
|
|
||||||
|
// Tap to show the TestRoute
|
||||||
|
await tester.tap(find.text('Tap'));
|
||||||
|
await tester.pumpAndSettle(); // route transition
|
||||||
|
expect(find.text('Tap'), findsNothing);
|
||||||
|
expect(find.text('Hello'), findsOneWidget);
|
||||||
|
// The new route's text and icons will NOT inherit the DefaultTextStyle or
|
||||||
|
// IconTheme values.
|
||||||
|
expect(getTextStyle('Hello').color, isNot(textColor));
|
||||||
|
expect(getTextStyle('Hello').fontSize, isNot(fontSize));
|
||||||
|
expect(getIconStyle().color, isNot(iconColor));
|
||||||
|
expect(getIconStyle().fontSize, isNot(iconSize));
|
||||||
|
|
||||||
|
// Return to the home route
|
||||||
|
useCaptureAll = true;
|
||||||
|
Navigator.of(navigatorContext).pop();
|
||||||
|
await tester.pumpAndSettle(); // route transition
|
||||||
|
|
||||||
|
// Verify that all is the same as it was when the test started
|
||||||
|
expect(find.text('Tap'), findsOneWidget);
|
||||||
|
expect(find.text('Hello'), findsNothing);
|
||||||
|
expect(getTextStyle('Tap').color, textColor);
|
||||||
|
expect(getTextStyle('Tap').fontSize, fontSize);
|
||||||
|
expect(getIconStyle().color, iconColor);
|
||||||
|
expect(getIconStyle().fontSize, iconSize);
|
||||||
|
|
||||||
|
// Tap to show the TestRoute. The test route's IconTextBox will have been
|
||||||
|
// wrapped with InheritedTheme.captureAll().
|
||||||
|
await tester.tap(find.text('Tap'));
|
||||||
|
await tester.pumpAndSettle(); // route transition
|
||||||
|
expect(find.text('Tap'), findsNothing);
|
||||||
|
expect(find.text('Hello'), findsOneWidget);
|
||||||
|
// The new route's text and icons will inherit the DefaultTextStyle or
|
||||||
|
// IconTheme values because captureAll.
|
||||||
|
expect(getTextStyle('Hello').color, textColor);
|
||||||
|
expect(getTextStyle('Hello').fontSize, fontSize);
|
||||||
|
expect(getIconStyle().color, iconColor);
|
||||||
|
expect(getIconStyle().fontSize, iconSize);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user