Unverified Commit 14a9b4a7 authored by Darren Austin's avatar Darren Austin Committed by GitHub

Migrate AppBar to Material 3 (#101884)

parent 0c3c38dc
......@@ -17,6 +17,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:gen_defaults/app_bar_template.dart';
import 'package:gen_defaults/button_template.dart';
import 'package:gen_defaults/card_template.dart';
import 'package:gen_defaults/dialog_template.dart';
......@@ -78,6 +79,7 @@ Future<void> main(List<String> args) async {
tokens['colorsLight'] = _readTokenFile('color_light.json');
tokens['colorsDark'] = _readTokenFile('color_dark.json');
AppBarTemplate('$materialLib/app_bar.dart', tokens).updateFile();
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();
......
// 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 AppBarTemplate extends TokenTemplate {
const AppBarTemplate(super.fileName, super.tokens)
: super(
colorSchemePrefix: '_colors.',
textThemePrefix: '_textTheme.',
);
@override
String generate() => '''
// Generated version ${tokens["version"]}
class _TokenDefaultsM3 extends AppBarTheme {
_TokenDefaultsM3(this.context)
: super(
elevation: ${elevation('md.comp.top-app-bar.small.container')},
scrolledUnderElevation: ${elevation('md.comp.top-app-bar.small.on-scroll.container')},
titleSpacing: NavigationToolbar.kMiddleSpacing,
toolbarHeight: ${tokens['md.comp.top-app-bar.small.container.height']},
);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
late final TextTheme _textTheme = _theme.textTheme;
@override
Color? get backgroundColor => ${componentColor('md.comp.top-app-bar.small.container')};
@override
Color? get foregroundColor => ${color('md.comp.top-app-bar.small.headline.color')};
@override
Color? get surfaceTintColor => ${componentColor('md.comp.top-app-bar.small.container.surface-tint-layer')};
@override
IconThemeData? get iconTheme => IconThemeData(
color: ${componentColor('md.comp.top-app-bar.small.leading-icon')},
size: ${tokens['md.comp.top-app-bar.small.leading-icon.size']},
);
@override
IconThemeData? get actionsIconTheme => IconThemeData(
color: ${componentColor('md.comp.top-app-bar.small.trailing-icon')},
size: ${tokens['md.comp.top-app-bar.small.trailing-icon.size']},
);
@override
TextStyle? get toolbarTextStyle => _textTheme.bodyText2;
@override
TextStyle? get titleTextStyle => ${textStyle('md.comp.top-app-bar.small.headline')};
}''';
}
......@@ -161,7 +161,9 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
this.flexibleSpace,
this.bottom,
this.elevation,
this.scrolledUnderElevation,
this.shadowColor,
this.surfaceTintColor,
this.shape,
this.backgroundColor,
this.foregroundColor,
......@@ -372,7 +374,12 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// {@template flutter.material.appbar.elevation}
/// The z-coordinate at which to place this app bar relative to its parent.
///
/// This property controls the size of the shadow below the app bar.
/// This property controls the size of the shadow below the app bar if
/// [shadowColor] is not null.
///
/// If [surfaceTintColor] is not null then it will apply a surface tint overlay
/// to the background color (see [Material.surfaceTintColor] for more
/// detail).
///
/// The value must be non-negative.
///
......@@ -383,11 +390,37 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
///
/// See also:
///
/// * [scrolledUnderElevation], which will be used when the app bar has
/// something scrolled underneath it.
/// * [shadowColor], which is the color of the shadow below the app bar.
/// * [surfaceTintColor], which determines the elevation overlay that will
/// be applied to the background of the app bar.
/// * [shape], which defines the shape of the app bar's [Material] and its
/// shadow.
final double? elevation;
/// {@template flutter.material.appbar.scrolledUnderElevation}
/// The elevation that will be used if this app bar has something
/// scrolled underneath it.
///
/// If non-null then it [AppBarTheme.scrolledUnderElevation] of
/// [ThemeData.appBarTheme] will be used. If that is also null then [elevation]
/// will be used.
///
/// The value must be non-negative.
///
/// {@endtemplate}
///
/// See also:
/// * [elevation], which will be used if there is no content scrolled under
/// the app bar.
/// * [shadowColor], which is the color of the shadow below the app bar.
/// * [surfaceTintColor], which determines the elevation overlay that will
/// be applied to the background of the app bar.
/// * [shape], which defines the shape of the app bar's [Material] and its
/// shadow.
final double? scrolledUnderElevation;
/// {@template flutter.material.appbar.shadowColor}
/// The color of the shadow below the app bar.
///
......@@ -402,6 +435,17 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// * [shape], which defines the shape of the app bar and its shadow.
final Color? shadowColor;
/// {@template flutter.material.appbar.surfaceTintColor}
/// The color of the surface tint overlay applied to the app bar's
/// background color to indicate elevation.
///
/// If null no overlay will be applied.
/// {@endtemplate}
///
/// See also:
/// * [Material.surfaceTintColor], which described this feature in more detail.
final Color? surfaceTintColor;
/// {@template flutter.material.appbar.shape}
/// The shape of the app bar's [Material] as well as its shadow.
///
......@@ -709,12 +753,8 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// * [SystemChrome.setSystemUIOverlayStyle]
final SystemUiOverlayStyle? systemOverlayStyle;
bool _getEffectiveCenterTitle(ThemeData theme) {
if (centerTitle != null)
return centerTitle!;
if (theme.appBarTheme.centerTitle != null)
return theme.appBarTheme.centerTitle!;
bool platformCenter() {
assert(theme.platform != null);
switch (theme.platform) {
case TargetPlatform.android:
......@@ -728,14 +768,16 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
}
}
return centerTitle
?? theme.appBarTheme.centerTitle
?? platformCenter();
}
@override
State<AppBar> createState() => _AppBarState();
}
class _AppBarState extends State<AppBar> {
static const double _defaultElevation = 4.0;
static const Color _defaultShadowColor = Color(0xFF000000);
ScrollNotificationObserverState? _scrollNotificationObserver;
bool _scrolledUnder = false;
......@@ -795,8 +837,8 @@ class _AppBarState extends State<AppBar> {
assert(!widget.primary || debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
final AppBarTheme appBarTheme = AppBarTheme.of(context);
final AppBarTheme defaults = theme.useMaterial3 ? _TokenDefaultsM3(context) : _DefaultsM2(context);
final ScaffoldState? scaffold = Scaffold.maybeOf(context);
final ModalRoute<dynamic>? parentRoute = ModalRoute.of(context);
......@@ -821,12 +863,23 @@ class _AppBarState extends State<AppBar> {
states,
widget.backgroundColor,
appBarTheme.backgroundColor,
colorScheme.brightness == Brightness.dark ? colorScheme.surface : colorScheme.primary,
defaults.backgroundColor!,
);
final Color foregroundColor = widget.foregroundColor
?? appBarTheme.foregroundColor
?? (colorScheme.brightness == Brightness.dark ? colorScheme.onSurface : colorScheme.onPrimary);
?? defaults.foregroundColor!;
final double elevation = widget.elevation
?? appBarTheme.elevation
?? defaults.elevation!;
final double effectiveElevation = states.contains(MaterialState.scrolledUnder)
? widget.scrolledUnderElevation
?? appBarTheme.scrolledUnderElevation
?? defaults.scrolledUnderElevation
?? elevation
: elevation;
IconThemeData overallIconTheme = backwardsCompatibility
? widget.iconTheme
......@@ -834,10 +887,13 @@ class _AppBarState extends State<AppBar> {
?? theme.primaryIconTheme
: widget.iconTheme
?? appBarTheme.iconTheme
?? theme.iconTheme.copyWith(color: foregroundColor);
?? defaults.iconTheme!.copyWith(color: foregroundColor);
IconThemeData actionsIconTheme = widget.actionsIconTheme
?? appBarTheme.actionsIconTheme
?? widget.iconTheme
?? appBarTheme.iconTheme
?? defaults.actionsIconTheme?.copyWith(color: foregroundColor)
?? overallIconTheme;
TextStyle? toolbarTextStyle = backwardsCompatibility
......@@ -846,7 +902,7 @@ class _AppBarState extends State<AppBar> {
?? theme.primaryTextTheme.bodyText2
: widget.toolbarTextStyle
?? appBarTheme.toolbarTextStyle
?? theme.textTheme.bodyText2?.copyWith(color: foregroundColor);
?? defaults.toolbarTextStyle?.copyWith(color: foregroundColor);
TextStyle? titleTextStyle = backwardsCompatibility
? widget.textTheme?.headline6
......@@ -854,7 +910,7 @@ class _AppBarState extends State<AppBar> {
?? theme.primaryTextTheme.headline6
: widget.titleTextStyle
?? appBarTheme.titleTextStyle
?? theme.textTheme.headline6?.copyWith(color: foregroundColor);
?? defaults.titleTextStyle?.copyWith(color: foregroundColor);
if (widget.toolbarOpacity != 1.0) {
final double opacity = const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.toolbarOpacity);
......@@ -1050,6 +1106,7 @@ class _AppBarState extends State<AppBar> {
)
: widget.systemOverlayStyle
?? appBarTheme.systemOverlayStyle
?? defaults.systemOverlayStyle
?? _systemOverlayStyleForBrightness(ThemeData.estimateBrightnessForColor(backgroundColor));
return Semantics(
......@@ -1058,13 +1115,14 @@ class _AppBarState extends State<AppBar> {
value: overlayStyle,
child: Material(
color: backgroundColor,
elevation: widget.elevation
?? appBarTheme.elevation
?? _defaultElevation,
elevation: effectiveElevation,
shadowColor: widget.shadowColor
?? appBarTheme.shadowColor
?? _defaultShadowColor,
shape: widget.shape ?? appBarTheme.shape,
?? defaults.shadowColor,
surfaceTintColor: widget.surfaceTintColor
?? appBarTheme.surfaceTintColor
?? defaults.surfaceTintColor,
shape: widget.shape ?? appBarTheme.shape ?? defaults.shape,
child: Semantics(
explicitChildNodes: true,
child: appBar,
......@@ -1084,7 +1142,9 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
required this.flexibleSpace,
required this.bottom,
required this.elevation,
required this.scrolledUnderElevation,
required this.shadowColor,
required this.surfaceTintColor,
required this.forceElevated,
required this.backgroundColor,
required this.foregroundColor,
......@@ -1126,7 +1186,9 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final Widget? flexibleSpace;
final PreferredSizeWidget? bottom;
final double? elevation;
final double? scrolledUnderElevation;
final Color? shadowColor;
final Color? surfaceTintColor;
final bool forceElevated;
final Color? backgroundColor;
final Color? foregroundColor;
......@@ -1201,7 +1263,9 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
: flexibleSpace,
bottom: bottom,
elevation: forceElevated || isScrolledUnder ? elevation : 0.0,
scrolledUnderElevation: scrolledUnderElevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
brightness: brightness,
......@@ -1368,7 +1432,9 @@ class SliverAppBar extends StatefulWidget {
this.flexibleSpace,
this.bottom,
this.elevation,
this.scrolledUnderElevation,
this.shadowColor,
this.surfaceTintColor,
this.forceElevated = false,
this.backgroundColor,
this.foregroundColor,
......@@ -1454,11 +1520,21 @@ class SliverAppBar extends StatefulWidget {
/// This property is used to configure an [AppBar].
final double? elevation;
/// {@macro flutter.material.appbar.scrolledUnderElevation}
///
/// This property is used to configure an [AppBar].
final double? scrolledUnderElevation;
/// {@macro flutter.material.appbar.shadowColor}
///
/// This property is used to configure an [AppBar].
final Color? shadowColor;
/// {@macro flutter.material.appbar.surfaceTintColor}
///
/// This property is used to configure an [AppBar].
final Color? surfaceTintColor;
/// Whether to show the shadow appropriate for the [elevation] even if the
/// content is not scrolled under the [AppBar].
///
......@@ -1760,7 +1836,9 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
flexibleSpace: widget.flexibleSpace,
bottom: widget.bottom,
elevation: widget.elevation,
scrolledUnderElevation: widget.scrolledUnderElevation,
shadowColor: widget.shadowColor,
surfaceTintColor: widget.surfaceTintColor,
forceElevated: widget.forceElevated,
backgroundColor: widget.backgroundColor,
foregroundColor: widget.foregroundColor,
......@@ -1832,3 +1910,82 @@ class _RenderAppBarTitleBox extends RenderAligningShiftedBox {
alignChild();
}
}
class _DefaultsM2 extends AppBarTheme {
_DefaultsM2(this.context)
: super(
elevation: 4.0,
shadowColor: const Color(0xFF000000),
titleSpacing: NavigationToolbar.kMiddleSpacing,
toolbarHeight: kToolbarHeight,
);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
Color? get backgroundColor => _colors.brightness == Brightness.dark ? _colors.surface : _colors.primary;
@override
Color? get foregroundColor => _colors.brightness == Brightness.dark ? _colors.onSurface : _colors.onPrimary;
@override
IconThemeData? get iconTheme => _theme.iconTheme;
@override
TextStyle? get toolbarTextStyle => _theme.textTheme.bodyText2;
@override
TextStyle? get titleTextStyle => _theme.textTheme.headline6;
}
// 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 AppBarTheme {
_TokenDefaultsM3(this.context)
: super(
elevation: 0.0,
scrolledUnderElevation: 3.0,
titleSpacing: NavigationToolbar.kMiddleSpacing,
toolbarHeight: 64.0,
);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
late final TextTheme _textTheme = _theme.textTheme;
@override
Color? get backgroundColor => _colors.surface;
@override
Color? get foregroundColor => _colors.onSurface;
@override
Color? get surfaceTintColor => _colors.surfaceTint;
@override
IconThemeData? get iconTheme => IconThemeData(
color: _colors.onSurface,
size: 24.0,
);
@override
IconThemeData? get actionsIconTheme => IconThemeData(
color: _colors.onSurfaceVariant,
size: 24.0,
);
@override
TextStyle? get toolbarTextStyle => _textTheme.bodyText2;
@override
TextStyle? get titleTextStyle => _textTheme.titleLarge;
}
// END GENERATED TOKEN PROPERTIES
......@@ -37,7 +37,9 @@ class AppBarTheme with Diagnosticable {
Color? backgroundColor,
this.foregroundColor,
this.elevation,
this.scrolledUnderElevation,
this.shadowColor,
this.surfaceTintColor,
this.shape,
this.iconTheme,
this.actionsIconTheme,
......@@ -121,10 +123,18 @@ class AppBarTheme with Diagnosticable {
/// descendant [AppBar] widgets.
final double? elevation;
/// Overrides the default value of [AppBar.scrolledUnderElevation] in all
/// descendant [AppBar] widgets.
final double? scrolledUnderElevation;
/// Overrides the default value for [AppBar.shadowColor] in all
/// descendant widgets.
final Color? shadowColor;
/// Overrides the default value for [AppBar.surfaceTintColor] in all
/// descendant widgets.
final Color? surfaceTintColor;
/// Overrides the default value for [AppBar.shape] in all
/// descendant widgets.
final ShapeBorder? shape;
......@@ -237,7 +247,9 @@ class AppBarTheme with Diagnosticable {
Color? backgroundColor,
Color? foregroundColor,
double? elevation,
double? scrolledUnderElevation,
Color? shadowColor,
Color? surfaceTintColor,
ShapeBorder? shape,
IconThemeData? iconTheme,
@Deprecated(
......@@ -266,7 +278,9 @@ class AppBarTheme with Diagnosticable {
backgroundColor: backgroundColor ?? color ?? this.backgroundColor,
foregroundColor: foregroundColor ?? this.foregroundColor,
elevation: elevation ?? this.elevation,
scrolledUnderElevation: scrolledUnderElevation ?? this.scrolledUnderElevation,
shadowColor: shadowColor ?? this.shadowColor,
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
shape: shape ?? this.shape,
iconTheme: iconTheme ?? this.iconTheme,
actionsIconTheme: actionsIconTheme ?? this.actionsIconTheme,
......@@ -298,7 +312,9 @@ class AppBarTheme with Diagnosticable {
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
foregroundColor: Color.lerp(a?.foregroundColor, b?.foregroundColor, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
scrolledUnderElevation: lerpDouble(a?.scrolledUnderElevation, b?.scrolledUnderElevation, t),
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
iconTheme: IconThemeData.lerp(a?.iconTheme, b?.iconTheme, t),
actionsIconTheme: IconThemeData.lerp(a?.actionsIconTheme, b?.actionsIconTheme, t),
......@@ -319,7 +335,9 @@ class AppBarTheme with Diagnosticable {
backgroundColor,
foregroundColor,
elevation,
scrolledUnderElevation,
shadowColor,
surfaceTintColor,
shape,
iconTheme,
actionsIconTheme,
......@@ -344,7 +362,9 @@ class AppBarTheme with Diagnosticable {
&& other.backgroundColor == backgroundColor
&& other.foregroundColor == foregroundColor
&& other.elevation == elevation
&& other.scrolledUnderElevation == scrolledUnderElevation
&& other.shadowColor == shadowColor
&& other.surfaceTintColor == surfaceTintColor
&& other.shape == shape
&& other.iconTheme == iconTheme
&& other.actionsIconTheme == actionsIconTheme
......@@ -365,7 +385,9 @@ class AppBarTheme with Diagnosticable {
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<double>('scrolledUnderElevation', scrolledUnderElevation, defaultValue: null));
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme, defaultValue: null));
properties.add(DiagnosticsProperty<IconThemeData>('actionsIconTheme', actionsIconTheme, defaultValue: null));
......
......@@ -400,6 +400,9 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final Color? backgroundColor = _getBackgroundColor(context);
final Color? modelShadowColor = widget.shadowColor ?? (theme.useMaterial3 ? null : theme.shadowColor);
// If no shadow color is specified, use 0 for elevation in the model so a drop shadow won't be painted.
final double modelElevation = modelShadowColor != null ? widget.elevation : 0;
assert(
backgroundColor != null || widget.type == MaterialType.transparency,
'If Material type is not MaterialType.transparency, a color must '
......@@ -449,9 +452,9 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
duration: widget.animationDuration,
shape: BoxShape.rectangle,
clipBehavior: widget.clipBehavior,
elevation: widget.elevation,
elevation: modelElevation,
color: color,
shadowColor: widget.shadowColor ?? (theme.useMaterial3 ? const Color(0x00000000) : theme.shadowColor),
shadowColor: modelShadowColor ?? const Color(0x00000000),
animateColor: false,
child: contents,
);
......@@ -476,7 +479,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
clipBehavior: widget.clipBehavior,
elevation: widget.elevation,
color: backgroundColor!,
shadowColor: widget.shadowColor ?? (theme.useMaterial3 ? const Color(0x00000000) : theme.shadowColor),
shadowColor: modelShadowColor,
surfaceTintColor: widget.surfaceTintColor,
child: contents,
);
......@@ -742,8 +745,7 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
assert(shape != null),
assert(clipBehavior != null),
assert(elevation != null && elevation >= 0.0),
assert(color != null),
assert(shadowColor != null);
assert(color != null);
/// The widget below this widget in the tree.
///
......@@ -777,7 +779,7 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
final Color color;
/// The target shadow color.
final Color shadowColor;
final Color? shadowColor;
/// The target surface tint color.
final Color? surfaceTintColor;
......@@ -808,11 +810,13 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
widget.elevation,
(dynamic value) => Tween<double>(begin: value as double),
) as Tween<double>?;
_shadowColor = visitor(
_shadowColor = widget.shadowColor != null
? visitor(
_shadowColor,
widget.shadowColor,
(dynamic value) => ColorTween(begin: value as Color),
) as ColorTween?;
) as ColorTween?
: null;
_surfaceTintColor = widget.surfaceTintColor != null
? visitor(
_surfaceTintColor,
......@@ -834,15 +838,18 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
final Color color = Theme.of(context).useMaterial3
? ElevationOverlay.applySurfaceTint(widget.color, _surfaceTintColor?.evaluate(animation), elevation)
: ElevationOverlay.applyOverlay(context, widget.color, elevation);
// If no shadow color is specified, use 0 for elevation in the model so a drop shadow won't be painted.
final double modelElevation = widget.shadowColor != null ? elevation : 0;
final Color shadowColor = _shadowColor?.evaluate(animation) ?? const Color(0x00000000);
return PhysicalShape(
clipper: ShapeBorderClipper(
shape: shape,
textDirection: Directionality.maybeOf(context),
),
clipBehavior: widget.clipBehavior,
elevation: elevation,
elevation: modelElevation,
color: color,
shadowColor: _shadowColor!.evaluate(animation)!,
shadowColor: shadowColor,
child: _ShapeBorderPaint(
shape: shape,
borderOnForeground: widget.borderOnForeground,
......
......@@ -1203,6 +1203,7 @@ class ThemeData with Diagnosticable {
/// Components that have been migrated to Material 3 are:
///
/// * [AlertDialog]
/// * [AppBar]
/// * [Card]
/// * [Dialog]
/// * [ElevatedButton]
......
......@@ -954,6 +954,8 @@ void main() {
});
testWidgets('AppBar uses the specified elevation or defaults to 4.0', (WidgetTester tester) async {
final bool useMaterial3 = ThemeData().useMaterial3;
Widget buildAppBar([double? elevation]) {
return MaterialApp(
home: Scaffold(
......@@ -967,15 +969,48 @@ void main() {
matching: find.byType(Material),
));
// Default elevation should be _AppBarState._defaultElevation = 4.0
// Default elevation should be used for the material.
await tester.pumpWidget(buildAppBar());
expect(getMaterial().elevation, 4.0);
expect(getMaterial().elevation, useMaterial3 ? 0 : 4);
// AppBar should use the specified elevation.
await tester.pumpWidget(buildAppBar(8.0));
expect(getMaterial().elevation, 8.0);
});
testWidgets('scrolledUnderElevation', (WidgetTester tester) async {
Widget buildAppBar({double? elevation, double? scrolledUnderElevation}) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Title'),
elevation: elevation,
scrolledUnderElevation: scrolledUnderElevation,
),
body: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) => ListTile(title: Text('Item $index')),
),
),
);
}
Material getMaterial() => tester.widget<Material>(find.descendant(
of: find.byType(AppBar),
matching: find.byType(Material),
));
await tester.pumpWidget(buildAppBar(elevation: 2, scrolledUnderElevation: 10));
// Starts with the base elevation.
expect(getMaterial().elevation, 2);
await tester.fling(find.text('Item 2'), const Offset(0.0, -600.0), 2000.0);
await tester.pumpAndSettle();
// After scrolling it should be the scrolledUnderElevation.
expect(getMaterial().elevation, 10);
});
group('SliverAppBar elevation', () {
Widget buildSliverAppBar(bool forceElevated, {double? elevation, double? themeElevation}) {
return MaterialApp(
......@@ -996,15 +1031,16 @@ void main() {
// Regression test for https://github.com/flutter/flutter/issues/59158.
AppBar getAppBar() => tester.widget<AppBar>(find.byType(AppBar));
Material getMaterial() => tester.widget<Material>(find.byType(Material));
final bool useMaterial3 = ThemeData().useMaterial3;
// When forceElevated is off, SliverAppBar should not be elevated.
await tester.pumpWidget(buildSliverAppBar(false));
expect(getMaterial().elevation, 0.0);
// Default elevation should be _AppBarState._defaultElevation = 4.0, and
// Default elevation should be used by the material, but
// the AppBar's elevation should not be specified by SliverAppBar.
await tester.pumpWidget(buildSliverAppBar(true));
expect(getMaterial().elevation, 4.0);
expect(getMaterial().elevation, useMaterial3 ? 0.0 : 4.0);
expect(getAppBar().elevation, null);
// SliverAppBar should use the specified elevation.
......@@ -1312,6 +1348,8 @@ void main() {
final Key key = UniqueKey();
await tester.pumpWidget(
MaterialApp(
// Test was designed against InkSplash so need to make sure that is used.
theme: ThemeData(splashFactory: InkSplash.splashFactory),
home: Center(
child: AppBar(
title: const Text('Abc'),
......@@ -2005,44 +2043,55 @@ void main() {
));
});
testWidgets('AppBar draws a light system bar for a light theme with a dark background', (WidgetTester tester) async {
final ThemeData lightTheme = ThemeData(primarySwatch: Colors.deepOrange);
await tester.pumpWidget(MaterialApp(
theme: lightTheme,
testWidgets('Default system bar brightness based on AppBar background color brightness.', (WidgetTester tester) async {
Widget buildAppBar(ThemeData theme) {
return MaterialApp(
theme: theme,
home: Scaffold(
appBar: AppBar(
title: const Text('test'),
appBar: AppBar(title: const Text('Title')),
),
);
}
// Using a light theme.
{
await tester.pumpWidget(buildAppBar(ThemeData.from(colorScheme: const ColorScheme.light())));
final Material appBarMaterial = tester.widget<Material>(
find.descendant(
of: find.byType(AppBar),
matching: find.byType(Material),
),
));
);
final Brightness appBarBrightness = ThemeData.estimateBrightnessForColor(appBarMaterial.color!);
final Brightness onAppBarBrightness = appBarBrightness == Brightness.light
? Brightness.dark
: Brightness.light;
expect(lightTheme.primaryColorBrightness, Brightness.dark);
expect(lightTheme.colorScheme.brightness, Brightness.light);
expect(SystemChrome.latestStyle, const SystemUiOverlayStyle(
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.light,
expect(SystemChrome.latestStyle, SystemUiOverlayStyle(
statusBarBrightness: appBarBrightness,
statusBarIconBrightness: onAppBarBrightness,
));
});
}
testWidgets('AppBar draws a dark system bar for a dark theme with a light background', (WidgetTester tester) async {
final ThemeData darkTheme = ThemeData(brightness: Brightness.dark, cardColor: Colors.white);
await tester.pumpWidget(
MaterialApp(
theme: darkTheme,
home: Scaffold(
appBar: AppBar(
title: const Text('test'),
),
),
// Using a dark theme.
{
await tester.pumpWidget(buildAppBar(ThemeData.from(colorScheme: const ColorScheme.dark())));
final Material appBarMaterial = tester.widget<Material>(
find.descendant(
of: find.byType(AppBar),
matching: find.byType(Material),
),
);
final Brightness appBarBrightness = ThemeData.estimateBrightnessForColor(appBarMaterial.color!);
final Brightness onAppBarBrightness = appBarBrightness == Brightness.light
? Brightness.dark
: Brightness.light;
expect(darkTheme.primaryColorBrightness, Brightness.dark);
expect(darkTheme.colorScheme.brightness, Brightness.dark);
expect(SystemChrome.latestStyle, const SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.dark,
expect(SystemChrome.latestStyle, SystemUiOverlayStyle(
statusBarBrightness: appBarBrightness,
statusBarIconBrightness: onAppBarBrightness,
));
}
});
testWidgets('Changing SliverAppBar snap from true to false', (WidgetTester tester) async {
......@@ -2207,6 +2256,8 @@ void main() {
Widget buildFrame() {
return MaterialApp(
// Test designed against 2014 font sizes.
theme: ThemeData(textTheme: Typography.englishLike2014),
home: Builder(
builder: (BuildContext context) {
return MediaQuery(
......@@ -2245,6 +2296,8 @@ void main() {
Widget buildFrame() {
return MaterialApp(
// Test designed against 2014 font sizes.
theme: ThemeData(textTheme: Typography.englishLike2014),
home: Builder(
builder: (BuildContext context) {
return Directionality(
......@@ -2536,6 +2589,7 @@ void main() {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light().copyWith(
useMaterial3: false,
appBarTheme: const AppBarTheme(
backwardsCompatibility: false,
),
......
......@@ -15,8 +15,10 @@ void main() {
});
testWidgets('Passing no AppBarTheme returns defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
appBar: AppBar(
actions: <Widget>[
......@@ -33,10 +35,25 @@ void main() {
final RichText actionIconText = _getAppBarIconRichText(tester);
final DefaultTextStyle text = _getAppBarText(tester);
if (theme.useMaterial3) {
expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.light);
expect(widget.color, theme.colorScheme.surface);
expect(widget.elevation, 0);
expect(widget.shadowColor, null);
expect(widget.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(widget.shape, null);
expect(iconTheme.data, IconThemeData(color: theme.colorScheme.onSurface, size: 24));
expect(actionsIconTheme.data, IconThemeData(color: theme.colorScheme.onSurfaceVariant, size: 24));
expect(actionIconText.text.style!.color, Colors.black);
expect(text.style, Typography.material2021().englishLike.bodyText2!.merge(Typography.material2021().black.bodyText2).copyWith(color: theme.colorScheme.onSurface));
expect(tester.getSize(find.byType(AppBar)).height, kToolbarHeight);
expect(tester.getSize(find.byType(AppBar)).width, 800);
} else {
expect(SystemChrome.latestStyle!.statusBarBrightness, SystemUiOverlayStyle.light.statusBarBrightness);
expect(widget.color, Colors.blue);
expect(widget.elevation, 4.0);
expect(widget.shadowColor, Colors.black);
expect(widget.surfaceTintColor, null);
expect(widget.shape, null);
expect(iconTheme.data, const IconThemeData(color: Colors.white));
expect(actionsIconTheme.data, const IconThemeData(color: Colors.white));
......@@ -44,6 +61,7 @@ void main() {
expect(text.style, Typography.material2014().englishLike.bodyText2!.merge(Typography.material2014().white.bodyText2));
expect(tester.getSize(find.byType(AppBar)).height, kToolbarHeight);
expect(tester.getSize(find.byType(AppBar)).width, 800);
}
});
testWidgets('AppBar uses values from AppBarTheme', (WidgetTester tester) async {
......@@ -73,6 +91,7 @@ void main() {
expect(widget.color, appBarTheme.backgroundColor);
expect(widget.elevation, appBarTheme.elevation);
expect(widget.shadowColor, appBarTheme.shadowColor);
expect(widget.surfaceTintColor, appBarTheme.surfaceTintColor);
expect(widget.shape, const StadiumBorder());
expect(iconTheme.data, appBarTheme.iconTheme);
expect(actionsIconTheme.data, appBarTheme.actionsIconTheme);
......@@ -132,7 +151,8 @@ void main() {
const SystemUiOverlayStyle systemOverlayStyle = SystemUiOverlayStyle.light;
const Color color = Colors.orange;
const double elevation = 3.0;
const Color shadowColor = Colors.red;
const Color shadowColor = Colors.purple;
const Color surfaceTintColor = Colors.brown;
const ShapeBorder shape = RoundedRectangleBorder();
const IconThemeData iconThemeData = IconThemeData(color: Colors.green);
const IconThemeData actionsIconThemeData = IconThemeData(color: Colors.lightBlue);
......@@ -151,6 +171,7 @@ void main() {
systemOverlayStyle: systemOverlayStyle,
elevation: elevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
shape: shape,
iconTheme: iconThemeData,
actionsIconTheme: actionsIconThemeData,
......@@ -174,6 +195,7 @@ void main() {
expect(widget.color, color);
expect(widget.elevation, elevation);
expect(widget.shadowColor, shadowColor);
expect(widget.surfaceTintColor, surfaceTintColor);
expect(widget.shape, shape);
expect(iconTheme.data, iconThemeData);
expect(actionsIconTheme.data, actionsIconThemeData);
......@@ -228,6 +250,7 @@ void main() {
expect(widget.color, appBarTheme.backgroundColor);
expect(widget.elevation, appBarTheme.elevation);
expect(widget.shadowColor, appBarTheme.shadowColor);
expect(widget.surfaceTintColor, appBarTheme.surfaceTintColor);
expect(iconTheme.data, appBarTheme.iconTheme);
expect(actionsIconTheme.data, appBarTheme.actionsIconTheme);
expect(actionIconText.text.style!.color, appBarTheme.actionsIconTheme!.color);
......@@ -235,15 +258,13 @@ void main() {
});
testWidgets('ThemeData colorScheme is used when no AppBarTheme is set', (WidgetTester tester) async {
late ThemeData theme;
final ThemeData lightTheme = ThemeData.from(colorScheme: const ColorScheme.light());
final ThemeData darkTheme = ThemeData.from(colorScheme: const ColorScheme.dark());
Widget buildFrame(ThemeData appTheme) {
return MaterialApp(
theme: appTheme,
home: Builder(
builder: (BuildContext context) {
// This ThemeData has been localized with ThemeData.localize. The
// appTheme parameter has not, so its textTheme is incomplete.
theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
actions: <Widget>[
......@@ -256,15 +277,74 @@ void main() {
);
}
if (lightTheme.useMaterial3) {
// M3 AppBar defaults for light themes:
// - elevation: 0
// - shadow color: null
// - surface tint color: ColorScheme.surfaceTint
// - background color: ColorScheme.surface
// - foreground color: ColorScheme.onSurface
// - actions text: style bodyText2, foreground color
// - status bar brightness: light (based on color scheme brightness)
{
await tester.pumpWidget(buildFrame(lightTheme));
final Material widget = _getAppBarMaterial(tester);
final IconTheme iconTheme = _getAppBarIconTheme(tester);
final IconTheme actionsIconTheme = _getAppBarActionsIconTheme(tester);
final RichText actionIconText = _getAppBarIconRichText(tester);
final DefaultTextStyle text = _getAppBarText(tester);
expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.light);
expect(widget.color, lightTheme.colorScheme.surface);
expect(widget.elevation, 0);
expect(widget.shadowColor, null);
expect(widget.surfaceTintColor, lightTheme.colorScheme.surfaceTint);
expect(iconTheme.data.color, lightTheme.colorScheme.onSurface);
expect(actionsIconTheme.data.color, lightTheme.colorScheme.onSurface);
expect(actionIconText.text.style!.color, lightTheme.colorScheme.onSurface);
expect(text.style, Typography.material2021().englishLike.bodyText2!.merge(Typography.material2021().black.bodyText2).copyWith(color: lightTheme.colorScheme.onSurface));
}
// M3 AppBar defaults for dark themes:
// - elevation: 0
// - shadow color: null
// - surface tint color: ColorScheme.surfaceTint
// - background color: ColorScheme.surface
// - foreground color: ColorScheme.onSurface
// - actions text: style bodyText2, foreground color
// - status bar brightness: dark (based on background color)
{
await tester.pumpWidget(buildFrame(ThemeData.from(colorScheme: const ColorScheme.dark())));
await tester.pumpAndSettle(); // Theme change animation
final Material widget = _getAppBarMaterial(tester);
final IconTheme iconTheme = _getAppBarIconTheme(tester);
final IconTheme actionsIconTheme = _getAppBarActionsIconTheme(tester);
final RichText actionIconText = _getAppBarIconRichText(tester);
final DefaultTextStyle text = _getAppBarText(tester);
expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.dark);
expect(widget.color, darkTheme.colorScheme.surface);
expect(widget.elevation, 0);
expect(widget.shadowColor, null);
expect(widget.surfaceTintColor, darkTheme.colorScheme.surfaceTint);
expect(iconTheme.data.color, darkTheme.colorScheme.onSurface);
expect(actionsIconTheme.data.color, darkTheme.colorScheme.onSurface);
expect(actionIconText.text.style!.color, darkTheme.colorScheme.onSurface);
expect(text.style, Typography.material2021().englishLike.bodyText2!.merge(Typography.material2021().black.bodyText2).copyWith(color: darkTheme.colorScheme.onSurface));
}
} else {
// AppBar defaults for light themes:
// - elevation: 4
// - shadow color: black
// - surface tint color: null
// - background color: ColorScheme.primary
// - foreground color: ColorScheme.onPrimary
// - actions text: style bodyText2, foreground color
// - status bar brightness: light (based on color scheme brightness)
{
await tester.pumpWidget(buildFrame(ThemeData.from(colorScheme: const ColorScheme.light())));
await tester.pumpWidget(buildFrame(lightTheme));
final Material widget = _getAppBarMaterial(tester);
final IconTheme iconTheme = _getAppBarIconTheme(tester);
......@@ -273,24 +353,26 @@ void main() {
final DefaultTextStyle text = _getAppBarText(tester);
expect(SystemChrome.latestStyle!.statusBarBrightness, SystemUiOverlayStyle.light.statusBarBrightness);
expect(widget.color, theme.colorScheme.primary);
expect(widget.color, lightTheme.colorScheme.primary);
expect(widget.elevation, 4.0);
expect(widget.shadowColor, Colors.black);
expect(iconTheme.data.color, theme.colorScheme.onPrimary);
expect(actionsIconTheme.data.color, theme.colorScheme.onPrimary);
expect(actionIconText.text.style!.color, theme.colorScheme.onPrimary);
expect(text.style.compareTo(theme.textTheme.bodyText2!.copyWith(color: theme.colorScheme.onPrimary)), RenderComparison.identical);
expect(widget.surfaceTintColor, null);
expect(iconTheme.data.color, lightTheme.colorScheme.onPrimary);
expect(actionsIconTheme.data.color, lightTheme.colorScheme.onPrimary);
expect(actionIconText.text.style!.color, lightTheme.colorScheme.onPrimary);
expect(text.style, Typography.material2014().englishLike.bodyText2!.merge(Typography.material2014().black.bodyText2).copyWith(color: lightTheme.colorScheme.onPrimary));
}
// AppBar defaults for dark themes:
// - elevation: 4
// - shadow color: black
// - surface tint color: null
// - background color: ColorScheme.surface
// - foreground color: ColorScheme.onSurface
// - actions text: style bodyText2, foreground color
// - status bar brightness: dark (based on background color)
{
await tester.pumpWidget(buildFrame(ThemeData.from(colorScheme: const ColorScheme.dark())));
await tester.pumpWidget(buildFrame(darkTheme));
await tester.pumpAndSettle(); // Theme change animation
final Material widget = _getAppBarMaterial(tester);
......@@ -300,13 +382,15 @@ void main() {
final DefaultTextStyle text = _getAppBarText(tester);
expect(SystemChrome.latestStyle!.statusBarBrightness, SystemUiOverlayStyle.light.statusBarBrightness);
expect(widget.color, theme.colorScheme.surface);
expect(widget.color, darkTheme.colorScheme.surface);
expect(widget.elevation, 4.0);
expect(widget.shadowColor, Colors.black);
expect(iconTheme.data.color, theme.colorScheme.onSurface);
expect(actionsIconTheme.data.color, theme.colorScheme.onSurface);
expect(actionIconText.text.style!.color, theme.colorScheme.onSurface);
expect(text.style.compareTo(theme.textTheme.bodyText2!.copyWith(color: theme.colorScheme.onSurface)), RenderComparison.identical);
expect(widget.surfaceTintColor, null);
expect(iconTheme.data.color, darkTheme.colorScheme.onSurface);
expect(actionsIconTheme.data.color, darkTheme.colorScheme.onSurface);
expect(actionIconText.text.style!.color, darkTheme.colorScheme.onSurface);
expect(text.style, Typography.material2014().englishLike.bodyText2!.merge(Typography.material2014().black.bodyText2).copyWith(color: darkTheme.colorScheme.onSurface));
}
}
});
......@@ -315,7 +399,7 @@ void main() {
Widget buildFrame({ Color? appIconColor, Color? appBarIconColor }) {
return MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light()),
theme: ThemeData.from(useMaterial3: false, colorScheme: const ColorScheme.light()),
home: IconTheme(
data: IconThemeData(color: appIconColor),
child: Builder(
......@@ -408,6 +492,22 @@ void main() {
expect(appBar.shadowColor, Colors.yellow);
});
testWidgets('AppBar.surfaceTintColor takes priority over AppBarTheme.surfaceTintColor', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: ThemeData(appBarTheme: const AppBarTheme(surfaceTintColor: Colors.red)),
home: Scaffold(
appBar: AppBar(
title: const Text('Title'),
surfaceTintColor: Colors.yellow,
),
),
));
final AppBar appBar = tester.widget(find.byType(AppBar));
// The AppBar.surfaceTintColor should be used instead of AppBarTheme.surfaceTintColor.
expect(appBar.surfaceTintColor, Colors.yellow);
});
testWidgets('AppBar uses AppBarTheme.titleSpacing', (WidgetTester tester) async {
const double kTitleSpacing = 10;
await tester.pumpWidget(MaterialApp(
......@@ -493,6 +593,7 @@ void main() {
backgroundColor: Color(0xff000001),
elevation: 8.0,
shadowColor: Color(0xff000002),
surfaceTintColor: Color(0xff000003),
centerTitle: true,
titleSpacing: 40.0,
).debugFillProperties(builder);
......@@ -507,6 +608,7 @@ void main() {
'backgroundColor: Color(0xff000001)',
'elevation: 8.0',
'shadowColor: Color(0xff000002)',
'surfaceTintColor: Color(0xff000003)',
'centerTitle: true',
'titleSpacing: 40.0',
]);
......@@ -524,6 +626,7 @@ AppBarTheme _appBarTheme() {
const Color backgroundColor = Colors.lightBlue;
const double elevation = 6.0;
const Color shadowColor = Colors.red;
const Color surfaceTintColor = Colors.green;
const IconThemeData iconThemeData = IconThemeData(color: Colors.black);
const IconThemeData actionsIconThemeData = IconThemeData(color: Colors.pink);
return const AppBarTheme(
......@@ -532,6 +635,7 @@ AppBarTheme _appBarTheme() {
backgroundColor: backgroundColor,
elevation: elevation,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
shape: StadiumBorder(),
iconTheme: iconThemeData,
toolbarHeight: 96,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment