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:
Qun Cheng 2024-09-05 15:00:23 -07:00 committed by GitHub
parent 4d17998755
commit 6dd929ab28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 487 additions and 64 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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));
}

View File

@ -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(