Skip to content

Commit

Permalink
feat: add icon to AlertDialog (#104920)
Browse files Browse the repository at this point in the history
  • Loading branch information
werainkhatri authored Jun 20, 2022
1 parent e649210 commit 672859a
Show file tree
Hide file tree
Showing 5 changed files with 398 additions and 23 deletions.
3 changes: 3 additions & 0 deletions dev/tools/gen_defaults/lib/dialog_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class _TokenDefaultsM3 extends DialogTheme {
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
Color? get iconColor => _colors.secondary;
// TODO(darrenaustin): overlay should be handled by Material widget: https://github.com/flutter/flutter/issues/9160
@override
Color? get backgroundColor => ElevationOverlay.colorWithOverlay(${componentColor("md.comp.dialog.container")}, _colors.primary, ${elevation("md.comp.dialog.container")});
Expand Down
117 changes: 101 additions & 16 deletions packages/flutter/lib/src/material/dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -257,16 +257,19 @@ class AlertDialog extends StatelessWidget {
///
/// Typically used in conjunction with [showDialog].
///
/// The [contentPadding] must not be null. The [titlePadding] defaults to
/// null, which implies a default that depends on the values of the other
/// properties. See the documentation of [titlePadding] for details.
/// The [titlePadding] and [contentPadding] default to null, which implies a
/// default that depends on the values of the other properties. See the
/// documentation of [titlePadding] and [contentPadding] for details.
const AlertDialog({
super.key,
this.icon,
this.iconPadding,
this.iconColor,
this.title,
this.titlePadding,
this.titleTextStyle,
this.content,
this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
this.contentPadding,
this.contentTextStyle,
this.actions,
this.actionsPadding,
Expand All @@ -283,11 +286,35 @@ class AlertDialog extends StatelessWidget {
this.shape,
this.alignment,
this.scrollable = false,
}) : assert(contentPadding != null),
assert(clipBehavior != null);
}) : assert(clipBehavior != null);

/// An optional icon to display at the top of the dialog.
///
/// Typically, an [Icon] widget. Providing an icon centers the [title]'s text.
final Widget? icon;

/// Color for the [Icon] in the [icon] of this [AlertDialog].
///
/// If null, [DialogTheme.iconColor] is used. If that is null, defaults to
/// color scheme's [ColorScheme.secondary] if [ThemeData.useMaterial3] is
/// true, black otherwise.
final Color? iconColor;

/// Padding around the [icon].
///
/// If there is no [icon], no padding will be provided. Otherwise, this
/// padding is used.
///
/// This property defaults to providing 24 pixels on the top, left, and right
/// of the [icon]. If [title] is _not_ null, 16 pixels of bottom padding is
/// added to separate the [icon] from the [title]. If the [title] is null and
/// [content] is _not_ null, then no bottom padding is provided (but see
/// [contentPadding]). In any other case 24 pixels of bottom padding is
/// added.
final EdgeInsetsGeometry? iconPadding;

/// The (optional) title of the dialog is displayed in a large font at the top
/// of the dialog.
/// of the dialog, below the (optional) [icon].
///
/// Typically a [Text] widget.
final Widget? title;
Expand Down Expand Up @@ -321,11 +348,17 @@ class AlertDialog extends StatelessWidget {

/// Padding around the content.
///
/// If there is no content, no padding will be provided. Otherwise, padding of
/// 20 pixels is provided above the content to separate the content from the
/// title, and padding of 24 pixels is provided on the left, right, and bottom
/// to separate the content from the other edges of the dialog.
final EdgeInsetsGeometry contentPadding;
/// If there is no [content], no padding will be provided. Otherwise, this
/// padding is used.
///
/// This property defaults to providing a padding of 20 pixels above the
/// [content] to separate the [content] from the [title], and 24 pixels on the
/// left, right, and bottom to separate the [content] from the other edges of
/// the dialog.
///
/// If [ThemeData.useMaterial3] is true, the top padding separating the
/// content from the title defaults to 16 pixels instead of 20 pixels.
final EdgeInsetsGeometry? contentPadding;

/// Style for the text in the [content] of this [AlertDialog].
///
Expand Down Expand Up @@ -508,21 +541,55 @@ class AlertDialog extends StatelessWidget {
final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.of(context).textScaleFactor);
final TextDirection? textDirection = Directionality.maybeOf(context);

Widget? iconWidget;
Widget? titleWidget;
Widget? contentWidget;
Widget? actionsWidget;

if (icon != null) {
final bool belowIsTitle = title != null;
final bool belowIsContent = !belowIsTitle && content != null;
final EdgeInsets defaultIconPadding = EdgeInsets.only(
left: 24.0,
top: 24.0,
right: 24.0,
bottom: belowIsTitle ? 16.0 : belowIsContent ? 0.0 : 24.0,
);
final EdgeInsets effectiveIconPadding = iconPadding?.resolve(textDirection) ?? defaultIconPadding;
iconWidget = Padding(
padding: EdgeInsets.only(
left: effectiveIconPadding.left * paddingScaleFactor,
right: effectiveIconPadding.right * paddingScaleFactor,
top: effectiveIconPadding.top * paddingScaleFactor,
bottom: effectiveIconPadding.bottom,
),
child: IconTheme(
data: IconThemeData(
color: iconColor ?? dialogTheme.iconColor ?? defaults.iconColor,
),
child: icon!,
),
);
}

if (title != null) {
final EdgeInsets defaultTitlePadding = EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0);
final EdgeInsets defaultTitlePadding = EdgeInsets.only(
left: 24.0,
top: icon == null ? 24.0 : 0.0,
right: 24.0,
bottom: content == null ? 20.0 : 0.0,
);
final EdgeInsets effectiveTitlePadding = titlePadding?.resolve(textDirection) ?? defaultTitlePadding;
titleWidget = Padding(
padding: EdgeInsets.only(
left: effectiveTitlePadding.left * paddingScaleFactor,
right: effectiveTitlePadding.right * paddingScaleFactor,
top: effectiveTitlePadding.top * paddingScaleFactor,
top: icon == null ? effectiveTitlePadding.top * paddingScaleFactor : effectiveTitlePadding.top,
bottom: effectiveTitlePadding.bottom,
),
child: DefaultTextStyle(
style: titleTextStyle ?? dialogTheme.titleTextStyle ?? defaults.titleTextStyle!,
textAlign: icon == null ? TextAlign.start : TextAlign.center,
child: Semantics(
// For iOS platform, the focus always lands on the title.
// Set nameRoute to false to avoid title being announce twice.
Expand All @@ -535,12 +602,20 @@ class AlertDialog extends StatelessWidget {
}

if (content != null) {
final EdgeInsets effectiveContentPadding = contentPadding.resolve(textDirection);
final EdgeInsets defaultContentPadding = EdgeInsets.only(
left: 24.0,
top: theme.useMaterial3 ? 16.0 : 20.0,
right: 24.0,
bottom: 24.0,
);
final EdgeInsets effectiveContentPadding = contentPadding?.resolve(textDirection) ?? defaultContentPadding;
contentWidget = Padding(
padding: EdgeInsets.only(
left: effectiveContentPadding.left * paddingScaleFactor,
right: effectiveContentPadding.right * paddingScaleFactor,
top: title == null ? effectiveContentPadding.top * paddingScaleFactor : effectiveContentPadding.top,
top: title == null && icon == null
? effectiveContentPadding.top * paddingScaleFactor
: effectiveContentPadding.top,
bottom: effectiveContentPadding.bottom,
),
child: DefaultTextStyle(
Expand Down Expand Up @@ -580,6 +655,7 @@ class AlertDialog extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (icon != null) iconWidget!,
if (title != null) titleWidget!,
if (content != null) contentWidget!,
],
Expand All @@ -591,6 +667,7 @@ class AlertDialog extends StatelessWidget {
];
} else {
columnChildren = <Widget>[
if (icon != null) iconWidget!,
if (title != null) titleWidget!,
if (content != null) Flexible(child: contentWidget!),
if (actions != null) actionsWidget!,
Expand Down Expand Up @@ -1187,6 +1264,7 @@ double _paddingScaleFactor(double textScaleFactor) {
class _DefaultsM2 extends DialogTheme {
_DefaultsM2(this.context)
: _textTheme = Theme.of(context).textTheme,
_iconTheme = Theme.of(context).iconTheme,
super(
alignment: Alignment.center,
elevation: 24.0,
Expand All @@ -1195,6 +1273,10 @@ class _DefaultsM2 extends DialogTheme {

final BuildContext context;
final TextTheme _textTheme;
final IconThemeData _iconTheme;

@override
Color? get iconColor => _iconTheme.color;

@override
Color? get backgroundColor => Theme.of(context).dialogBackgroundColor;
Expand Down Expand Up @@ -1228,6 +1310,9 @@ class _TokenDefaultsM3 extends DialogTheme {
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;

@override
Color? get iconColor => _colors.secondary;

// TODO(darrenaustin): overlay should be handled by Material widget: https://github.com/flutter/flutter/issues/9160
@override
Color? get backgroundColor => ElevationOverlay.colorWithOverlay(_colors.surface, _colors.primary, 6.0);
Expand Down
9 changes: 9 additions & 0 deletions packages/flutter/lib/src/material/dialog_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class DialogTheme with Diagnosticable {
this.elevation,
this.shape,
this.alignment,
this.iconColor,
this.titleTextStyle,
this.contentTextStyle,
this.actionsPadding,
Expand Down Expand Up @@ -60,13 +61,17 @@ class DialogTheme with Diagnosticable {
/// Overrides the default value for [AlertDialog.actionsPadding].
final EdgeInsetsGeometry? actionsPadding;

/// Used to configure the [IconTheme] for the [AlertDialog.icon] widget.
final Color? iconColor;

/// Creates a copy of this object but with the given fields replaced with the
/// new values.
DialogTheme copyWith({
Color? backgroundColor,
double? elevation,
ShapeBorder? shape,
AlignmentGeometry? alignment,
Color? iconColor,
TextStyle? titleTextStyle,
TextStyle? contentTextStyle,
EdgeInsetsGeometry? actionsPadding,
Expand All @@ -76,6 +81,7 @@ class DialogTheme with Diagnosticable {
elevation: elevation ?? this.elevation,
shape: shape ?? this.shape,
alignment: alignment ?? this.alignment,
iconColor: iconColor ?? this.iconColor,
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
contentTextStyle: contentTextStyle ?? this.contentTextStyle,
actionsPadding: actionsPadding ?? this.actionsPadding,
Expand All @@ -99,6 +105,7 @@ class DialogTheme with Diagnosticable {
elevation: lerpDouble(a?.elevation, b?.elevation, 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),
Expand All @@ -121,6 +128,7 @@ class DialogTheme with Diagnosticable {
&& other.elevation == elevation
&& other.shape == shape
&& other.alignment == alignment
&& other.iconColor == iconColor
&& other.titleTextStyle == titleTextStyle
&& other.contentTextStyle == contentTextStyle
&& other.actionsPadding == actionsPadding;
Expand All @@ -133,6 +141,7 @@ class DialogTheme with Diagnosticable {
properties.add(DoubleProperty('elevation', elevation));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
properties.add(ColorProperty('iconColor', iconColor));
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('actionsPadding', actionsPadding, defaultValue: null));
Expand Down
Loading

0 comments on commit 672859a

Please sign in to comment.