mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Migrate IconButton
to Material 3 - Part 1 (#105176)
* Added standard IconButton for M3 with new ButtonStyle field * Added IconButton examples for standard, filled, filled_tonal, and outlined types Co-authored-by: Qun Cheng <quncheng@google.com>
This commit is contained in:
parent
1718519188
commit
66a84d1fed
@ -22,6 +22,7 @@ import 'package:gen_defaults/button_template.dart';
|
||||
import 'package:gen_defaults/card_template.dart';
|
||||
import 'package:gen_defaults/dialog_template.dart';
|
||||
import 'package:gen_defaults/fab_template.dart';
|
||||
import 'package:gen_defaults/icon_button_template.dart';
|
||||
import 'package:gen_defaults/navigation_bar_template.dart';
|
||||
import 'package:gen_defaults/navigation_rail_template.dart';
|
||||
import 'package:gen_defaults/surface_tint.dart';
|
||||
@ -55,6 +56,9 @@ Future<void> main(List<String> args) async {
|
||||
'fab_large_primary.json',
|
||||
'fab_primary.json',
|
||||
'fab_small_primary.json',
|
||||
'icon_button.json',
|
||||
'icon_button_filled.json',
|
||||
'icon_button_filled_tonal.json',
|
||||
'motion.json',
|
||||
'navigation_bar.json',
|
||||
'navigation_rail.json',
|
||||
@ -86,6 +90,7 @@ Future<void> main(List<String> args) async {
|
||||
CardTemplate('$materialLib/card.dart', tokens).updateFile();
|
||||
DialogTemplate('$materialLib/dialog.dart', tokens).updateFile();
|
||||
FABTemplate('$materialLib/floating_action_button.dart', tokens).updateFile();
|
||||
IconButtonTemplate('$materialLib/icon_button.dart', tokens).updateFile();
|
||||
NavigationBarTemplate('$materialLib/navigation_bar.dart', tokens).updateFile();
|
||||
NavigationRailTemplate('$materialLib/navigation_rail.dart', tokens).updateFile();
|
||||
SurfaceTintTemplate('$materialLib/elevation_overlay.dart', tokens).updateFile();
|
||||
|
99
dev/tools/gen_defaults/lib/icon_button_template.dart
Normal file
99
dev/tools/gen_defaults/lib/icon_button_template.dart
Normal file
@ -0,0 +1,99 @@
|
||||
// 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 'template.dart';
|
||||
|
||||
class IconButtonTemplate extends TokenTemplate {
|
||||
const IconButtonTemplate(super.fileName, super.tokens)
|
||||
: super(colorSchemePrefix: '_colors.',
|
||||
);
|
||||
|
||||
@override
|
||||
String generate() => '''
|
||||
// Generated version ${tokens["version"]}
|
||||
class _TokenDefaultsM3 extends ButtonStyle {
|
||||
_TokenDefaultsM3(this.context)
|
||||
: super(
|
||||
animationDuration: kThemeChangeDuration,
|
||||
enableFeedback: true,
|
||||
alignment: Alignment.center,
|
||||
);
|
||||
|
||||
final BuildContext context;
|
||||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||||
|
||||
// No default text style
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Color?>? get backgroundColor =>
|
||||
ButtonStyleButton.allOrNull<Color>(Colors.transparent);
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Color?>? get foregroundColor =>
|
||||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled))
|
||||
return ${componentColor('md.comp.icon-button.disabled.icon')};
|
||||
return ${componentColor('md.comp.icon-button.unselected.icon')};
|
||||
});
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Color?>? get overlayColor =>
|
||||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered))
|
||||
return ${componentColor('md.comp.icon-button.unselected.hover.state-layer')};
|
||||
if (states.contains(MaterialState.focused))
|
||||
return ${componentColor('md.comp.icon-button.unselected.focus.state-layer')};
|
||||
if (states.contains(MaterialState.pressed))
|
||||
return ${componentColor('md.comp.icon-button.unselected.pressed.state-layer')};
|
||||
return null;
|
||||
});
|
||||
|
||||
// No default shadow color
|
||||
|
||||
// No default surface tint color
|
||||
|
||||
@override
|
||||
MaterialStateProperty<double>? get elevation =>
|
||||
ButtonStyleButton.allOrNull<double>(0.0);
|
||||
|
||||
@override
|
||||
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
|
||||
ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(const EdgeInsets.all(8.0));
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Size>? get minimumSize =>
|
||||
ButtonStyleButton.allOrNull<Size>(const Size(${tokens["md.comp.icon-button.state-layer.size"]}, ${tokens["md.comp.icon-button.state-layer.size"]}));
|
||||
|
||||
// No default fixedSize
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Size>? get maximumSize =>
|
||||
ButtonStyleButton.allOrNull<Size>(Size.infinite);
|
||||
|
||||
// No default side
|
||||
|
||||
@override
|
||||
MaterialStateProperty<OutlinedBorder>? get shape =>
|
||||
ButtonStyleButton.allOrNull<OutlinedBorder>(${shape("md.comp.icon-button.state-layer")});
|
||||
|
||||
@override
|
||||
MaterialStateProperty<MouseCursor?>? get mouseCursor =>
|
||||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled))
|
||||
return SystemMouseCursors.basic;
|
||||
return SystemMouseCursors.click;
|
||||
});
|
||||
|
||||
@override
|
||||
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
|
||||
|
||||
@override
|
||||
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
|
||||
|
||||
@override
|
||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||
}
|
||||
''';
|
||||
|
||||
}
|
118
examples/api/lib/material/icon_button/icon_button.2.dart
Normal file
118
examples/api/lib/material/icon_button/icon_button.2.dart
Normal file
@ -0,0 +1,118 @@
|
||||
// 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 IconButton
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const IconButtonApp());
|
||||
}
|
||||
|
||||
class IconButtonApp extends StatelessWidget {
|
||||
const IconButtonApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
|
||||
title: 'Icon Button Types',
|
||||
home: const Scaffold(
|
||||
body: ButtonTypesExample(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonTypesExample extends StatelessWidget {
|
||||
const ButtonTypesExample({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Row(
|
||||
children: const <Widget>[
|
||||
Spacer(),
|
||||
ButtonTypesGroup(enabled: true),
|
||||
ButtonTypesGroup(enabled: false),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonTypesGroup extends StatelessWidget {
|
||||
const ButtonTypesGroup({ super.key, required this.enabled });
|
||||
|
||||
final bool enabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final VoidCallback? onPressed = enabled ? () {} : null;
|
||||
final ColorScheme colors = Theme.of(context).colorScheme;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
IconButton(icon: const Icon(Icons.filter_drama), onPressed: onPressed),
|
||||
|
||||
// Use a standard IconButton with specific style to implement the
|
||||
// 'Filled' type.
|
||||
IconButton(
|
||||
icon: const Icon(Icons.filter_drama),
|
||||
onPressed: onPressed,
|
||||
style: IconButton.styleFrom(
|
||||
foregroundColor: colors.onPrimary,
|
||||
backgroundColor: colors.primary,
|
||||
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
|
||||
hoverColor: colors.onPrimary.withOpacity(0.08),
|
||||
focusColor: colors.onPrimary.withOpacity(0.12),
|
||||
highlightColor: colors.onPrimary.withOpacity(0.12),
|
||||
)
|
||||
),
|
||||
|
||||
// Use a standard IconButton with specific style to implement the
|
||||
// 'Filled Tonal' type.
|
||||
IconButton(
|
||||
icon: const Icon(Icons.filter_drama),
|
||||
onPressed: onPressed,
|
||||
style: IconButton.styleFrom(
|
||||
foregroundColor: colors.onSecondaryContainer,
|
||||
backgroundColor: colors.secondaryContainer,
|
||||
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
|
||||
hoverColor: colors.onSecondaryContainer.withOpacity(0.08),
|
||||
focusColor: colors.onSecondaryContainer.withOpacity(0.12),
|
||||
highlightColor: colors.onSecondaryContainer.withOpacity(0.12),
|
||||
),
|
||||
),
|
||||
|
||||
// Use a standard IconButton with specific style to implement the
|
||||
// 'Outlined' type.
|
||||
IconButton(
|
||||
icon: const Icon(Icons.filter_drama),
|
||||
onPressed: onPressed,
|
||||
style: IconButton.styleFrom(
|
||||
focusColor: colors.onSurfaceVariant.withOpacity(0.12),
|
||||
highlightColor: colors.onSurface.withOpacity(0.12),
|
||||
side: onPressed == null
|
||||
? BorderSide(color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12))
|
||||
: BorderSide(color: colors.outline),
|
||||
).copyWith(
|
||||
foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return colors.onSurface;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -8,11 +8,16 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'button_style.dart';
|
||||
import 'button_style_button.dart';
|
||||
import 'color_scheme.dart';
|
||||
import 'colors.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'icons.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'material.dart';
|
||||
import 'material_state.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'tooltip.dart';
|
||||
@ -92,6 +97,17 @@ const double _kMinButtonSize = kMinInteractiveDimension;
|
||||
/// ** See code in examples/api/lib/material/icon_button/icon_button.1.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// Material Design 3 introduced new types (standard and contained) of [IconButton]s.
|
||||
/// The default [IconButton] is the standard type, and contained icon buttons can be produced
|
||||
/// by configuring the [IconButton] widget's properties.
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample shows creation of [IconButton] widgets for standard, filled,
|
||||
/// filled tonal and outlined types, as described in: https://m3.material.io/components/icon-buttons/overview
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/icon_button/icon_button.2.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Icons], the library of Material Icons.
|
||||
@ -134,6 +150,7 @@ class IconButton extends StatelessWidget {
|
||||
this.tooltip,
|
||||
this.enableFeedback = true,
|
||||
this.constraints,
|
||||
this.style,
|
||||
required this.icon,
|
||||
}) : assert(padding != null),
|
||||
assert(alignment != null),
|
||||
@ -184,6 +201,8 @@ class IconButton extends StatelessWidget {
|
||||
|
||||
/// The splash radius.
|
||||
///
|
||||
/// If [ThemeData.useMaterial3] is set to true, this will not be used.
|
||||
///
|
||||
/// If null, default splash radius of [Material.defaultSplashRadius] is used.
|
||||
final double? splashRadius;
|
||||
|
||||
@ -230,6 +249,8 @@ class IconButton extends StatelessWidget {
|
||||
/// fill the button area if the touch is held for long enough time. If the splash
|
||||
/// color has transparency then the highlight and button color will show through.
|
||||
///
|
||||
/// If [ThemeData.useMaterial3] is set to true, this will not be used.
|
||||
///
|
||||
/// Defaults to the Theme's splash color, [ThemeData.splashColor].
|
||||
final Color? splashColor;
|
||||
|
||||
@ -301,10 +322,125 @@ class IconButton extends StatelessWidget {
|
||||
/// and `Theme.of(context).visualDensity` otherwise.
|
||||
final BoxConstraints? constraints;
|
||||
|
||||
/// Customizes this button's appearance.
|
||||
///
|
||||
/// Non-null properties of this style override the corresponding
|
||||
/// properties in [_IconButtonM3.themeStyleOf] and [_IconButtonM3.defaultStyleOf].
|
||||
/// [MaterialStateProperty]s that resolve to non-null values will similarly
|
||||
/// override the corresponding [MaterialStateProperty]s in [_IconButtonM3.themeStyleOf]
|
||||
/// and [_IconButtonM3.defaultStyleOf].
|
||||
///
|
||||
/// The [style] is only used for Material 3 [IconButton]. If [ThemeData.useMaterial3]
|
||||
/// is set to true, [style] is preferred for icon button customization, and any
|
||||
/// parameters defined in [style] will override the same parameters in [IconButton].
|
||||
///
|
||||
/// For example, if [IconButton]'s [visualDensity] is set to [VisualDensity.standard]
|
||||
/// and [style]'s [visualDensity] is set to [VisualDensity.compact],
|
||||
/// the icon button will have [VisualDensity.compact] to define the button's layout.
|
||||
///
|
||||
/// Null by default.
|
||||
final ButtonStyle? style;
|
||||
|
||||
/// A static convenience method that constructs an icon button
|
||||
/// [ButtonStyle] given simple values. This method is only used for Material 3.
|
||||
///
|
||||
/// The [foregroundColor] color is used to create a [MaterialStateProperty]
|
||||
/// [ButtonStyle.foregroundColor] value. Specify a value for [foregroundColor]
|
||||
/// to specify the color of the button's icons. The [hoverColor], [focusColor]
|
||||
/// and [highlightColor] colors are used to indicate the hover, focus,
|
||||
/// and pressed states. Use [backgroundColor] for the button's background
|
||||
/// fill color. Use [disabledForegroundColor] and [disabledBackgroundColor]
|
||||
/// to specify the button's disabled icon and fill color.
|
||||
///
|
||||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
||||
/// parameters are used to construct [ButtonStyle].mouseCursor.
|
||||
///
|
||||
/// All of the other parameters are either used directly or used to
|
||||
/// create a [MaterialStateProperty] with a single value for all
|
||||
/// states.
|
||||
///
|
||||
/// All parameters default to null, by default this method returns
|
||||
/// a [ButtonStyle] that doesn't override anything.
|
||||
///
|
||||
/// For example, to override the default icon color for a
|
||||
/// [IconButton], as well as its overlay color, with all of the
|
||||
/// standard opacity adjustments for the pressed, focused, and
|
||||
/// hovered states, one could write:
|
||||
///
|
||||
/// ```dart
|
||||
/// IconButton(
|
||||
/// style: IconButton.styleFrom(foregroundColor: Colors.green),
|
||||
/// )
|
||||
/// ```
|
||||
static ButtonStyle styleFrom({
|
||||
Color? foregroundColor,
|
||||
Color? backgroundColor,
|
||||
Color? disabledForegroundColor,
|
||||
Color? disabledBackgroundColor,
|
||||
Color? focusColor,
|
||||
Color? hoverColor,
|
||||
Color? highlightColor,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
double? elevation,
|
||||
Size? minimumSize,
|
||||
Size? fixedSize,
|
||||
Size? maximumSize,
|
||||
BorderSide? side,
|
||||
OutlinedBorder? shape,
|
||||
EdgeInsetsGeometry? padding,
|
||||
MouseCursor? enabledMouseCursor,
|
||||
MouseCursor? disabledMouseCursor,
|
||||
VisualDensity? visualDensity,
|
||||
MaterialTapTargetSize? tapTargetSize,
|
||||
Duration? animationDuration,
|
||||
bool? enableFeedback,
|
||||
AlignmentGeometry? alignment,
|
||||
InteractiveInkFeatureFactory? splashFactory,
|
||||
}) {
|
||||
final MaterialStateProperty<Color?>? buttonBackgroundColor = (backgroundColor == null && disabledBackgroundColor == null)
|
||||
? null
|
||||
: _IconButtonDefaultBackground(backgroundColor, disabledBackgroundColor);
|
||||
final MaterialStateProperty<Color?>? buttonForegroundColor = (foregroundColor == null && disabledForegroundColor == null)
|
||||
? null
|
||||
: _IconButtonDefaultForeground(foregroundColor, disabledForegroundColor);
|
||||
final MaterialStateProperty<Color?>? overlayColor = (foregroundColor == null && hoverColor == null && focusColor == null && highlightColor == null)
|
||||
? null
|
||||
: _IconButtonDefaultOverlay(foregroundColor, focusColor, hoverColor, highlightColor);
|
||||
final MaterialStateProperty<MouseCursor>? mouseCursor = (enabledMouseCursor == null && disabledMouseCursor == null)
|
||||
? null
|
||||
: _IconButtonDefaultMouseCursor(enabledMouseCursor!, disabledMouseCursor!);
|
||||
|
||||
return ButtonStyle(
|
||||
backgroundColor: buttonBackgroundColor,
|
||||
foregroundColor: buttonForegroundColor,
|
||||
overlayColor: overlayColor,
|
||||
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
||||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||||
elevation: ButtonStyleButton.allOrNull<double>(elevation),
|
||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||||
fixedSize: ButtonStyleButton.allOrNull<Size>(fixedSize),
|
||||
maximumSize: ButtonStyleButton.allOrNull<Size>(maximumSize),
|
||||
side: ButtonStyleButton.allOrNull<BorderSide>(side),
|
||||
shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
|
||||
mouseCursor: mouseCursor,
|
||||
visualDensity: visualDensity,
|
||||
tapTargetSize: tapTargetSize,
|
||||
animationDuration: animationDuration,
|
||||
enableFeedback: enableFeedback,
|
||||
alignment: alignment,
|
||||
splashFactory: splashFactory,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final ThemeData theme = Theme.of(context);
|
||||
if (!theme.useMaterial3) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
}
|
||||
|
||||
Color? currentColor;
|
||||
if (onPressed != null) {
|
||||
currentColor = color;
|
||||
@ -321,6 +457,55 @@ class IconButton extends StatelessWidget {
|
||||
final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
|
||||
final double effectiveIconSize = iconSize ?? IconTheme.of(context).size ?? 24.0;
|
||||
|
||||
if (theme.useMaterial3) {
|
||||
final Size? minSize = constraints == null
|
||||
? null
|
||||
: Size(constraints!.minWidth, constraints!.minHeight);
|
||||
final Size? maxSize = constraints == null
|
||||
? null
|
||||
: Size(constraints!.maxWidth, constraints!.maxHeight);
|
||||
|
||||
ButtonStyle adjustedStyle = styleFrom(
|
||||
visualDensity: visualDensity,
|
||||
foregroundColor: color,
|
||||
disabledForegroundColor: disabledColor,
|
||||
focusColor: focusColor,
|
||||
hoverColor: hoverColor,
|
||||
highlightColor: highlightColor,
|
||||
padding: padding,
|
||||
minimumSize: minSize,
|
||||
maximumSize: maxSize,
|
||||
alignment: alignment,
|
||||
enabledMouseCursor: mouseCursor,
|
||||
disabledMouseCursor: mouseCursor,
|
||||
enableFeedback: enableFeedback,
|
||||
);
|
||||
if (style != null) {
|
||||
adjustedStyle = style!.merge(adjustedStyle);
|
||||
}
|
||||
|
||||
Widget iconButton = IconTheme.merge(
|
||||
data: IconThemeData(
|
||||
size: effectiveIconSize,
|
||||
),
|
||||
child: icon,
|
||||
);
|
||||
if (tooltip != null) {
|
||||
iconButton = Tooltip(
|
||||
message: tooltip,
|
||||
child: iconButton,
|
||||
);
|
||||
}
|
||||
return _IconButtonM3(
|
||||
key: key,
|
||||
style: adjustedStyle,
|
||||
onPressed: onPressed,
|
||||
autofocus: autofocus,
|
||||
focusNode: focusNode,
|
||||
child: iconButton,
|
||||
);
|
||||
}
|
||||
|
||||
Widget result = ConstrainedBox(
|
||||
constraints: adjustedConstraints,
|
||||
child: Padding(
|
||||
@ -389,3 +574,245 @@ class IconButton extends StatelessWidget {
|
||||
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
class _IconButtonM3 extends ButtonStyleButton {
|
||||
const _IconButtonM3({
|
||||
super.key,
|
||||
required super.onPressed,
|
||||
super.style,
|
||||
super.focusNode,
|
||||
super.autofocus = false,
|
||||
required Widget super.child,
|
||||
}) : super(
|
||||
onLongPress: null,
|
||||
onHover: null,
|
||||
onFocusChange: null,
|
||||
clipBehavior: Clip.none);
|
||||
|
||||
/// ## Material 3 defaults
|
||||
///
|
||||
/// If [ThemeData.useMaterial3] is set to true the following defaults will
|
||||
/// be used:
|
||||
///
|
||||
/// * `textStyle` - null
|
||||
/// * `backgroundColor` - transparent
|
||||
/// * `foregroundColor`
|
||||
/// * disabled - Theme.colorScheme.onSurface(0.38)
|
||||
/// * others - Theme.colorScheme.onSurfaceVariant
|
||||
/// * `overlayColor`
|
||||
/// * hovered or focused - Theme.colorScheme.onSurfaceVariant(0.08)
|
||||
/// * pressed - Theme.colorScheme.onSurfaceVariant(0.12)
|
||||
/// * others - null
|
||||
/// * `shadowColor` - null
|
||||
/// * `surfaceTintColor` - null
|
||||
/// * `elevation` - 0
|
||||
/// * `padding` - all(8)
|
||||
/// * `minimumSize` - Size(40, 40)
|
||||
/// * `fixedSize` - null
|
||||
/// * `maximumSize` - Size.infinite
|
||||
/// * `side` - null
|
||||
/// * `shape` - StadiumBorder()
|
||||
/// * `mouseCursor`
|
||||
/// * disabled - SystemMouseCursors.basic
|
||||
/// * others - SystemMouseCursors.click
|
||||
/// * `visualDensity` - theme.visualDensity
|
||||
/// * `tapTargetSize` - theme.materialTapTargetSize
|
||||
/// * `animationDuration` - kThemeChangeDuration
|
||||
/// * `enableFeedback` - true
|
||||
/// * `alignment` - Alignment.center
|
||||
/// * `splashFactory` - Theme.splashFactory
|
||||
@override
|
||||
ButtonStyle defaultStyleOf(BuildContext context) {
|
||||
return _TokenDefaultsM3(context);
|
||||
}
|
||||
|
||||
/// Returns null because [IconButton] doesn't have its component theme.
|
||||
@override
|
||||
ButtonStyle? themeStyleOf(BuildContext context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class _IconButtonDefaultBackground extends MaterialStateProperty<Color?> {
|
||||
_IconButtonDefaultBackground(this.background, this.disabledBackground);
|
||||
|
||||
final Color? background;
|
||||
final Color? disabledBackground;
|
||||
|
||||
@override
|
||||
Color? resolve(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return disabledBackground;
|
||||
}
|
||||
return background;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '{disabled: $disabledBackground, otherwise: $background}';
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class _IconButtonDefaultForeground extends MaterialStateProperty<Color?> {
|
||||
_IconButtonDefaultForeground(this.foregroundColor, this.disabledForegroundColor);
|
||||
|
||||
final Color? foregroundColor;
|
||||
final Color? disabledForegroundColor;
|
||||
|
||||
@override
|
||||
Color? resolve(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return disabledForegroundColor;
|
||||
}
|
||||
return foregroundColor;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '{disabled: $disabledForegroundColor, otherwise: $foregroundColor}';
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class _IconButtonDefaultOverlay extends MaterialStateProperty<Color?> {
|
||||
_IconButtonDefaultOverlay(this.foregroundColor, this.focusColor, this.hoverColor, this.highlightColor);
|
||||
|
||||
final Color? foregroundColor;
|
||||
final Color? focusColor;
|
||||
final Color? hoverColor;
|
||||
final Color? highlightColor;
|
||||
|
||||
@override
|
||||
Color? resolve(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor ?? foregroundColor?.withOpacity(0.08);
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusColor ?? foregroundColor?.withOpacity(0.08);
|
||||
}
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return highlightColor ?? foregroundColor?.withOpacity(0.12);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '{hovered: $hoverColor, focused: $focusColor, pressed: $highlightColor, otherwise: null}';
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class _IconButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor> with Diagnosticable {
|
||||
_IconButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
|
||||
|
||||
final MouseCursor enabledCursor;
|
||||
final MouseCursor disabledCursor;
|
||||
|
||||
@override
|
||||
MouseCursor resolve(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return disabledCursor;
|
||||
}
|
||||
return enabledCursor;
|
||||
}
|
||||
}
|
||||
|
||||
// BEGIN GENERATED TOKEN PROPERTIES
|
||||
|
||||
// Generated code to the end of this file. Do not edit by hand.
|
||||
// These defaults are generated from the Material Design Token
|
||||
// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||
|
||||
// Generated version v0_98
|
||||
class _TokenDefaultsM3 extends ButtonStyle {
|
||||
_TokenDefaultsM3(this.context)
|
||||
: super(
|
||||
animationDuration: kThemeChangeDuration,
|
||||
enableFeedback: true,
|
||||
alignment: Alignment.center,
|
||||
);
|
||||
|
||||
final BuildContext context;
|
||||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||||
|
||||
// No default text style
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Color?>? get backgroundColor =>
|
||||
ButtonStyleButton.allOrNull<Color>(Colors.transparent);
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Color?>? get foregroundColor =>
|
||||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return _colors.onSurface.withOpacity(0.38);
|
||||
}
|
||||
return _colors.onSurfaceVariant;
|
||||
});
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Color?>? get overlayColor =>
|
||||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return _colors.onSurfaceVariant.withOpacity(0.08);
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return _colors.onSurfaceVariant.withOpacity(0.08);
|
||||
}
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return _colors.onSurfaceVariant.withOpacity(0.12);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// No default shadow color
|
||||
|
||||
// No default surface tint color
|
||||
|
||||
@override
|
||||
MaterialStateProperty<double>? get elevation =>
|
||||
ButtonStyleButton.allOrNull<double>(0.0);
|
||||
|
||||
@override
|
||||
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
|
||||
ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(const EdgeInsets.all(8.0));
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Size>? get minimumSize =>
|
||||
ButtonStyleButton.allOrNull<Size>(const Size(40.0, 40.0));
|
||||
|
||||
// No default fixedSize
|
||||
|
||||
@override
|
||||
MaterialStateProperty<Size>? get maximumSize =>
|
||||
ButtonStyleButton.allOrNull<Size>(Size.infinite);
|
||||
|
||||
// No default side
|
||||
|
||||
@override
|
||||
MaterialStateProperty<OutlinedBorder>? get shape =>
|
||||
ButtonStyleButton.allOrNull<OutlinedBorder>(const StadiumBorder());
|
||||
|
||||
@override
|
||||
MaterialStateProperty<MouseCursor?>? get mouseCursor =>
|
||||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return SystemMouseCursors.basic;
|
||||
}
|
||||
return SystemMouseCursors.click;
|
||||
});
|
||||
|
||||
@override
|
||||
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
|
||||
|
||||
@override
|
||||
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
|
||||
|
||||
@override
|
||||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||||
}
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES
|
||||
|
@ -22,14 +22,17 @@ class MockOnPressedFunction {
|
||||
|
||||
void main() {
|
||||
late MockOnPressedFunction mockOnPressedFunction;
|
||||
|
||||
const ColorScheme colorScheme = ColorScheme.light();
|
||||
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||
setUp(() {
|
||||
mockOnPressedFunction = MockOnPressedFunction();
|
||||
});
|
||||
|
||||
testWidgets('test default icon buttons are sized up to 48', (WidgetTester tester) async {
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconButton(
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.link),
|
||||
@ -45,14 +48,16 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('test small icons are sized up to 48dp', (WidgetTester tester) async {
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconButton(
|
||||
iconSize: 10.0,
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
||||
@ -60,15 +65,17 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('test icons can be small when total size is >48dp', (WidgetTester tester) async {
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconButton(
|
||||
iconSize: 10.0,
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
||||
@ -76,18 +83,19 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('when both iconSize and IconTheme.of(context).size are null, size falls back to 24.0', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconTheme(
|
||||
data: const IconThemeData(),
|
||||
child: IconButton(
|
||||
focusNode: focusNode,
|
||||
focusNode: FocusNode(debugLabel: 'Ink Focus'),
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final RenderBox icon = tester.renderObject(find.byType(Icon));
|
||||
@ -96,9 +104,11 @@ void main() {
|
||||
|
||||
testWidgets('when null, iconSize is overridden by closest IconTheme', (WidgetTester tester) async {
|
||||
RenderBox icon;
|
||||
final bool material3 = theme.useMaterial3;
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconTheme(
|
||||
data: const IconThemeData(size: 10),
|
||||
child: IconButton(
|
||||
@ -106,7 +116,7 @@ void main() {
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
icon = tester.renderObject(find.byType(Icon));
|
||||
@ -114,8 +124,10 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
useMaterial3: material3,
|
||||
iconTheme: const IconThemeData(size: 10),
|
||||
),
|
||||
child: IconButton(
|
||||
@ -123,7 +135,7 @@ void main() {
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
icon = tester.renderObject(find.byType(Icon));
|
||||
@ -131,8 +143,10 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
useMaterial3: material3,
|
||||
iconTheme: const IconThemeData(size: 20),
|
||||
),
|
||||
child: IconTheme(
|
||||
@ -151,10 +165,12 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconTheme(
|
||||
data: const IconThemeData(size: 20),
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
useMaterial3: material3,
|
||||
iconTheme: const IconThemeData(size: 10),
|
||||
),
|
||||
child: IconButton(
|
||||
@ -171,8 +187,10 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('when non-null, iconSize precedes IconTheme.of(context).size', (WidgetTester tester) async {
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconTheme(
|
||||
data: const IconThemeData(size: 30.0),
|
||||
child: IconButton(
|
||||
@ -188,28 +206,36 @@ void main() {
|
||||
expect(icon.size, const Size(10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Small icons with non-null constraints can be <48dp', (WidgetTester tester) async {
|
||||
testWidgets('Small icons with non-null constraints can be <48dp for M2, but =48dp for M3', (WidgetTester tester) async {
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconButton(
|
||||
iconSize: 10.0,
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.link),
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
||||
final RenderBox icon = tester.renderObject(find.byType(Icon));
|
||||
|
||||
// By default IconButton has a padding of 8.0 on all sides, so both
|
||||
// width and height are 10.0 + 2 * 8.0 = 26.0
|
||||
expect(iconButton.size, const Size(26.0, 26.0));
|
||||
// M3 IconButton is a subclass of ButtonStyleButton which has a minimum
|
||||
// Size(48.0, 48.0).
|
||||
expect(iconButton.size, material3 ? const Size(48.0, 48.0) : const Size(26.0, 26.0));
|
||||
expect(icon.size, const Size(10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Small icons with non-null constraints and custom padding can be <48dp', (WidgetTester tester) async {
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: IconButton(
|
||||
iconSize: 10.0,
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
@ -221,17 +247,23 @@ void main() {
|
||||
);
|
||||
|
||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
||||
final RenderBox icon = tester.renderObject(find.byType(Icon));
|
||||
|
||||
// This IconButton has a padding of 3.0 on all sides, so both
|
||||
// width and height are 10.0 + 2 * 3.0 = 16.0
|
||||
expect(iconButton.size, const Size(16.0, 16.0));
|
||||
// M3 IconButton is a subclass of ButtonStyleButton which has a minimum
|
||||
// Size(48.0, 48.0).
|
||||
expect(iconButton.size, material3 ? const Size(48.0, 48.0) : const Size(16.0, 16.0));
|
||||
expect(icon.size, const Size(10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Small icons comply with VisualDensity requirements', (WidgetTester tester) async {
|
||||
final bool material3 = theme.useMaterial3;
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: material3,
|
||||
child: Theme(
|
||||
data: ThemeData(visualDensity: const VisualDensity(horizontal: 1, vertical: -1)),
|
||||
data: ThemeData(visualDensity: const VisualDensity(horizontal: 1, vertical: -1), useMaterial3: material3),
|
||||
child: IconButton(
|
||||
iconSize: 10.0,
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
@ -248,12 +280,13 @@ void main() {
|
||||
// width by 4 pixels and decreases its height by 4 pixels, giving
|
||||
// final width 32.0 + 4.0 = 36.0 and
|
||||
// final height 32.0 - 4.0 = 28.0
|
||||
expect(iconButton.size, const Size(36.0, 28.0));
|
||||
expect(iconButton.size, material3 ? const Size(52.0, 44.0) : const Size(36.0, 28.0));
|
||||
});
|
||||
|
||||
testWidgets('test default icon buttons are constrained', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
@ -287,11 +320,34 @@ void main() {
|
||||
|
||||
final RenderBox box = tester.renderObject(find.byType(IconButton));
|
||||
expect(box.size, const Size(48.0, 600.0));
|
||||
|
||||
// Test for Material 3
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: true),
|
||||
home: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget> [
|
||||
IconButton(
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final RenderBox boxM3 = tester.renderObject(find.byType(IconButton));
|
||||
expect(boxM3.size, const Size(48.0, 600.0));
|
||||
});
|
||||
|
||||
testWidgets('test default padding', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
child: IconButton(
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
@ -307,6 +363,7 @@ void main() {
|
||||
testWidgets('test tooltip', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
@ -325,6 +382,7 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
@ -347,6 +405,7 @@ void main() {
|
||||
testWidgets('IconButton AppBar size', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: <Widget>[
|
||||
@ -368,11 +427,12 @@ void main() {
|
||||
|
||||
// This test is very similar to the '...explicit splashColor and highlightColor' test
|
||||
// in buttons_test.dart. If you change this one, you may want to also change that one.
|
||||
testWidgets('IconButton with explicit splashColor and highlightColor', (WidgetTester tester) async {
|
||||
testWidgets('IconButton with explicit splashColor and highlightColor - M2', (WidgetTester tester) async {
|
||||
const Color directSplashColor = Color(0xFF00000F);
|
||||
const Color directHighlightColor = Color(0xFF0000F0);
|
||||
|
||||
Widget buttonWidget = wrap(
|
||||
useMaterial3: false,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.android),
|
||||
splashColor: directSplashColor,
|
||||
@ -383,7 +443,7 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
Theme(
|
||||
data: ThemeData(),
|
||||
data: ThemeData(useMaterial3: false),
|
||||
child: buttonWidget,
|
||||
),
|
||||
);
|
||||
@ -404,6 +464,7 @@ void main() {
|
||||
const Color themeHighlightColor1 = Color(0xFF00FF00);
|
||||
|
||||
buttonWidget = wrap(
|
||||
useMaterial3: false,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.android),
|
||||
onPressed: () { /* enable the button */ },
|
||||
@ -415,6 +476,7 @@ void main() {
|
||||
data: ThemeData(
|
||||
highlightColor: themeHighlightColor1,
|
||||
splashColor: themeSplashColor1,
|
||||
useMaterial3: false,
|
||||
),
|
||||
child: buttonWidget,
|
||||
),
|
||||
@ -435,6 +497,7 @@ void main() {
|
||||
data: ThemeData(
|
||||
highlightColor: themeHighlightColor2,
|
||||
splashColor: themeSplashColor2,
|
||||
useMaterial3: false,
|
||||
),
|
||||
child: buttonWidget, // same widget, so does not get updated because of us
|
||||
),
|
||||
@ -450,10 +513,11 @@ void main() {
|
||||
await gesture.up();
|
||||
});
|
||||
|
||||
testWidgets('IconButton with explicit splash radius', (WidgetTester tester) async {
|
||||
testWidgets('IconButton with explicit splash radius - M2', (WidgetTester tester) async {
|
||||
const double splashRadius = 30.0;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: false),
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
@ -480,11 +544,12 @@ void main() {
|
||||
await gesture.up();
|
||||
});
|
||||
|
||||
testWidgets('IconButton Semantics (enabled)', (WidgetTester tester) async {
|
||||
testWidgets('IconButton Semantics (enabled) - M2', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: false,
|
||||
child: IconButton(
|
||||
onPressed: mockOnPressedFunction.handler,
|
||||
icon: const Icon(Icons.link, semanticLabel: 'link'),
|
||||
@ -513,11 +578,12 @@ void main() {
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('IconButton Semantics (disabled)', (WidgetTester tester) async {
|
||||
testWidgets('IconButton Semantics (disabled) - M2', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: false,
|
||||
child: const IconButton(
|
||||
onPressed: null,
|
||||
icon: Icon(Icons.link, semanticLabel: 'link'),
|
||||
@ -545,6 +611,7 @@ void main() {
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
child: IconButton(
|
||||
focusNode: focusNode,
|
||||
autofocus: true,
|
||||
@ -559,6 +626,7 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
child: IconButton(
|
||||
focusNode: focusNode,
|
||||
autofocus: true,
|
||||
@ -575,6 +643,7 @@ void main() {
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
child: MediaQuery(
|
||||
data: const MediaQueryData(
|
||||
navigationMode: NavigationMode.directional,
|
||||
@ -594,6 +663,7 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
child: MediaQuery(
|
||||
data: const MediaQueryData(
|
||||
navigationMode: NavigationMode.directional,
|
||||
@ -617,6 +687,7 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
useMaterial3: theme.useMaterial3,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
@ -658,8 +729,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('IconButton with disabled feedback', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(Material(
|
||||
child: Directionality(
|
||||
final Widget button = Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
@ -668,8 +738,13 @@ void main() {
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
theme.useMaterial3
|
||||
? MaterialApp(theme: theme, home: button)
|
||||
: Material(child: button)
|
||||
);
|
||||
await tester.tap(find.byType(IconButton), pointer: 1);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(feedback.clickSoundCount, 0);
|
||||
@ -677,8 +752,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('IconButton with enabled feedback', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(Material(
|
||||
child: Directionality(
|
||||
final Widget button = Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
@ -686,8 +760,13 @@ void main() {
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
theme.useMaterial3
|
||||
? MaterialApp(theme: theme, home: button)
|
||||
: Material(child: button),
|
||||
);
|
||||
await tester.tap(find.byType(IconButton), pointer: 1);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(feedback.clickSoundCount, 1);
|
||||
@ -695,8 +774,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('IconButton with enabled feedback by default', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(Material(
|
||||
child: Directionality(
|
||||
final Widget button = Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
@ -704,8 +782,13 @@ void main() {
|
||||
icon: const Icon(Icons.link),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
theme.useMaterial3
|
||||
? MaterialApp(theme: theme, home: button)
|
||||
: Material(child: button),
|
||||
);
|
||||
await tester.tap(find.byType(IconButton), pointer: 1);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(feedback.clickSoundCount, 1);
|
||||
@ -715,9 +798,11 @@ void main() {
|
||||
|
||||
testWidgets('IconButton responds to density changes.', (WidgetTester tester) async {
|
||||
const Key key = Key('test');
|
||||
final bool material3 = theme.useMaterial3;
|
||||
Future<void> buildTest(VisualDensity visualDensity) async {
|
||||
return tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
@ -733,27 +818,34 @@ void main() {
|
||||
}
|
||||
|
||||
await buildTest(VisualDensity.standard);
|
||||
final RenderBox box = tester.renderObject(find.byKey(key));
|
||||
final RenderBox box = tester.renderObject(find.byType(IconButton));
|
||||
await tester.pumpAndSettle();
|
||||
expect(box.size, equals(const Size(48, 48)));
|
||||
|
||||
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(box.size, equals(const Size(60, 60)));
|
||||
expect(box.size, equals(material3 ? const Size(64, 64) : const Size(60, 60)));
|
||||
|
||||
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(box.size, equals(const Size(40, 40)));
|
||||
// IconButton is a subclass of ButtonStyleButton in Material 3, so the negative
|
||||
// visualDensity cannot be applied to horizontal padding.
|
||||
// The size of the Button with padding is (24 + 8 + 8, 24) -> (40, 24)
|
||||
// minSize of M3 IconButton is (48 - 12, 48 - 12) -> (36, 36)
|
||||
// So, the button size in Material 3 is (40, 36)
|
||||
expect(box.size, equals(material3 ? const Size(40, 36) : const Size(40, 40)));
|
||||
|
||||
await buildTest(const VisualDensity(horizontal: 3.0, vertical: -3.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(box.size, equals(const Size(60, 40)));
|
||||
expect(box.size, equals(material3 ? const Size(64, 36) : const Size(60, 40)));
|
||||
});
|
||||
|
||||
testWidgets('IconButton.mouseCursor changes cursor on hover', (WidgetTester tester) async {
|
||||
// Test argument works
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
@ -765,6 +857,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
||||
@ -776,7 +869,9 @@ void main() {
|
||||
|
||||
// Test default is click
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
@ -787,6 +882,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
|
||||
@ -794,7 +890,9 @@ void main() {
|
||||
|
||||
testWidgets('disabled IconButton has basic mouse cursor', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const Material(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: const Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
@ -805,6 +903,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
||||
@ -817,7 +916,9 @@ void main() {
|
||||
|
||||
testWidgets('IconButton.mouseCursor overrides implicit setting of mouse cursor', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const Material(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: const Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
@ -829,6 +930,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
||||
@ -839,7 +941,9 @@ void main() {
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.none);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
@ -851,14 +955,368 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.none);
|
||||
});
|
||||
|
||||
|
||||
testWidgets('IconButton defaults - M3', (WidgetTester tester) async {
|
||||
final ThemeData themeM3 = ThemeData.from(colorScheme: colorScheme, useMaterial3: true);
|
||||
|
||||
// Enabled IconButton
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: themeM3,
|
||||
home: Center(
|
||||
child: IconButton(
|
||||
onPressed: () { },
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Finder buttonMaterial = find.descendant(
|
||||
of: find.byType(IconButton),
|
||||
matching: find.byType(Material),
|
||||
);
|
||||
|
||||
Material material = tester.widget<Material>(buttonMaterial);
|
||||
expect(material.animationDuration, const Duration(milliseconds: 200));
|
||||
expect(material.borderOnForeground, true);
|
||||
expect(material.borderRadius, null);
|
||||
expect(material.clipBehavior, Clip.none);
|
||||
expect(material.color, Colors.transparent);
|
||||
expect(material.elevation, 0.0);
|
||||
expect(material.shadowColor, null);
|
||||
expect(material.shape, const StadiumBorder());
|
||||
expect(material.textStyle, null);
|
||||
expect(material.type, MaterialType.button);
|
||||
|
||||
final Align align = tester.firstWidget<Align>(find.ancestor(of: find.byIcon(Icons.ac_unit), matching: find.byType(Align)));
|
||||
expect(align.alignment, Alignment.center);
|
||||
|
||||
final Offset center = tester.getCenter(find.byType(IconButton));
|
||||
final TestGesture gesture = await tester.startGesture(center);
|
||||
await tester.pump(); // start the splash animation
|
||||
await tester.pump(const Duration(milliseconds: 100)); // splash is underway
|
||||
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
material = tester.widget<Material>(buttonMaterial);
|
||||
// No change vs enabled and not pressed.
|
||||
expect(material.animationDuration, const Duration(milliseconds: 200));
|
||||
expect(material.borderOnForeground, true);
|
||||
expect(material.borderRadius, null);
|
||||
expect(material.clipBehavior, Clip.none);
|
||||
expect(material.color, Colors.transparent);
|
||||
expect(material.elevation, 0.0);
|
||||
expect(material.shadowColor, null);
|
||||
expect(material.shape, const StadiumBorder());
|
||||
expect(material.textStyle, null);
|
||||
expect(material.type, MaterialType.button);
|
||||
|
||||
// Disabled TextButton
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: themeM3,
|
||||
home: const Center(
|
||||
child: IconButton(
|
||||
onPressed: null,
|
||||
icon: Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
material = tester.widget<Material>(buttonMaterial);
|
||||
expect(material.animationDuration, const Duration(milliseconds: 200));
|
||||
expect(material.borderOnForeground, true);
|
||||
expect(material.borderRadius, null);
|
||||
expect(material.clipBehavior, Clip.none);
|
||||
expect(material.color, Colors.transparent);
|
||||
expect(material.elevation, 0.0);
|
||||
expect(material.shadowColor, null);
|
||||
expect(material.shape, const StadiumBorder());
|
||||
expect(material.textStyle, null);
|
||||
expect(material.type, MaterialType.button);
|
||||
});
|
||||
|
||||
testWidgets('Default IconButton meets a11y contrast guidelines - M3', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: IconButton(
|
||||
onPressed: () { },
|
||||
focusNode: focusNode,
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Default, not disabled.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(IconButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
await expectLater(tester, meetsGuideline(textContrastGuideline));
|
||||
|
||||
await gesture.removePointer();
|
||||
},
|
||||
skip: isBrowser, // https://github.com/flutter/flutter/issues/44115
|
||||
);
|
||||
|
||||
testWidgets('IconButton uses stateful color for icon color in different states - M3', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
const Color pressedColor = Color(0x00000001);
|
||||
const Color hoverColor = Color(0x00000002);
|
||||
const Color focusedColor = Color(0x00000003);
|
||||
const Color defaultColor = Color(0x00000004);
|
||||
|
||||
Color getIconColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.pressed)) {
|
||||
return pressedColor;
|
||||
}
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return hoverColor;
|
||||
}
|
||||
if (states.contains(MaterialState.focused)) {
|
||||
return focusedColor;
|
||||
}
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
Widget wrap({ required Widget child }) {
|
||||
return FocusTraversalGroup(
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: MaterialStateProperty.resolveWith<Color>(getIconColor),
|
||||
),
|
||||
onPressed: () {},
|
||||
focusNode: focusNode,
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Color? iconColor() => _iconStyle(tester, Icons.ac_unit)?.color;
|
||||
|
||||
// Default, not disabled.
|
||||
expect(iconColor(), equals(defaultColor));
|
||||
|
||||
// Focused.
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), focusedColor);
|
||||
|
||||
// Hovered.
|
||||
final Offset center = tester.getCenter(find.byType(IconButton));
|
||||
final TestGesture gesture = await tester.createGesture(
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(center);
|
||||
await tester.pumpAndSettle();
|
||||
expect(iconColor(), hoverColor);
|
||||
|
||||
// Highlighted (pressed).
|
||||
await gesture.down(center);
|
||||
await tester.pump(); // Start the splash and highlight animations.
|
||||
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
|
||||
expect(iconColor(), pressedColor);
|
||||
});
|
||||
|
||||
testWidgets('Does IconButton contribute semantics - M3', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: Theme(
|
||||
data: ThemeData(useMaterial3: true),
|
||||
child: IconButton(
|
||||
style: const ButtonStyle(
|
||||
// Specifying minimumSize to mimic the original minimumSize for
|
||||
// RaisedButton so that the semantics tree's rect and transform
|
||||
// match the original version of this test.
|
||||
minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)),
|
||||
),
|
||||
onPressed: () { },
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics.rootChild(
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
|
||||
transform: Matrix4.translationValues(356.0, 276.0, 0.0),
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.isFocusable,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
ignoreId: true,
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('IconButton size is configurable by ThemeData.materialTapTargetSize - M3', (WidgetTester tester) async {
|
||||
Widget buildFrame(MaterialTapTargetSize tapTargetSize) {
|
||||
return Theme(
|
||||
data: ThemeData(materialTapTargetSize: tapTargetSize, useMaterial3: true),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
style: IconButton.styleFrom(minimumSize: const Size(40, 40)),
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
onPressed: () { },
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(MaterialTapTargetSize.padded));
|
||||
expect(tester.getSize(find.byType(IconButton)), const Size(48.0, 48.0));
|
||||
|
||||
await tester.pumpWidget(buildFrame(MaterialTapTargetSize.shrinkWrap));
|
||||
expect(tester.getSize(find.byType(IconButton)), const Size(40.0, 40.0));
|
||||
});
|
||||
|
||||
testWidgets('Override IconButton default padding - M3', (WidgetTester tester) async {
|
||||
// Use [IconButton]'s padding property to override default value.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: IconButton(
|
||||
padding: const EdgeInsets.all(20),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final Padding paddingWidget1 = tester.widget<Padding>(
|
||||
find.descendant(
|
||||
of: find.byType(IconButton),
|
||||
matching: find.byType(Padding),
|
||||
),
|
||||
);
|
||||
expect(paddingWidget1.padding, const EdgeInsets.all(20));
|
||||
|
||||
// Use [IconButton.style]'s padding property to override default value.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: IconButton(
|
||||
style: IconButton.styleFrom(padding: const EdgeInsets.all(20)),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final Padding paddingWidget2 = tester.widget<Padding>(
|
||||
find.descendant(
|
||||
of: find.byType(IconButton),
|
||||
matching: find.byType(Padding),
|
||||
),
|
||||
);
|
||||
expect(paddingWidget2.padding, const EdgeInsets.all(20));
|
||||
|
||||
// [IconButton.style]'s padding will override [IconButton]'s padding if both
|
||||
// values are not null.
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: IconButton(
|
||||
padding: const EdgeInsets.all(15),
|
||||
style: IconButton.styleFrom(padding: const EdgeInsets.all(22)),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.ac_unit),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final Padding paddingWidget3 = tester.widget<Padding>(
|
||||
find.descendant(
|
||||
of: find.byType(IconButton),
|
||||
matching: find.byType(Padding),
|
||||
),
|
||||
);
|
||||
expect(paddingWidget3.padding, const EdgeInsets.all(22));
|
||||
});
|
||||
}
|
||||
|
||||
Widget wrap({required Widget child, required bool useMaterial3}) {
|
||||
return useMaterial3
|
||||
? MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
|
||||
home: FocusTraversalGroup(
|
||||
policy: ReadingOrderTraversalPolicy(),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(child: child),
|
||||
)),
|
||||
)
|
||||
: FocusTraversalGroup(
|
||||
policy: ReadingOrderTraversalPolicy(),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
@ -868,3 +1326,10 @@ Widget wrap({ required Widget child }) {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
|
||||
final RichText iconRichText = tester.widget<RichText>(
|
||||
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
|
||||
);
|
||||
return iconRichText.text.style;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user