Unverified Commit 6e155728 authored by MH Johnson's avatar MH Johnson Committed by GitHub

[Material] Theme-able TextStyles for AlertDialog (#25339)

* Themable elevation on dialogs.

* AlertDialogs Title/Content text styles

* Themable elevation on dialogs.

AlertDialogs Title/Content text styles

* finish merging change

* fixing tests

* Docs fixes

* Change const to final to fix analyzer errors

* Fix analyzer errors

* Remove dart:ui import

* Hans Comments
parent f8ab7265
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -204,8 +203,10 @@ class AlertDialog extends StatelessWidget { ...@@ -204,8 +203,10 @@ class AlertDialog extends StatelessWidget {
Key key, Key key,
this.title, this.title,
this.titlePadding, this.titlePadding,
this.titleTextStyle,
this.content, this.content,
this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
this.contentTextStyle,
this.actions, this.actions,
this.backgroundColor, this.backgroundColor,
this.elevation, this.elevation,
...@@ -232,6 +233,12 @@ class AlertDialog extends StatelessWidget { ...@@ -232,6 +233,12 @@ class AlertDialog extends StatelessWidget {
/// [actions]. /// [actions].
final EdgeInsetsGeometry titlePadding; final EdgeInsetsGeometry titlePadding;
/// Style for the text in the [title] of this [AlertDialog].
///
/// If null, [DialogTheme.titleTextStyle] is used, if that's null, defaults to
/// [ThemeData.textTheme.title].
final TextStyle titleTextStyle;
/// The (optional) content of the dialog is displayed in the center of the /// The (optional) content of the dialog is displayed in the center of the
/// dialog in a lighter font. /// dialog in a lighter font.
/// ///
...@@ -248,6 +255,12 @@ class AlertDialog extends StatelessWidget { ...@@ -248,6 +255,12 @@ class AlertDialog extends StatelessWidget {
/// to separate the content from the other edges of the dialog. /// to separate the content from the other edges of the dialog.
final EdgeInsetsGeometry contentPadding; final EdgeInsetsGeometry contentPadding;
/// Style for the text in the [content] of this [AlertDialog].
///
/// If null, [DialogTheme.contentTextStyle] is used, if that's null, defaults
/// to [ThemeData.textTheme.subhead].
final TextStyle contentTextStyle;
/// The (optional) set of actions that are displayed at the bottom of the /// The (optional) set of actions that are displayed at the bottom of the
/// dialog. /// dialog.
/// ///
...@@ -287,6 +300,8 @@ class AlertDialog extends StatelessWidget { ...@@ -287,6 +300,8 @@ class AlertDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = Theme.of(context);
final DialogTheme dialogTheme = DialogTheme.of(context);
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
String label = semanticLabel; String label = semanticLabel;
...@@ -294,7 +309,7 @@ class AlertDialog extends StatelessWidget { ...@@ -294,7 +309,7 @@ class AlertDialog extends StatelessWidget {
children.add(Padding( children.add(Padding(
padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0), padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
child: DefaultTextStyle( child: DefaultTextStyle(
style: Theme.of(context).textTheme.title, style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.title,
child: Semantics(child: title, namesRoute: true), child: Semantics(child: title, namesRoute: true),
), ),
)); ));
...@@ -314,7 +329,7 @@ class AlertDialog extends StatelessWidget { ...@@ -314,7 +329,7 @@ class AlertDialog extends StatelessWidget {
child: Padding( child: Padding(
padding: contentPadding, padding: contentPadding,
child: DefaultTextStyle( child: DefaultTextStyle(
style: Theme.of(context).textTheme.subhead, style: contentTextStyle ?? dialogTheme.contentTextStyle ?? theme.textTheme.subhead,
child: content, child: content,
), ),
), ),
......
...@@ -18,6 +18,10 @@ import 'theme.dart'; ...@@ -18,6 +18,10 @@ import 'theme.dart';
/// When Shape is `null`, the dialog defaults to a [RoundedRectangleBorder] with /// When Shape is `null`, the dialog defaults to a [RoundedRectangleBorder] with
/// a border radius of 2.0 on all corners. /// a border radius of 2.0 on all corners.
/// ///
/// [titleTextStyle] and [contentTextStyle] are used in [AlertDialogs].
/// If null, they default to [ThemeData.textTheme.title] and [ThemeData.textTheme.subhead],
/// respectively.
///
/// See also: /// See also:
/// ///
/// * [Dialog], a material dialog that can be customized using this [DialogTheme]. /// * [Dialog], a material dialog that can be customized using this [DialogTheme].
...@@ -25,9 +29,18 @@ import 'theme.dart'; ...@@ -25,9 +29,18 @@ import 'theme.dart';
/// application. /// application.
class DialogTheme extends Diagnosticable { class DialogTheme extends Diagnosticable {
/// Creates a dialog theme that can be used for [ThemeData.dialogTheme]. /// Creates a dialog theme that can be used for [ThemeData.dialogTheme].
const DialogTheme({ this.backgroundColor, this.elevation, this.shape }); const DialogTheme({
this.backgroundColor,
this.elevation,
this.shape,
this.titleTextStyle,
this.contentTextStyle,
});
/// Default value for [Dialog.backgroundColor]. /// Default value for [Dialog.backgroundColor].
///
/// If null, [ThemeData.dialogBackgroundColor] is used, if that's null,
/// defaults to [Colors.white].
final Color backgroundColor; final Color backgroundColor;
/// Default value for [Dialog.elevation]. /// Default value for [Dialog.elevation].
...@@ -38,13 +51,31 @@ class DialogTheme extends Diagnosticable { ...@@ -38,13 +51,31 @@ class DialogTheme extends Diagnosticable {
/// Default value for [Dialog.shape]. /// Default value for [Dialog.shape].
final ShapeBorder shape; final ShapeBorder shape;
/// Used to configure the [DefaultTextStyle] for the [AlertDialog.title] widget.
///
/// If null, defaults to [ThemeData.textTheme.title].
final TextStyle titleTextStyle;
/// Used to configure the [DefaultTextStyle] for the [AlertDialog.content] widget.
///
/// If null, defaults to [ThemeData.textTheme.subhead].
final TextStyle contentTextStyle;
/// Creates a copy of this object but with the given fields replaced with the /// Creates a copy of this object but with the given fields replaced with the
/// new values. /// new values.
DialogTheme copyWith({ Color backgroundColor, double elevation, ShapeBorder shape }) { DialogTheme copyWith({
Color backgroundColor,
double elevation,
ShapeBorder shape,
TextStyle titleTextStyle,
TextStyle contentTextStyle,
}) {
return DialogTheme( return DialogTheme(
backgroundColor: backgroundColor ?? this.backgroundColor, backgroundColor: backgroundColor ?? this.backgroundColor,
elevation: elevation ?? this.elevation, elevation: elevation ?? this.elevation,
shape: shape ?? this.shape, shape: shape ?? this.shape,
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
contentTextStyle: contentTextStyle ?? this.contentTextStyle,
); );
} }
...@@ -64,6 +95,8 @@ class DialogTheme extends Diagnosticable { ...@@ -64,6 +95,8 @@ class DialogTheme extends Diagnosticable {
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t), elevation: lerpDouble(a?.elevation, b?.elevation, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t), shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t)
); );
} }
...@@ -79,7 +112,9 @@ class DialogTheme extends Diagnosticable { ...@@ -79,7 +112,9 @@ class DialogTheme extends Diagnosticable {
final DialogTheme typedOther = other; final DialogTheme typedOther = other;
return typedOther.backgroundColor == backgroundColor return typedOther.backgroundColor == backgroundColor
&& typedOther.elevation == elevation && typedOther.elevation == elevation
&& typedOther.shape == shape; && typedOther.shape == shape
&& typedOther.titleTextStyle == titleTextStyle
&& typedOther.contentTextStyle == contentTextStyle;
} }
@override @override
...@@ -88,5 +123,7 @@ class DialogTheme extends Diagnosticable { ...@@ -88,5 +123,7 @@ class DialogTheme extends Diagnosticable {
properties.add(DiagnosticsProperty<Color>('backgroundColor', backgroundColor)); properties.add(DiagnosticsProperty<Color>('backgroundColor', backgroundColor));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null)); properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<double>('elevation', elevation)); properties.add(DiagnosticsProperty<double>('elevation', elevation));
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
} }
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:matcher/matcher.dart'; import 'package:matcher/matcher.dart';
...@@ -39,6 +40,10 @@ Material _getMaterialFromDialog(WidgetTester tester) { ...@@ -39,6 +40,10 @@ Material _getMaterialFromDialog(WidgetTester tester) {
return tester.widget<Material>(find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material))); return tester.widget<Material>(find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material)));
} }
RenderParagraph _getTextRenderObjectFromDialog(WidgetTester tester, String text) {
return tester.element<StatelessElement>(find.descendant(of: find.byType(AlertDialog), matching: find.text(text))).renderObject;
}
const ShapeBorder _defaultDialogShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))); const ShapeBorder _defaultDialogShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0)));
void main() { void main() {
...@@ -116,6 +121,40 @@ void main() { ...@@ -116,6 +121,40 @@ void main() {
expect(materialWidget.elevation, customElevation); expect(materialWidget.elevation, customElevation);
}); });
testWidgets('Custom Title Text Style', (WidgetTester tester) async {
const String titleText = 'Title';
const TextStyle titleTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
title: Text(titleText),
titleTextStyle: titleTextStyle,
actions: <Widget>[ ],
);
await tester.pumpWidget(_appWithAlertDialog(tester, dialog));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph title = _getTextRenderObjectFromDialog(tester, titleText);
expect(title.text.style, titleTextStyle);
});
testWidgets('Custom Content Text Style', (WidgetTester tester) async {
const String contentText = 'Content';
const TextStyle contentTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
content: Text(contentText),
contentTextStyle: contentTextStyle,
actions: <Widget>[ ],
);
await tester.pumpWidget(_appWithAlertDialog(tester, dialog));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
expect(content.text.style, contentTextStyle);
});
testWidgets('Custom dialog shape', (WidgetTester tester) async { testWidgets('Custom dialog shape', (WidgetTester tester) async {
const RoundedRectangleBorder customBorder = const RoundedRectangleBorder customBorder =
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))); RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));
......
...@@ -39,20 +39,28 @@ Material _getMaterialFromDialog(WidgetTester tester) { ...@@ -39,20 +39,28 @@ Material _getMaterialFromDialog(WidgetTester tester) {
return tester.widget<Material>(find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material))); return tester.widget<Material>(find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material)));
} }
RenderParagraph _getTextRenderObjectFromDialog(WidgetTester tester, String text) {
return tester.element<StatelessElement>(find.descendant(of: find.byType(AlertDialog), matching: find.text(text))).renderObject;
}
void main() { void main() {
testWidgets('Dialog Theme implements debugFillDescription', (WidgetTester tester) async { testWidgets('Dialog Theme implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const DialogTheme( const DialogTheme(
backgroundColor: Color(0xff123456), backgroundColor: Color(0xff123456),
elevation: 8.0, elevation: 8.0,
shape: null, shape: null,
titleTextStyle: TextStyle(color: Color(0xffffffff)),
contentTextStyle: TextStyle(color: Color(0xff000000))
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties final List<String> description = builder.properties
.where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info)) .where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode n) => n.toString()).toList(); .map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[ expect(description, <String>[
'backgroundColor: Color(0xff123456)', 'backgroundColor: Color(0xff123456)',
'elevation: 8.0' 'elevation: 8.0',
'titleTextStyle: TextStyle(inherit: true, color: Color(0xffffffff))',
'contentTextStyle: TextStyle(inherit: true, color: Color(0xff000000))',
]); ]);
}); });
...@@ -128,4 +136,106 @@ void main() { ...@@ -128,4 +136,106 @@ void main() {
skip: !Platform.isLinux, skip: !Platform.isLinux,
); );
}); });
testWidgets('Custom Title Text Style - Constructor Param', (WidgetTester tester) async {
const String titleText = 'Title';
const TextStyle titleTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
title: Text(titleText),
titleTextStyle: titleTextStyle,
actions: <Widget>[ ],
);
await tester.pumpWidget(_appWithAlertDialog(tester, dialog));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph title = _getTextRenderObjectFromDialog(tester, titleText);
expect(title.text.style, titleTextStyle);
});
testWidgets('Custom Title Text Style - Dialog Theme', (WidgetTester tester) async {
const String titleText = 'Title';
const TextStyle titleTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
title: Text(titleText),
actions: <Widget>[ ],
);
final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(titleTextStyle: titleTextStyle));
await tester.pumpWidget(_appWithAlertDialog(tester, dialog, theme: theme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph title = _getTextRenderObjectFromDialog(tester, titleText);
expect(title.text.style, titleTextStyle);
});
testWidgets('Custom Title Text Style - Theme', (WidgetTester tester) async {
const String titleText = 'Title';
const TextStyle titleTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
title: Text(titleText),
actions: <Widget>[ ],
);
final ThemeData theme = ThemeData(textTheme: const TextTheme(title: titleTextStyle));
await tester.pumpWidget(_appWithAlertDialog(tester, dialog, theme: theme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph title = _getTextRenderObjectFromDialog(tester, titleText);
expect(title.text.style.color, titleTextStyle.color);
});
testWidgets('Custom Content Text Style - Constructor Param', (WidgetTester tester) async {
const String contentText = 'Content';
const TextStyle contentTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
content: Text(contentText),
contentTextStyle: contentTextStyle,
actions: <Widget>[ ],
);
await tester.pumpWidget(_appWithAlertDialog(tester, dialog));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
expect(content.text.style, contentTextStyle);
});
testWidgets('Custom Content Text Style - Dialog Theme', (WidgetTester tester) async {
const String contentText = 'Content';
const TextStyle contentTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
content: Text(contentText),
actions: <Widget>[ ],
);
final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(contentTextStyle: contentTextStyle));
await tester.pumpWidget(_appWithAlertDialog(tester, dialog, theme: theme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
expect(content.text.style, contentTextStyle);
});
testWidgets('Custom Content Text Style - Theme', (WidgetTester tester) async {
const String contentText = 'Content';
const TextStyle contentTextStyle = TextStyle(color: Colors.pink);
const AlertDialog dialog = AlertDialog(
content: Text(contentText),
actions: <Widget>[ ],
);
final ThemeData theme = ThemeData(textTheme: const TextTheme(subhead: contentTextStyle));
await tester.pumpWidget(_appWithAlertDialog(tester, dialog, theme: theme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
expect(content.text.style.color, contentTextStyle.color);
});
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment