[flutter_releases] Flutter beta 2.13.0-0.4.pre Framework Cherrypicks (#103101)
* Add the new hash
* [flutter.js] Wait for reg.update, then activate sw (if not active yet). (#101464)
* Avoid scheduling a forced frame when there is no child to the renderView (#102556)
* Migrate AppBar to Material 3 (#101884)
* Keeping the super parameters
* Fixing semi-colon issue
Co-authored-by: David Iglesias <ditman@gmail.com>
Co-authored-by: Dan Field <dnfield@google.com>
Co-authored-by: Darren Austin <darrenaustin@google.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 2d92142..9aa00d6 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-3096903c8923608d3c1ccf8058a29c31a2bfbc53
+c5caf749fe75db788dba8def502c46c094435605
diff --git a/dev/bots/service_worker_test.dart b/dev/bots/service_worker_test.dart
index 0b30533..10432c7 100644
--- a/dev/bots/service_worker_test.dart
+++ b/dev/bots/service_worker_test.dart
@@ -17,13 +17,22 @@
final String _flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
final String _flutter = path.join(_flutterRoot, 'bin', 'flutter$_bat');
final String _testAppDirectory = path.join(_flutterRoot, 'dev', 'integration_tests', 'web');
+final String _testAppWebDirectory = path.join(_testAppDirectory, 'web');
final String _appBuildDirectory = path.join(_testAppDirectory, 'build', 'web');
final String _target = path.join('lib', 'service_worker_test.dart');
final String _targetPath = path.join(_testAppDirectory, _target);
+enum ServiceWorkerTestType {
+ withoutFlutterJs,
+ withFlutterJs,
+ withFlutterJsShort,
+}
+
// Run a web service worker test as a standalone Dart program.
Future<void> main() async {
- await runWebServiceWorkerTest(headless: false);
+ await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
+ await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
+ await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
}
Future<void> _setAppVersion(int version) async {
@@ -36,7 +45,23 @@
);
}
-Future<void> _rebuildApp({ required int version }) async {
+String _testTypeToIndexFile(ServiceWorkerTestType type) {
+ late String indexFile;
+ switch (type) {
+ case ServiceWorkerTestType.withFlutterJs:
+ indexFile = 'index_with_flutterjs.html';
+ break;
+ case ServiceWorkerTestType.withoutFlutterJs:
+ indexFile = 'index_without_flutterjs.html';
+ break;
+ case ServiceWorkerTestType.withFlutterJsShort:
+ indexFile = 'index_with_flutterjs_short.html';
+ break;
+ }
+ return indexFile;
+}
+
+Future<void> _rebuildApp({ required int version, required ServiceWorkerTestType testType }) async {
await _setAppVersion(version);
await runCommand(
_flutter,
@@ -44,6 +69,14 @@
workingDirectory: _testAppDirectory,
);
await runCommand(
+ 'cp',
+ <String>[
+ _testTypeToIndexFile(testType),
+ 'index.html',
+ ],
+ workingDirectory: _testAppWebDirectory,
+ );
+ await runCommand(
_flutter,
<String>['build', 'web', '--profile', '-t', _target],
workingDirectory: _testAppDirectory,
@@ -69,9 +102,8 @@
Future<void> runWebServiceWorkerTest({
required bool headless,
+ required ServiceWorkerTestType testType,
}) async {
- await _rebuildApp(version: 1);
-
final Map<String, int> requestedPathCounts = <String, int>{};
void expectRequestCounts(Map<String, int> expectedCounts) {
expect(requestedPathCounts, expectedCounts);
@@ -124,10 +156,64 @@
);
}
+ // Preserve old index.html as index_og.html so we can restore it later for other tests
+ await runCommand(
+ 'mv',
+ <String>[
+ 'index.html',
+ 'index_og.html',
+ ],
+ workingDirectory: _testAppWebDirectory,
+ );
+
+ final bool shouldExpectFlutterJs = testType != ServiceWorkerTestType.withoutFlutterJs;
+
+ print('BEGIN runWebServiceWorkerTest(headless: $headless, testType: $testType)\n');
+
try {
+ /////
+ // Attempt to load a different version of the service worker!
+ /////
+ await _rebuildApp(version: 1, testType: testType);
+
+ print('Call update() on the current web worker');
+ await startAppServer(cacheControl: 'max-age=0');
+ await waitForAppToLoad(<String, int> {
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 1,
+ 'CLOSE': 1,
+ });
+ expect(reportedVersion, '1');
+ reportedVersion = null;
+
+ await server!.chrome.reloadPage(ignoreCache: true);
+ await waitForAppToLoad(<String, int> {
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 2,
+ 'CLOSE': 2,
+ });
+ expect(reportedVersion, '1');
+ reportedVersion = null;
+
+ await _rebuildApp(version: 2, testType: testType);
+
+ await server!.chrome.reloadPage(ignoreCache: true);
+ await waitForAppToLoad(<String, int>{
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 3,
+ 'CLOSE': 3,
+ });
+ expect(reportedVersion, '2');
+
+ reportedVersion = null;
+ requestedPathCounts.clear();
+ await server!.stop();
+
//////////////////////////////////////////////////////
// Caching server
//////////////////////////////////////////////////////
+ await _rebuildApp(version: 1, testType: testType);
+
print('With cache: test first page load');
await startAppServer(cacheControl: 'max-age=3600');
await waitForAppToLoad(<String, int>{
@@ -140,6 +226,8 @@
// once by the initial page load, and once by the service worker.
// Other resources are loaded once only by the service worker.
'index.html': 2,
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 1,
'main.dart.js': 1,
'flutter_service_worker.js': 1,
'assets/FontManifest.json': 1,
@@ -171,7 +259,7 @@
reportedVersion = null;
print('With cache: test page reload after rebuild');
- await _rebuildApp(version: 2);
+ await _rebuildApp(version: 2, testType: testType);
// Since we're caching, we need to ignore cache when reloading the page.
await server!.chrome.reloadPage(ignoreCache: true);
@@ -181,6 +269,8 @@
});
expectRequestCounts(<String, int>{
'index.html': 2,
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 1,
'flutter_service_worker.js': 2,
'main.dart.js': 1,
'assets/NOTICES': 1,
@@ -200,7 +290,7 @@
// Non-caching server
//////////////////////////////////////////////////////
print('No cache: test first page load');
- await _rebuildApp(version: 3);
+ await _rebuildApp(version: 3, testType: testType);
await startAppServer(cacheControl: 'max-age=0');
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
@@ -209,6 +299,8 @@
expectRequestCounts(<String, int>{
'index.html': 2,
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 1,
// We still download some resources multiple times if the server is non-caching.
'main.dart.js': 2,
'assets/FontManifest.json': 2,
@@ -231,10 +323,14 @@
await server!.chrome.reloadPage();
await waitForAppToLoad(<String, int>{
'CLOSE': 1,
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 1,
'flutter_service_worker.js': 1,
});
expectRequestCounts(<String, int>{
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 1,
'flutter_service_worker.js': 1,
'CLOSE': 1,
if (!headless)
@@ -244,7 +340,7 @@
reportedVersion = null;
print('No cache: test page reload after rebuild');
- await _rebuildApp(version: 4);
+ await _rebuildApp(version: 4, testType: testType);
// TODO(yjbanov): when running Chrome with DevTools protocol, for some
// reason a hard refresh is still required. This works without a hard
@@ -258,6 +354,8 @@
});
expectRequestCounts(<String, int>{
'index.html': 2,
+ if (shouldExpectFlutterJs)
+ 'flutter.js': 1,
'flutter_service_worker.js': 2,
'main.dart.js': 2,
'assets/NOTICES': 1,
@@ -274,7 +372,17 @@
expect(reportedVersion, '4');
reportedVersion = null;
} finally {
+ await runCommand(
+ 'mv',
+ <String>[
+ 'index_og.html',
+ 'index.html',
+ ],
+ workingDirectory: _testAppWebDirectory,
+ );
await _setAppVersion(1);
await server?.stop();
}
+
+ print('END runWebServiceWorkerTest(headless: $headless, testType: $testType)\n');
}
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index f58b9eb..c2efd2d 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -1070,7 +1070,9 @@
() => _runGalleryE2eWebTest('profile', canvasKit: true),
() => _runGalleryE2eWebTest('release'),
() => _runGalleryE2eWebTest('release', canvasKit: true),
- () => runWebServiceWorkerTest(headless: true),
+ () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
+ () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
+ () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
() => _runWebStackTraceTest('release', 'lib/stack_trace.dart'),
() => _runWebStackTraceTest('profile', 'lib/framework_stack_trace.dart'),
diff --git a/dev/integration_tests/web/web/index_with_flutterjs.html b/dev/integration_tests/web/web/index_with_flutterjs.html
new file mode 100644
index 0000000..8334b5b
--- /dev/null
+++ b/dev/integration_tests/web/web/index_with_flutterjs.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<!-- 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. -->
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+
+ <title>Web Test</title>
+ <!-- iOS meta tags & icons -->
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black">
+ <meta name="apple-mobile-web-app-title" content="Web Test">
+ <link rel="manifest" href="manifest.json">
+ <script>
+ // The value below is injected by flutter build, do not touch.
+ var serviceWorkerVersion = null;
+ </script>
+ <!-- This script adds the flutter initialization JS code -->
+ <script src="flutter.js" defer></script>
+</head>
+<body>
+ <script>
+ window.addEventListener('load', function(ev) {
+ // Download main.dart.js
+ _flutter.loader.loadEntrypoint({
+ serviceWorker: {
+ serviceWorkerVersion: serviceWorkerVersion,
+ }
+ }).then(function(engineInitializer) {
+ return engineInitializer.initializeEngine();
+ }).then(function(appRunner) {
+ return appRunner.runApp();
+ });
+ });
+ </script>
+</body>
+</html>
diff --git a/dev/integration_tests/web/web/index_with_flutterjs_short.html b/dev/integration_tests/web/web/index_with_flutterjs_short.html
new file mode 100644
index 0000000..21a494f
--- /dev/null
+++ b/dev/integration_tests/web/web/index_with_flutterjs_short.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<!-- 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. -->
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+
+ <title>Web Test</title>
+ <!-- iOS meta tags & icons -->
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black">
+ <meta name="apple-mobile-web-app-title" content="Web Test">
+ <link rel="manifest" href="manifest.json">
+ <script>
+ // The value below is injected by flutter build, do not touch.
+ var serviceWorkerVersion = null;
+ </script>
+ <!-- This script adds the flutter initialization JS code -->
+ <script src="flutter.js" defer></script>
+</head>
+<body>
+ <script>
+ window.addEventListener('load', function(ev) {
+ // Download main.dart.js
+ _flutter.loader.loadEntrypoint({
+ serviceWorker: {
+ serviceWorkerVersion: serviceWorkerVersion,
+ }
+ }).then(function(engineInitializer) {
+ return engineInitializer.autoStart();
+ });
+ });
+ </script>
+</body>
+</html>
diff --git a/dev/integration_tests/web/web/index_without_flutterjs.html b/dev/integration_tests/web/web/index_without_flutterjs.html
new file mode 100644
index 0000000..05d4dee
--- /dev/null
+++ b/dev/integration_tests/web/web/index_without_flutterjs.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<!-- 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. -->
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+
+ <title>Web Test</title>
+ <!-- iOS meta tags & icons -->
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black">
+ <meta name="apple-mobile-web-app-title" content="Web Test">
+ <link rel="manifest" href="manifest.json">
+</head>
+<body>
+ <!-- This script installs service_worker.js to provide PWA functionality to
+ application. For more information, see:
+ https://developers.google.com/web/fundamentals/primers/service-workers -->
+ <script>
+ var serviceWorkerVersion = null;
+ var scriptLoaded = false;
+ function loadMainDartJs() {
+ if (scriptLoaded) {
+ return;
+ }
+ scriptLoaded = true;
+ var scriptTag = document.createElement('script');
+ scriptTag.src = 'main.dart.js';
+ scriptTag.type = 'application/javascript';
+ document.body.append(scriptTag);
+ }
+
+ if ('serviceWorker' in navigator) {
+ // Service workers are supported. Use them.
+ window.addEventListener('load', function () {
+ // Wait for registration to finish before dropping the <script> tag.
+ // Otherwise, the browser will load the script multiple times,
+ // potentially different versions.
+ var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
+ navigator.serviceWorker.register(serviceWorkerUrl)
+ .then((reg) => {
+ function waitForActivation(serviceWorker) {
+ serviceWorker.addEventListener('statechange', () => {
+ if (serviceWorker.state == 'activated') {
+ console.log('Installed new service worker.');
+ loadMainDartJs();
+ }
+ });
+ }
+ if (!reg.active && (reg.installing || reg.waiting)) {
+ // No active web worker and we have installed or are installing
+ // one for the first time. Simply wait for it to activate.
+ waitForActivation(reg.installing ?? reg.waiting);
+ } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
+ // When the app updates the serviceWorkerVersion changes, so we
+ // need to ask the service worker to update.
+ console.log('New service worker available.');
+ reg.update();
+ waitForActivation(reg.installing);
+ } else {
+ // Existing service worker is still good.
+ console.log('Loading app from service worker.');
+ loadMainDartJs();
+ }
+ });
+
+ // If service worker doesn't succeed in a reasonable amount of time,
+ // fallback to plaint <script> tag.
+ setTimeout(() => {
+ if (!scriptLoaded) {
+ console.warn(
+ 'Failed to load app from service worker. Falling back to plain <script> tag.',
+ );
+ loadMainDartJs();
+ }
+ }, 4000);
+ });
+ } else {
+ // Service workers not supported. Just drop the <script> tag.
+ loadMainDartJs();
+ }
+ </script>
+</body>
+</html>
diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart
index 0694020..d2b4fc5 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/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 @@
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();
diff --git a/dev/tools/gen_defaults/lib/app_bar_template.dart b/dev/tools/gen_defaults/lib/app_bar_template.dart
new file mode 100644
index 0000000..06b2ddf
--- /dev/null
+++ b/dev/tools/gen_defaults/lib/app_bar_template.dart
@@ -0,0 +1,58 @@
+// 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')};
+}''';
+}
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index 086095c..28303fe 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -161,7 +161,9 @@
this.flexibleSpace,
this.bottom,
this.elevation,
+ this.scrolledUnderElevation,
this.shadowColor,
+ this.surfaceTintColor,
this.shape,
this.backgroundColor,
this.foregroundColor,
@@ -373,7 +375,12 @@
/// {@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.
///
@@ -384,11 +391,37 @@
///
/// 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.
///
@@ -403,6 +436,17 @@
/// * [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.
///
@@ -710,23 +754,24 @@
/// * [SystemChrome.setSystemUIOverlayStyle]
final SystemUiOverlayStyle? systemOverlayStyle;
-
bool _getEffectiveCenterTitle(ThemeData theme) {
- if (centerTitle != null)
- return centerTitle!;
- if (theme.appBarTheme.centerTitle != null)
- return theme.appBarTheme.centerTitle!;
- assert(theme.platform != null);
- switch (theme.platform) {
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- case TargetPlatform.linux:
- case TargetPlatform.windows:
- return false;
- case TargetPlatform.iOS:
- case TargetPlatform.macOS:
- return actions == null || actions!.length < 2;
+ bool platformCenter() {
+ assert(theme.platform != null);
+ switch (theme.platform) {
+ case TargetPlatform.android:
+ case TargetPlatform.fuchsia:
+ case TargetPlatform.linux:
+ case TargetPlatform.windows:
+ return false;
+ case TargetPlatform.iOS:
+ case TargetPlatform.macOS:
+ return actions == null || actions!.length < 2;
+ }
}
+
+ return centerTitle
+ ?? theme.appBarTheme.centerTitle
+ ?? platformCenter();
}
@override
@@ -734,9 +779,6 @@
}
class _AppBarState extends State<AppBar> {
- static const double _defaultElevation = 4.0;
- static const Color _defaultShadowColor = Color(0xFF000000);
-
ScrollNotificationObserverState? _scrollNotificationObserver;
bool _scrolledUnder = false;
@@ -796,8 +838,8 @@
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);
@@ -822,12 +864,23 @@
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
@@ -835,10 +888,13 @@
?? 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
@@ -847,7 +903,7 @@
?? theme.primaryTextTheme.bodyText2
: widget.toolbarTextStyle
?? appBarTheme.toolbarTextStyle
- ?? theme.textTheme.bodyText2?.copyWith(color: foregroundColor);
+ ?? defaults.toolbarTextStyle?.copyWith(color: foregroundColor);
TextStyle? titleTextStyle = backwardsCompatibility
? widget.textTheme?.headline6
@@ -855,7 +911,7 @@
?? 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);
@@ -1051,6 +1107,7 @@
)
: widget.systemOverlayStyle
?? appBarTheme.systemOverlayStyle
+ ?? defaults.systemOverlayStyle
?? _systemOverlayStyleForBrightness(ThemeData.estimateBrightnessForColor(backgroundColor));
return Semantics(
@@ -1059,13 +1116,14 @@
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,
@@ -1085,7 +1143,9 @@
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,
@@ -1127,7 +1187,9 @@
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;
@@ -1202,7 +1264,9 @@
: flexibleSpace,
bottom: bottom,
elevation: forceElevated || isScrolledUnder ? elevation : 0.0,
+ scrolledUnderElevation: scrolledUnderElevation,
shadowColor: shadowColor,
+ surfaceTintColor: surfaceTintColor,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
brightness: brightness,
@@ -1369,7 +1433,9 @@
this.flexibleSpace,
this.bottom,
this.elevation,
+ this.scrolledUnderElevation,
this.shadowColor,
+ this.surfaceTintColor,
this.forceElevated = false,
this.backgroundColor,
this.foregroundColor,
@@ -1456,11 +1522,21 @@
/// 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].
///
@@ -1762,7 +1838,9 @@
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,
@@ -1835,3 +1913,82 @@
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
diff --git a/packages/flutter/lib/src/material/app_bar_theme.dart b/packages/flutter/lib/src/material/app_bar_theme.dart
index 583e1bd..5eb0ff4 100644
--- a/packages/flutter/lib/src/material/app_bar_theme.dart
+++ b/packages/flutter/lib/src/material/app_bar_theme.dart
@@ -37,7 +37,9 @@
Color? backgroundColor,
this.foregroundColor,
this.elevation,
+ this.scrolledUnderElevation,
this.shadowColor,
+ this.surfaceTintColor,
this.shape,
this.iconTheme,
this.actionsIconTheme,
@@ -121,10 +123,18 @@
/// 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 @@
Color? backgroundColor,
Color? foregroundColor,
double? elevation,
+ double? scrolledUnderElevation,
Color? shadowColor,
+ Color? surfaceTintColor,
ShapeBorder? shape,
IconThemeData? iconTheme,
@Deprecated(
@@ -266,7 +278,9 @@
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 @@
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 @@
backgroundColor,
foregroundColor,
elevation,
+ scrolledUnderElevation,
shadowColor,
+ surfaceTintColor,
shape,
iconTheme,
actionsIconTheme,
@@ -344,7 +362,9 @@
&& 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 @@
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));
diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart
index 8b21086..1e0c1dc 100644
--- a/packages/flutter/lib/src/material/material.dart
+++ b/packages/flutter/lib/src/material/material.dart
@@ -401,6 +401,9 @@
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 '
@@ -450,9 +453,9 @@
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,
);
@@ -477,7 +480,7 @@
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,
);
@@ -745,7 +748,6 @@
assert(clipBehavior != null),
assert(elevation != null && elevation >= 0.0),
assert(color != null),
- assert(shadowColor != null),
super(key: key, curve: curve, duration: duration);
/// The widget below this widget in the tree.
@@ -780,7 +782,7 @@
final Color color;
/// The target shadow color.
- final Color shadowColor;
+ final Color? shadowColor;
/// The target surface tint color.
final Color? surfaceTintColor;
@@ -811,11 +813,13 @@
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,
@@ -837,15 +841,18 @@
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,
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index 95b445a..9f66b1a 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -1257,6 +1257,7 @@
/// Components that have been migrated to Material 3 are:
///
/// * [AlertDialog]
+ /// * [AppBar]
/// * [Card]
/// * [Dialog]
/// * [ElevatedButton]
diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart
index 7a33811..b3431e7 100644
--- a/packages/flutter/lib/src/rendering/binding.dart
+++ b/packages/flutter/lib/src/rendering/binding.dart
@@ -235,10 +235,13 @@
///
/// See [dart:ui.PlatformDispatcher.onMetricsChanged].
@protected
+ @visibleForTesting
void handleMetricsChanged() {
assert(renderView != null);
renderView.configuration = createViewConfiguration();
- scheduleForcedFrame();
+ if (renderView.child != null) {
+ scheduleForcedFrame();
+ }
}
/// Called when the platform text scale factor changes.
diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart
index 3f78736..7501f43 100644
--- a/packages/flutter/test/material/app_bar_test.dart
+++ b/packages/flutter/test/material/app_bar_test.dart
@@ -955,6 +955,8 @@
});
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(
@@ -968,15 +970,48 @@
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(
@@ -997,15 +1032,16 @@
// 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.
@@ -1313,6 +1349,8 @@
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'),
@@ -2006,44 +2044,55 @@
));
});
- 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'),
- ),
- ),
- ));
-
- 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,
+ 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')),
),
- ),
- );
+ );
+ }
- expect(darkTheme.primaryColorBrightness, Brightness.dark);
- expect(darkTheme.colorScheme.brightness, Brightness.dark);
- expect(SystemChrome.latestStyle, const SystemUiOverlayStyle(
- statusBarBrightness: Brightness.light,
- statusBarIconBrightness: Brightness.dark,
- ));
+ // 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,
+ ));
+ }
+
+ // 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 {
@@ -2208,6 +2257,8 @@
Widget buildFrame() {
return MaterialApp(
+ // Test designed against 2014 font sizes.
+ theme: ThemeData(textTheme: Typography.englishLike2014),
home: Builder(
builder: (BuildContext context) {
return MediaQuery(
@@ -2246,6 +2297,8 @@
Widget buildFrame() {
return MaterialApp(
+ // Test designed against 2014 font sizes.
+ theme: ThemeData(textTheme: Typography.englishLike2014),
home: Builder(
builder: (BuildContext context) {
return Directionality(
@@ -2537,6 +2590,7 @@
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light().copyWith(
+ useMaterial3: false,
appBarTheme: const AppBarTheme(
backwardsCompatibility: false,
),
diff --git a/packages/flutter/test/material/app_bar_theme_test.dart b/packages/flutter/test/material/app_bar_theme_test.dart
index 171dbe5..856ccd2 100644
--- a/packages/flutter/test/material/app_bar_theme_test.dart
+++ b/packages/flutter/test/material/app_bar_theme_test.dart
@@ -15,8 +15,10 @@
});
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,17 +35,33 @@
final RichText actionIconText = _getAppBarIconRichText(tester);
final DefaultTextStyle text = _getAppBarText(tester);
- expect(SystemChrome.latestStyle!.statusBarBrightness, SystemUiOverlayStyle.light.statusBarBrightness);
- expect(widget.color, Colors.blue);
- expect(widget.elevation, 4.0);
- expect(widget.shadowColor, Colors.black);
- expect(widget.shape, null);
- expect(iconTheme.data, const IconThemeData(color: Colors.white));
- expect(actionsIconTheme.data, const IconThemeData(color: Colors.white));
- expect(actionIconText.text.style!.color, Colors.white);
- 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);
+ 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));
+ expect(actionIconText.text.style!.color, Colors.white);
+ 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 @@
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 @@
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 @@
systemOverlayStyle: systemOverlayStyle,
elevation: elevation,
shadowColor: shadowColor,
+ surfaceTintColor: surfaceTintColor,
shape: shape,
iconTheme: iconThemeData,
actionsIconTheme: actionsIconThemeData,
@@ -174,6 +195,7 @@
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 @@
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 @@
});
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,57 +277,120 @@
);
}
- // AppBar defaults for light themes:
- // - elevation: 4
- // - shadow color: black
- // - 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())));
+ 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);
+ 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, SystemUiOverlayStyle.light.statusBarBrightness);
- expect(widget.color, theme.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(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));
+ }
- // AppBar defaults for dark themes:
- // - elevation: 4
- // - shadow color: black
- // - 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
+ // 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);
+ 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, SystemUiOverlayStyle.light.statusBarBrightness);
- expect(widget.color, theme.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(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(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, SystemUiOverlayStyle.light.statusBarBrightness);
+ expect(widget.color, lightTheme.colorScheme.primary);
+ expect(widget.elevation, 4.0);
+ expect(widget.shadowColor, Colors.black);
+ 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(darkTheme));
+ 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, SystemUiOverlayStyle.light.statusBarBrightness);
+ expect(widget.color, darkTheme.colorScheme.surface);
+ expect(widget.elevation, 4.0);
+ expect(widget.shadowColor, Colors.black);
+ 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 @@
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 @@
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 @@
backgroundColor: Color(0xff000001),
elevation: 8.0,
shadowColor: Color(0xff000002),
+ surfaceTintColor: Color(0xff000003),
centerTitle: true,
titleSpacing: 40.0,
).debugFillProperties(builder);
@@ -507,6 +608,7 @@
'backgroundColor: Color(0xff000001)',
'elevation: 8.0',
'shadowColor: Color(0xff000002)',
+ 'surfaceTintColor: Color(0xff000003)',
'centerTitle: true',
'titleSpacing: 40.0',
]);
@@ -524,6 +626,7 @@
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 @@
backgroundColor: backgroundColor,
elevation: elevation,
shadowColor: shadowColor,
+ surfaceTintColor: surfaceTintColor,
shape: StadiumBorder(),
iconTheme: iconThemeData,
toolbarHeight: 96,
diff --git a/packages/flutter/test/rendering/binding_test.dart b/packages/flutter/test/rendering/binding_test.dart
new file mode 100644
index 0000000..c10df12
--- /dev/null
+++ b/packages/flutter/test/rendering/binding_test.dart
@@ -0,0 +1,22 @@
+// 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 'package:flutter/rendering.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ test('handleMetricsChanged does not scheduleForcedFrame unless there is a child to the renderView', () async {
+ expect(SchedulerBinding.instance.hasScheduledFrame, false);
+ RendererBinding.instance.handleMetricsChanged();
+ expect(SchedulerBinding.instance.hasScheduledFrame, false);
+
+ RendererBinding.instance.renderView.child = RenderLimitedBox();
+ RendererBinding.instance.handleMetricsChanged();
+ expect(SchedulerBinding.instance.hasScheduledFrame, true);
+ });
+}
diff --git a/packages/flutter_tools/lib/src/web/flutter_js.dart b/packages/flutter_tools/lib/src/web/flutter_js.dart
index dfff827..af8950b 100644
--- a/packages/flutter_tools/lib/src/web/flutter_js.dart
+++ b/packages/flutter_tools/lib/src/web/flutter_js.dart
@@ -31,7 +31,7 @@
// we support. In the meantime, we use the "revealing module" pattern.
// Watchdog to prevent injecting the main entrypoint multiple times.
- _scriptLoaded = false;
+ _scriptLoaded = null;
// Resolver for the pending promise returned by loadEntrypoint.
_didCreateEngineInitializerResolve = null;
@@ -61,31 +61,38 @@
console.warn("Do not call didCreateEngineInitializer by hand. Start with loadEntrypoint instead.");
}
this._didCreateEngineInitializerResolve(engineInitializer);
+ // Remove this method after it's done, so Flutter Web can hot restart.
+ delete this.didCreateEngineInitializer;
}).bind(this);
_loadEntrypoint(entrypointUrl) {
- if (this._scriptLoaded) {
- return null;
+ if (!this._scriptLoaded) {
+ this._scriptLoaded = new Promise((resolve, reject) => {
+ let scriptTag = document.createElement("script");
+ scriptTag.src = entrypointUrl;
+ scriptTag.type = "application/javascript";
+ this._didCreateEngineInitializerResolve = resolve; // Cache the resolve, so it can be called from Flutter.
+ scriptTag.addEventListener("error", reject);
+ document.body.append(scriptTag);
+ });
}
- this._scriptLoaded = true;
-
- return new Promise((resolve, reject) => {
- let scriptTag = document.createElement("script");
- scriptTag.src = entrypointUrl;
- scriptTag.type = "application/javascript";
- this._didCreateEngineInitializerResolve = resolve; // Cache the resolve, so it can be called from Flutter.
- scriptTag.addEventListener("error", reject);
- document.body.append(scriptTag);
- });
+ return this._scriptLoaded;
}
_waitForServiceWorkerActivation(serviceWorker, entrypointUrl) {
- if (!serviceWorker) return;
+ if (!serviceWorker || serviceWorker.state == "activated") {
+ if (!serviceWorker) {
+ console.warn("Cannot activate a null service worker. Falling back to plain <script> tag.");
+ } else {
+ console.debug("Service worker already active.");
+ }
+ return this._loadEntrypoint(entrypointUrl);
+ }
return new Promise((resolve, _) => {
serviceWorker.addEventListener("statechange", () => {
if (serviceWorker.state == "activated") {
- console.log("Installed new service worker.");
+ console.debug("Installed new service worker.");
resolve(this._loadEntrypoint(entrypointUrl));
}
});
@@ -103,22 +110,26 @@
timeoutMillis = 4000,
} = serviceWorkerOptions;
- var serviceWorkerUrl = "flutter_service_worker.js?v=" + serviceWorkerVersion;
+ let serviceWorkerUrl = "flutter_service_worker.js?v=" + serviceWorkerVersion;
let loader = navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
- return this._waitForServiceWorkerActivation(reg.installing || reg.waiting, entrypointUrl);
+ let sw = reg.installing || reg.waiting;
+ return this._waitForServiceWorkerActivation(sw, entrypointUrl);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
- console.log("New service worker available.");
- reg.update();
- return this._waitForServiceWorkerActivation(reg.installing, entrypointUrl);
+ console.debug("New service worker available.");
+ return reg.update().then((reg) => {
+ console.debug("Service worker updated.");
+ let sw = reg.installing || reg.waiting || reg.active;
+ return this._waitForServiceWorkerActivation(sw, entrypointUrl);
+ });
} else {
// Existing service worker is still good.
- console.log("Loading app from service worker.");
+ console.debug("Loading app from service worker.");
return this._loadEntrypoint(entrypointUrl);
}
});