Unverified Commit 50098f14 authored by MH Johnson's avatar MH Johnson Committed by GitHub

[Material] Theme for Dialogs (#23569)

* [Material] Add dialog theme.

* Address Hans' first round comments.

* Address Hans' Second round comments.

* Fix theme tests

* Address Will's comments, add golden test

* Update goldens

* Custom Border radius 16.0

* Address Hans' Comments

* Second round comments

* fix imports
parent 1b9cba4b
055a75c5a63bf2b2c5142b38b88829593908fd10
24abcd16b6342dc01bffcdee8ee01f8e18e80918
......@@ -12,6 +12,7 @@ import 'button_bar.dart';
import 'button_theme.dart';
import 'colors.dart';
import 'debug.dart';
import 'dialog_theme.dart';
import 'ink_well.dart';
import 'material.dart';
import 'material_localizations.dart';
......@@ -81,6 +82,7 @@ class Dialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final DialogTheme dialogTheme = DialogTheme.of(context);
return AnimatedPadding(
padding: MediaQuery.of(context).viewInsets + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
duration: insetAnimationDuration,
......@@ -99,7 +101,7 @@ class Dialog extends StatelessWidget {
color: _getColor(context),
type: MaterialType.card,
child: child,
shape: shape ?? _defaultDialogShape,
shape: shape ?? dialogTheme.shape ?? _defaultDialogShape,
),
),
),
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
/// Defines a theme for [Dialog] widgets.
///
/// Descendant widgets obtain the current [DialogTheme] object using
/// `DialogTheme.of(context)`. Instances of [DialogTheme] can be customized with
/// [DialogTheme.copyWith].
///
/// When Shape is `null`, the dialog defaults to a [RoundedRectangleBorder] with
/// a border radius of 2.0 on all corners.
///
/// See also:
///
/// * [Dialog], a material dialog that can be customized using this [DialogTheme].
/// * [ThemeData], which describes the overall theme information for the
/// application.
class DialogTheme extends Diagnosticable {
/// Creates a dialog theme that can be used for [ThemeData.dialogTheme].
const DialogTheme({ this.shape });
/// Default value for [Dialog.shape].
final ShapeBorder shape;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
DialogTheme copyWith({ ShapeBorder shape }) {
return DialogTheme(shape: shape ?? this.shape);
}
/// 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.
///
/// The arguments must not be null.
///
/// {@macro dart.ui.shadow.lerp}
static DialogTheme lerp(DialogTheme a, DialogTheme b, double t) {
assert(t != null);
return DialogTheme(
shape: ShapeBorder.lerp(a?.shape, b?.shape, t)
);
}
@override
int get hashCode => shape.hashCode;
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final DialogTheme typedOther = other;
return typedOther.shape == shape;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
}
}
......@@ -12,6 +12,7 @@ import 'button_theme.dart';
import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'dialog_theme.dart';
import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory;
import 'input_decorator.dart';
......@@ -150,6 +151,7 @@ class ThemeData extends Diagnosticable {
MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme,
ColorScheme colorScheme,
DialogTheme dialogTheme,
Typography typography,
}) {
brightness ??= Brightness.light;
......@@ -243,6 +245,7 @@ class ThemeData extends Diagnosticable {
brightness: brightness,
labelStyle: textTheme.body2,
);
dialogTheme ??= const DialogTheme();
return ThemeData.raw(
brightness: brightness,
......@@ -289,6 +292,7 @@ class ThemeData extends Diagnosticable {
materialTapTargetSize: materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme,
colorScheme: colorScheme,
dialogTheme: dialogTheme,
typography: typography,
);
}
......@@ -347,6 +351,7 @@ class ThemeData extends Diagnosticable {
@required this.materialTapTargetSize,
@required this.pageTransitionsTheme,
@required this.colorScheme,
@required this.dialogTheme,
@required this.typography,
}) : assert(brightness != null),
assert(primaryColor != null),
......@@ -391,6 +396,7 @@ class ThemeData extends Diagnosticable {
assert(materialTapTargetSize != null),
assert(pageTransitionsTheme != null),
assert(colorScheme != null),
assert(dialogTheme != null),
assert(typography != null);
// Warning: make sure these properties are in the exact same order as in
......@@ -617,6 +623,9 @@ class ThemeData extends Diagnosticable {
/// that is possible without significant backwards compatibility breaks.
final ColorScheme colorScheme;
/// A theme for customizing the shape of a dialog.
final DialogTheme dialogTheme;
/// The color and geometry [TextTheme] values used to configure [textTheme],
/// [primaryTextTheme], and [accentTextTheme].
final Typography typography;
......@@ -667,6 +676,7 @@ class ThemeData extends Diagnosticable {
MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme,
ColorScheme colorScheme,
DialogTheme dialogTheme,
Typography typography,
}) {
return ThemeData.raw(
......@@ -714,6 +724,7 @@ class ThemeData extends Diagnosticable {
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme,
colorScheme: colorScheme ?? this.colorScheme,
dialogTheme: dialogTheme ?? this.dialogTheme,
typography: typography ?? this.typography,
);
}
......@@ -840,6 +851,7 @@ class ThemeData extends Diagnosticable {
materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme,
colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
typography: Typography.lerp(a.typography, b.typography, t),
);
}
......@@ -896,6 +908,7 @@ class ThemeData extends Diagnosticable {
(otherData.materialTapTargetSize == materialTapTargetSize) &&
(otherData.pageTransitionsTheme == pageTransitionsTheme) &&
(otherData.colorScheme == colorScheme) &&
(otherData.dialogTheme == dialogTheme) &&
(otherData.typography == typography);
}
......@@ -952,6 +965,7 @@ class ThemeData extends Diagnosticable {
materialTapTargetSize,
pageTransitionsTheme,
colorScheme,
dialogTheme,
typography,
),
),
......@@ -1003,6 +1017,7 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme));
properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme));
properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme));
properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography));
}
}
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/src/material/dialog_theme.dart';
import 'package:flutter_test/flutter_test.dart';
MaterialApp _appWithAlertDialog(WidgetTester tester, AlertDialog dialog, {ThemeData theme}) {
return MaterialApp(
theme: theme,
home: Material(
child: Builder(
builder: (BuildContext context) {
return Center(
child: RaisedButton(
child: const Text('X'),
onPressed: () {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return RepaintBoundary(key: _painterKey, child: dialog);
},
);
}
)
);
}
)
),
);
}
final Key _painterKey = UniqueKey();
void main() {
testWidgets('Custom dialog shape', (WidgetTester tester) async {
const RoundedRectangleBorder customBorder =
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));
const AlertDialog dialog = AlertDialog(
title: Text('Title'),
actions: <Widget>[ ],
);
final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(shape: customBorder));
await tester.pumpWidget(
_appWithAlertDialog(tester, dialog, theme: theme)
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1));
final StatefulElement widget = tester.element(
find.descendant(of: find.byType(AlertDialog), matching: find.byType(Material)));
final Material materialWidget = widget.state.widget;
expect(materialWidget.shape, customBorder);
});
testWidgets('Custom dialog shape matches golden', (WidgetTester tester) async {
const RoundedRectangleBorder customBorder =
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));
const AlertDialog dialog = AlertDialog(
title: Text('Title'),
actions: <Widget>[ ],
);
final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(shape: customBorder));
await tester.pumpWidget(_appWithAlertDialog(tester, dialog, theme: theme));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
await expectLater(
find.byKey(_painterKey),
matchesGoldenFile('dialog_theme.dialog_with_custom_border.png'),
skip: !Platform.isLinux,
);
});
}
\ 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