Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1a25b7a

Browse files
authored
[web] change status bar color based on SystemUiOverlayStyle (#40599)
Closes flutter/flutter#123365 In my [example code](flutter/flutter#123365 (comment)) I'm using `SystemUiOverlayStyle.dark` which has `null` `statusBarColor` by default (_which can be changed via `Change status bar color` button_) in this case we do not override browser's default status bar color. | Old behaviour | New behaviour | | -- | -- | | <video src="https://user-images.githubusercontent.com/8436039/227386349-e30cce47-ffc6-4465-bb30-cd2888f77d32.mp4" /> | <video src="https://user-images.githubusercontent.com/8436039/227580430-a25e57dc-9d5a-43a1-bcb3-3bd8abd753d8.mp4" /> | In case of PWA the when `statusBarColor` is `null` it will use `theme_color` property from `manifest.json` (I don't know from where does flutter generate `manifest.json`). The default status bar color for PWA is: `"theme_color": "#0175C2",` https://github.com/flutter/flutter/blob/f4caee6efbc0b0094f3cee9e31a7486e3d030819/examples/api/web/manifest.json#L7 | Old PWA behaviour | New PWA behaviour | | -- | -- | | <video src="https://user-images.githubusercontent.com/8436039/227607373-79be7294-d9f7-4a45-9014-28720acce0c7.mp4" /> | <video src="https://user-images.githubusercontent.com/8436039/227607329-be16131f-4104-419a-8e16-2b229d73d95e.mp4" /> | [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 023d442 commit 1a25b7a

File tree

6 files changed

+210
-102
lines changed

6 files changed

+210
-102
lines changed

lib/web_ui/lib/src/engine/platform_dispatcher.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,14 +514,20 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
514514
replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
515515
return;
516516
case 'SystemChrome.setApplicationSwitcherDescription':
517-
final Map<String, dynamic> arguments = decoded.arguments as Map<String, dynamic>;
518-
// TODO(ferhat): Find more appropriate defaults? Or noop when values are null?
517+
final Map<String, Object?> arguments = decoded.arguments as Map<String, Object?>;
519518
final String label = arguments['label'] as String? ?? '';
519+
// TODO(web): Stop setting the color from here, https://github.com/flutter/flutter/issues/123365
520520
final int primaryColor = arguments['primaryColor'] as int? ?? 0xFF000000;
521521
domDocument.title = label;
522522
setThemeColor(ui.Color(primaryColor));
523523
replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
524524
return;
525+
case 'SystemChrome.setSystemUIOverlayStyle':
526+
final Map<String, Object?> arguments = decoded.arguments as Map<String, Object?>;
527+
final int? statusBarColor = arguments['statusBarColor'] as int?;
528+
setThemeColor(statusBarColor == null ? null : ui.Color(statusBarColor));
529+
replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
530+
return;
525531
case 'SystemChrome.setPreferredOrientations':
526532
final List<dynamic> arguments = decoded.arguments as List<dynamic>;
527533
flutterViewEmbedder.setPreferredOrientation(arguments).then((bool success) {

lib/web_ui/lib/src/engine/util.dart

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -679,16 +679,21 @@ void setClipPath(DomElement element, String? value) {
679679
}
680680
}
681681

682-
void setThemeColor(ui.Color color) {
682+
void setThemeColor(ui.Color? color) {
683683
DomHTMLMetaElement? theme =
684684
domDocument.querySelector('#flutterweb-theme') as DomHTMLMetaElement?;
685-
if (theme == null) {
686-
theme = createDomHTMLMetaElement()
687-
..id = 'flutterweb-theme'
688-
..name = 'theme-color';
689-
domDocument.head!.append(theme);
685+
686+
if (color != null) {
687+
if (theme == null) {
688+
theme = createDomHTMLMetaElement()
689+
..id = 'flutterweb-theme'
690+
..name = 'theme-color';
691+
domDocument.head!.append(theme);
692+
}
693+
theme.content = colorToCssString(color)!;
694+
} else {
695+
theme?.remove();
690696
}
691-
theme.content = colorToCssString(color)!;
692697
}
693698

694699
bool? _ellipseFeatureDetected;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:test/bootstrap/browser.dart';
6+
import 'package:test/test.dart';
7+
import 'package:ui/src/engine.dart';
8+
import 'package:ui/ui.dart' as ui;
9+
10+
void main() {
11+
internalBootstrapBrowserTest(() => testMain);
12+
}
13+
14+
Future<void> testMain() async {
15+
ensureFlutterViewEmbedderInitialized();
16+
17+
String? getCssThemeColor() {
18+
final DomHTMLMetaElement? theme =
19+
domDocument.querySelector('#flutterweb-theme') as DomHTMLMetaElement?;
20+
return theme?.content;
21+
}
22+
23+
const MethodCodec codec = JSONMethodCodec();
24+
25+
group('Title and Primary Color/Theme meta', () {
26+
test('is set on the document by platform message', () {
27+
// Run the unit test without emulating Flutter tester environment.
28+
ui.debugEmulateFlutterTesterEnvironment = false;
29+
30+
// TODO(yjbanov): https://github.com/flutter/flutter/issues/39159
31+
domDocument.title = '';
32+
expect(domDocument.title, '');
33+
expect(getCssThemeColor(), isNull);
34+
35+
ui.window.sendPlatformMessage(
36+
'flutter/platform',
37+
codec.encodeMethodCall(const MethodCall(
38+
'SystemChrome.setApplicationSwitcherDescription',
39+
<String, dynamic>{
40+
'label': 'Title Test',
41+
'primaryColor': 0xFF00FF00,
42+
},
43+
)),
44+
null,
45+
);
46+
47+
const ui.Color expectedPrimaryColor = ui.Color(0xFF00FF00);
48+
49+
expect(domDocument.title, 'Title Test');
50+
expect(getCssThemeColor(), colorToCssString(expectedPrimaryColor));
51+
52+
ui.window.sendPlatformMessage(
53+
'flutter/platform',
54+
codec.encodeMethodCall(const MethodCall(
55+
'SystemChrome.setApplicationSwitcherDescription',
56+
<String, dynamic>{
57+
'label': 'Different title',
58+
'primaryColor': 0xFFFABADA,
59+
},
60+
)),
61+
null,
62+
);
63+
64+
const ui.Color expectedNewPrimaryColor = ui.Color(0xFFFABADA);
65+
66+
expect(domDocument.title, 'Different title');
67+
expect(getCssThemeColor(), colorToCssString(expectedNewPrimaryColor));
68+
});
69+
70+
test('supports null title and primaryColor', () {
71+
// Run the unit test without emulating Flutter tester environment.
72+
ui.debugEmulateFlutterTesterEnvironment = false;
73+
74+
const ui.Color expectedNullColor = ui.Color(0xFF000000);
75+
// TODO(yjbanov): https://github.com/flutter/flutter/issues/39159
76+
domDocument.title = 'Something Else';
77+
expect(domDocument.title, 'Something Else');
78+
79+
ui.window.sendPlatformMessage(
80+
'flutter/platform',
81+
codec.encodeMethodCall(const MethodCall(
82+
'SystemChrome.setApplicationSwitcherDescription',
83+
<String, dynamic>{
84+
'label': null,
85+
'primaryColor': null,
86+
},
87+
)),
88+
null,
89+
);
90+
91+
expect(domDocument.title, '');
92+
expect(getCssThemeColor(), colorToCssString(expectedNullColor));
93+
94+
domDocument.title = 'Something Else';
95+
expect(domDocument.title, 'Something Else');
96+
97+
ui.window.sendPlatformMessage(
98+
'flutter/platform',
99+
codec.encodeMethodCall(const MethodCall(
100+
'SystemChrome.setApplicationSwitcherDescription',
101+
<String, dynamic>{},
102+
)),
103+
null,
104+
);
105+
106+
expect(domDocument.title, '');
107+
expect(getCssThemeColor(), colorToCssString(expectedNullColor));
108+
});
109+
});
110+
}

lib/web_ui/test/engine/platform_dispatcher_test.dart renamed to lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,27 @@ void testMain() {
7272
);
7373
});
7474

75+
test('responds to flutter/platform SystemChrome.setSystemUIOverlayStyle',
76+
() async {
77+
const MethodCodec codec = JSONMethodCodec();
78+
final Completer<ByteData?> completer = Completer<ByteData?>();
79+
ui.PlatformDispatcher.instance.sendPlatformMessage(
80+
'flutter/platform',
81+
codec.encodeMethodCall(const MethodCall(
82+
'SystemChrome.setSystemUIOverlayStyle',
83+
<String, dynamic>{},
84+
)),
85+
completer.complete,
86+
);
87+
88+
final ByteData? response = await completer.future;
89+
expect(response, isNotNull);
90+
expect(
91+
codec.decodeEnvelope(response!),
92+
true,
93+
);
94+
});
95+
7596
test('responds to flutter/contextmenu enable', () async {
7697
const MethodCodec codec = JSONMethodCodec();
7798
final Completer<ByteData?> completer = Completer<ByteData?>();
@@ -144,7 +165,8 @@ void testMain() {
144165
() async {
145166
final DomElement root = domDocument.documentElement!;
146167
final String oldFontSize = root.style.fontSize;
147-
final ui.VoidCallback? oldCallback = ui.PlatformDispatcher.instance.onTextScaleFactorChanged;
168+
final ui.VoidCallback? oldCallback =
169+
ui.PlatformDispatcher.instance.onTextScaleFactorChanged;
148170

149171
addTearDown(() {
150172
root.style.fontSize = oldFontSize;
@@ -162,15 +184,17 @@ void testMain() {
162184
await Future<void>.delayed(Duration.zero);
163185
expect(root.style.fontSize, '20px');
164186
expect(isCalled, isTrue);
165-
expect(ui.PlatformDispatcher.instance.textScaleFactor, findBrowserTextScaleFactor());
187+
expect(ui.PlatformDispatcher.instance.textScaleFactor,
188+
findBrowserTextScaleFactor());
166189

167190
isCalled = false;
168191

169192
root.style.fontSize = '16px';
170193
await Future<void>.delayed(Duration.zero);
171194
expect(root.style.fontSize, '16px');
172195
expect(isCalled, isTrue);
173-
expect(ui.PlatformDispatcher.instance.textScaleFactor, findBrowserTextScaleFactor());
196+
expect(ui.PlatformDispatcher.instance.textScaleFactor,
197+
findBrowserTextScaleFactor());
174198
});
175199
});
176200
}
@@ -183,7 +207,6 @@ class MockHighContrastSupport implements HighContrastSupport {
183207
@override
184208
bool get isHighContrastEnabled => isEnabled;
185209

186-
187210
void invokeListeners(bool val) {
188211
for (final HighContrastListener listener in _listeners) {
189212
listener(val);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:test/bootstrap/browser.dart';
6+
import 'package:test/test.dart';
7+
import 'package:ui/src/engine.dart';
8+
import 'package:ui/ui.dart' as ui;
9+
10+
void main() {
11+
internalBootstrapBrowserTest(() => testMain);
12+
}
13+
14+
void testMain() {
15+
ensureFlutterViewEmbedderInitialized();
16+
17+
const MethodCodec codec = JSONMethodCodec();
18+
19+
void sendSetSystemUIOverlayStyle({ui.Color? statusBarColor}) {
20+
ui.window.sendPlatformMessage(
21+
'flutter/platform',
22+
codec.encodeMethodCall(MethodCall(
23+
'SystemChrome.setSystemUIOverlayStyle',
24+
<String, dynamic>{
25+
'statusBarColor': statusBarColor?.value,
26+
},
27+
)),
28+
null,
29+
);
30+
}
31+
32+
String? getCssThemeColor() {
33+
final DomHTMLMetaElement? theme =
34+
domDocument.querySelector('#flutterweb-theme') as DomHTMLMetaElement?;
35+
return theme?.content;
36+
}
37+
38+
group('SystemUIOverlayStyle', () {
39+
test('theme color is set / removed by platform message', () {
40+
// Run the unit test without emulating Flutter tester environment.
41+
ui.debugEmulateFlutterTesterEnvironment = false;
42+
43+
expect(getCssThemeColor(), null);
44+
45+
const ui.Color statusBarColor = ui.Color(0xFFF44336);
46+
sendSetSystemUIOverlayStyle(statusBarColor: statusBarColor);
47+
expect(getCssThemeColor(), colorToCssString(statusBarColor));
48+
49+
sendSetSystemUIOverlayStyle();
50+
expect(getCssThemeColor(), null);
51+
});
52+
});
53+
}

lib/web_ui/test/ui/title_test.dart

Lines changed: 0 additions & 89 deletions
This file was deleted.

0 commit comments

Comments
 (0)