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')};
}''';
}
......@@ -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,
(dynamic value) => ColorTween(begin: value as Color),
) as ColorTween?;
_shadowColor = widget.shadowColor != null
? visitor(
_shadowColor,
widget.shadowColor,
(dynamic value) => ColorTween(begin: value as Color),
) 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,
home: Scaffold(
appBar: AppBar(
title: const Text('test'),
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('Title')),
),
),
));
expect(lightTheme.primaryColorBrightness, Brightness.dark);
expect(lightTheme.colorScheme.brightness, Brightness.light);
expect(SystemChrome.latestStyle, const SystemUiOverlayStyle(
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.light,
));
});
);
}
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 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(SystemChrome.latestStyle, SystemUiOverlayStyle(
statusBarBrightness: appBarBrightness,
statusBarIconBrightness: onAppBarBrightness,
));
}
expect(darkTheme.primaryColorBrightness, Brightness.dark);
expect(darkTheme.colorScheme.brightness, Brightness.dark);
expect(SystemChrome.latestStyle, const SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
statusBarIconBrightness: Brightness.dark,
));
// 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(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,
),
......
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