Normalize Dialog theme (#153982)
This PR is to make preparations to make `DialogTheme` conform to Flutter's conventions for component themes: * Added a `DialogThemeData` class which defines overrides for the defaults for `Dialog` properties. * Added 2 `DialogTheme` constructor parameters: `DialogThemeData? data` and `Widget? child`. This is now the preferred way to configure a `DialogTheme`: ``` DialogTheme( data: DialogThemeData(color: xxx, elevation: xxx, ...), child: Dialog(...) ) ``` These two properties are made nullable to not break existing apps which has customized `ThemeData.dialogTheme`. * Changed the type of theme defaults from `DialogTheme` to `DialogThemeData`. TODO: * Fix internal failures. * Change the type of `ThemeData.dialogTheme` from `DialogTheme` to `DialogThemeData`. This may cause breaking changes, a migration guide will be created. Addresses the "theme normalization" sub project within https://github.com/flutter/flutter/issues/91772
This commit is contained in:
parent
4d17998755
commit
6dd929ab28
@ -12,7 +12,7 @@ class DialogTemplate extends TokenTemplate {
|
||||
|
||||
@override
|
||||
String generate() => '''
|
||||
class _${blockName}DefaultsM3 extends DialogTheme {
|
||||
class _${blockName}DefaultsM3 extends DialogThemeData {
|
||||
_${blockName}DefaultsM3(this.context)
|
||||
: super(
|
||||
alignment: Alignment.center,
|
||||
@ -54,7 +54,7 @@ class DialogFullscreenTemplate extends TokenTemplate {
|
||||
|
||||
@override
|
||||
String generate() => '''
|
||||
class _${blockName}DefaultsM3 extends DialogTheme {
|
||||
class _${blockName}DefaultsM3 extends DialogThemeData {
|
||||
const _${blockName}DefaultsM3(this.context): super(clipBehavior: Clip.none);
|
||||
|
||||
final BuildContext context;
|
||||
|
@ -108,7 +108,7 @@ class Dialog extends StatelessWidget {
|
||||
/// and a surface tint overlay on the background color if [surfaceTintColor] is
|
||||
/// non null.
|
||||
///
|
||||
/// If null then [DialogTheme.elevation] is used, and if that is null then
|
||||
/// If null then [DialogThemeData.elevation] is used, and if that is null then
|
||||
/// the elevation will match the Material Design specification for Dialogs.
|
||||
///
|
||||
/// See also:
|
||||
@ -197,7 +197,7 @@ class Dialog extends StatelessWidget {
|
||||
/// See the enum [Clip] for details of all possible options and their common
|
||||
/// use cases.
|
||||
///
|
||||
/// If null, then [DialogTheme.clipBehavior] is used. If that is also null,
|
||||
/// If null, then [DialogThemeData.clipBehavior] is used. If that is also null,
|
||||
/// defaults to [Clip.none].
|
||||
/// {@endtemplate}
|
||||
final Clip? clipBehavior;
|
||||
@ -214,7 +214,7 @@ class Dialog extends StatelessWidget {
|
||||
/// {@template flutter.material.dialog.alignment}
|
||||
/// How to align the [Dialog].
|
||||
///
|
||||
/// If null, then [DialogTheme.alignment] is used. If that is also null, the
|
||||
/// If null, then [DialogThemeData.alignment] is used. If that is also null, the
|
||||
/// default is [Alignment.center].
|
||||
/// {@endtemplate}
|
||||
final AlignmentGeometry? alignment;
|
||||
@ -230,10 +230,10 @@ class Dialog extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final DialogTheme dialogTheme = DialogTheme.of(context);
|
||||
final DialogThemeData dialogTheme = DialogTheme.of(context).data;
|
||||
final EdgeInsets effectivePadding = MediaQuery.viewInsetsOf(context)
|
||||
+ (insetPadding ?? dialogTheme.insetPadding ?? _defaultInsetPadding);
|
||||
final DialogTheme defaults = theme.useMaterial3
|
||||
final DialogThemeData defaults = theme.useMaterial3
|
||||
? (_fullscreen ? _DialogFullscreenDefaultsM3(context) : _DialogDefaultsM3(context))
|
||||
: _DialogDefaultsM2(context);
|
||||
|
||||
@ -481,7 +481,7 @@ class AlertDialog extends StatelessWidget {
|
||||
|
||||
/// Color for the [Icon] in the [icon] of this [AlertDialog].
|
||||
///
|
||||
/// If null, [DialogTheme.iconColor] is used. If that is null, defaults to
|
||||
/// If null, [DialogThemeData.iconColor] is used. If that is null, defaults to
|
||||
/// color scheme's [ColorScheme.secondary] if [ThemeData.useMaterial3] is
|
||||
/// true, black otherwise.
|
||||
final Color? iconColor;
|
||||
@ -519,7 +519,7 @@ class AlertDialog extends StatelessWidget {
|
||||
|
||||
/// Style for the text in the [title] of this [AlertDialog].
|
||||
///
|
||||
/// If null, [DialogTheme.titleTextStyle] is used. If that's null, defaults to
|
||||
/// If null, [DialogThemeData.titleTextStyle] is used. If that's null, defaults to
|
||||
/// [TextTheme.headlineSmall] of [ThemeData.textTheme] if
|
||||
/// [ThemeData.useMaterial3] is true, [TextTheme.titleLarge] otherwise.
|
||||
final TextStyle? titleTextStyle;
|
||||
@ -554,7 +554,7 @@ class AlertDialog extends StatelessWidget {
|
||||
|
||||
/// Style for the text in the [content] of this [AlertDialog].
|
||||
///
|
||||
/// If null, [DialogTheme.contentTextStyle] is used. If that's null, defaults
|
||||
/// If null, [DialogThemeData.contentTextStyle] is used. If that's null, defaults
|
||||
/// to [TextTheme.bodyMedium] of [ThemeData.textTheme] if
|
||||
/// [ThemeData.useMaterial3] is true, [TextTheme.titleMedium] otherwise.
|
||||
final TextStyle? contentTextStyle;
|
||||
@ -720,8 +720,8 @@ class AlertDialog extends StatelessWidget {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
final ThemeData theme = Theme.of(context);
|
||||
|
||||
final DialogTheme dialogTheme = DialogTheme.of(context);
|
||||
final DialogTheme defaults = theme.useMaterial3 ? _DialogDefaultsM3(context) : _DialogDefaultsM2(context);
|
||||
final DialogThemeData dialogTheme = DialogTheme.of(context).data;
|
||||
final DialogThemeData defaults = theme.useMaterial3 ? _DialogDefaultsM3(context) : _DialogDefaultsM2(context);
|
||||
|
||||
String? label = semanticLabel;
|
||||
switch (theme.platform) {
|
||||
@ -1155,7 +1155,7 @@ class SimpleDialog extends StatelessWidget {
|
||||
|
||||
/// Style for the text in the [title] of this [SimpleDialog].
|
||||
///
|
||||
/// If null, [DialogTheme.titleTextStyle] is used. If that's null, defaults to
|
||||
/// If null, [DialogThemeData.titleTextStyle] is used. If that's null, defaults to
|
||||
/// [TextTheme.titleLarge] of [ThemeData.textTheme].
|
||||
final TextStyle? titleTextStyle;
|
||||
|
||||
@ -1235,7 +1235,7 @@ class SimpleDialog extends StatelessWidget {
|
||||
|
||||
// The paddingScaleFactor is used to adjust the padding of Dialog
|
||||
// children.
|
||||
final TextStyle defaultTextStyle = titleTextStyle ?? DialogTheme.of(context).titleTextStyle ?? theme.textTheme.titleLarge!;
|
||||
final TextStyle defaultTextStyle = titleTextStyle ?? DialogTheme.of(context).data.titleTextStyle ?? theme.textTheme.titleLarge!;
|
||||
final double fontSize = defaultTextStyle.fontSize ?? kDefaultFontSize;
|
||||
final double fontSizeToScale = fontSize == 0.0 ? kDefaultFontSize : fontSize;
|
||||
final double effectiveTextScale = MediaQuery.textScalerOf(context).scale(fontSizeToScale) / fontSizeToScale;
|
||||
@ -1342,7 +1342,7 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a
|
||||
///
|
||||
/// The `barrierColor` argument is used to specify the color of the modal
|
||||
/// barrier that darkens everything below the dialog. If `null` the `barrierColor`
|
||||
/// field from `DialogTheme` is used. If that is `null` the default color
|
||||
/// field from `DialogThemeData` is used. If that is `null` the default color
|
||||
/// `Colors.black54` is used.
|
||||
///
|
||||
/// The `useSafeArea` argument is used to indicate if the dialog should only
|
||||
@ -1443,7 +1443,10 @@ Future<T?> showDialog<T>({
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(DialogRoute<T>(
|
||||
context: context,
|
||||
builder: builder,
|
||||
barrierColor: barrierColor ?? Theme.of(context).dialogTheme.barrierColor ?? Colors.black54,
|
||||
barrierColor: barrierColor
|
||||
?? DialogTheme.of(context).barrierColor
|
||||
?? Theme.of(context).dialogTheme.data.barrierColor
|
||||
?? Colors.black54,
|
||||
barrierDismissible: barrierDismissible,
|
||||
barrierLabel: barrierLabel,
|
||||
useSafeArea: useSafeArea,
|
||||
@ -1633,7 +1636,7 @@ double _scalePadding(double textScaleFactor) {
|
||||
}
|
||||
|
||||
// Hand coded defaults based on Material Design 2.
|
||||
class _DialogDefaultsM2 extends DialogTheme {
|
||||
class _DialogDefaultsM2 extends DialogThemeData {
|
||||
_DialogDefaultsM2(this.context)
|
||||
: _textTheme = Theme.of(context).textTheme,
|
||||
_iconTheme = Theme.of(context).iconTheme,
|
||||
@ -1674,7 +1677,7 @@ class _DialogDefaultsM2 extends DialogTheme {
|
||||
// Design token database by the script:
|
||||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
|
||||
class _DialogDefaultsM3 extends DialogTheme {
|
||||
class _DialogDefaultsM3 extends DialogThemeData {
|
||||
_DialogDefaultsM3(this.context)
|
||||
: super(
|
||||
alignment: Alignment.center,
|
||||
@ -1718,7 +1721,7 @@ class _DialogDefaultsM3 extends DialogTheme {
|
||||
// Design token database by the script:
|
||||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
|
||||
class _DialogFullscreenDefaultsM3 extends DialogTheme {
|
||||
class _DialogFullscreenDefaultsM3 extends DialogThemeData {
|
||||
const _DialogFullscreenDefaultsM3(this.context): super(clipBehavior: Clip.none);
|
||||
|
||||
final BuildContext context;
|
||||
|
@ -28,9 +28,288 @@ import 'theme.dart';
|
||||
/// * [ThemeData], which describes the overall theme information for the
|
||||
/// application.
|
||||
@immutable
|
||||
class DialogTheme with Diagnosticable {
|
||||
class DialogTheme extends InheritedTheme with Diagnosticable {
|
||||
/// Creates a dialog theme that can be used for [ThemeData.dialogTheme].
|
||||
const DialogTheme({
|
||||
super.key,
|
||||
Color? backgroundColor,
|
||||
double? elevation,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
ShapeBorder? shape,
|
||||
AlignmentGeometry? alignment,
|
||||
Color? iconColor,
|
||||
TextStyle? titleTextStyle,
|
||||
TextStyle? contentTextStyle,
|
||||
EdgeInsetsGeometry? actionsPadding,
|
||||
Color? barrierColor,
|
||||
EdgeInsets? insetPadding,
|
||||
Clip? clipBehavior,
|
||||
DialogThemeData? data,
|
||||
Widget? child,
|
||||
}) : assert(
|
||||
data == null ||
|
||||
(backgroundColor ??
|
||||
elevation ??
|
||||
shadowColor ??
|
||||
surfaceTintColor ??
|
||||
shape ??
|
||||
alignment ??
|
||||
iconColor ??
|
||||
titleTextStyle ??
|
||||
contentTextStyle ??
|
||||
actionsPadding ??
|
||||
barrierColor ??
|
||||
insetPadding ??
|
||||
clipBehavior) == null),
|
||||
_data = data,
|
||||
_backgroundColor = backgroundColor,
|
||||
_elevation = elevation,
|
||||
_shadowColor = shadowColor,
|
||||
_surfaceTintColor = surfaceTintColor,
|
||||
_shape = shape,
|
||||
_alignment = alignment,
|
||||
_iconColor = iconColor,
|
||||
_titleTextStyle = titleTextStyle,
|
||||
_contentTextStyle = contentTextStyle,
|
||||
_actionsPadding = actionsPadding,
|
||||
_barrierColor = barrierColor,
|
||||
_insetPadding = insetPadding,
|
||||
_clipBehavior = clipBehavior,
|
||||
super(child: child ?? const SizedBox());
|
||||
|
||||
final DialogThemeData? _data;
|
||||
final Color? _backgroundColor;
|
||||
final double? _elevation;
|
||||
final Color? _shadowColor;
|
||||
final Color? _surfaceTintColor;
|
||||
final ShapeBorder? _shape;
|
||||
final AlignmentGeometry? _alignment;
|
||||
final TextStyle? _titleTextStyle;
|
||||
final TextStyle? _contentTextStyle;
|
||||
final EdgeInsetsGeometry? _actionsPadding;
|
||||
final Color? _iconColor;
|
||||
final Color? _barrierColor;
|
||||
final EdgeInsets? _insetPadding;
|
||||
final Clip? _clipBehavior;
|
||||
|
||||
/// Overrides the default value for [Dialog.backgroundColor].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.backgroundColor] property in [data] instead.
|
||||
Color? get backgroundColor => _data != null ? _data.backgroundColor : _backgroundColor;
|
||||
|
||||
/// Overrides the default value for [Dialog.elevation].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.elevation] property in [data] instead.
|
||||
double? get elevation => _data != null ? _data.elevation : _elevation;
|
||||
|
||||
/// Overrides the default value for [Dialog.shadowColor].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.shadowColor] property in [data] instead.
|
||||
Color? get shadowColor => _data != null ? _data.shadowColor : _shadowColor;
|
||||
|
||||
/// Overrides the default value for [Dialog.surfaceTintColor].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.surfaceTintColor] property in [data] instead.
|
||||
Color? get surfaceTintColor => _data != null ? _data.surfaceTintColor : _surfaceTintColor;
|
||||
|
||||
/// Overrides the default value for [Dialog.shape].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.shape] property in [data] instead.
|
||||
ShapeBorder? get shape => _data != null ? _data.shape : _shape;
|
||||
|
||||
/// Overrides the default value for [Dialog.alignment].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.alignment] property in [data] instead.
|
||||
AlignmentGeometry? get alignment => _data != null ? _data.alignment : _alignment;
|
||||
|
||||
/// Overrides the default value for [DefaultTextStyle] for [SimpleDialog.title] and
|
||||
/// [AlertDialog.title].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.titleTextStyle] property in [data] instead.
|
||||
TextStyle? get titleTextStyle => _data != null ? _data.titleTextStyle : _titleTextStyle;
|
||||
|
||||
/// Overrides the default value for [DefaultTextStyle] for [SimpleDialog.children] and
|
||||
/// [AlertDialog.content].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.contentTextStyle] property in [data] instead.
|
||||
TextStyle? get contentTextStyle => _data != null ? _data.contentTextStyle : _contentTextStyle;
|
||||
|
||||
/// Overrides the default value for [AlertDialog.actionsPadding].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.actionsPadding] property in [data] instead.
|
||||
EdgeInsetsGeometry? get actionsPadding => _data != null ? _data.actionsPadding : _actionsPadding;
|
||||
|
||||
/// Used to configure the [IconTheme] for the [AlertDialog.icon] widget.
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.iconColor] property in [data] instead.
|
||||
Color? get iconColor => _data != null ? _data.iconColor : _iconColor;
|
||||
|
||||
/// Overrides the default value for [barrierColor] in [showDialog].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.barrierColor] property in [data] instead.
|
||||
Color? get barrierColor => _data != null ? _data.barrierColor : _barrierColor;
|
||||
|
||||
/// Overrides the default value for [Dialog.insetPadding].
|
||||
EdgeInsets? get insetPadding => _data != null ? _data.insetPadding : _insetPadding;
|
||||
|
||||
/// Overrides the default value of [Dialog.clipBehavior].
|
||||
///
|
||||
/// This property is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.clipBehavior] property in [data] instead.
|
||||
Clip? get clipBehavior => _data != null ? _data.clipBehavior : _clipBehavior;
|
||||
|
||||
/// The properties used for all descendant [Dialog] widgets.
|
||||
DialogThemeData get data {
|
||||
return _data ?? DialogThemeData(
|
||||
backgroundColor: _backgroundColor,
|
||||
elevation: _elevation,
|
||||
shadowColor: _shadowColor,
|
||||
surfaceTintColor: _surfaceTintColor,
|
||||
shape: _shape,
|
||||
alignment: _alignment,
|
||||
iconColor: _iconColor,
|
||||
titleTextStyle: _titleTextStyle,
|
||||
contentTextStyle: _contentTextStyle,
|
||||
actionsPadding: _actionsPadding,
|
||||
barrierColor: _barrierColor,
|
||||
insetPadding: _insetPadding,
|
||||
clipBehavior: _clipBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// The [ThemeData.dialogTheme] property of the ambient [Theme].
|
||||
static DialogTheme of(BuildContext context) {
|
||||
final DialogTheme? dialogTheme = context.dependOnInheritedWidgetOfExactType<DialogTheme>();
|
||||
return dialogTheme ?? Theme.of(context).dialogTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
return DialogTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(DialogTheme oldWidget) => data != oldWidget.data;
|
||||
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
///
|
||||
/// This method is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.copyWith] instead.
|
||||
DialogTheme copyWith({
|
||||
Color? backgroundColor,
|
||||
double? elevation,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
ShapeBorder? shape,
|
||||
AlignmentGeometry? alignment,
|
||||
Color? iconColor,
|
||||
TextStyle? titleTextStyle,
|
||||
TextStyle? contentTextStyle,
|
||||
EdgeInsetsGeometry? actionsPadding,
|
||||
Color? barrierColor,
|
||||
EdgeInsets? insetPadding,
|
||||
Clip? clipBehavior,
|
||||
}) {
|
||||
return DialogTheme(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
elevation: elevation ?? this.elevation,
|
||||
shadowColor: shadowColor ?? this.shadowColor,
|
||||
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
|
||||
shape: shape ?? this.shape,
|
||||
alignment: alignment ?? this.alignment,
|
||||
iconColor: iconColor ?? this.iconColor,
|
||||
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
|
||||
contentTextStyle: contentTextStyle ?? this.contentTextStyle,
|
||||
actionsPadding: actionsPadding ?? this.actionsPadding,
|
||||
barrierColor: barrierColor ?? this.barrierColor,
|
||||
insetPadding: insetPadding ?? this.insetPadding,
|
||||
clipBehavior: clipBehavior ?? this.clipBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two dialog themes.
|
||||
///
|
||||
/// {@macro dart.ui.shadow.lerp}
|
||||
///
|
||||
/// This method is obsolete and will be deprecated in a future release:
|
||||
/// please use the [DialogThemeData.lerp] instead.
|
||||
static DialogTheme lerp(DialogTheme? a, DialogTheme? b, double t) {
|
||||
if (identical(a, b) && a != null) {
|
||||
return a;
|
||||
}
|
||||
return DialogTheme(
|
||||
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
|
||||
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
||||
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
|
||||
surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
|
||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
||||
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
|
||||
iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
|
||||
titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
|
||||
contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
|
||||
actionsPadding: EdgeInsetsGeometry.lerp(a?.actionsPadding, b?.actionsPadding, t),
|
||||
barrierColor: Color.lerp(a?.barrierColor, b?.barrierColor, t),
|
||||
insetPadding: EdgeInsets.lerp(a?.insetPadding, b?.insetPadding, t),
|
||||
clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
|
||||
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
|
||||
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
|
||||
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
|
||||
properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null));
|
||||
properties.add(ColorProperty('barrierColor', barrierColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsets>('insetPadding', insetPadding, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines default property values for descendant [Dialog] widgets.
|
||||
///
|
||||
/// Descendant widgets obtain the current [DialogThemeData] object using
|
||||
/// `CardTheme.of(context).data`. Instances of [DialogThemeData] can be
|
||||
/// customized with [DialogThemeData.copyWith].
|
||||
///
|
||||
/// Typically a [DialogThemeData] is specified as part of the overall [Theme]
|
||||
/// with [ThemeData.dialogTheme].
|
||||
///
|
||||
/// All [DialogThemeData] properties are `null` by default. When null, the [Dialog]
|
||||
/// will use the values from [ThemeData] if they exist, otherwise it will
|
||||
/// provide its own defaults. See the individual [Dialog] properties for details.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Dialog], a dialog that can be customized using this [DialogTheme].
|
||||
/// * [AlertDialog], a dialog that can be customized using this [DialogTheme].
|
||||
/// * [SimpleDialog], a dialog that can be customized using this [DialogTheme].
|
||||
/// * [ThemeData], which describes the overall theme information for the
|
||||
/// application.
|
||||
@immutable
|
||||
class DialogThemeData with Diagnosticable {
|
||||
/// Creates a dialog theme that can be used for [ThemeData.dialogTheme].
|
||||
const DialogThemeData({
|
||||
this.backgroundColor,
|
||||
this.elevation,
|
||||
this.shadowColor,
|
||||
@ -89,7 +368,7 @@ class DialogTheme with Diagnosticable {
|
||||
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
DialogTheme copyWith({
|
||||
DialogThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
double? elevation,
|
||||
Color? shadowColor,
|
||||
@ -104,7 +383,7 @@ class DialogTheme with Diagnosticable {
|
||||
EdgeInsets? insetPadding,
|
||||
Clip? clipBehavior,
|
||||
}) {
|
||||
return DialogTheme(
|
||||
return DialogThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
elevation: elevation ?? this.elevation,
|
||||
shadowColor: shadowColor ?? this.shadowColor,
|
||||
@ -121,19 +400,14 @@ class DialogTheme with Diagnosticable {
|
||||
);
|
||||
}
|
||||
|
||||
/// The data from the closest [DialogTheme] instance given the build context.
|
||||
static DialogTheme of(BuildContext context) {
|
||||
return Theme.of(context).dialogTheme;
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two dialog themes.
|
||||
/// Linearly interpolate between two [DialogThemeData].
|
||||
///
|
||||
/// {@macro dart.ui.shadow.lerp}
|
||||
static DialogTheme lerp(DialogTheme? a, DialogTheme? b, double t) {
|
||||
static DialogThemeData lerp(DialogThemeData? a, DialogThemeData? b, double t) {
|
||||
if (identical(a, b) && a != null) {
|
||||
return a;
|
||||
}
|
||||
return DialogTheme(
|
||||
return DialogThemeData(
|
||||
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
|
||||
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
||||
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
|
||||
@ -175,7 +449,7 @@ class DialogTheme with Diagnosticable {
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is DialogTheme
|
||||
return other is DialogThemeData
|
||||
&& other.backgroundColor == backgroundColor
|
||||
&& other.elevation == elevation
|
||||
&& other.shadowColor == shadowColor
|
||||
@ -194,17 +468,17 @@ class DialogTheme with Diagnosticable {
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
properties.add(DoubleProperty('elevation', elevation));
|
||||
properties.add(ColorProperty('shadowColor', shadowColor));
|
||||
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor));
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
|
||||
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
|
||||
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
|
||||
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
|
||||
properties.add(ColorProperty('iconColor', iconColor));
|
||||
properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null));
|
||||
properties.add(ColorProperty('barrierColor', barrierColor));
|
||||
properties.add(ColorProperty('barrierColor', barrierColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsets>('insetPadding', insetPadding, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
|
||||
}
|
||||
|
@ -12,27 +12,45 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
MaterialApp _appWithDialog(WidgetTester tester, Widget dialog, { ThemeData? theme }) {
|
||||
MaterialApp _appWithDialog(
|
||||
WidgetTester tester,
|
||||
Widget dialog, {
|
||||
ThemeData? theme,
|
||||
DialogThemeData? dialogTheme
|
||||
}
|
||||
) {
|
||||
Widget dialogBuilder = Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
child: const Text('X'),
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RepaintBoundary(
|
||||
key: _painterKey,
|
||||
child: dialog
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (dialogTheme != null) {
|
||||
dialogBuilder = DialogTheme(
|
||||
data: dialogTheme,
|
||||
child: dialogBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
return MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
child: const Text('X'),
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RepaintBoundary(key: _painterKey, child: dialog);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: dialogBuilder,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -51,25 +69,78 @@ RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {
|
||||
return tester.element<StatelessElement>(find.text(text)).renderObject! as RenderParagraph;
|
||||
}
|
||||
|
||||
RenderParagraph _getIconRenderObject(WidgetTester tester, IconData icon) {
|
||||
return tester.renderObject<RenderParagraph>(find.descendant(
|
||||
of: find.byIcon(icon),
|
||||
matching: find.byType(RichText)
|
||||
));
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('DialogTheme copyWith, ==, hashCode basics', () {
|
||||
expect(const DialogTheme(), const DialogTheme().copyWith());
|
||||
expect(const DialogTheme().hashCode, const DialogTheme().copyWith().hashCode);
|
||||
test('DialogThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const DialogThemeData(), const DialogThemeData().copyWith());
|
||||
expect(const DialogThemeData().hashCode, const DialogThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
test('DialogTheme lerp special cases', () {
|
||||
expect(DialogTheme.lerp(null, null, 0), const DialogTheme());
|
||||
const DialogTheme theme = DialogTheme();
|
||||
expect(identical(DialogTheme.lerp(theme, theme, 0.5), theme), true);
|
||||
test('DialogThemeData lerp special cases', () {
|
||||
expect(DialogThemeData.lerp(null, null, 0), const DialogThemeData());
|
||||
const DialogThemeData theme = DialogThemeData();
|
||||
expect(identical(DialogThemeData.lerp(theme, theme, 0.5), theme), true);
|
||||
});
|
||||
|
||||
testWidgets('Dialog Theme implements debugFillProperties', (WidgetTester tester) async {
|
||||
test('DialogThemeData defaults', () {
|
||||
const DialogThemeData dialogThemeData = DialogThemeData();
|
||||
|
||||
expect(dialogThemeData.backgroundColor, null);
|
||||
expect(dialogThemeData.elevation, null);
|
||||
expect(dialogThemeData.shadowColor, null);
|
||||
expect(dialogThemeData.surfaceTintColor, null);
|
||||
expect(dialogThemeData.shape, null);
|
||||
expect(dialogThemeData.alignment, null);
|
||||
expect(dialogThemeData.iconColor, null);
|
||||
expect(dialogThemeData.titleTextStyle, null);
|
||||
expect(dialogThemeData.contentTextStyle, null);
|
||||
expect(dialogThemeData.actionsPadding, null);
|
||||
expect(dialogThemeData.barrierColor, null);
|
||||
expect(dialogThemeData.insetPadding, null);
|
||||
expect(dialogThemeData.clipBehavior, null);
|
||||
|
||||
const DialogTheme dialogTheme = DialogTheme(data: DialogThemeData(), child: SizedBox());
|
||||
expect(dialogTheme.backgroundColor, null);
|
||||
expect(dialogTheme.elevation, null);
|
||||
expect(dialogTheme.shadowColor, null);
|
||||
expect(dialogTheme.surfaceTintColor, null);
|
||||
expect(dialogTheme.shape, null);
|
||||
expect(dialogTheme.alignment, null);
|
||||
expect(dialogTheme.iconColor, null);
|
||||
expect(dialogTheme.titleTextStyle, null);
|
||||
expect(dialogTheme.contentTextStyle, null);
|
||||
expect(dialogTheme.actionsPadding, null);
|
||||
expect(dialogTheme.barrierColor, null);
|
||||
expect(dialogTheme.insetPadding, null);
|
||||
expect(dialogTheme.clipBehavior, null);
|
||||
});
|
||||
|
||||
testWidgets('Default DialogThemeData debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const DialogTheme(
|
||||
const DialogThemeData().debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[]);
|
||||
});
|
||||
|
||||
testWidgets('DialogThemeData implements debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const DialogThemeData(
|
||||
backgroundColor: Color(0xff123456),
|
||||
elevation: 8.0,
|
||||
shadowColor: Color(0xff000001),
|
||||
surfaceTintColor: Color(0xff000002),
|
||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.5))),
|
||||
alignment: Alignment.bottomLeft,
|
||||
iconColor: Color(0xff654321),
|
||||
titleTextStyle: TextStyle(color: Color(0xffffffff)),
|
||||
@ -87,6 +158,7 @@ void main() {
|
||||
'elevation: 8.0',
|
||||
'shadowColor: Color(0xff000001)',
|
||||
'surfaceTintColor: Color(0xff000002)',
|
||||
'shape: BeveledRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(20.5))',
|
||||
'alignment: Alignment.bottomLeft',
|
||||
'iconColor: Color(0xff654321)',
|
||||
'titleTextStyle: TextStyle(inherit: true, color: Color(0xffffffff))',
|
||||
@ -98,6 +170,80 @@ void main() {
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('Local DialogThemeData overrides dialog defaults', (WidgetTester tester) async {
|
||||
const Color themeBackgroundColor = Color(0xff123456);
|
||||
const double themeElevation = 8.0;
|
||||
const Color themeShadowColor = Color(0xff000001);
|
||||
const Color themeSurfaceTintColor = Color(0xff000002);
|
||||
const BeveledRectangleBorder themeShape = BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.5)));
|
||||
const AlignmentGeometry themeAlignment = Alignment.bottomLeft;
|
||||
const Color themeIconColor = Color(0xff654321);
|
||||
const TextStyle themeTitleTextStyle = TextStyle(color: Color(0xffffffff));
|
||||
const TextStyle themeContentTextStyle = TextStyle(color: Color(0xff000000));
|
||||
const EdgeInsetsGeometry themeActionsPadding = EdgeInsets.all(8.0);
|
||||
const Color themeBarrierColor = Color(0xff000005);
|
||||
const EdgeInsets themeInsetPadding = EdgeInsets.all(30.0);
|
||||
const Clip themeClipBehavior = Clip.antiAlias;
|
||||
const AlertDialog dialog = AlertDialog(
|
||||
title: Text('Title'),
|
||||
content: Text('Content'),
|
||||
icon: Icon(Icons.search),
|
||||
actions: <Widget>[
|
||||
Icon(Icons.cancel)
|
||||
],
|
||||
);
|
||||
|
||||
const DialogThemeData dialogTheme = DialogThemeData(
|
||||
backgroundColor: themeBackgroundColor,
|
||||
elevation: themeElevation,
|
||||
shadowColor: themeShadowColor,
|
||||
surfaceTintColor: themeSurfaceTintColor,
|
||||
shape: themeShape,
|
||||
alignment: themeAlignment,
|
||||
iconColor: themeIconColor,
|
||||
titleTextStyle: themeTitleTextStyle,
|
||||
contentTextStyle: themeContentTextStyle,
|
||||
actionsPadding: themeActionsPadding,
|
||||
barrierColor: themeBarrierColor,
|
||||
insetPadding: themeInsetPadding,
|
||||
clipBehavior: themeClipBehavior,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_appWithDialog(
|
||||
tester,
|
||||
dialog,
|
||||
dialogTheme: dialogTheme
|
||||
));
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Material materialWidget = _getMaterialAlertDialog(tester);
|
||||
expect(materialWidget.color, themeBackgroundColor);
|
||||
expect(materialWidget.elevation, themeElevation);
|
||||
expect(materialWidget.shadowColor, themeShadowColor);
|
||||
expect(materialWidget.surfaceTintColor, themeSurfaceTintColor);
|
||||
expect(materialWidget.shape, themeShape);
|
||||
expect(materialWidget.clipBehavior, Clip.antiAlias);
|
||||
final Offset bottomLeft = tester.getBottomLeft(find.descendant(
|
||||
of: find.byType(Dialog),
|
||||
matching: find.byType(Material)
|
||||
));
|
||||
expect(bottomLeft.dx, 30.0); // 30 is the padding value.
|
||||
expect(bottomLeft.dy, 570.0); // 600 - 30
|
||||
expect(_getIconRenderObject(tester, Icons.search).text.style?.color, themeIconColor);
|
||||
expect(_getTextRenderObject(tester, 'Title').text.style?.color, themeTitleTextStyle.color);
|
||||
expect(_getTextRenderObject(tester, 'Content').text.style?.color, themeContentTextStyle.color);
|
||||
final ModalBarrier modalBarrier = tester.widget(find.byType(ModalBarrier).last);
|
||||
expect(modalBarrier.color, themeBarrierColor);
|
||||
|
||||
final Finder findPadding = find.ancestor(
|
||||
of: find.byIcon(Icons.cancel),
|
||||
matching: find.byType(Padding)
|
||||
).first;
|
||||
final Padding padding = tester.widget<Padding>(findPadding);
|
||||
expect(padding.padding, themeActionsPadding);
|
||||
});
|
||||
|
||||
testWidgets('Dialog background color', (WidgetTester tester) async {
|
||||
const Color customColor = Colors.pink;
|
||||
const AlertDialog dialog = AlertDialog(
|
||||
|
Loading…
x
Reference in New Issue
Block a user