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/card_template.dart';
|
||||||
import 'package:gen_defaults/dialog_template.dart';
|
import 'package:gen_defaults/dialog_template.dart';
|
||||||
import 'package:gen_defaults/fab_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_bar_template.dart';
|
||||||
import 'package:gen_defaults/navigation_rail_template.dart';
|
import 'package:gen_defaults/navigation_rail_template.dart';
|
||||||
import 'package:gen_defaults/surface_tint.dart';
|
import 'package:gen_defaults/surface_tint.dart';
|
||||||
@ -55,6 +56,9 @@ Future<void> main(List<String> args) async {
|
|||||||
'fab_large_primary.json',
|
'fab_large_primary.json',
|
||||||
'fab_primary.json',
|
'fab_primary.json',
|
||||||
'fab_small_primary.json',
|
'fab_small_primary.json',
|
||||||
|
'icon_button.json',
|
||||||
|
'icon_button_filled.json',
|
||||||
|
'icon_button_filled_tonal.json',
|
||||||
'motion.json',
|
'motion.json',
|
||||||
'navigation_bar.json',
|
'navigation_bar.json',
|
||||||
'navigation_rail.json',
|
'navigation_rail.json',
|
||||||
@ -86,6 +90,7 @@ Future<void> main(List<String> args) async {
|
|||||||
CardTemplate('$materialLib/card.dart', tokens).updateFile();
|
CardTemplate('$materialLib/card.dart', tokens).updateFile();
|
||||||
DialogTemplate('$materialLib/dialog.dart', tokens).updateFile();
|
DialogTemplate('$materialLib/dialog.dart', tokens).updateFile();
|
||||||
FABTemplate('$materialLib/floating_action_button.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();
|
NavigationBarTemplate('$materialLib/navigation_bar.dart', tokens).updateFile();
|
||||||
NavigationRailTemplate('$materialLib/navigation_rail.dart', tokens).updateFile();
|
NavigationRailTemplate('$materialLib/navigation_rail.dart', tokens).updateFile();
|
||||||
SurfaceTintTemplate('$materialLib/elevation_overlay.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/rendering.dart';
|
||||||
import 'package:flutter/widgets.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 'constants.dart';
|
||||||
import 'debug.dart';
|
import 'debug.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
|
import 'material_state.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
import 'theme_data.dart';
|
import 'theme_data.dart';
|
||||||
import 'tooltip.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 **
|
/// ** See code in examples/api/lib/material/icon_button/icon_button.1.dart **
|
||||||
/// {@end-tool}
|
/// {@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:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [Icons], the library of Material Icons.
|
/// * [Icons], the library of Material Icons.
|
||||||
@ -134,6 +150,7 @@ class IconButton extends StatelessWidget {
|
|||||||
this.tooltip,
|
this.tooltip,
|
||||||
this.enableFeedback = true,
|
this.enableFeedback = true,
|
||||||
this.constraints,
|
this.constraints,
|
||||||
|
this.style,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
}) : assert(padding != null),
|
}) : assert(padding != null),
|
||||||
assert(alignment != null),
|
assert(alignment != null),
|
||||||
@ -184,6 +201,8 @@ class IconButton extends StatelessWidget {
|
|||||||
|
|
||||||
/// The splash radius.
|
/// 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.
|
/// If null, default splash radius of [Material.defaultSplashRadius] is used.
|
||||||
final double? splashRadius;
|
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
|
/// 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.
|
/// 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].
|
/// Defaults to the Theme's splash color, [ThemeData.splashColor].
|
||||||
final Color? splashColor;
|
final Color? splashColor;
|
||||||
|
|
||||||
@ -301,10 +322,125 @@ class IconButton extends StatelessWidget {
|
|||||||
/// and `Theme.of(context).visualDensity` otherwise.
|
/// and `Theme.of(context).visualDensity` otherwise.
|
||||||
final BoxConstraints? constraints;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(debugCheckHasMaterial(context));
|
|
||||||
final ThemeData theme = Theme.of(context);
|
final ThemeData theme = Theme.of(context);
|
||||||
|
if (!theme.useMaterial3) {
|
||||||
|
assert(debugCheckHasMaterial(context));
|
||||||
|
}
|
||||||
|
|
||||||
Color? currentColor;
|
Color? currentColor;
|
||||||
if (onPressed != null) {
|
if (onPressed != null) {
|
||||||
currentColor = color;
|
currentColor = color;
|
||||||
@ -321,6 +457,55 @@ class IconButton extends StatelessWidget {
|
|||||||
final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
|
final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
|
||||||
final double effectiveIconSize = iconSize ?? IconTheme.of(context).size ?? 24.0;
|
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(
|
Widget result = ConstrainedBox(
|
||||||
constraints: adjustedConstraints,
|
constraints: adjustedConstraints,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -389,3 +574,245 @@ class IconButton extends StatelessWidget {
|
|||||||
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
|
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() {
|
void main() {
|
||||||
late MockOnPressedFunction mockOnPressedFunction;
|
late MockOnPressedFunction mockOnPressedFunction;
|
||||||
|
const ColorScheme colorScheme = ColorScheme.light();
|
||||||
|
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||||
setUp(() {
|
setUp(() {
|
||||||
mockOnPressedFunction = MockOnPressedFunction();
|
mockOnPressedFunction = MockOnPressedFunction();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('test default icon buttons are sized up to 48', (WidgetTester tester) async {
|
testWidgets('test default icon buttons are sized up to 48', (WidgetTester tester) async {
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
@ -45,14 +48,16 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('test small icons are sized up to 48dp', (WidgetTester tester) async {
|
testWidgets('test small icons are sized up to 48dp', (WidgetTester tester) async {
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
iconSize: 10.0,
|
iconSize: 10.0,
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
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 {
|
testWidgets('test icons can be small when total size is >48dp', (WidgetTester tester) async {
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
iconSize: 10.0,
|
iconSize: 10.0,
|
||||||
padding: const EdgeInsets.all(30.0),
|
padding: const EdgeInsets.all(30.0),
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
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 {
|
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(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconTheme(
|
child: IconTheme(
|
||||||
data: const IconThemeData(),
|
data: const IconThemeData(),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
focusNode: focusNode,
|
focusNode: FocusNode(debugLabel: 'Ink Focus'),
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
final RenderBox icon = tester.renderObject(find.byType(Icon));
|
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 {
|
testWidgets('when null, iconSize is overridden by closest IconTheme', (WidgetTester tester) async {
|
||||||
RenderBox icon;
|
RenderBox icon;
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconTheme(
|
child: IconTheme(
|
||||||
data: const IconThemeData(size: 10),
|
data: const IconThemeData(size: 10),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -106,7 +116,7 @@ void main() {
|
|||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
icon = tester.renderObject(find.byType(Icon));
|
icon = tester.renderObject(find.byType(Icon));
|
||||||
@ -114,8 +124,10 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
|
useMaterial3: material3,
|
||||||
iconTheme: const IconThemeData(size: 10),
|
iconTheme: const IconThemeData(size: 10),
|
||||||
),
|
),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -123,7 +135,7 @@ void main() {
|
|||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
icon = tester.renderObject(find.byType(Icon));
|
icon = tester.renderObject(find.byType(Icon));
|
||||||
@ -131,8 +143,10 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
|
useMaterial3: material3,
|
||||||
iconTheme: const IconThemeData(size: 20),
|
iconTheme: const IconThemeData(size: 20),
|
||||||
),
|
),
|
||||||
child: IconTheme(
|
child: IconTheme(
|
||||||
@ -151,10 +165,12 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconTheme(
|
child: IconTheme(
|
||||||
data: const IconThemeData(size: 20),
|
data: const IconThemeData(size: 20),
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
|
useMaterial3: material3,
|
||||||
iconTheme: const IconThemeData(size: 10),
|
iconTheme: const IconThemeData(size: 10),
|
||||||
),
|
),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -171,8 +187,10 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('when non-null, iconSize precedes IconTheme.of(context).size', (WidgetTester tester) async {
|
testWidgets('when non-null, iconSize precedes IconTheme.of(context).size', (WidgetTester tester) async {
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconTheme(
|
child: IconTheme(
|
||||||
data: const IconThemeData(size: 30.0),
|
data: const IconThemeData(size: 30.0),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -188,28 +206,36 @@ void main() {
|
|||||||
expect(icon.size, const Size(10.0, 10.0));
|
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(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
iconSize: 10.0,
|
iconSize: 10.0,
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
icon: const Icon(Icons.link),
|
icon: const Icon(Icons.link),
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
),
|
)
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
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
|
// 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
|
// 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 {
|
testWidgets('Small icons with non-null constraints and custom padding can be <48dp', (WidgetTester tester) async {
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
iconSize: 10.0,
|
iconSize: 10.0,
|
||||||
padding: const EdgeInsets.all(3.0),
|
padding: const EdgeInsets.all(3.0),
|
||||||
@ -221,17 +247,23 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
|
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
|
// This IconButton has a padding of 3.0 on all sides, so both
|
||||||
// width and height are 10.0 + 2 * 3.0 = 16.0
|
// 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 {
|
testWidgets('Small icons comply with VisualDensity requirements', (WidgetTester tester) async {
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: material3,
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: ThemeData(visualDensity: const VisualDensity(horizontal: 1, vertical: -1)),
|
data: ThemeData(visualDensity: const VisualDensity(horizontal: 1, vertical: -1), useMaterial3: material3),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
iconSize: 10.0,
|
iconSize: 10.0,
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
@ -248,12 +280,13 @@ void main() {
|
|||||||
// width by 4 pixels and decreases its height by 4 pixels, giving
|
// width by 4 pixels and decreases its height by 4 pixels, giving
|
||||||
// final width 32.0 + 4.0 = 36.0 and
|
// final width 32.0 + 4.0 = 36.0 and
|
||||||
// final height 32.0 - 4.0 = 28.0
|
// 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 {
|
testWidgets('test default icon buttons are constrained', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: theme.useMaterial3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
@ -287,11 +320,34 @@ void main() {
|
|||||||
|
|
||||||
final RenderBox box = tester.renderObject(find.byType(IconButton));
|
final RenderBox box = tester.renderObject(find.byType(IconButton));
|
||||||
expect(box.size, const Size(48.0, 600.0));
|
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 {
|
testWidgets('test default padding', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: theme.useMaterial3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
icon: const Icon(Icons.ac_unit),
|
icon: const Icon(Icons.ac_unit),
|
||||||
@ -307,6 +363,7 @@ void main() {
|
|||||||
testWidgets('test tooltip', (WidgetTester tester) async {
|
testWidgets('test tooltip', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -325,6 +382,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -347,6 +405,7 @@ void main() {
|
|||||||
testWidgets('IconButton AppBar size', (WidgetTester tester) async {
|
testWidgets('IconButton AppBar size', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
@ -368,11 +427,12 @@ void main() {
|
|||||||
|
|
||||||
// This test is very similar to the '...explicit splashColor and highlightColor' test
|
// 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.
|
// 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 directSplashColor = Color(0xFF00000F);
|
||||||
const Color directHighlightColor = Color(0xFF0000F0);
|
const Color directHighlightColor = Color(0xFF0000F0);
|
||||||
|
|
||||||
Widget buttonWidget = wrap(
|
Widget buttonWidget = wrap(
|
||||||
|
useMaterial3: false,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.android),
|
icon: const Icon(Icons.android),
|
||||||
splashColor: directSplashColor,
|
splashColor: directSplashColor,
|
||||||
@ -383,7 +443,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Theme(
|
Theme(
|
||||||
data: ThemeData(),
|
data: ThemeData(useMaterial3: false),
|
||||||
child: buttonWidget,
|
child: buttonWidget,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -404,6 +464,7 @@ void main() {
|
|||||||
const Color themeHighlightColor1 = Color(0xFF00FF00);
|
const Color themeHighlightColor1 = Color(0xFF00FF00);
|
||||||
|
|
||||||
buttonWidget = wrap(
|
buttonWidget = wrap(
|
||||||
|
useMaterial3: false,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.android),
|
icon: const Icon(Icons.android),
|
||||||
onPressed: () { /* enable the button */ },
|
onPressed: () { /* enable the button */ },
|
||||||
@ -415,6 +476,7 @@ void main() {
|
|||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
highlightColor: themeHighlightColor1,
|
highlightColor: themeHighlightColor1,
|
||||||
splashColor: themeSplashColor1,
|
splashColor: themeSplashColor1,
|
||||||
|
useMaterial3: false,
|
||||||
),
|
),
|
||||||
child: buttonWidget,
|
child: buttonWidget,
|
||||||
),
|
),
|
||||||
@ -435,6 +497,7 @@ void main() {
|
|||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
highlightColor: themeHighlightColor2,
|
highlightColor: themeHighlightColor2,
|
||||||
splashColor: themeSplashColor2,
|
splashColor: themeSplashColor2,
|
||||||
|
useMaterial3: false,
|
||||||
),
|
),
|
||||||
child: buttonWidget, // same widget, so does not get updated because of us
|
child: buttonWidget, // same widget, so does not get updated because of us
|
||||||
),
|
),
|
||||||
@ -450,10 +513,11 @@ void main() {
|
|||||||
await gesture.up();
|
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;
|
const double splashRadius = 30.0;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: false),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -480,11 +544,12 @@ void main() {
|
|||||||
await gesture.up();
|
await gesture.up();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('IconButton Semantics (enabled)', (WidgetTester tester) async {
|
testWidgets('IconButton Semantics (enabled) - M2', (WidgetTester tester) async {
|
||||||
final SemanticsTester semantics = SemanticsTester(tester);
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: false,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: mockOnPressedFunction.handler,
|
onPressed: mockOnPressedFunction.handler,
|
||||||
icon: const Icon(Icons.link, semanticLabel: 'link'),
|
icon: const Icon(Icons.link, semanticLabel: 'link'),
|
||||||
@ -513,11 +578,12 @@ void main() {
|
|||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('IconButton Semantics (disabled)', (WidgetTester tester) async {
|
testWidgets('IconButton Semantics (disabled) - M2', (WidgetTester tester) async {
|
||||||
final SemanticsTester semantics = SemanticsTester(tester);
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: false,
|
||||||
child: const IconButton(
|
child: const IconButton(
|
||||||
onPressed: null,
|
onPressed: null,
|
||||||
icon: Icon(Icons.link, semanticLabel: 'link'),
|
icon: Icon(Icons.link, semanticLabel: 'link'),
|
||||||
@ -545,6 +611,7 @@ void main() {
|
|||||||
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
|
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: theme.useMaterial3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@ -559,6 +626,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: theme.useMaterial3,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@ -575,6 +643,7 @@ void main() {
|
|||||||
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
|
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: theme.useMaterial3,
|
||||||
child: MediaQuery(
|
child: MediaQuery(
|
||||||
data: const MediaQueryData(
|
data: const MediaQueryData(
|
||||||
navigationMode: NavigationMode.directional,
|
navigationMode: NavigationMode.directional,
|
||||||
@ -594,6 +663,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: theme.useMaterial3,
|
||||||
child: MediaQuery(
|
child: MediaQuery(
|
||||||
data: const MediaQueryData(
|
data: const MediaQueryData(
|
||||||
navigationMode: NavigationMode.directional,
|
navigationMode: NavigationMode.directional,
|
||||||
@ -617,6 +687,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
wrap(
|
wrap(
|
||||||
|
useMaterial3: theme.useMaterial3,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -658,8 +729,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('IconButton with disabled feedback', (WidgetTester tester) async {
|
testWidgets('IconButton with disabled feedback', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(Material(
|
final Widget button = Directionality(
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -668,8 +738,13 @@ void main() {
|
|||||||
icon: const Icon(Icons.link),
|
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.tap(find.byType(IconButton), pointer: 1);
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
expect(feedback.clickSoundCount, 0);
|
expect(feedback.clickSoundCount, 0);
|
||||||
@ -677,8 +752,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('IconButton with enabled feedback', (WidgetTester tester) async {
|
testWidgets('IconButton with enabled feedback', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(Material(
|
final Widget button = Directionality(
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -686,8 +760,13 @@ void main() {
|
|||||||
icon: const Icon(Icons.link),
|
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.tap(find.byType(IconButton), pointer: 1);
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
expect(feedback.clickSoundCount, 1);
|
expect(feedback.clickSoundCount, 1);
|
||||||
@ -695,8 +774,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('IconButton with enabled feedback by default', (WidgetTester tester) async {
|
testWidgets('IconButton with enabled feedback by default', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(Material(
|
final Widget button = Directionality(
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -704,8 +782,13 @@ void main() {
|
|||||||
icon: const Icon(Icons.link),
|
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.tap(find.byType(IconButton), pointer: 1);
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
expect(feedback.clickSoundCount, 1);
|
expect(feedback.clickSoundCount, 1);
|
||||||
@ -715,9 +798,11 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('IconButton responds to density changes.', (WidgetTester tester) async {
|
testWidgets('IconButton responds to density changes.', (WidgetTester tester) async {
|
||||||
const Key key = Key('test');
|
const Key key = Key('test');
|
||||||
|
final bool material3 = theme.useMaterial3;
|
||||||
Future<void> buildTest(VisualDensity visualDensity) async {
|
Future<void> buildTest(VisualDensity visualDensity) async {
|
||||||
return tester.pumpWidget(
|
return tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
home: Material(
|
home: Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@ -733,27 +818,34 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await buildTest(VisualDensity.standard);
|
await buildTest(VisualDensity.standard);
|
||||||
final RenderBox box = tester.renderObject(find.byKey(key));
|
final RenderBox box = tester.renderObject(find.byType(IconButton));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(box.size, equals(const Size(48, 48)));
|
expect(box.size, equals(const Size(48, 48)));
|
||||||
|
|
||||||
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
|
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
|
||||||
await tester.pumpAndSettle();
|
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 buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
|
||||||
await tester.pumpAndSettle();
|
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 buildTest(const VisualDensity(horizontal: 3.0, vertical: -3.0));
|
||||||
await tester.pumpAndSettle();
|
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 {
|
testWidgets('IconButton.mouseCursor changes cursor on hover', (WidgetTester tester) async {
|
||||||
// Test argument works
|
// Test argument works
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Material(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Material(
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
@ -765,6 +857,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
||||||
@ -776,7 +869,9 @@ void main() {
|
|||||||
|
|
||||||
// Test default is click
|
// Test default is click
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Material(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Material(
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
@ -787,6 +882,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
|
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
|
||||||
@ -794,7 +890,9 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('disabled IconButton has basic mouse cursor', (WidgetTester tester) async {
|
testWidgets('disabled IconButton has basic mouse cursor', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
const Material(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: const Material(
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
@ -805,6 +903,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
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 {
|
testWidgets('IconButton.mouseCursor overrides implicit setting of mouse cursor', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
const Material(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: const Material(
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
@ -829,6 +930,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
|
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);
|
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.none);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
Material(
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Material(
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: Center(
|
child: Center(
|
||||||
@ -851,14 +955,368 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.none);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }) {
|
Widget wrap({required Widget child, required bool useMaterial3}) {
|
||||||
return FocusTraversalGroup(
|
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(),
|
policy: ReadingOrderTraversalPolicy(),
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
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