From c14ca6d32112cfd7801347ac25ebc1b6863ff118 Mon Sep 17 00:00:00 2001 From: Darren Austin Date: Fri, 8 Apr 2022 17:03:21 -0700 Subject: [PATCH] Migrate common buttons to Material 3 (#100794) --- dev/tools/gen_defaults/bin/gen_defaults.dart | 4 + .../gen_defaults/lib/button_template.dart | 153 ++++++++++++ dev/tools/gen_defaults/lib/template.dart | 28 ++- .../material/button_style/button_style.0.dart | 95 ++++++++ .../lib/src/material/button_style.dart | 34 +++ .../lib/src/material/button_style_button.dart | 2 + .../lib/src/material/elevated_button.dart | 218 +++++++++++++++--- .../lib/src/material/outlined_button.dart | 207 ++++++++++++++--- .../flutter/lib/src/material/text_button.dart | 193 +++++++++++++--- .../flutter/lib/src/material/theme_data.dart | 21 +- .../test/material/button_style_test.dart | 16 +- .../test/material/elevated_button_test.dart | 85 ++++--- .../test/material/outlined_button_test.dart | 174 ++++++++------ .../test/material/text_button_test.dart | 119 ++++++---- 14 files changed, 1113 insertions(+), 236 deletions(-) create mode 100644 dev/tools/gen_defaults/lib/button_template.dart create mode 100644 examples/api/lib/material/button_style/button_style.0.dart diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart index 187e0f757c..0694020d09 100644 --- a/dev/tools/gen_defaults/bin/gen_defaults.dart +++ b/dev/tools/gen_defaults/bin/gen_defaults.dart @@ -17,6 +17,7 @@ import 'dart:convert'; import 'dart:io'; +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'; @@ -77,6 +78,9 @@ Future main(List args) async { tokens['colorsLight'] = _readTokenFile('color_light.json'); tokens['colorsDark'] = _readTokenFile('color_dark.json'); + ButtonTemplate('md.comp.elevated-button', '$materialLib/elevated_button.dart', tokens).updateFile(); + ButtonTemplate('md.comp.outlined-button', '$materialLib/outlined_button.dart', tokens).updateFile(); + ButtonTemplate('md.comp.text-button', '$materialLib/text_button.dart', tokens).updateFile(); CardTemplate('$materialLib/card.dart', tokens).updateFile(); DialogTemplate('$materialLib/dialog.dart', tokens).updateFile(); FABTemplate('$materialLib/floating_action_button.dart', tokens).updateFile(); diff --git a/dev/tools/gen_defaults/lib/button_template.dart b/dev/tools/gen_defaults/lib/button_template.dart new file mode 100644 index 0000000000..381087194a --- /dev/null +++ b/dev/tools/gen_defaults/lib/button_template.dart @@ -0,0 +1,153 @@ +// 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 ButtonTemplate extends TokenTemplate { + const ButtonTemplate(this.tokenGroup, String fileName, Map tokens) + : super(fileName, tokens, + colorSchemePrefix: '_colors.', + ); + + final String tokenGroup; + + String _backgroundColor() { + if (tokens.containsKey('$tokenGroup.container.color')) { + return ''' + + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return ${componentColor('$tokenGroup.disabled.container')}; + return ${componentColor('$tokenGroup.container')}; + })'''; + } + return ''' + + ButtonStyleButton.allOrNull(Colors.transparent)'''; + } + + String _elevation() { + if (tokens.containsKey('$tokenGroup.container.elevation')) { + return ''' + + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return ${elevation("$tokenGroup.disabled.container")}; + if (states.contains(MaterialState.hovered)) + return ${elevation("$tokenGroup.hover.container")}; + if (states.contains(MaterialState.focused)) + return ${elevation("$tokenGroup.focus.container")}; + if (states.contains(MaterialState.pressed)) + return ${elevation("$tokenGroup.pressed.container")}; + return ${elevation("$tokenGroup.container")}; + })'''; + } + return ''' + + ButtonStyleButton.allOrNull(0.0)'''; + } + + @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; + + @override + MaterialStateProperty get textStyle => + MaterialStateProperty.all(${textStyle("$tokenGroup.label-text")}); + + @override + MaterialStateProperty? get backgroundColor =>${_backgroundColor()}; + + @override + MaterialStateProperty? get foregroundColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return ${componentColor('$tokenGroup.disabled.label-text')}; + return ${componentColor('$tokenGroup.label-text')}; + }); + + @override + MaterialStateProperty? get overlayColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.hovered)) + return ${componentColor('$tokenGroup.hover.state-layer')}; + if (states.contains(MaterialState.focused)) + return ${componentColor('$tokenGroup.focus.state-layer')}; + if (states.contains(MaterialState.pressed)) + return ${componentColor('$tokenGroup.pressed.state-layer')}; + return null; + }); + +${tokens.containsKey("$tokenGroup.container.shadow-color") ? ''' + @override + MaterialStateProperty? get shadowColor => + ButtonStyleButton.allOrNull(${color("$tokenGroup.container.shadow-color")});''' : ''' + // No default shadow color'''} + +${tokens.containsKey("$tokenGroup.container.surface-tint-layer.color") ? ''' + @override + MaterialStateProperty? get surfaceTintColor => + ButtonStyleButton.allOrNull(${color("$tokenGroup.container.surface-tint-layer.color")});''' : ''' + // No default surface tint color'''} + + @override + MaterialStateProperty? get elevation =>${_elevation()}; + + @override + MaterialStateProperty? get padding => + ButtonStyleButton.allOrNull(_scaledPadding(context)); + + @override + MaterialStateProperty? get minimumSize => + ButtonStyleButton.allOrNull(const Size(64.0, ${tokens["$tokenGroup.container.height"]})); + + // No default fixedSize + + @override + MaterialStateProperty? get maximumSize => + ButtonStyleButton.allOrNull(Size.infinite); + +${tokens.containsKey("$tokenGroup.outline.color") ? ''' + @override + MaterialStateProperty? get side => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return ${border("$tokenGroup.disabled.outline")}; + return ${border("$tokenGroup.outline")}; + });''' : ''' + // No default side'''} + + @override + MaterialStateProperty? get shape => + ButtonStyleButton.allOrNull(${shape("$tokenGroup.container")}); + + @override + MaterialStateProperty? get mouseCursor => + MaterialStateProperty.resolveWith((Set 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; +} +'''; +} diff --git a/dev/tools/gen_defaults/lib/template.dart b/dev/tools/gen_defaults/lib/template.dart index f63bea0e35..ff378377af 100644 --- a/dev/tools/gen_defaults/lib/template.dart +++ b/dev/tools/gen_defaults/lib/template.dart @@ -70,8 +70,8 @@ abstract class TokenTemplate { /// * [componentColor], that provides support for an optional opacity. String color(String colorToken) { return tokens.containsKey(colorToken) - ? '$colorSchemePrefix${tokens[colorToken]}' - : 'null'; + ? '$colorSchemePrefix${tokens[colorToken]}' + : 'null'; } /// Generate a [ColorScheme] color name for the given component's color @@ -91,17 +91,25 @@ abstract class TokenTemplate { if (!tokens.containsKey(colorToken)) return 'null'; String value = color(colorToken); - final String tokenOpacity = '$componentToken.opacity'; - if (tokens.containsKey(tokenOpacity)) { - final dynamic opacityValue = tokens[tokenOpacity]; - final String opacity = opacityValue is double - ? opacityValue.toString() - : tokens[tokens[tokenOpacity]!]!.toString(); - value += '.withOpacity($opacity)'; + final String opacityToken = '$componentToken.opacity'; + if (tokens.containsKey(opacityToken)) { + value += '.withOpacity(${opacity(opacityToken)})'; } return value; } + /// Generate the opacity value for the given token. + String? opacity(String token) { + final dynamic value = tokens[token]; + if (value == null) { + return null; + } + if (value is double) { + return value.toString(); + } + return tokens[value].toString(); + } + /// Generate an elevation value for the given component token. String elevation(String componentToken) { return tokens[tokens['$componentToken.elevation']!]!.toString(); @@ -135,7 +143,7 @@ abstract class TokenTemplate { return 'null'; } final String borderColor = componentColor(componentToken); - final double width = tokens['$componentToken.width'] as double; + final double width = (tokens['$componentToken.width'] ?? 1.0) as double; return 'BorderSide(color: $borderColor${width != 1.0 ? ", width: $width" : ""})'; } diff --git a/examples/api/lib/material/button_style/button_style.0.dart b/examples/api/lib/material/button_style/button_style.0.dart new file mode 100644 index 0000000000..bff8602c6a --- /dev/null +++ b/examples/api/lib/material/button_style/button_style.0.dart @@ -0,0 +1,95 @@ +// 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 ElevatedButton + +import 'package:flutter/material.dart'; + +void main() { + runApp(const ButtonApp()); +} + +class ButtonApp extends StatelessWidget { + const ButtonApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), + title: 'Button Types', + home: const Scaffold( + body: ButtonTypesExample(), + ), + ); + } +} + +class ButtonTypesExample extends StatelessWidget { + const ButtonTypesExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Row( + children: const [ + Spacer(), + ButtonTypesGroup(enabled: true), + ButtonTypesGroup(enabled: false), + Spacer(), + ], + ), + ); + } +} + +class ButtonTypesGroup extends StatelessWidget { + const ButtonTypesGroup({ Key? key, required this.enabled }) : super(key: key); + + final bool enabled; + + @override + Widget build(BuildContext context) { + final VoidCallback? onPressed = enabled ? () {} : null; + return Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton(onPressed: onPressed, child: const Text('Elevated')), + + // Use an ElevatedButton with specific style to implement the + // 'Filled' type. + ElevatedButton( + style: ElevatedButton.styleFrom( + // Foreground color + onPrimary: Theme.of(context).colorScheme.onPrimary, + // Background color + primary: Theme.of(context).colorScheme.primary, + ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), + onPressed: onPressed, + child: const Text('Filled'), + ), + + // Use an ElevatedButton with specific style to implement the + // 'Filled Tonal' type. + ElevatedButton( + style: ElevatedButton.styleFrom( + // Foreground color + onPrimary: Theme.of(context).colorScheme.onSecondaryContainer, + // Background color + primary: Theme.of(context).colorScheme.secondaryContainer, + ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), + onPressed: onPressed, + child: const Text('Filled Tonal'), + ), + + OutlinedButton(onPressed: onPressed, child: const Text('Outlined')), + + TextButton(onPressed: onPressed, child: const Text('Text')), + ], + ), + ); + } +} diff --git a/packages/flutter/lib/src/material/button_style.dart b/packages/flutter/lib/src/material/button_style.dart index 3d1ad80a59..4026fd7ed6 100644 --- a/packages/flutter/lib/src/material/button_style.dart +++ b/packages/flutter/lib/src/material/button_style.dart @@ -91,6 +91,27 @@ import 'theme_data.dart'; /// home: MyAppHome(), /// ) /// ``` +/// +/// ## Material 3 button types +/// +/// Material Design 3 specifies five types of common buttons. Flutter provides +/// support for these using the following button classes: +/// +/// +/// | Type | Flutter implementation | +/// | :----------- | :---------------------- | +/// | Elevated | [ElevatedButton] | +/// | Filled | Styled [ElevatedButton] | +/// | Filled Tonal | Styled [ElevatedButton] | +/// | Outlined | [OutlinedButton] | +/// | Text | [TextButton] | +/// +/// {@tool dartpad} +/// This sample shows how to create each of the Material 3 button types with Flutter. +/// +/// ** See code in examples/api/lib/material/button_style/button_style.0.dart ** +/// {@end-tool} +/// /// See also: /// /// * [TextButtonTheme], the theme for [TextButton]s. @@ -105,6 +126,7 @@ class ButtonStyle with Diagnosticable { this.foregroundColor, this.overlayColor, this.shadowColor, + this.surfaceTintColor, this.elevation, this.padding, this.minimumSize, @@ -150,6 +172,11 @@ class ButtonStyle with Diagnosticable { /// [ThemeData.applyElevationOverlayColor]. final MaterialStateProperty? shadowColor; + /// The surface tint color of the button's [Material]. + /// + /// See [Material.surfaceTintColor] for more details. + final MaterialStateProperty? surfaceTintColor; + /// The elevation of the button's [Material]. final MaterialStateProperty? elevation; @@ -267,6 +294,7 @@ class ButtonStyle with Diagnosticable { MaterialStateProperty? foregroundColor, MaterialStateProperty? overlayColor, MaterialStateProperty? shadowColor, + MaterialStateProperty? surfaceTintColor, MaterialStateProperty? elevation, MaterialStateProperty? padding, MaterialStateProperty? minimumSize, @@ -288,6 +316,7 @@ class ButtonStyle with Diagnosticable { foregroundColor: foregroundColor ?? this.foregroundColor, overlayColor: overlayColor ?? this.overlayColor, shadowColor: shadowColor ?? this.shadowColor, + surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor, elevation: elevation ?? this.elevation, padding: padding ?? this.padding, minimumSize: minimumSize ?? this.minimumSize, @@ -319,6 +348,7 @@ class ButtonStyle with Diagnosticable { foregroundColor: foregroundColor ?? style.foregroundColor, overlayColor: overlayColor ?? style.overlayColor, shadowColor: shadowColor ?? style.shadowColor, + surfaceTintColor: surfaceTintColor ?? style.surfaceTintColor, elevation: elevation ?? style.elevation, padding: padding ?? style.padding, minimumSize: minimumSize ?? style.minimumSize, @@ -343,6 +373,7 @@ class ButtonStyle with Diagnosticable { foregroundColor, overlayColor, shadowColor, + surfaceTintColor, elevation, padding, minimumSize, @@ -371,6 +402,7 @@ class ButtonStyle with Diagnosticable { && other.foregroundColor == foregroundColor && other.overlayColor == overlayColor && other.shadowColor == shadowColor + && other.surfaceTintColor == surfaceTintColor && other.elevation == elevation && other.padding == padding && other.minimumSize == minimumSize @@ -395,6 +427,7 @@ class ButtonStyle with Diagnosticable { properties.add(DiagnosticsProperty>('foregroundColor', foregroundColor, defaultValue: null)); properties.add(DiagnosticsProperty>('overlayColor', overlayColor, defaultValue: null)); properties.add(DiagnosticsProperty>('shadowColor', shadowColor, defaultValue: null)); + properties.add(DiagnosticsProperty>('surfaceTintColor', surfaceTintColor, defaultValue: null)); properties.add(DiagnosticsProperty>('elevation', elevation, defaultValue: null)); properties.add(DiagnosticsProperty>('padding', padding, defaultValue: null)); properties.add(DiagnosticsProperty>('minimumSize', minimumSize, defaultValue: null)); @@ -421,6 +454,7 @@ class ButtonStyle with Diagnosticable { foregroundColor: _lerpProperties(a?.foregroundColor, b?.foregroundColor, t, Color.lerp), overlayColor: _lerpProperties(a?.overlayColor, b?.overlayColor, t, Color.lerp), shadowColor: _lerpProperties(a?.shadowColor, b?.shadowColor, t, Color.lerp), + surfaceTintColor: _lerpProperties(a?.surfaceTintColor, b?.surfaceTintColor, t, Color.lerp), elevation: _lerpProperties(a?.elevation, b?.elevation, t, lerpDouble), padding: _lerpProperties(a?.padding, b?.padding, t, EdgeInsetsGeometry.lerp), minimumSize: _lerpProperties(a?.minimumSize, b?.minimumSize, t, Size.lerp), diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 385fd22896..c30480b32c 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -247,6 +247,7 @@ class _ButtonStyleState extends State with MaterialStateMixin Color? resolvedBackgroundColor = resolve((ButtonStyle? style) => style?.backgroundColor); final Color? resolvedForegroundColor = resolve((ButtonStyle? style) => style?.foregroundColor); final Color? resolvedShadowColor = resolve((ButtonStyle? style) => style?.shadowColor); + final Color? resolvedSurfaceTintColor = resolve((ButtonStyle? style) => style?.surfaceTintColor); final EdgeInsetsGeometry? resolvedPadding = resolve((ButtonStyle? style) => style?.padding); final Size? resolvedMinimumSize = resolve((ButtonStyle? style) => style?.minimumSize); final Size? resolvedFixedSize = resolve((ButtonStyle? style) => style?.fixedSize); @@ -343,6 +344,7 @@ class _ButtonStyleState extends State with MaterialStateMixin shape: resolvedShape!.copyWith(side: resolvedSide), color: resolvedBackgroundColor, shadowColor: resolvedShadowColor, + surfaceTintColor: resolvedSurfaceTintColor, type: resolvedBackgroundColor == null ? MaterialType.transparency : MaterialType.button, animationDuration: resolvedAnimationDuration, clipBehavior: widget.clipBehavior, diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index dbb3285199..b01c3d6828 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -148,6 +148,7 @@ class ElevatedButton extends ButtonStyleButton { Color? onPrimary, Color? onSurface, Color? shadowColor, + Color? surfaceTintColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -187,6 +188,7 @@ class ElevatedButton extends ButtonStyleButton { foregroundColor: foregroundColor, overlayColor: overlayColor, shadowColor: ButtonStyleButton.allOrNull(shadowColor), + surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), elevation: elevationValue, padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -228,6 +230,8 @@ class ElevatedButton extends ButtonStyleButton { /// The color of the [ButtonStyle.textStyle] is not used, the /// [ButtonStyle.foregroundColor] color is used instead. /// + /// ## Material 2 defaults + /// /// * `textStyle` - Theme.textTheme.button /// * `backgroundColor` /// * disabled - Theme.colorScheme.onSurface(0.12) @@ -245,7 +249,7 @@ class ElevatedButton extends ButtonStyleButton { /// * hovered or focused - 4 /// * pressed - 8 /// * `padding` - /// * textScaleFactor <= 1 - horizontal(16) + /// * `textScaleFactor <= 1` - horizontal(16) /// * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8)) /// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4)) /// * `3 < textScaleFactor` - horizontal(4) @@ -276,38 +280,75 @@ class ElevatedButton extends ButtonStyleButton { /// outline, is null. That means that the outline is defined by the button /// shape's [OutlinedBorder.side]. Typically the default value of an /// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn. + /// + /// ## Material 3 defaults + /// + /// If [ThemeData.useMaterial3] is set to true the following defaults will + /// be used: + /// + /// * `textStyle` - Theme.textTheme.labelLarge + /// * `backgroundColor` + /// * disabled - Theme.colorScheme.onSurface(0.12) + /// * others - Theme.colorScheme.surface + /// * `foregroundColor` + /// * disabled - Theme.colorScheme.onSurface(0.38) + /// * others - Theme.colorScheme.primary + /// * `overlayColor` + /// * hovered - Theme.colorScheme.primary(0.08) + /// * focused or pressed - Theme.colorScheme.primary(0.12) + /// * `shadowColor` - Theme.colorScheme.shadow + /// * `surfaceTintColor` - Theme.colorScheme.surfaceTint + /// * `elevation` + /// * disabled - 0 + /// * default - 1 + /// * hovered - 3 + /// * focused or pressed - 1 + /// * `padding` + /// * `textScaleFactor <= 1` - horizontal(16) + /// * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8)) + /// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4)) + /// * `3 < textScaleFactor` - horizontal(4) + /// * `minimumSize` - Size(64, 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) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; - final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding( - const EdgeInsets.symmetric(horizontal: 16), - const EdgeInsets.symmetric(horizontal: 8), - const EdgeInsets.symmetric(horizontal: 4), - MediaQuery.maybeOf(context)?.textScaleFactor ?? 1, - ); - - return styleFrom( - primary: colorScheme.primary, - onPrimary: colorScheme.onPrimary, - onSurface: colorScheme.onSurface, - shadowColor: theme.shadowColor, - elevation: 2, - textStyle: theme.textTheme.button, - padding: scaledPadding, - minimumSize: const Size(64, 36), - maximumSize: Size.infinite, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), - enabledMouseCursor: SystemMouseCursors.click, - disabledMouseCursor: SystemMouseCursors.basic, - visualDensity: theme.visualDensity, - tapTargetSize: theme.materialTapTargetSize, - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - splashFactory: theme.useMaterial3 ? theme.splashFactory : InkRipple.splashFactory, - ); + return Theme.of(context).useMaterial3 + ? _TokenDefaultsM3(context) + : styleFrom( + primary: colorScheme.primary, + onPrimary: colorScheme.onPrimary, + onSurface: colorScheme.onSurface, + shadowColor: theme.shadowColor, + elevation: 2, + textStyle: theme.textTheme.button, + padding: _scaledPadding(context), + minimumSize: const Size(64, 36), + maximumSize: Size.infinite, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), + enabledMouseCursor: SystemMouseCursors.click, + disabledMouseCursor: SystemMouseCursors.basic, + visualDensity: theme.visualDensity, + tapTargetSize: theme.materialTapTargetSize, + animationDuration: kThemeChangeDuration, + enableFeedback: true, + alignment: Alignment.center, + splashFactory: InkRipple.splashFactory, + ); } /// Returns the [ElevatedButtonThemeData.style] of the closest @@ -318,6 +359,15 @@ class ElevatedButton extends ButtonStyleButton { } } +EdgeInsetsGeometry _scaledPadding(BuildContext context) { + return ButtonStyleButton.scaledPadding( + const EdgeInsets.symmetric(horizontal: 16), + const EdgeInsets.symmetric(horizontal: 8), + const EdgeInsets.symmetric(horizontal: 4), + MediaQuery.maybeOf(context)?.textScaleFactor ?? 1, + ); +} + @immutable class _ElevatedButtonDefaultBackground extends MaterialStateProperty with Diagnosticable { _ElevatedButtonDefaultBackground(this.primary, this.onSurface); @@ -457,3 +507,115 @@ class _ElevatedButtonWithIconChild extends StatelessWidget { ); } } + +// 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_92 +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; + + @override + MaterialStateProperty get textStyle => + MaterialStateProperty.all(Theme.of(context).textTheme.labelLarge); + + @override + MaterialStateProperty? get backgroundColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return _colors.onSurface.withOpacity(0.12); + return _colors.surface; + }); + + @override + MaterialStateProperty? get foregroundColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return _colors.onSurface.withOpacity(0.38); + return _colors.primary; + }); + + @override + MaterialStateProperty? get overlayColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.hovered)) + return _colors.primary.withOpacity(0.08); + if (states.contains(MaterialState.focused)) + return _colors.primary.withOpacity(0.12); + if (states.contains(MaterialState.pressed)) + return _colors.primary.withOpacity(0.12); + return null; + }); + + @override + MaterialStateProperty? get shadowColor => + ButtonStyleButton.allOrNull(_colors.shadow); + + @override + MaterialStateProperty? get surfaceTintColor => + ButtonStyleButton.allOrNull(_colors.surfaceTint); + + @override + MaterialStateProperty? get elevation => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return 0.0; + if (states.contains(MaterialState.hovered)) + return 3.0; + if (states.contains(MaterialState.focused)) + return 1.0; + if (states.contains(MaterialState.pressed)) + return 1.0; + return 1.0; + }); + + @override + MaterialStateProperty? get padding => + ButtonStyleButton.allOrNull(_scaledPadding(context)); + + @override + MaterialStateProperty? get minimumSize => + ButtonStyleButton.allOrNull(const Size(64.0, 40.0)); + + // No default fixedSize + + @override + MaterialStateProperty? get maximumSize => + ButtonStyleButton.allOrNull(Size.infinite); + + // No default side + + @override + MaterialStateProperty? get shape => + ButtonStyleButton.allOrNull(const StadiumBorder()); + + @override + MaterialStateProperty? get mouseCursor => + MaterialStateProperty.resolveWith((Set 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 diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 0e233e618e..e6fbdcd795 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -146,6 +146,7 @@ class OutlinedButton extends ButtonStyleButton { Color? onSurface, Color? backgroundColor, Color? shadowColor, + Color? surfaceTintColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -179,6 +180,7 @@ class OutlinedButton extends ButtonStyleButton { backgroundColor: ButtonStyleButton.allOrNull(backgroundColor), overlayColor: overlayColor, shadowColor: ButtonStyleButton.allOrNull(shadowColor), + surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -219,6 +221,8 @@ class OutlinedButton extends ButtonStyleButton { /// The color of the [ButtonStyle.textStyle] is not used, the /// [ButtonStyle.foregroundColor] is used instead. /// + /// ## Material 2 defaults + /// /// * `textStyle` - Theme.textTheme.button /// * `backgroundColor` - transparent /// * `foregroundColor` @@ -248,41 +252,75 @@ class OutlinedButton extends ButtonStyleButton { /// * `enableFeedback` - true /// * `alignment` - Alignment.center /// * `splashFactory` - InkRipple.splashFactory + /// + /// ## Material 3 defaults + /// + /// If [ThemeData.useMaterial3] is set to true the following defaults will + /// be used: + /// + /// * `textStyle` - Theme.textTheme.labelLarge + /// * `backgroundColor` - transparent + /// * `foregroundColor` + /// * disabled - Theme.colorScheme.onSurface(0.38) + /// * others - Theme.colorScheme.primary + /// * `overlayColor` + /// * hovered - Theme.colorScheme.primary(0.08) + /// * focused or pressed - Theme.colorScheme.primary(0.12) + /// * others - null + /// * `shadowColor` - null + /// * `surfaceTintColor` - null + /// * `elevation` - 0 + /// * `padding` + /// * `textScaleFactor <= 1` - horizontal(16) + /// * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8)) + /// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4)) + /// * `3 < textScaleFactor` - horizontal(4) + /// * `minimumSize` - Size(64, 40) + /// * `fixedSize` - null + /// * `maximumSize` - Size.infinite + /// * `side` + /// * disabled - BorderSide(color: Theme.colorScheme.onSurface(0.12)) + /// * others - BorderSide(color: Theme.colorScheme.outline) + /// * `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) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; - final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding( - const EdgeInsets.symmetric(horizontal: 16), - const EdgeInsets.symmetric(horizontal: 8), - const EdgeInsets.symmetric(horizontal: 4), - MediaQuery.maybeOf(context)?.textScaleFactor ?? 1, - ); - - return styleFrom( - primary: colorScheme.primary, - onSurface: colorScheme.onSurface, - backgroundColor: Colors.transparent, - shadowColor: theme.shadowColor, - elevation: 0, - textStyle: theme.textTheme.button, - padding: scaledPadding, - minimumSize: const Size(64, 36), - maximumSize: Size.infinite, - side: BorderSide( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12), - ), - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), - enabledMouseCursor: SystemMouseCursors.click, - disabledMouseCursor: SystemMouseCursors.basic, - visualDensity: theme.visualDensity, - tapTargetSize: theme.materialTapTargetSize, - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - splashFactory: theme.useMaterial3 ? theme.splashFactory : InkRipple.splashFactory, - ); + return Theme.of(context).useMaterial3 + ? _TokenDefaultsM3(context) + : styleFrom( + primary: colorScheme.primary, + onSurface: colorScheme.onSurface, + backgroundColor: Colors.transparent, + shadowColor: theme.shadowColor, + elevation: 0, + textStyle: theme.textTheme.button, + padding: _scaledPadding(context), + minimumSize: const Size(64, 36), + maximumSize: Size.infinite, + side: BorderSide( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12), + ), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), + enabledMouseCursor: SystemMouseCursors.click, + disabledMouseCursor: SystemMouseCursors.basic, + visualDensity: theme.visualDensity, + tapTargetSize: theme.materialTapTargetSize, + animationDuration: kThemeChangeDuration, + enableFeedback: true, + alignment: Alignment.center, + splashFactory: InkRipple.splashFactory, + ); } @override @@ -291,6 +329,15 @@ class OutlinedButton extends ButtonStyleButton { } } +EdgeInsetsGeometry _scaledPadding(BuildContext context) { + return ButtonStyleButton.scaledPadding( + const EdgeInsets.symmetric(horizontal: 16), + const EdgeInsets.symmetric(horizontal: 8), + const EdgeInsets.symmetric(horizontal: 4), + MediaQuery.maybeOf(context)?.textScaleFactor ?? 1, + ); +} + @immutable class _OutlinedButtonDefaultForeground extends MaterialStateProperty with Diagnosticable { _OutlinedButtonDefaultForeground(this.primary, this.onSurface); @@ -382,3 +429,103 @@ class _OutlinedButtonWithIconChild extends StatelessWidget { ); } } + +// 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_92 +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; + + @override + MaterialStateProperty get textStyle => + MaterialStateProperty.all(Theme.of(context).textTheme.labelLarge); + + @override + MaterialStateProperty? get backgroundColor => + ButtonStyleButton.allOrNull(Colors.transparent); + + @override + MaterialStateProperty? get foregroundColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return _colors.onSurface.withOpacity(0.38); + return _colors.primary; + }); + + @override + MaterialStateProperty? get overlayColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.hovered)) + return _colors.primary.withOpacity(0.08); + if (states.contains(MaterialState.focused)) + return _colors.primary.withOpacity(0.12); + if (states.contains(MaterialState.pressed)) + return _colors.primary.withOpacity(0.12); + return null; + }); + + // No default shadow color + + // No default surface tint color + + @override + MaterialStateProperty? get elevation => + ButtonStyleButton.allOrNull(0.0); + + @override + MaterialStateProperty? get padding => + ButtonStyleButton.allOrNull(_scaledPadding(context)); + + @override + MaterialStateProperty? get minimumSize => + ButtonStyleButton.allOrNull(const Size(64.0, 40.0)); + + // No default fixedSize + + @override + MaterialStateProperty? get maximumSize => + ButtonStyleButton.allOrNull(Size.infinite); + + @override + MaterialStateProperty? get side => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return BorderSide(color: _colors.onSurface.withOpacity(0.12)); + return BorderSide(color: _colors.outline); + }); + + @override + MaterialStateProperty? get shape => + ButtonStyleButton.allOrNull(const StadiumBorder()); + + @override + MaterialStateProperty? get mouseCursor => + MaterialStateProperty.resolveWith((Set 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 diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index 92fac91523..2caa127f5b 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -147,6 +147,7 @@ class TextButton extends ButtonStyleButton { Color? onSurface, Color? backgroundColor, Color? shadowColor, + Color? surfaceTintColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -180,6 +181,7 @@ class TextButton extends ButtonStyleButton { foregroundColor: foregroundColor, overlayColor: overlayColor, shadowColor: ButtonStyleButton.allOrNull(shadowColor), + surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -223,6 +225,8 @@ class TextButton extends ButtonStyleButton { /// The color of the [ButtonStyle.textStyle] is not used, the /// [ButtonStyle.foregroundColor] color is used instead. /// + /// ## Material 2 defaults + /// /// * `textStyle` - Theme.textTheme.button /// * `backgroundColor` - transparent /// * `foregroundColor` @@ -264,38 +268,70 @@ class TextButton extends ButtonStyleButton { /// outline, is null. That means that the outline is defined by the button /// shape's [OutlinedBorder.side]. Typically the default value of an /// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn. + /// + /// ## Material 3 defaults + /// + /// If [ThemeData.useMaterial3] is set to true the following defaults will + /// be used: + /// + /// * `textStyle` - Theme.textTheme.labelLarge + /// * `backgroundColor` - transparent + /// * `foregroundColor` + /// * disabled - Theme.colorScheme.onSurface(0.38) + /// * others - Theme.colorScheme.primary + /// * `overlayColor` + /// * hovered - Theme.colorScheme.primary(0.08) + /// * focused or pressed - Theme.colorScheme.primary(0.12) + /// * others - null + /// * `shadowColor` - null + /// * `surfaceTintColor` - null + /// * `elevation` - 0 + /// * `padding` + /// * `textScaleFactor <= 1` - all(8) + /// * `1 < textScaleFactor <= 2` - lerp(all(8), horizontal(8)) + /// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4)) + /// * `3 < textScaleFactor` - horizontal(4) + /// * `minimumSize` - Size(64, 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) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; - final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding( - const EdgeInsets.all(8), - const EdgeInsets.symmetric(horizontal: 8), - const EdgeInsets.symmetric(horizontal: 4), - MediaQuery.maybeOf(context)?.textScaleFactor ?? 1, - ); - - return styleFrom( - primary: colorScheme.primary, - onSurface: colorScheme.onSurface, - backgroundColor: Colors.transparent, - shadowColor: theme.shadowColor, - elevation: 0, - textStyle: theme.textTheme.button, - padding: scaledPadding, - minimumSize: const Size(64, 36), - maximumSize: Size.infinite, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), - enabledMouseCursor: SystemMouseCursors.click, - disabledMouseCursor: SystemMouseCursors.basic, - visualDensity: theme.visualDensity, - tapTargetSize: theme.materialTapTargetSize, - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - splashFactory: theme.useMaterial3 ? theme.splashFactory : InkRipple.splashFactory, - ); + return Theme.of(context).useMaterial3 + ? _TokenDefaultsM3(context) + : styleFrom( + primary: colorScheme.primary, + onSurface: colorScheme.onSurface, + backgroundColor: Colors.transparent, + shadowColor: theme.shadowColor, + elevation: 0, + textStyle: theme.textTheme.button, + padding: _scaledPadding(context), + minimumSize: const Size(64, 36), + maximumSize: Size.infinite, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), + enabledMouseCursor: SystemMouseCursors.click, + disabledMouseCursor: SystemMouseCursors.basic, + visualDensity: theme.visualDensity, + tapTargetSize: theme.materialTapTargetSize, + animationDuration: kThemeChangeDuration, + enableFeedback: true, + alignment: Alignment.center, + splashFactory: InkRipple.splashFactory, + ); } /// Returns the [TextButtonThemeData.style] of the closest @@ -306,6 +342,15 @@ class TextButton extends ButtonStyleButton { } } +EdgeInsetsGeometry _scaledPadding(BuildContext context) { + return ButtonStyleButton.scaledPadding( + const EdgeInsets.all(8), + const EdgeInsets.symmetric(horizontal: 8), + const EdgeInsets.symmetric(horizontal: 4), + MediaQuery.maybeOf(context)?.textScaleFactor ?? 1, + ); +} + @immutable class _TextButtonDefaultForeground extends MaterialStateProperty { _TextButtonDefaultForeground(this.primary, this.onSurface); @@ -424,3 +469,97 @@ class _TextButtonWithIconChild extends StatelessWidget { ); } } + +// 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_92 +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; + + @override + MaterialStateProperty get textStyle => + MaterialStateProperty.all(Theme.of(context).textTheme.labelLarge); + + @override + MaterialStateProperty? get backgroundColor => + ButtonStyleButton.allOrNull(Colors.transparent); + + @override + MaterialStateProperty? get foregroundColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) + return _colors.onSurface.withOpacity(0.38); + return _colors.primary; + }); + + @override + MaterialStateProperty? get overlayColor => + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.hovered)) + return _colors.primary.withOpacity(0.08); + if (states.contains(MaterialState.focused)) + return _colors.primary.withOpacity(0.12); + if (states.contains(MaterialState.pressed)) + return _colors.primary.withOpacity(0.12); + return null; + }); + + // No default shadow color + + // No default surface tint color + + @override + MaterialStateProperty? get elevation => + ButtonStyleButton.allOrNull(0.0); + + @override + MaterialStateProperty? get padding => + ButtonStyleButton.allOrNull(_scaledPadding(context)); + + @override + MaterialStateProperty? get minimumSize => + ButtonStyleButton.allOrNull(const Size(64.0, 40.0)); + + // No default fixedSize + + @override + MaterialStateProperty? get maximumSize => + ButtonStyleButton.allOrNull(Size.infinite); + + // No default side + + @override + MaterialStateProperty? get shape => + ButtonStyleButton.allOrNull(const StadiumBorder()); + + @override + MaterialStateProperty? get mouseCursor => + MaterialStateProperty.resolveWith((Set 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 diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 8038c0178b..5870bb0cc7 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -1170,21 +1170,25 @@ class ThemeData with Diagnosticable { /// {@endtemplate} final VisualDensity visualDensity; - /// A temporary flag used to opt-in to new Material 3 features. + /// A temporary flag used to opt-in to Material 3 features. /// /// If true, then components that have been migrated to Material 3 will - /// start using new colors, typography and other features of Material 3. + /// use new colors, typography and other features of Material 3. /// If false, they will use the Material 2 look and feel. /// /// If a [ThemeData] is constructed with [useMaterial3] set to true, then /// some properties will get special defaults. However, just copying a [ThemeData] /// with [useMaterial3] set to true will not change any of these properties in the /// resulting [ThemeData]. These properties are: - /// | Property | [useMaterial3] default | fallback default | - /// |:---|:---|:---| - /// | [typography] | [Typography.material2021] | [Typography.material2014] | - /// | [splashFactory] | [InkSparkle]* | [InkSplash] | - /// *if and only if the target platform is Android and the app is not running on the web. + /// + /// + /// | Property | Material 3 default | Fallback default | + /// | :-------------- | :--------------------------- | :------------------------ | + /// | [typography] | [Typography.material2021] | [Typography.material2014] | + /// | [splashFactory] | [InkSparkle]* or [InkRipple] | [InkSplash] | + /// + /// \* if and only if the target platform is Android and the app is not + /// running on the web, otherwise it will fallback to [InkRipple]. /// /// During the migration to Material 3, turning this on may yield /// inconsistent look and feel in your app. Some components will be migrated @@ -1201,12 +1205,15 @@ class ThemeData with Diagnosticable { /// * [AlertDialog] /// * [Card] /// * [Dialog] + /// * [ElevatedButton] /// * [FloatingActionButton] /// * [Material] /// * [NavigationBar] /// * [NavigationRail] + /// * [OutlinedButton] /// * [StretchingOverscrollIndicator], replacing the /// [GlowingOverscrollIndicator] + /// * [TextButton] /// /// See also: /// diff --git a/packages/flutter/test/material/button_style_test.dart b/packages/flutter/test/material/button_style_test.dart index b9a2640429..32afc9bb4f 100644 --- a/packages/flutter/test/material/button_style_test.dart +++ b/packages/flutter/test/material/button_style_test.dart @@ -20,6 +20,8 @@ void main() { expect(style.backgroundColor, null); expect(style.foregroundColor, null); expect(style.overlayColor, null); + expect(style.shadowColor, null); + expect(style.surfaceTintColor, null); expect(style.elevation, null); expect(style.padding, null); expect(style.minimumSize, null); @@ -53,10 +55,12 @@ void main() { backgroundColor: MaterialStateProperty.all(const Color(0xfffffff1)), foregroundColor: MaterialStateProperty.all(const Color(0xfffffff2)), overlayColor: MaterialStateProperty.all(const Color(0xfffffff3)), + shadowColor: MaterialStateProperty.all(const Color(0xfffffff4)), + surfaceTintColor: MaterialStateProperty.all(const Color(0xfffffff5)), elevation: MaterialStateProperty.all(1.5), padding: MaterialStateProperty.all(const EdgeInsets.all(1.0)), minimumSize: MaterialStateProperty.all(const Size(1.0, 2.0)), - side: MaterialStateProperty.all(const BorderSide(width: 4.0, color: Color(0xfffffff4))), + side: MaterialStateProperty.all(const BorderSide(width: 4.0, color: Color(0xfffffff6))), maximumSize: MaterialStateProperty.all(const Size(100.0, 200.0)), shape: MaterialStateProperty.all(const StadiumBorder()), mouseCursor: MaterialStateProperty.all(SystemMouseCursors.forbidden), @@ -75,11 +79,13 @@ void main() { 'backgroundColor: MaterialStateProperty.all(Color(0xfffffff1))', 'foregroundColor: MaterialStateProperty.all(Color(0xfffffff2))', 'overlayColor: MaterialStateProperty.all(Color(0xfffffff3))', + 'shadowColor: MaterialStateProperty.all(Color(0xfffffff4))', + 'surfaceTintColor: MaterialStateProperty.all(Color(0xfffffff5))', 'elevation: MaterialStateProperty.all(1.5)', 'padding: MaterialStateProperty.all(EdgeInsets.all(1.0))', 'minimumSize: MaterialStateProperty.all(Size(1.0, 2.0))', 'maximumSize: MaterialStateProperty.all(Size(100.0, 200.0))', - 'side: MaterialStateProperty.all(BorderSide(Color(0xfffffff4), 4.0, BorderStyle.solid))', + 'side: MaterialStateProperty.all(BorderSide(Color(0xfffffff6), 4.0, BorderStyle.solid))', 'shape: MaterialStateProperty.all(StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none)))', 'mouseCursor: MaterialStateProperty.all(SystemMouseCursor(forbidden))', 'tapTargetSize: shrinkWrap', @@ -93,6 +99,8 @@ void main() { final MaterialStateProperty backgroundColor = MaterialStateProperty.all(const Color(0xfffffff1)); final MaterialStateProperty foregroundColor = MaterialStateProperty.all(const Color(0xfffffff2)); final MaterialStateProperty overlayColor = MaterialStateProperty.all(const Color(0xfffffff3)); + final MaterialStateProperty shadowColor = MaterialStateProperty.all(const Color(0xfffffff4)); + final MaterialStateProperty surfaceTintColor = MaterialStateProperty.all(const Color(0xfffffff5)); final MaterialStateProperty elevation = MaterialStateProperty.all(1); final MaterialStateProperty padding = MaterialStateProperty.all(const EdgeInsets.all(1)); final MaterialStateProperty minimumSize = MaterialStateProperty.all(const Size(1, 2)); @@ -111,6 +119,8 @@ void main() { backgroundColor: backgroundColor, foregroundColor: foregroundColor, overlayColor: overlayColor, + shadowColor: shadowColor, + surfaceTintColor: surfaceTintColor, elevation: elevation, padding: padding, minimumSize: minimumSize, @@ -132,6 +142,8 @@ void main() { backgroundColor: backgroundColor, foregroundColor: foregroundColor, overlayColor: overlayColor, + shadowColor: shadowColor, + surfaceTintColor: surfaceTintColor, elevation: elevation, padding: padding, minimumSize: minimumSize, diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index 12afdebd80..3478826985 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -14,11 +14,13 @@ import '../widgets/semantics_tester.dart'; void main() { testWidgets('ElevatedButton, ElevatedButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); + final ThemeData theme = ThemeData.from(colorScheme: colorScheme); + final bool material3 = theme.useMaterial3; // Enabled ElevatedButton await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: Center( child: ElevatedButton( onPressed: () { }, @@ -39,11 +41,13 @@ void main() { expect(material.borderOnForeground, true); expect(material.borderRadius, null); expect(material.clipBehavior, Clip.none); - expect(material.color, colorScheme.primary); - expect(material.elevation, 2); + expect(material.color, material3 ? colorScheme.onPrimary : colorScheme.primary); + expect(material.elevation, material3 ? 1: 2); expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); - expect(material.textStyle!.color, colorScheme.onPrimary); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); + expect(material.textStyle!.color, material3 ? colorScheme.primary : colorScheme.onPrimary); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); @@ -56,8 +60,13 @@ void main() { final TestGesture gesture = await tester.startGesture(center); await tester.pump(); // start the splash animation await tester.pump(const Duration(milliseconds: 100)); // splash is underway - final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - expect(inkFeatures, paints..circle(color: colorScheme.onPrimary.withAlpha(0x3d))); // splash color is onPrimary(0.24) + + // Material 3 uses the InkSparkle which uses a shader, so we can't capture + // the effect with paint methods. + if (!material3) { + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + expect(inkFeatures, paints..circle(color: colorScheme.onPrimary.withOpacity(0.24))); + } // Only elevation changes when enabled and pressed. material = tester.widget(buttonMaterial); @@ -65,11 +74,13 @@ void main() { expect(material.borderOnForeground, true); expect(material.borderRadius, null); expect(material.clipBehavior, Clip.none); - expect(material.color, colorScheme.primary); - expect(material.elevation, 8); + expect(material.color, material3 ? colorScheme.onPrimary : colorScheme.primary); + expect(material.elevation, material3 ? 1 : 8); expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); - expect(material.textStyle!.color, colorScheme.onPrimary); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); + expect(material.textStyle!.color, material3 ? colorScheme.primary : colorScheme.onPrimary); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); @@ -82,7 +93,7 @@ void main() { final Key iconButtonKey = UniqueKey(); await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: Center( child: ElevatedButton.icon( key: iconButtonKey, @@ -104,11 +115,13 @@ void main() { expect(material.borderOnForeground, true); expect(material.borderRadius, null); expect(material.clipBehavior, Clip.none); - expect(material.color, colorScheme.primary); - expect(material.elevation, 2); + expect(material.color, material3 ? colorScheme.onPrimary : colorScheme.primary); + expect(material.elevation, material3? 1 : 2); expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); - expect(material.textStyle!.color, colorScheme.onPrimary); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); + expect(material.textStyle!.color, material3 ? colorScheme.primary : colorScheme.onPrimary); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); expect(material.textStyle!.fontWeight, FontWeight.w500); @@ -117,7 +130,7 @@ void main() { // Disabled ElevatedButton await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: const Center( child: ElevatedButton( onPressed: null, @@ -138,7 +151,9 @@ void main() { expect(material.color, colorScheme.onSurface.withOpacity(0.12)); expect(material.elevation, 0.0); expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); expect(material.textStyle!.color, colorScheme.onSurface.withOpacity(0.38)); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); @@ -770,6 +785,9 @@ void main() { Future buildTest(VisualDensity visualDensity, {bool useText = false}) async { return tester.pumpWidget( MaterialApp( + // Test was setup using fonts from Material 2, so make sure we always + // test against englishLike2014. + theme: ThemeData(textTheme: Typography.englishLike2014), home: Directionality( textDirection: TextDirection.rtl, child: Center( @@ -961,7 +979,15 @@ void main() { testWidgets(testName, (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: const ColorScheme.light()), + theme: ThemeData( + colorScheme: const ColorScheme.light(), + // Force Material 2 defaults for the typography and size + // default values as the test was designed against these settings. + textTheme: Typography.englishLike2014, + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom(minimumSize: const Size(64, 36)), + ), + ), home: Builder( builder: (BuildContext context) { return MediaQuery( @@ -1137,7 +1163,7 @@ void main() { Widget buildFrame({ required bool enabled }) { return MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: false), home: Center( child: ElevatedButton( onPressed: enabled ? () { } : null, @@ -1181,23 +1207,29 @@ void main() { const Color borderColor = Color(0xff4caf50); await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: const ColorScheme.light()), + theme: ThemeData(colorScheme: const ColorScheme.light(), textTheme: Typography.englishLike2014, useMaterial3: false), home: Center( child: ElevatedButton( style: ElevatedButton.styleFrom( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(16)), - side: BorderSide(width: 4, color: borderColor), + side: BorderSide(width: 10, color: borderColor), ), + minimumSize: const Size(64, 36), ), - onPressed: () { }, + onPressed: () {}, child: const Text('button'), ), ), ), ); - expect(find.byType(ElevatedButton), paints ..path(strokeWidth: 4) ..drrect(color: borderColor)); + expect(find.byType(ElevatedButton), paints ..drrect( + // Outer and inner rect that give the outline a width of 10. + outer: RRect.fromLTRBR(0.0, 0.0, 116.0, 36.0, const Radius.circular(16)), + inner: RRect.fromLTRBR(10.0, 10.0, 106.0, 26.0, const Radius.circular(16 - 10)), + color: borderColor) + ); }); testWidgets('Fixed size ElevatedButtons', (WidgetTester tester) async { @@ -1261,8 +1293,8 @@ void main() { await tester.pumpAndSettle(); } - // Default splashFactory (from Theme.of().splashFactory), one splash circle drawn. - await tester.pumpWidget(buildFrame()); + // InkRipple.splashFactory, one splash circle drawn. + await tester.pumpWidget(buildFrame(splashFactory: InkRipple.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); final MaterialInkController material = Material.of(tester.element(find.text('test')))!; @@ -1386,6 +1418,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(textTheme: Typography.englishLike2014), home: Scaffold( body: Center( child: Column( diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index bcdbeb47f4..ecbf00c3cd 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -14,11 +14,13 @@ import '../widgets/semantics_tester.dart'; void main() { testWidgets('OutlinedButton, OutlinedButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); + final ThemeData theme = ThemeData.from(colorScheme: colorScheme); + final bool material3 = theme.useMaterial3; // Enabled OutlinedButton await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: Center( child: OutlinedButton( onPressed: () { }, @@ -40,12 +42,14 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); - expect(material.shape, isInstanceOf()); - RoundedRectangleBorder materialShape = material.shape! as RoundedRectangleBorder; - expect(materialShape.side, BorderSide(color: colorScheme.onSurface.withOpacity(0.12))); - expect(materialShape.borderRadius, const BorderRadius.all(Radius.circular(4.0))); + expect(material.shape, material3 + ? StadiumBorder(side: BorderSide(color: colorScheme.outline)) + : RoundedRectangleBorder( + side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12)), + borderRadius: const BorderRadius.all(Radius.circular(4)) + )); expect(material.textStyle!.color, colorScheme.primary); expect(material.textStyle!.fontFamily, 'Roboto'); @@ -60,8 +64,13 @@ void main() { final TestGesture gesture = await tester.startGesture(center); await tester.pump(); // start the splash animation await tester.pump(const Duration(milliseconds: 100)); // splash is underway - final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - expect(inkFeatures, paints..circle(color: colorScheme.primary.withAlpha(0x1f))); // splash color is primary(0.12) + + // Material 3 uses the InkSparkle which uses a shader, so we can't capture + // the effect with paint methods. + if (!material3) { + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + expect(inkFeatures, paints..circle(color: colorScheme.primary.withOpacity(0.12))); + } await gesture.up(); await tester.pumpAndSettle(); @@ -73,12 +82,14 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); - expect(material.shape, isInstanceOf()); - materialShape = material.shape! as RoundedRectangleBorder; - expect(materialShape.side, BorderSide(color: colorScheme.onSurface.withOpacity(0.12))); - expect(materialShape.borderRadius, const BorderRadius.all(Radius.circular(4.0))); + expect(material.shape, material3 + ? StadiumBorder(side: BorderSide(color: colorScheme.outline)) + : RoundedRectangleBorder( + side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12)), + borderRadius: const BorderRadius.all(Radius.circular(4)) + )); expect(material.textStyle!.color, colorScheme.primary); expect(material.textStyle!.fontFamily, 'Roboto'); @@ -90,7 +101,7 @@ void main() { final Key iconButtonKey = UniqueKey(); await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: Center( child: OutlinedButton.icon( key: iconButtonKey, @@ -114,12 +125,14 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); - expect(material.shape, isInstanceOf()); - materialShape = material.shape! as RoundedRectangleBorder; - expect(materialShape.side, BorderSide(color: colorScheme.onSurface.withOpacity(0.12))); - expect(materialShape.borderRadius, const BorderRadius.all(Radius.circular(4.0))); + expect(material.shape, material3 + ? StadiumBorder(side: BorderSide(color: colorScheme.outline)) + : RoundedRectangleBorder( + side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12)), + borderRadius: const BorderRadius.all(Radius.circular(4)) + )); expect(material.textStyle!.color, colorScheme.primary); expect(material.textStyle!.fontFamily, 'Roboto'); @@ -130,7 +143,7 @@ void main() { // Disabled OutlinedButton await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: const Center( child: OutlinedButton( onPressed: null, @@ -147,12 +160,14 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); - expect(material.shape, isInstanceOf()); - materialShape = material.shape! as RoundedRectangleBorder; - expect(materialShape.side, BorderSide(color: colorScheme.onSurface.withOpacity(0.12))); - expect(materialShape.borderRadius, const BorderRadius.all(Radius.circular(4.0))); + expect(material.shape, material3 + ? StadiumBorder(side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12))) + : RoundedRectangleBorder( + side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12)), + borderRadius: const BorderRadius.all(Radius.circular(4)) + )); expect(material.textStyle!.color, colorScheme.onSurface.withOpacity(0.38)); expect(material.textStyle!.fontFamily, 'Roboto'); @@ -531,6 +546,10 @@ void main() { child: OutlinedButton( style: ButtonStyle( side: MaterialStateProperty.resolveWith(getBorderSide), + // Test assumes a rounded rect for the shape + shape: ButtonStyleButton.allOrNull( + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))) + ), ), onPressed: () {}, focusNode: focusNode, @@ -808,13 +827,14 @@ void main() { return Directionality( textDirection: TextDirection.ltr, child: Theme( - data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), + data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, textTheme: Typography.englishLike2014), child: Container( alignment: Alignment.topLeft, child: OutlinedButton( style: OutlinedButton.styleFrom( shape: const RoundedRectangleBorder(), // default border radius is 0 backgroundColor: fillColor, + minimumSize: const Size(64, 36), ).copyWith( side: MaterialStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.disabled)) @@ -964,20 +984,24 @@ void main() { testWidgets('OutlinedButton scales textScaleFactor', (WidgetTester tester) async { await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Center( - child: OutlinedButton( - style: ButtonStyle( - // Specifying minimumSize to mimic the original minimumSize for - // RaisedButton so that the corresponding button size matches - // the original version of this test. - minimumSize: MaterialStateProperty.all(const Size(88, 36)), + Theme( + // Force Material 2 typography. + data: ThemeData(textTheme: Typography.englishLike2014), + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: Center( + child: OutlinedButton( + style: ButtonStyle( + // Specifying minimumSize to mimic the original minimumSize for + // RaisedButton so that the corresponding button size matches + // the original version of this test. + minimumSize: MaterialStateProperty.all(const Size(88, 36)), + ), + onPressed: () {}, + child: const Text('ABC'), ), - onPressed: () {}, - child: const Text('ABC'), ), ), ), @@ -989,20 +1013,24 @@ void main() { // textScaleFactor expands text, but not button. await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(textScaleFactor: 1.3), - child: Center( - child: OutlinedButton( - style: ButtonStyle( - // Specifying minimumSize to mimic the original minimumSize for - // RaisedButton so that the corresponding button size matches - // the original version of this test. - minimumSize: MaterialStateProperty.all(const Size(88, 36)), + Theme( + // Force Material 2 typography. + data: ThemeData(textTheme: Typography.englishLike2014), + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(textScaleFactor: 1.3), + child: Center( + child: OutlinedButton( + style: ButtonStyle( + // Specifying minimumSize to mimic the original minimumSize for + // RaisedButton so that the corresponding button size matches + // the original version of this test. + minimumSize: MaterialStateProperty.all(const Size(88, 36)), + ), + onPressed: () {}, + child: const Text('ABC'), ), - onPressed: () {}, - child: const Text('ABC'), ), ), ), @@ -1017,14 +1045,18 @@ void main() { // Set text scale large enough to expand text and button. await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(textScaleFactor: 3.0), - child: Center( - child: OutlinedButton( - onPressed: () {}, - child: const Text('ABC'), + Theme( + // Force Material 2 typography. + data: ThemeData(textTheme: Typography.englishLike2014), + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(textScaleFactor: 3.0), + child: Center( + child: OutlinedButton( + onPressed: () {}, + child: const Text('ABC'), + ), ), ), ), @@ -1039,7 +1071,6 @@ void main() { expect(tester.getSize(find.byType(Text)).height, equals(42.0)); }); - testWidgets('OutlinedButton onPressed and onLongPress callbacks are distinctly recognized', (WidgetTester tester) async { bool didPressButton = false; bool didLongPressButton = false; @@ -1078,11 +1109,15 @@ void main() { Future buildTest(VisualDensity visualDensity, {bool useText = false}) async { return tester.pumpWidget( MaterialApp( + theme: ThemeData(textTheme: Typography.englishLike2014), home: Directionality( textDirection: TextDirection.rtl, child: Center( child: OutlinedButton( - style: ButtonStyle(visualDensity: visualDensity), + style: ButtonStyle( + visualDensity: visualDensity, + minimumSize: ButtonStyleButton.allOrNull(const Size(64, 36)), + ), key: key, onPressed: () {}, child: useText @@ -1203,7 +1238,15 @@ void main() { testWidgets(testName, (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: const ColorScheme.light()), + theme: ThemeData( + colorScheme: const ColorScheme.light(), + // Force Material 2 defaults for the typography and size + // default values as the test was designed against these settings. + textTheme: Typography.englishLike2014, + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom(minimumSize: const Size(64, 36)), + ), + ), home: Builder( builder: (BuildContext context) { return MediaQuery( @@ -1434,8 +1477,8 @@ void main() { await tester.pumpAndSettle(); } - // Default splashFactory (from Theme.of().splashFactory), one splash circle drawn. - await tester.pumpWidget(buildFrame()); + // InkRipple.splashFactory, one splash circle drawn. + await tester.pumpWidget(buildFrame(splashFactory: InkRipple.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); final MaterialInkController material = Material.of(tester.element(find.text('test')))!; @@ -1559,6 +1602,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(textTheme: Typography.englishLike2014), home: Scaffold( body: Center( child: Column( diff --git a/packages/flutter/test/material/text_button_test.dart b/packages/flutter/test/material/text_button_test.dart index ba90dc96d1..8da1d07bb9 100644 --- a/packages/flutter/test/material/text_button_test.dart +++ b/packages/flutter/test/material/text_button_test.dart @@ -14,11 +14,13 @@ import '../widgets/semantics_tester.dart'; void main() { testWidgets('TextButton, TextButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); + final ThemeData theme = ThemeData.from(colorScheme: colorScheme); + final bool material3 = theme.useMaterial3; // Enabled TextButton await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: Center( child: TextButton( onPressed: () { }, @@ -40,8 +42,10 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); expect(material.textStyle!.color, colorScheme.primary); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); @@ -55,8 +59,13 @@ void main() { final TestGesture gesture = await tester.startGesture(center); await tester.pump(); // start the splash animation await tester.pump(const Duration(milliseconds: 100)); // splash is underway - final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - expect(inkFeatures, paints..circle(color: colorScheme.primary.withAlpha(0x1f))); // splash color is primary(0.12) + + // Material 3 uses the InkSparkle which uses a shader, so we can't capture + // the effect with paint methods. + if (!material3) { + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + expect(inkFeatures, paints..circle(color: colorScheme.primary.withOpacity(0.12))); + } await gesture.up(); await tester.pumpAndSettle(); @@ -68,8 +77,10 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); expect(material.textStyle!.color, colorScheme.primary); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); @@ -80,7 +91,7 @@ void main() { final Key iconButtonKey = UniqueKey(); await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: Center( child: TextButton.icon( key: iconButtonKey, @@ -104,8 +115,10 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); expect(material.textStyle!.color, colorScheme.primary); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); @@ -115,7 +128,7 @@ void main() { // Disabled TextButton await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: theme, home: const Center( child: TextButton( onPressed: null, @@ -132,8 +145,10 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, const Color(0xff000000)); - expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))); + expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shape, material3 + ? const StadiumBorder() + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); expect(material.textStyle!.color, colorScheme.onSurface.withOpacity(0.38)); expect(material.textStyle!.fontFamily, 'Roboto'); expect(material.textStyle!.fontSize, 14); @@ -517,14 +532,18 @@ void main() { testWidgets('Does TextButton scale with font scale changes', (WidgetTester tester) async { await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Center( - child: TextButton( - onPressed: () { }, - child: const Text('ABC'), + Theme( + // Force Material 2 typography. + data: ThemeData(textTheme: Typography.englishLike2014), + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: Center( + child: TextButton( + onPressed: () { }, + child: const Text('ABC'), + ), ), ), ), @@ -536,14 +555,18 @@ void main() { // textScaleFactor expands text, but not button. await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(textScaleFactor: 1.3), - child: Center( - child: TextButton( - onPressed: () { }, - child: const Text('ABC'), + Theme( + // Force Material 2 typography. + data: ThemeData(textTheme: Typography.englishLike2014), + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(textScaleFactor: 1.3), + child: Center( + child: TextButton( + onPressed: () { }, + child: const Text('ABC'), + ), ), ), ), @@ -559,14 +582,18 @@ void main() { // Set text scale large enough to expand text and button. await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(textScaleFactor: 3.0), - child: Center( - child: TextButton( - onPressed: () { }, - child: const Text('ABC'), + Theme( + // Force Material 2 typography. + data: ThemeData(textTheme: Typography.englishLike2014), + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(textScaleFactor: 3.0), + child: Center( + child: TextButton( + onPressed: () { }, + child: const Text('ABC'), + ), ), ), ), @@ -581,7 +608,6 @@ void main() { expect(tester.getSize(find.byType(Text)).height, equals(42.0)); }); - testWidgets('TextButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { return Theme( @@ -591,6 +617,7 @@ void main() { child: Center( child: TextButton( key: key, + style: TextButton.styleFrom(minimumSize: const Size(64, 36)), child: const SizedBox(width: 50.0, height: 8.0), onPressed: () { }, ), @@ -855,6 +882,7 @@ void main() { Future buildTest(VisualDensity visualDensity, { bool useText = false }) async { return tester.pumpWidget( MaterialApp( + theme: ThemeData(textTheme: Typography.englishLike2014), home: Directionality( textDirection: TextDirection.rtl, child: Center( @@ -993,7 +1021,15 @@ void main() { testWidgets(testName, (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: const ColorScheme.light()), + theme: ThemeData( + colorScheme: const ColorScheme.light(), + // Force Material 2 defaults for the typography and size + // default values as the test was designed against these settings. + textTheme: Typography.englishLike2014, + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom(minimumSize: const Size(64, 36)), + ), + ), home: Builder( builder: (BuildContext context) { return MediaQuery( @@ -1227,8 +1263,8 @@ void main() { await tester.pumpAndSettle(); } - // Default splashFactory (from Theme.of().splashFactory), one splash circle drawn. - await tester.pumpWidget(buildFrame()); + // InkRipple.splashFactory, one splash circle drawn. + await tester.pumpWidget(buildFrame(splashFactory: InkRipple.splashFactory)); { final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('test'))); final MaterialInkController material = Material.of(tester.element(find.text('test')))!; @@ -1352,6 +1388,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(textTheme: Typography.englishLike2014), home: Scaffold( body: Center( child: Column(