Add PopupMenuTheme to enable theming color, shape, elevation, text style of Menu (#36088)
* [Menu] Create Menu theme * [Menu] Create Menu theme * [Menu] Formatting changes for Menu theme * [Menu] Fix spacing difference in theme_data.dart. * [Menu] Fix spacing difference in theme_data.dart. * Specifying types * Formatting changes * Address PR feedback * Formatting changes * Address PR feedback * Add inherited widget * Add inherited widget * Address PR feedback and add inherited widget. * Formatting changes. * Address PR feedback * Address PR feedback * Address PR feedback * Address PR feedback
This commit is contained in:
parent
a3a350df1c
commit
63992e4fde
@ -83,6 +83,7 @@ export 'src/material/page.dart';
|
||||
export 'src/material/page_transitions_theme.dart';
|
||||
export 'src/material/paginated_data_table.dart';
|
||||
export 'src/material/popup_menu.dart';
|
||||
export 'src/material/popup_menu_theme.dart';
|
||||
export 'src/material/progress_indicator.dart';
|
||||
export 'src/material/radio.dart';
|
||||
export 'src/material/radio_list_tile.dart';
|
||||
|
@ -16,6 +16,7 @@ import 'ink_well.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'material.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'popup_menu_theme.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
// Examples can assume:
|
||||
@ -171,6 +172,7 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
|
||||
this.value,
|
||||
this.enabled = true,
|
||||
this.height = _kMenuItemHeight,
|
||||
this.textStyle,
|
||||
@required this.child,
|
||||
}) : assert(enabled != null),
|
||||
assert(height != null),
|
||||
@ -191,6 +193,12 @@ class PopupMenuItem<T> extends PopupMenuEntry<T> {
|
||||
@override
|
||||
final double height;
|
||||
|
||||
/// The text style of the popup menu entry.
|
||||
///
|
||||
/// If this property is null, then [PopupMenuThemeData.textStyle] is used.
|
||||
/// If [PopupMenuThemeData.textStyle] is also null, then [ThemeData.textTheme.subhead] is used.
|
||||
final TextStyle textStyle;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// Typically a single-line [ListTile] (for menus with icons) or a [Text]. An
|
||||
@ -245,7 +253,9 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
TextStyle style = theme.textTheme.subhead;
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
TextStyle style = widget.textStyle ?? popupMenuTheme.textStyle ?? theme.textTheme.subhead;
|
||||
|
||||
if (!widget.enabled)
|
||||
style = style.copyWith(color: theme.disabledColor);
|
||||
|
||||
@ -433,6 +443,7 @@ class _PopupMenu<T> extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
|
||||
final List<Widget> children = <Widget>[];
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
|
||||
for (int i = 0; i < route.items.length; i += 1) {
|
||||
final double start = (i + 1) * unit;
|
||||
@ -486,8 +497,10 @@ class _PopupMenu<T> extends StatelessWidget {
|
||||
return Opacity(
|
||||
opacity: opacity.evaluate(route.animation),
|
||||
child: Material(
|
||||
shape: route.shape ?? popupMenuTheme.shape,
|
||||
color: route.color ?? popupMenuTheme.color,
|
||||
type: MaterialType.card,
|
||||
elevation: route.elevation,
|
||||
elevation: route.elevation ?? popupMenuTheme.elevation ?? 8.0,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
widthFactor: width.evaluate(route.animation),
|
||||
@ -589,8 +602,11 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
this.initialValue,
|
||||
this.elevation,
|
||||
this.theme,
|
||||
this.popupMenuTheme,
|
||||
this.barrierLabel,
|
||||
this.semanticLabel,
|
||||
this.shape,
|
||||
this.color,
|
||||
});
|
||||
|
||||
final RelativeRect position;
|
||||
@ -599,6 +615,9 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
final double elevation;
|
||||
final ThemeData theme;
|
||||
final String semanticLabel;
|
||||
final ShapeBorder shape;
|
||||
final Color color;
|
||||
final PopupMenuThemeData popupMenuTheme;
|
||||
|
||||
@override
|
||||
Animation<double> createAnimation() {
|
||||
@ -636,6 +655,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
}
|
||||
|
||||
Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
||||
if (popupMenuTheme != null)
|
||||
menu = PopupMenuTheme(textStyle: popupMenuTheme.textStyle, child: menu);
|
||||
if (theme != null)
|
||||
menu = Theme(data: theme, child: menu);
|
||||
|
||||
@ -717,8 +738,10 @@ Future<T> showMenu<T>({
|
||||
@required RelativeRect position,
|
||||
@required List<PopupMenuEntry<T>> items,
|
||||
T initialValue,
|
||||
double elevation = 8.0,
|
||||
double elevation,
|
||||
String semanticLabel,
|
||||
ShapeBorder shape,
|
||||
Color color,
|
||||
}) {
|
||||
assert(context != null);
|
||||
assert(position != null);
|
||||
@ -741,7 +764,10 @@ Future<T> showMenu<T>({
|
||||
elevation: elevation,
|
||||
semanticLabel: label,
|
||||
theme: Theme.of(context, shadowThemeOnly: true),
|
||||
popupMenuTheme: PopupMenuTheme.of(context),
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
shape: shape,
|
||||
color: color,
|
||||
));
|
||||
}
|
||||
|
||||
@ -826,12 +852,14 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
this.onSelected,
|
||||
this.onCanceled,
|
||||
this.tooltip,
|
||||
this.elevation = 8.0,
|
||||
this.elevation,
|
||||
this.padding = const EdgeInsets.all(8.0),
|
||||
this.child,
|
||||
this.icon,
|
||||
this.offset = Offset.zero,
|
||||
this.enabled = true,
|
||||
this.shape,
|
||||
this.color,
|
||||
}) : assert(itemBuilder != null),
|
||||
assert(offset != null),
|
||||
assert(enabled != null),
|
||||
@ -898,12 +926,28 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
/// but doesn't currently have anything to show in the menu.
|
||||
final bool enabled;
|
||||
|
||||
/// If provided, the shape used for the menu.
|
||||
///
|
||||
/// If this property is null, then [PopupMenuThemeData.shape] is used.
|
||||
/// If [PopupMenuThemeData.shape] is also null, then the default shape for
|
||||
/// [MaterialType.card] is used. This default shape is a rectangle with
|
||||
/// rounded edges of BorderRadius.circular(2.0).
|
||||
final ShapeBorder shape;
|
||||
|
||||
/// If provided, the background color used for the menu.
|
||||
///
|
||||
/// If this property is null, then [PopupMenuThemeData.color] is used.
|
||||
/// If [PopupMenuThemeData.color] is also null, then
|
||||
/// Theme.of(context).cardColor is used.
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
_PopupMenuButtonState<T> createState() => _PopupMenuButtonState<T>();
|
||||
}
|
||||
|
||||
class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
void showButtonMenu() {
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
final RenderBox button = context.findRenderObject();
|
||||
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
|
||||
final RelativeRect position = RelativeRect.fromRect(
|
||||
@ -918,10 +962,12 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
if (items.isNotEmpty) {
|
||||
showMenu<T>(
|
||||
context: context,
|
||||
elevation: widget.elevation,
|
||||
elevation: widget.elevation ?? popupMenuTheme.elevation,
|
||||
items: items,
|
||||
initialValue: widget.initialValue,
|
||||
position: position,
|
||||
shape: widget.shape ?? popupMenuTheme.shape,
|
||||
color: widget.color ?? popupMenuTheme.color,
|
||||
)
|
||||
.then<void>((T newValue) {
|
||||
if (!mounted)
|
||||
|
161
packages/flutter/lib/src/material/popup_menu_theme.dart
Normal file
161
packages/flutter/lib/src/material/popup_menu_theme.dart
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2019 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:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'theme.dart';
|
||||
|
||||
/// Defines the visual properties of the routes used to display popup menus
|
||||
/// as well as [PopupMenuItem] and [PopupMenuDivider] widgets.
|
||||
///
|
||||
/// Descendant widgets obtain the current [PopupMenuThemeData] object
|
||||
/// using `PopupMenuTheme.of(context)`. Instances of
|
||||
/// [PopupMenuThemeData] can be customized with
|
||||
/// [PopupMenuThemeData.copyWith].
|
||||
///
|
||||
/// Typically, a [PopupMenuThemeData] is specified as part of the
|
||||
/// overall [Theme] with [ThemeData.popupMenuTheme]. Otherwise,
|
||||
/// [PopupMenuTheme] can be used to configure its own widget subtree.
|
||||
///
|
||||
/// All [PopupMenuThemeData] properties are `null` by default.
|
||||
/// If any of these properties are null, the popup menu will provide its
|
||||
/// own defaults.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ThemeData], which describes the overall theme information for the
|
||||
/// application.
|
||||
class PopupMenuThemeData extends Diagnosticable {
|
||||
/// Creates the set of properties used to configure [PopupMenuTheme].
|
||||
const PopupMenuThemeData({
|
||||
this.color,
|
||||
this.shape,
|
||||
this.elevation,
|
||||
this.textStyle,
|
||||
});
|
||||
|
||||
/// The background color of the popup menu.
|
||||
final Color color;
|
||||
|
||||
/// The shape of the popup menu.
|
||||
final ShapeBorder shape;
|
||||
|
||||
/// The elevation of the popup menu.
|
||||
final double elevation;
|
||||
|
||||
/// The text style of items in the popup menu.
|
||||
final TextStyle textStyle;
|
||||
|
||||
/// Creates a copy of this object with the given fields replaced with the
|
||||
/// new values.
|
||||
PopupMenuThemeData copyWith({
|
||||
Color color,
|
||||
ShapeBorder shape,
|
||||
double elevation,
|
||||
TextStyle textStyle,
|
||||
}) {
|
||||
return PopupMenuThemeData(
|
||||
color: color ?? this.color,
|
||||
shape: shape ?? this.shape,
|
||||
elevation: elevation ?? this.elevation,
|
||||
textStyle: textStyle ?? this.textStyle,
|
||||
);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two popup menu themes.
|
||||
///
|
||||
/// If both arguments are null, then null is returned.
|
||||
///
|
||||
/// {@macro dart.ui.shadow.lerp}
|
||||
static PopupMenuThemeData lerp(PopupMenuThemeData a, PopupMenuThemeData b, double t) {
|
||||
assert(t != null);
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
return PopupMenuThemeData(
|
||||
color: Color.lerp(a?.color, b?.color, t),
|
||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
||||
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
||||
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashValues(
|
||||
color,
|
||||
shape,
|
||||
elevation,
|
||||
textStyle,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
final PopupMenuThemeData typedOther = other;
|
||||
return typedOther.elevation == elevation
|
||||
&& typedOther.color == color
|
||||
&& typedOther.shape == shape
|
||||
&& typedOther.textStyle == textStyle;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('color', color, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('text style', textStyle, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
/// An inherited widget that defines the configuration for
|
||||
/// popup menus in this widget's subtree.
|
||||
///
|
||||
/// Values specified here are used for popup menu properties that are not
|
||||
/// given an explicit non-null value.
|
||||
class PopupMenuTheme extends InheritedWidget {
|
||||
/// Creates a popup menu theme that controls the configurations for
|
||||
/// popup menus in its widget subtree.
|
||||
PopupMenuTheme({
|
||||
Key key,
|
||||
Color color,
|
||||
ShapeBorder shape,
|
||||
double elevation,
|
||||
TextStyle textStyle,
|
||||
Widget child,
|
||||
}) : data = PopupMenuThemeData(
|
||||
color: color,
|
||||
shape: shape,
|
||||
elevation: elevation,
|
||||
textStyle: textStyle,
|
||||
),
|
||||
super(key: key, child: child);
|
||||
|
||||
/// The properties for descendant popup menu widgets.
|
||||
final PopupMenuThemeData data;
|
||||
|
||||
/// The closest instance of this class's [data] value that encloses the given
|
||||
/// context. If there is no ancestor, it returns [ThemeData.popupMenuTheme].
|
||||
/// Applications can assume that the returned value will not be null.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// PopupMenuThemeData theme = PopupMenuTheme.of(context);
|
||||
/// ```
|
||||
static PopupMenuThemeData of(BuildContext context) {
|
||||
final PopupMenuTheme popupMenuTheme = context.inheritFromWidgetOfExactType(PopupMenuTheme);
|
||||
return popupMenuTheme?.data ?? Theme.of(context).popupMenuTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(PopupMenuTheme oldWidget) => data != oldWidget.data;
|
||||
}
|
@ -23,6 +23,7 @@ import 'ink_splash.dart';
|
||||
import 'ink_well.dart' show InteractiveInkFeatureFactory;
|
||||
import 'input_decorator.dart';
|
||||
import 'page_transitions_theme.dart';
|
||||
import 'popup_menu_theme.dart';
|
||||
import 'slider_theme.dart';
|
||||
import 'snack_bar_theme.dart';
|
||||
import 'tab_bar_theme.dart';
|
||||
@ -174,6 +175,7 @@ class ThemeData extends Diagnosticable {
|
||||
CupertinoThemeData cupertinoOverrideTheme,
|
||||
SnackBarThemeData snackBarTheme,
|
||||
BottomSheetThemeData bottomSheetTheme,
|
||||
PopupMenuThemeData popupMenuTheme,
|
||||
}) {
|
||||
brightness ??= Brightness.light;
|
||||
final bool isDark = brightness == Brightness.dark;
|
||||
@ -276,6 +278,7 @@ class ThemeData extends Diagnosticable {
|
||||
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
|
||||
snackBarTheme ??= const SnackBarThemeData();
|
||||
bottomSheetTheme ??= const BottomSheetThemeData();
|
||||
popupMenuTheme ??= const PopupMenuThemeData();
|
||||
|
||||
return ThemeData.raw(
|
||||
brightness: brightness,
|
||||
@ -336,6 +339,7 @@ class ThemeData extends Diagnosticable {
|
||||
cupertinoOverrideTheme: cupertinoOverrideTheme,
|
||||
snackBarTheme: snackBarTheme,
|
||||
bottomSheetTheme: bottomSheetTheme,
|
||||
popupMenuTheme: popupMenuTheme,
|
||||
);
|
||||
}
|
||||
|
||||
@ -408,6 +412,7 @@ class ThemeData extends Diagnosticable {
|
||||
@required this.cupertinoOverrideTheme,
|
||||
@required this.snackBarTheme,
|
||||
@required this.bottomSheetTheme,
|
||||
@required this.popupMenuTheme,
|
||||
}) : assert(brightness != null),
|
||||
assert(primaryColor != null),
|
||||
assert(primaryColorBrightness != null),
|
||||
@ -462,7 +467,8 @@ class ThemeData extends Diagnosticable {
|
||||
assert(floatingActionButtonTheme != null),
|
||||
assert(typography != null),
|
||||
assert(snackBarTheme != null),
|
||||
assert(bottomSheetTheme != null);
|
||||
assert(bottomSheetTheme != null),
|
||||
assert(popupMenuTheme != null);
|
||||
|
||||
// Warning: make sure these properties are in the exact same order as in
|
||||
// hashValues() and in the raw constructor and in the order of fields in
|
||||
@ -788,6 +794,10 @@ class ThemeData extends Diagnosticable {
|
||||
/// A theme for customizing the color, elevation, and shape of a bottom sheet.
|
||||
final BottomSheetThemeData bottomSheetTheme;
|
||||
|
||||
/// A theme for customizing the color, shape, elevation, and text style of
|
||||
/// popup menus.
|
||||
final PopupMenuThemeData popupMenuTheme;
|
||||
|
||||
/// Creates a copy of this theme but with the given fields replaced with the new values.
|
||||
ThemeData copyWith({
|
||||
Brightness brightness,
|
||||
@ -848,6 +858,7 @@ class ThemeData extends Diagnosticable {
|
||||
CupertinoThemeData cupertinoOverrideTheme,
|
||||
SnackBarThemeData snackBarTheme,
|
||||
BottomSheetThemeData bottomSheetTheme,
|
||||
PopupMenuThemeData popupMenuTheme,
|
||||
}) {
|
||||
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
|
||||
return ThemeData.raw(
|
||||
@ -909,6 +920,7 @@ class ThemeData extends Diagnosticable {
|
||||
cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
|
||||
snackBarTheme: snackBarTheme ?? this.snackBarTheme,
|
||||
bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme,
|
||||
popupMenuTheme: popupMenuTheme ?? this.popupMenuTheme,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1048,6 +1060,7 @@ class ThemeData extends Diagnosticable {
|
||||
cupertinoOverrideTheme: t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme,
|
||||
snackBarTheme: SnackBarThemeData.lerp(a.snackBarTheme, b.snackBarTheme, t),
|
||||
bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t),
|
||||
popupMenuTheme: PopupMenuThemeData.lerp(a.popupMenuTheme, b.popupMenuTheme, t),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1114,7 +1127,8 @@ class ThemeData extends Diagnosticable {
|
||||
(otherData.typography == typography) &&
|
||||
(otherData.cupertinoOverrideTheme == cupertinoOverrideTheme) &&
|
||||
(otherData.snackBarTheme == snackBarTheme) &&
|
||||
(otherData.bottomSheetTheme == bottomSheetTheme);
|
||||
(otherData.bottomSheetTheme == bottomSheetTheme) &&
|
||||
(otherData.popupMenuTheme == popupMenuTheme);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1181,6 +1195,7 @@ class ThemeData extends Diagnosticable {
|
||||
cupertinoOverrideTheme,
|
||||
snackBarTheme,
|
||||
bottomSheetTheme,
|
||||
popupMenuTheme,
|
||||
];
|
||||
return hashList(values);
|
||||
}
|
||||
@ -1244,6 +1259,7 @@ class ThemeData extends Diagnosticable {
|
||||
properties.add(DiagnosticsProperty<CupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme));
|
||||
properties.add(DiagnosticsProperty<SnackBarThemeData>('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme));
|
||||
properties.add(DiagnosticsProperty<BottomSheetThemeData>('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme));
|
||||
properties.add(DiagnosticsProperty<PopupMenuThemeData>('popupMenuTheme', popupMenuTheme, defaultValue: defaultData.popupMenuTheme));
|
||||
}
|
||||
}
|
||||
|
||||
|
314
packages/flutter/test/material/popup_menu_theme_test.dart
Normal file
314
packages/flutter/test/material/popup_menu_theme_test.dart
Normal file
@ -0,0 +1,314 @@
|
||||
// Copyright 2019 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/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
PopupMenuThemeData _popupMenuTheme() {
|
||||
return PopupMenuThemeData(
|
||||
color: Colors.orange,
|
||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
elevation: 12.0,
|
||||
textStyle: const TextStyle(color: Color(0xffffffff), textBaseline: TextBaseline.alphabetic),
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('PopupMenuThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const PopupMenuThemeData(), const PopupMenuThemeData().copyWith());
|
||||
expect(const PopupMenuThemeData().hashCode, const PopupMenuThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
test('PopupMenuThemeData null fields by default', () {
|
||||
const PopupMenuThemeData popupMenuTheme = PopupMenuThemeData();
|
||||
expect(popupMenuTheme.color, null);
|
||||
expect(popupMenuTheme.shape, null);
|
||||
expect(popupMenuTheme.elevation, null);
|
||||
expect(popupMenuTheme.textStyle, null);
|
||||
});
|
||||
|
||||
testWidgets('Default PopupMenuThemeData debugFillProperties',
|
||||
(WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const PopupMenuThemeData().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('PopupMenuThemeData implements debugFillProperties',
|
||||
(WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
PopupMenuThemeData(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2.0)),
|
||||
elevation: 2.0,
|
||||
textStyle: const TextStyle(color: Color(0xffffffff)),
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'color: Color(0xffffffff)',
|
||||
'shape: RoundedRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.circular(2.0))',
|
||||
'elevation: 2.0',
|
||||
'text style: TextStyle(inherit: true, color: Color(0xffffffff))'
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('Passing no PopupMenuThemeData returns defaults', (WidgetTester tester) async {
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<Object>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
child: const Text('Example'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, null);
|
||||
expect(button.shape, null);
|
||||
expect(button.elevation, 8.0);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style.fontFamily, 'Roboto');
|
||||
expect(text.style.color, const Color(0xdd000000));
|
||||
});
|
||||
|
||||
testWidgets('Popup menu uses values from PopupMenuThemeData', (WidgetTester tester) async {
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuTheme();
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(popupMenuTheme: popupMenuTheme),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<Object>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
child: const Text('Example'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, popupMenuTheme.color);
|
||||
expect(button.shape, popupMenuTheme.shape);
|
||||
expect(button.elevation, popupMenuTheme.elevation);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style, popupMenuTheme.textStyle);
|
||||
});
|
||||
|
||||
testWidgets('Popup menu widget properties take priority over theme', (WidgetTester tester) async {
|
||||
final PopupMenuThemeData popupMenuTheme = _popupMenuTheme();
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
|
||||
const Color color = Colors.purple;
|
||||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
||||
);
|
||||
const double elevation = 7.0;
|
||||
const TextStyle textStyle = TextStyle(color: Color(0x00000000), textBaseline: TextBaseline.alphabetic);
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(popupMenuTheme: popupMenuTheme),
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
elevation: elevation,
|
||||
color: color,
|
||||
shape: shape,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<Object>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
textStyle: textStyle,
|
||||
child: const Text('Example'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, color);
|
||||
expect(button.shape, shape);
|
||||
expect(button.elevation, elevation);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style, textStyle);
|
||||
});
|
||||
|
||||
testWidgets('ThemeData.popupMenuTheme properties are utilized', (WidgetTester tester) async {
|
||||
final Key popupButtonKey = UniqueKey();
|
||||
final Key popupButtonApp = UniqueKey();
|
||||
final Key popupItemKey = UniqueKey();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
key: popupButtonApp,
|
||||
home: Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuTheme(
|
||||
color: Colors.pink,
|
||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
elevation: 6.0,
|
||||
textStyle: const TextStyle(color: Color(0xfffff000), textBaseline: TextBaseline.alphabetic),
|
||||
child: PopupMenuButton<void>(
|
||||
key: popupButtonKey,
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<Object>>[
|
||||
PopupMenuItem<void>(
|
||||
key: popupItemKey,
|
||||
child: const Text('Example'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
await tester.tap(find.byKey(popupButtonKey));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
/// The last Material widget under popupButtonApp is the [PopupMenuButton]
|
||||
/// specified above, so by finding the last descendent of popupButtonApp
|
||||
/// that is of type Material, this code retrieves the built
|
||||
/// [PopupMenuButton].
|
||||
final Material button = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupButtonApp),
|
||||
matching: find.byType(Material),
|
||||
).last,
|
||||
);
|
||||
expect(button.color, Colors.pink);
|
||||
expect(button.shape, BeveledRectangleBorder(borderRadius: BorderRadius.circular(10)));
|
||||
expect(button.elevation, 6.0);
|
||||
|
||||
/// The last DefaultTextStyle widget under popupItemKey is the
|
||||
/// [PopupMenuItem] specified above, so by finding the last descendent of
|
||||
/// popupItemKey that is of type DefaultTextStyle, this code retrieves the
|
||||
/// built [PopupMenuItem].
|
||||
final DefaultTextStyle text = tester.widget<DefaultTextStyle>(
|
||||
find.descendant(
|
||||
of: find.byKey(popupItemKey),
|
||||
matching: find.byType(DefaultTextStyle),
|
||||
).last,
|
||||
);
|
||||
expect(text.style.color, const Color(0xfffff000));
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user