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

[web] change status bar color based on SystemUiOverlayStyle #40599

Merged
merged 12 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -514,14 +514,20 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
return;
case 'SystemChrome.setApplicationSwitcherDescription':
final Map<String, dynamic> arguments = decoded.arguments as Map<String, dynamic>;
// TODO(ferhat): Find more appropriate defaults? Or noop when values are null?
final Map<String, Object?> arguments = decoded.arguments as Map<String, Object?>;
final String label = arguments['label'] as String? ?? '';
// TODO(web): Stop setting the color from here, https://github.com/flutter/flutter/issues/123365
final int primaryColor = arguments['primaryColor'] as int? ?? 0xFF000000;
domDocument.title = label;
setThemeColor(ui.Color(primaryColor));
replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
return;
case 'SystemChrome.setSystemUIOverlayStyle':
final Map<String, Object?> arguments = decoded.arguments as Map<String, Object?>;
final int? statusBarColor = arguments['statusBarColor'] as int?;
setThemeColor(statusBarColor == null ? null : ui.Color(statusBarColor));
replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true));
return;
case 'SystemChrome.setPreferredOrientations':
final List<dynamic> arguments = decoded.arguments as List<dynamic>;
flutterViewEmbedder.setPreferredOrientation(arguments).then((bool success) {
Expand Down
19 changes: 12 additions & 7 deletions lib/web_ui/lib/src/engine/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -679,16 +679,21 @@ void setClipPath(DomElement element, String? value) {
}
}

void setThemeColor(ui.Color color) {
void setThemeColor(ui.Color? color) {
DomHTMLMetaElement? theme =
domDocument.querySelector('#flutterweb-theme') as DomHTMLMetaElement?;
if (theme == null) {
theme = createDomHTMLMetaElement()
..id = 'flutterweb-theme'
..name = 'theme-color';
domDocument.head!.append(theme);

if (color != null) {
if (theme == null) {
theme = createDomHTMLMetaElement()
..id = 'flutterweb-theme'
..name = 'theme-color';
domDocument.head!.append(theme);
}
theme.content = colorToCssString(color)!;
} else {
theme?.remove();
}
theme.content = colorToCssString(color)!;
}

bool? _ellipseFeatureDetected;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2013 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:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;

void main() {
internalBootstrapBrowserTest(() => testMain);
}

Future<void> testMain() async {
ensureFlutterViewEmbedderInitialized();

String? getCssThemeColor() {
final DomHTMLMetaElement? theme =
domDocument.querySelector('#flutterweb-theme') as DomHTMLMetaElement?;
return theme?.content;
}

const MethodCodec codec = JSONMethodCodec();

group('Title and Primary Color/Theme meta', () {
test('is set on the document by platform message', () {
// Run the unit test without emulating Flutter tester environment.
ui.debugEmulateFlutterTesterEnvironment = false;

// TODO(yjbanov): https://github.com/flutter/flutter/issues/39159
domDocument.title = '';
expect(domDocument.title, '');
expect(getCssThemeColor(), isNull);

ui.window.sendPlatformMessage(
'flutter/platform',
codec.encodeMethodCall(const MethodCall(
'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{
'label': 'Title Test',
'primaryColor': 0xFF00FF00,
},
)),
null,
);

const ui.Color expectedPrimaryColor = ui.Color(0xFF00FF00);

expect(domDocument.title, 'Title Test');
expect(getCssThemeColor(), colorToCssString(expectedPrimaryColor));

ui.window.sendPlatformMessage(
'flutter/platform',
codec.encodeMethodCall(const MethodCall(
'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{
'label': 'Different title',
'primaryColor': 0xFFFABADA,
},
)),
null,
);

const ui.Color expectedNewPrimaryColor = ui.Color(0xFFFABADA);

expect(domDocument.title, 'Different title');
expect(getCssThemeColor(), colorToCssString(expectedNewPrimaryColor));
});

test('supports null title and primaryColor', () {
// Run the unit test without emulating Flutter tester environment.
ui.debugEmulateFlutterTesterEnvironment = false;

const ui.Color expectedNullColor = ui.Color(0xFF000000);
// TODO(yjbanov): https://github.com/flutter/flutter/issues/39159
domDocument.title = 'Something Else';
expect(domDocument.title, 'Something Else');

ui.window.sendPlatformMessage(
'flutter/platform',
codec.encodeMethodCall(const MethodCall(
'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{
'label': null,
'primaryColor': null,
},
)),
null,
);

expect(domDocument.title, '');
expect(getCssThemeColor(), colorToCssString(expectedNullColor));

domDocument.title = 'Something Else';
expect(domDocument.title, 'Something Else');

ui.window.sendPlatformMessage(
'flutter/platform',
codec.encodeMethodCall(const MethodCall(
'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{},
)),
null,
);

expect(domDocument.title, '');
expect(getCssThemeColor(), colorToCssString(expectedNullColor));
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,27 @@ void testMain() {
);
});

test('responds to flutter/platform SystemChrome.setSystemUIOverlayStyle',
() async {
const MethodCodec codec = JSONMethodCodec();
final Completer<ByteData?> completer = Completer<ByteData?>();
ui.PlatformDispatcher.instance.sendPlatformMessage(
'flutter/platform',
codec.encodeMethodCall(const MethodCall(
'SystemChrome.setSystemUIOverlayStyle',
<String, dynamic>{},
)),
completer.complete,
);

final ByteData? response = await completer.future;
expect(response, isNotNull);
expect(
codec.decodeEnvelope(response!),
true,
);
});

test('responds to flutter/contextmenu enable', () async {
const MethodCodec codec = JSONMethodCodec();
final Completer<ByteData?> completer = Completer<ByteData?>();
Expand Down Expand Up @@ -144,7 +165,8 @@ void testMain() {
() async {
final DomElement root = domDocument.documentElement!;
final String oldFontSize = root.style.fontSize;
final ui.VoidCallback? oldCallback = ui.PlatformDispatcher.instance.onTextScaleFactorChanged;
final ui.VoidCallback? oldCallback =
ui.PlatformDispatcher.instance.onTextScaleFactorChanged;

addTearDown(() {
root.style.fontSize = oldFontSize;
Expand All @@ -162,15 +184,17 @@ void testMain() {
await Future<void>.delayed(Duration.zero);
expect(root.style.fontSize, '20px');
expect(isCalled, isTrue);
expect(ui.PlatformDispatcher.instance.textScaleFactor, findBrowserTextScaleFactor());
expect(ui.PlatformDispatcher.instance.textScaleFactor,
findBrowserTextScaleFactor());

isCalled = false;

root.style.fontSize = '16px';
await Future<void>.delayed(Duration.zero);
expect(root.style.fontSize, '16px');
expect(isCalled, isTrue);
expect(ui.PlatformDispatcher.instance.textScaleFactor, findBrowserTextScaleFactor());
expect(ui.PlatformDispatcher.instance.textScaleFactor,
findBrowserTextScaleFactor());
});
});
}
Expand All @@ -183,7 +207,6 @@ class MockHighContrastSupport implements HighContrastSupport {
@override
bool get isHighContrastEnabled => isEnabled;


void invokeListeners(bool val) {
for (final HighContrastListener listener in _listeners) {
listener(val);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2013 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:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;

void main() {
internalBootstrapBrowserTest(() => testMain);
}

void testMain() {
ensureFlutterViewEmbedderInitialized();

const MethodCodec codec = JSONMethodCodec();

void sendSetSystemUIOverlayStyle({ui.Color? statusBarColor}) {
ui.window.sendPlatformMessage(
'flutter/platform',
codec.encodeMethodCall(MethodCall(
'SystemChrome.setSystemUIOverlayStyle',
<String, dynamic>{
'statusBarColor': statusBarColor?.value,
},
)),
null,
);
}

String? getCssThemeColor() {
final DomHTMLMetaElement? theme =
domDocument.querySelector('#flutterweb-theme') as DomHTMLMetaElement?;
return theme?.content;
}

group('SystemUIOverlayStyle', () {
test('theme color is set / removed by platform message', () {
// Run the unit test without emulating Flutter tester environment.
ui.debugEmulateFlutterTesterEnvironment = false;

expect(getCssThemeColor(), null);

const ui.Color statusBarColor = ui.Color(0xFFF44336);
sendSetSystemUIOverlayStyle(statusBarColor: statusBarColor);
expect(getCssThemeColor(), colorToCssString(statusBarColor));

sendSetSystemUIOverlayStyle();
expect(getCssThemeColor(), null);
});
});
}
89 changes: 0 additions & 89 deletions lib/web_ui/test/ui/title_test.dart

This file was deleted.