Unverified Commit 8b86d238 authored by Qun Cheng's avatar Qun Cheng Committed by GitHub

Create `DropdownMenu` Widget to Support Material 3 (#116088)

* Created ComboBox

* Fixed failing tests

* Reverted the menu style tests change

* Addressed comments

* Updated documentation and rename foregroundColor variable

* Remamed ComboBox to DropdownMenu

* Removed a unused import

* Removed unused import
Co-authored-by: 's avatarQun Cheng <quncheng@google.com>
parent 1cb16a1e
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Flutter code sample for [DropdownMenu]s. The first dropdown menu has an outlined border
/// which is the default configuration, and the second one has a filled input decoration.
import 'package:flutter/material.dart';
void main() => runApp(const DropdownMenuExample());
class DropdownMenuExample extends StatelessWidget {
const DropdownMenuExample({super.key});
List<DropdownMenuEntry> getEntryList() {
final List<DropdownMenuEntry> entries = <DropdownMenuEntry>[];
for (int index = 0; index < EntryLabel.values.length; index++) {
// Disabled item 1, 2 and 6.
final bool enabled = index != 1 && index != 2 && index != 6;
entries.add(DropdownMenuEntry(label: EntryLabel.values[index].label, enabled: enabled));
}
return entries;
}
@override
Widget build(BuildContext context) {
final List<DropdownMenuEntry> dropdownMenuEntries = getEntryList();
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.green
),
home: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
DropdownMenu(
label: const Text('Label'),
dropdownMenuEntries: dropdownMenuEntries,
),
const SizedBox(width: 20),
DropdownMenu(
enableFilter: true,
leadingIcon: const Icon(Icons.search),
label: const Text('Label'),
dropdownMenuEntries: dropdownMenuEntries,
inputDecorationTheme: const InputDecorationTheme(filled: true),
)
],
),
)
),
),
);
}
}
enum EntryLabel {
item0('Item 0'),
item1('Item 1'),
item2('Item 2'),
item3('Item 3'),
item4('Item 4'),
item5('Item 5'),
item6('Item 6');
const EntryLabel(this.label);
final String label;
}
......@@ -77,6 +77,8 @@ export 'src/material/drawer.dart';
export 'src/material/drawer_header.dart';
export 'src/material/drawer_theme.dart';
export 'src/material/dropdown.dart';
export 'src/material/dropdown_menu.dart';
export 'src/material/dropdown_menu_theme.dart';
export 'src/material/elevated_button.dart';
export 'src/material/elevated_button_theme.dart';
export 'src/material/elevation_overlay.dart';
......
This diff is collapsed.
// Copyright 2014 The Flutter 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/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'input_decorator.dart';
import 'menu_style.dart';
import 'theme.dart';
// Examples can assume:
// late BuildContext context;
/// Overrides the default values of visual properties for descendant [DropdownMenu] widgets.
///
/// Descendant widgets obtain the current [DropdownMenuThemeData] object with
/// [DropdownMenuTheme.of]. Instances of [DropdownMenuTheme] can
/// be customized with [DropdownMenuThemeData.copyWith].
///
/// Typically a [DropdownMenuTheme] is specified as part of the overall [Theme] with
/// [ThemeData.dropdownMenuTheme].
///
/// All [DropdownMenuThemeData] properties are null by default. When null, the [DropdownMenu]
/// computes its own default values, typically based on the overall
/// theme's [ThemeData.colorScheme], [ThemeData.textTheme], and [ThemeData.iconTheme].
@immutable
class DropdownMenuThemeData with Diagnosticable {
/// Creates a [DropdownMenuThemeData] that can be used to override default properties
/// in a [DropdownMenuTheme] widget.
const DropdownMenuThemeData({
this.textStyle,
this.inputDecorationTheme,
this.menuStyle,
});
/// Overrides the default value for [DropdownMenu.textStyle].
final TextStyle? textStyle;
/// The input decoration theme for the [TextField]s in a [DropdownMenu].
///
/// If this is null, the [DropdownMenu] provides its own defaults.
final InputDecorationTheme? inputDecorationTheme;
/// Overrides the menu's default style in a [DropdownMenu].
///
/// Any values not set in the [MenuStyle] will use the menu default for that
/// property.
final MenuStyle? menuStyle;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
DropdownMenuThemeData copyWith({
TextStyle? textStyle,
InputDecorationTheme? inputDecorationTheme,
MenuStyle? menuStyle,
}) {
return DropdownMenuThemeData(
textStyle: textStyle ?? this.textStyle,
inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
menuStyle: menuStyle ?? this.menuStyle,
);
}
/// Linearly interpolates between two dropdown menu themes.
static DropdownMenuThemeData lerp(DropdownMenuThemeData? a, DropdownMenuThemeData? b, double t) {
return DropdownMenuThemeData(
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme,
menuStyle: MenuStyle.lerp(a?.menuStyle, b?.menuStyle, t),
);
}
@override
int get hashCode => Object.hash(
textStyle,
inputDecorationTheme,
menuStyle,
);
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is DropdownMenuThemeData
&& other.textStyle == textStyle
&& other.inputDecorationTheme == inputDecorationTheme
&& other.menuStyle == menuStyle;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null));
properties.add(DiagnosticsProperty<MenuStyle>('menuStyle', menuStyle, defaultValue: null));
}
}
/// An inherited widget that defines the visual properties for [DropdownMenu]s in this widget's subtree.
///
/// Values specified here are used for [DropdownMenu] properties that are not
/// given an explicit non-null value.
class DropdownMenuTheme extends InheritedTheme {
/// Creates a [DropdownMenuTheme] that controls visual parameters for
/// descendant [DropdownMenu]s.
const DropdownMenuTheme({
super.key,
required this.data,
required super.child,
}) : assert(data != null);
/// Specifies the visual properties used by descendant [DropdownMenu]
/// widgets.
final DropdownMenuThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [DropdownMenuTheme] widget, then
/// [ThemeData.dropdownMenuTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// DropdownMenuThemeData theme = DropdownMenuTheme.of(context);
/// ```
///
/// See also:
///
/// * [maybeOf], which returns null if it doesn't find a
/// [DropdownMenuTheme] ancestor.
static DropdownMenuThemeData of(BuildContext context) {
return maybeOf(context) ?? Theme.of(context).dropdownMenuTheme;
}
/// The data from the closest instance of this class that encloses the given
/// context, if any.
///
/// Use this function if you want to allow situations where no
/// [DropdownMenuTheme] is in scope. Prefer using [DropdownMenuTheme.of]
/// in situations where a [DropdownMenuThemeData] is expected to be
/// non-null.
///
/// If there is no [DropdownMenuTheme] in scope, then this function will
/// return null.
///
/// Typical usage is as follows:
///
/// ```dart
/// DropdownMenuThemeData? theme = DropdownMenuTheme.maybeOf(context);
/// if (theme == null) {
/// // Do something else instead.
/// }
/// ```
///
/// See also:
///
/// * [of], which will return [ThemeData.dropdownMenuTheme] if it doesn't
/// find a [DropdownMenuTheme] ancestor, instead of returning null.
static DropdownMenuThemeData? maybeOf(BuildContext context) {
assert(context != null);
return context.dependOnInheritedWidgetOfExactType<DropdownMenuTheme>()?.data;
}
@override
Widget wrap(BuildContext context, Widget child) {
return DropdownMenuTheme(data: data, child: child);
}
@override
bool updateShouldNotify(DropdownMenuTheme oldWidget) => data != oldWidget.data;
}
......@@ -131,6 +131,7 @@ class MenuAnchor extends StatefulWidget {
this.anchorTapClosesMenu = false,
this.onOpen,
this.onClose,
this.crossAxisUnconstrained = true,
required this.menuChildren,
this.builder,
this.child,
......@@ -213,6 +214,14 @@ class MenuAnchor extends StatefulWidget {
/// A callback that is invoked when the menu is closed.
final VoidCallback? onClose;
/// Determine if the menu panel can be wrapped by a [UnconstrainedBox] which allows
/// the panel to render at its "natural" size.
///
/// Defaults to true as it allows developers to render the menu panel at the
/// size it should be. When it is set to false, it can be useful when the menu should
/// be constrained in both main axis and cross axis, such as a [DropdownMenu].
final bool crossAxisUnconstrained;
/// A list of children containing the menu items that are the contents of the
/// menu surrounded by this [MenuAnchor].
///
......@@ -516,6 +525,7 @@ class _MenuAnchorState extends State<MenuAnchor> {
menuPosition: position,
clipBehavior: widget.clipBehavior,
menuChildren: widget.menuChildren,
crossAxisUnconstrained: widget.crossAxisUnconstrained,
),
),
to: overlay.context,
......@@ -791,6 +801,7 @@ class MenuItemButton extends StatefulWidget {
super.key,
this.onPressed,
this.onHover,
this.requestFocusOnHover = true,
this.onFocusChange,
this.focusNode,
this.shortcut,
......@@ -817,6 +828,11 @@ class MenuItemButton extends StatefulWidget {
/// area and false if a pointer has exited.
final ValueChanged<bool>? onHover;
/// Determine if hovering can request focus.
///
/// Defaults to true.
final bool requestFocusOnHover;
/// Handler called when the focus changes.
///
/// Called with true if this widget's node gains focus, and false if it loses
......@@ -1064,7 +1080,7 @@ class _MenuItemButtonState extends State<MenuItemButton> {
void _handleHover(bool hovering) {
widget.onHover?.call(hovering);
if (hovering) {
if (hovering && widget.requestFocusOnHover) {
assert(_debugMenuInfo('Requesting focus for $_focusNode from hover'));
_focusNode.requestFocus();
}
......@@ -2424,7 +2440,10 @@ class _MenuDirectionalFocusAction extends DirectionalFocusAction {
// correct node.
if (currentMenu.widget.childFocusNode != null) {
final FocusTraversalPolicy? policy = FocusTraversalGroup.maybeOf(primaryFocus!.context!);
policy?.invalidateScopeData(currentMenu.widget.childFocusNode!.nearestScope!);
if (currentMenu.widget.childFocusNode!.nearestScope != null) {
policy?.invalidateScopeData(currentMenu.widget.childFocusNode!.nearestScope!);
}
return false;
}
return false;
}
......@@ -2455,7 +2474,10 @@ class _MenuDirectionalFocusAction extends DirectionalFocusAction {
// correct node.
if (currentMenu.widget.childFocusNode != null) {
final FocusTraversalPolicy? policy = FocusTraversalGroup.maybeOf(primaryFocus!.context!);
policy?.invalidateScopeData(currentMenu.widget.childFocusNode!.nearestScope!);
if (currentMenu.widget.childFocusNode!.nearestScope != null) {
policy?.invalidateScopeData(currentMenu.widget.childFocusNode!.nearestScope!);
}
return false;
}
return false;
}
......@@ -3198,6 +3220,7 @@ class _MenuPanel extends StatefulWidget {
required this.menuStyle,
this.clipBehavior = Clip.none,
required this.orientation,
this.crossAxisUnconstrained = true,
required this.children,
});
......@@ -3209,6 +3232,13 @@ class _MenuPanel extends StatefulWidget {
/// Defaults to [Clip.none].
final Clip clipBehavior;
/// Determine if a [UnconstrainedBox] can be applied to the menu panel to allow it to render
/// at its "natural" size.
///
/// Defaults to true. When it is set to false, it can be useful when the menu should
/// be constrained in both main-axis and cross-axis, such as a [DropdownMenu].
final bool crossAxisUnconstrained;
/// The layout orientation of this panel.
final Axis orientation;
......@@ -3297,38 +3327,45 @@ class _MenuPanelState extends State<_MenuPanel> {
);
}
}
return ConstrainedBox(
constraints: effectiveConstraints,
child: UnconstrainedBox(
constrainedAxis: widget.orientation,
Widget menuPanel = _intrinsicCrossSize(
child: Material(
elevation: elevation,
shape: shape,
color: backgroundColor,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
type: backgroundColor == null ? MaterialType.transparency : MaterialType.canvas,
clipBehavior: Clip.hardEdge,
alignment: AlignmentDirectional.centerStart,
child: _intrinsicCrossSize(
child: Material(
elevation: elevation,
shape: shape,
color: backgroundColor,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
type: backgroundColor == null ? MaterialType.transparency : MaterialType.canvas,
clipBehavior: Clip.hardEdge,
child: Padding(
padding: resolvedPadding,
child: SingleChildScrollView(
scrollDirection: widget.orientation,
child: Flex(
crossAxisAlignment: CrossAxisAlignment.start,
textDirection: Directionality.of(context),
direction: widget.orientation,
mainAxisSize: MainAxisSize.min,
children: widget.children,
),
),
child: Padding(
padding: resolvedPadding,
child: SingleChildScrollView(
scrollDirection: widget.orientation,
child: Flex(
crossAxisAlignment: CrossAxisAlignment.start,
textDirection: Directionality.of(context),
direction: widget.orientation,
mainAxisSize: MainAxisSize.min,
children: widget.children,
),
),
),
),
);
if (widget.crossAxisUnconstrained) {
menuPanel = UnconstrainedBox(
constrainedAxis: widget.orientation,
clipBehavior: Clip.hardEdge,
alignment: AlignmentDirectional.centerStart,
child: menuPanel,
);
}
return ConstrainedBox(
constraints: effectiveConstraints,
child: menuPanel,
);
}
Widget _intrinsicCrossSize({required Widget child}) {
......@@ -3349,6 +3386,7 @@ class _Submenu extends StatelessWidget {
required this.menuPosition,
required this.alignmentOffset,
required this.clipBehavior,
this.crossAxisUnconstrained = true,
required this.menuChildren,
});
......@@ -3357,6 +3395,7 @@ class _Submenu extends StatelessWidget {
final Offset? menuPosition;
final Offset alignmentOffset;
final Clip clipBehavior;
final bool crossAxisUnconstrained;
final List<Widget> menuChildren;
@override
......@@ -3454,6 +3493,7 @@ class _Submenu extends StatelessWidget {
menuStyle: menuStyle,
clipBehavior: clipBehavior,
orientation: anchor._orientation,
crossAxisUnconstrained: crossAxisUnconstrained,
children: menuChildren,
),
),
......
......@@ -25,6 +25,7 @@ import 'data_table_theme.dart';
import 'dialog_theme.dart';
import 'divider_theme.dart';
import 'drawer_theme.dart';
import 'dropdown_menu_theme.dart';
import 'elevated_button_theme.dart';
import 'expansion_tile_theme.dart';
import 'filled_button_theme.dart';
......@@ -348,6 +349,7 @@ class ThemeData with Diagnosticable {
DialogTheme? dialogTheme,
DividerThemeData? dividerTheme,
DrawerThemeData? drawerTheme,
DropdownMenuThemeData? dropdownMenuTheme,
ElevatedButtonThemeData? elevatedButtonTheme,
ExpansionTileThemeData? expansionTileTheme,
FilledButtonThemeData? filledButtonTheme,
......@@ -602,6 +604,7 @@ class ThemeData with Diagnosticable {
dialogTheme ??= const DialogTheme();
dividerTheme ??= const DividerThemeData();
drawerTheme ??= const DrawerThemeData();
dropdownMenuTheme ??= const DropdownMenuThemeData();
elevatedButtonTheme ??= const ElevatedButtonThemeData();
expansionTileTheme ??= const ExpansionTileThemeData();
filledButtonTheme ??= const FilledButtonThemeData();
......@@ -699,6 +702,7 @@ class ThemeData with Diagnosticable {
dialogTheme: dialogTheme,
dividerTheme: dividerTheme,
drawerTheme: drawerTheme,
dropdownMenuTheme: dropdownMenuTheme,
elevatedButtonTheme: elevatedButtonTheme,
expansionTileTheme: expansionTileTheme,
filledButtonTheme: filledButtonTheme,
......@@ -812,6 +816,7 @@ class ThemeData with Diagnosticable {
required this.dialogTheme,
required this.dividerTheme,
required this.drawerTheme,
required this.dropdownMenuTheme,
required this.elevatedButtonTheme,
required this.expansionTileTheme,
required this.filledButtonTheme,
......@@ -983,6 +988,7 @@ class ThemeData with Diagnosticable {
assert(dialogTheme != null),
assert(dividerTheme != null),
assert(drawerTheme != null),
assert(dropdownMenuTheme != null),
assert(elevatedButtonTheme != null),
assert(expansionTileTheme != null),
assert(filledButtonTheme != null),
......@@ -1554,6 +1560,9 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the appearance and layout of [Drawer] widgets.
final DrawerThemeData drawerTheme;
/// A theme for customizing the appearance and layout of [DropdownMenu] widgets.
final DropdownMenuThemeData dropdownMenuTheme;
/// A theme for customizing the appearance and internal layout of
/// [ElevatedButton]s.
final ElevatedButtonThemeData elevatedButtonTheme;
......@@ -1816,6 +1825,13 @@ class ThemeData with Diagnosticable {
Color get toggleableActiveColor => _toggleableActiveColor!;
final Color? _toggleableActiveColor;
// The number 5 was chosen without any real science or research behind it. It
// copies of ThemeData in memory comfortably) and not too small (most apps
// shouldn't have more than 5 theme/localization pairs).
static const int _localizedThemeDataCacheSize = 5;
/// Caches localized themes to speed up the [localize] method.
/// Creates a copy of this theme but with the given fields replaced with the new values.
///
/// The [brightness] value is applied to the [colorScheme].
......@@ -1883,6 +1899,7 @@ class ThemeData with Diagnosticable {
DialogTheme? dialogTheme,
DividerThemeData? dividerTheme,
DrawerThemeData? drawerTheme,
DropdownMenuThemeData? dropdownMenuTheme,
ElevatedButtonThemeData? elevatedButtonTheme,
ExpansionTileThemeData? expansionTileTheme,
FilledButtonThemeData? filledButtonTheme,
......@@ -2047,6 +2064,7 @@ class ThemeData with Diagnosticable {
dialogTheme: dialogTheme ?? this.dialogTheme,
dividerTheme: dividerTheme ?? this.dividerTheme,
drawerTheme: drawerTheme ?? this.drawerTheme,
dropdownMenuTheme: dropdownMenuTheme ?? this.dropdownMenuTheme,
elevatedButtonTheme: elevatedButtonTheme ?? this.elevatedButtonTheme,
expansionTileTheme: expansionTileTheme ?? this.expansionTileTheme,
filledButtonTheme: filledButtonTheme ?? this.filledButtonTheme,
......@@ -2089,14 +2107,7 @@ class ThemeData with Diagnosticable {
bottomAppBarColor: bottomAppBarColor ?? _bottomAppBarColor,
);
}
// The number 5 was chosen without any real science or research behind it. It
// just seemed like a number that's not too big (we should be able to fit 5
// copies of ThemeData in memory comfortably) and not too small (most apps
// shouldn't have more than 5 theme/localization pairs).
static const int _localizedThemeDataCacheSize = 5;
/// Caches localized themes to speed up the [localize] method.
static final _FifoCache<_IdentityThemeDataCacheKey, ThemeData> _localizedThemeDataCache =
_FifoCache<_IdentityThemeDataCacheKey, ThemeData>(_localizedThemeDataCacheSize);
......@@ -2253,6 +2264,7 @@ class ThemeData with Diagnosticable {
dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t),
drawerTheme: DrawerThemeData.lerp(a.drawerTheme, b.drawerTheme, t)!,
dropdownMenuTheme: DropdownMenuThemeData.lerp(a.dropdownMenuTheme, b.dropdownMenuTheme, t),
elevatedButtonTheme: ElevatedButtonThemeData.lerp(a.elevatedButtonTheme, b.elevatedButtonTheme, t)!,
expansionTileTheme: ExpansionTileThemeData.lerp(a.expansionTileTheme, b.expansionTileTheme, t)!,
filledButtonTheme: FilledButtonThemeData.lerp(a.filledButtonTheme, b.filledButtonTheme, t)!,
......@@ -2361,6 +2373,7 @@ class ThemeData with Diagnosticable {
other.dialogTheme == dialogTheme &&
other.dividerTheme == dividerTheme &&
other.drawerTheme == drawerTheme &&
other.dropdownMenuTheme == dropdownMenuTheme &&
other.elevatedButtonTheme == elevatedButtonTheme &&
other.expansionTileTheme == expansionTileTheme &&
other.filledButtonTheme == filledButtonTheme &&
......@@ -2466,6 +2479,7 @@ class ThemeData with Diagnosticable {
dialogTheme,
dividerTheme,
drawerTheme,
dropdownMenuTheme,
elevatedButtonTheme,
expansionTileTheme,
filledButtonTheme,
......@@ -2573,6 +2587,7 @@ class ThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DividerThemeData>('dividerTheme', dividerTheme, defaultValue: defaultData.dividerTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DrawerThemeData>('drawerTheme', drawerTheme, defaultValue: defaultData.drawerTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DropdownMenuThemeData>('dropdownMenuTheme', dropdownMenuTheme, defaultValue: defaultData.dropdownMenuTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<ExpansionTileThemeData>('expansionTileTheme', expansionTileTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<FilledButtonThemeData>('filledButtonTheme', filledButtonTheme, defaultValue: defaultData.filledButtonTheme, level: DiagnosticLevel.debug));
......
This diff is collapsed.
This diff is collapsed.
......@@ -784,6 +784,7 @@ void main() {
dialogTheme: const DialogTheme(backgroundColor: Colors.black),
dividerTheme: const DividerThemeData(color: Colors.black),
drawerTheme: const DrawerThemeData(),
dropdownMenuTheme: const DropdownMenuThemeData(),
elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(backgroundColor: Colors.green)),
expansionTileTheme: const ExpansionTileThemeData(backgroundColor: Colors.black),
filledButtonTheme: FilledButtonThemeData(style: FilledButton.styleFrom(foregroundColor: Colors.green)),
......@@ -904,6 +905,7 @@ void main() {
dialogTheme: const DialogTheme(backgroundColor: Colors.white),
dividerTheme: const DividerThemeData(color: Colors.white),
drawerTheme: const DrawerThemeData(),
dropdownMenuTheme: const DropdownMenuThemeData(),
elevatedButtonTheme: const ElevatedButtonThemeData(),
expansionTileTheme: const ExpansionTileThemeData(backgroundColor: Colors.black),
filledButtonTheme: const FilledButtonThemeData(),
......@@ -1253,6 +1255,7 @@ void main() {
'dialogTheme',
'dividerTheme',
'drawerTheme',
'dropdownMenuTheme',
'elevatedButtonTheme',
'expansionTileTheme',
'filledButtonTheme',
......
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