Skip to content

Commit 75df59e

Browse files
authored
Fix Status Bar Icon Brightness (#162297)
On Android devices, the statusBar is now by default transparent starting android API 35 [here](https://developer.android.com/develop/ui/views/layout/edge-to-edge#:~:text=system%20bars%20transparent) (due to automatic opt-in to edge to edge mode [here](https://developer.android.com/develop/ui/views/layout/edge-to-edge#:~:text=Important%3A%20Edge%2Dto%2Dedge%20is%20enforced%20on%20Android%2015%20(API%20level%2035)%20and%20higher%20once%20your%20app%20targets%20SDK%2035.%20If%20your%20app%20is%20not%20already%20edge%2Dto%2Dedge%2C%20portions%20of%20your%20app%20may%20be%20obscured%20and%20you%20must%20handle%20insets.%20Depending%20on%20the%20app%2C%20this%20work%20may%20or%20may%20not%20be%20significant.)), which is making the statusBar icons difficult to see. We decided to make the change in Cupertino/Material layer as opposed to the embedder layer becasue we cannot make assumptions on the defaults when we don't know what kind of Widgets the Flutter dev will be using. In MaterialApp, the `statusBarIconBrightness` is made to be dark because the system's brightness (dark mode or light mode) **does not affect** the brightness of the default app. This means when I turn on dark mode on my phone, the app does not use a dark theme—it uses the same light theme. In CupertinoApp, the `statusBarIconBrightness` is made to be opposite of the system's brightness (dark mode or light mode) because the system **does affect** the brightness of the default app. The system and the default app have the same brightness. This means when I turn on dark mode on my phone, the app also uses a dark theme. Fixes #160305 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 41c3008 commit 75df59e

File tree

5 files changed

+125
-0
lines changed

5 files changed

+125
-0
lines changed

packages/flutter/lib/src/cupertino/app.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
library;
1111

1212
import 'package:flutter/gestures.dart';
13+
import 'package:flutter/services.dart';
1314
import 'package:flutter/widgets.dart';
1415

1516
import 'button.dart';
@@ -652,6 +653,14 @@ class _CupertinoAppState extends State<CupertinoApp> {
652653
final CupertinoThemeData effectiveThemeData = (widget.theme ?? const CupertinoThemeData())
653654
.resolveFrom(context);
654655

656+
// Prefer theme brightness if set, otherwise check system brightness.
657+
final Brightness brightness =
658+
effectiveThemeData.brightness ?? MediaQuery.platformBrightnessOf(context);
659+
660+
SystemChrome.setSystemUIOverlayStyle(
661+
brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
662+
);
663+
655664
return ScrollConfiguration(
656665
behavior: widget.scrollBehavior ?? const CupertinoScrollBehavior(),
657666
child: CupertinoUserInterfaceLevel(

packages/flutter/lib/src/material/app.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,10 @@ class _MaterialAppState extends State<MaterialApp> {
10091009
theme = widget.highContrastTheme;
10101010
}
10111011
theme ??= widget.theme ?? ThemeData.light();
1012+
SystemChrome.setSystemUIOverlayStyle(
1013+
theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
1014+
);
1015+
10121016
return theme;
10131017
}
10141018

packages/flutter/lib/src/services/system_chrome.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export 'dart:ui' show Brightness, Color;
1818

1919
export 'binding.dart' show SystemUiChangeCallback;
2020

21+
// Examples can assume:
22+
// import 'dart:ui' as ui;
23+
// import 'package:flutter/services.dart';
24+
// import 'package:flutter/material.dart';
25+
// import 'package:flutter/widgets.dart';
26+
// late BuildContext context;
27+
2128
/// Specifies a particular device orientation.
2229
///
2330
/// To determine which values correspond to which orientations, first position
@@ -644,6 +651,20 @@ abstract final class SystemChrome {
644651
/// ** See code in examples/api/lib/services/system_chrome/system_chrome.set_system_u_i_overlay_style.1.dart **
645652
/// {@end-tool}
646653
///
654+
/// To imperatively set the style of the system overlays, use [SystemChrome.setSystemUIOverlayStyle].
655+
///
656+
/// {@tool snippet}
657+
/// The following example uses SystemChrome to set the status bar icon brightness based on system brightness.
658+
/// ```dart
659+
/// final Brightness brightness = MediaQuery.platformBrightnessOf(context);
660+
/// SystemChrome.setSystemUIOverlayStyle(
661+
/// SystemUiOverlayStyle(
662+
/// statusBarIconBrightness: brightness == Brightness.dark ? Brightness.light : Brightness.dark,
663+
/// ),
664+
/// );
665+
/// ```
666+
/// {@end-tool}
667+
///
647668
/// See also:
648669
///
649670
/// * [AppBar.systemOverlayStyle], a convenient property for declaratively setting

packages/flutter/test/cupertino/app_test.dart

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,72 @@ void main() {
481481
},
482482
);
483483

484+
testWidgets('CupertinoApp uses the dark SystemUIOverlayStyle when the background is light', (
485+
WidgetTester tester,
486+
) async {
487+
await tester.pumpWidget(
488+
const CupertinoApp(
489+
theme: CupertinoThemeData(brightness: Brightness.light),
490+
home: CupertinoPageScaffold(child: Text('Hello')),
491+
),
492+
);
493+
494+
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.dark);
495+
});
496+
497+
testWidgets('CupertinoApp uses the light SystemUIOverlayStyle when the background is dark', (
498+
WidgetTester tester,
499+
) async {
500+
await tester.pumpWidget(
501+
const CupertinoApp(
502+
theme: CupertinoThemeData(brightness: Brightness.dark),
503+
home: CupertinoPageScaffold(child: Text('Hello')),
504+
),
505+
);
506+
507+
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.light);
508+
});
509+
510+
testWidgets(
511+
'CupertinoApp uses the dark SystemUIOverlayStyle when theme brightness is null and the system is in light mode',
512+
(WidgetTester tester) async {
513+
// The theme brightness is null by default.
514+
// The system is in light mode by default.
515+
await tester.pumpWidget(
516+
MediaQuery(
517+
data: const MediaQueryData(),
518+
child: CupertinoApp(
519+
builder: (BuildContext context, Widget? child) {
520+
return const Placeholder();
521+
},
522+
),
523+
),
524+
);
525+
526+
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.dark);
527+
},
528+
);
529+
530+
testWidgets(
531+
'CupertinoApp uses the light SystemUIOverlayStyle when theme brightness is null and the system is in dark mode',
532+
(WidgetTester tester) async {
533+
// The theme brightness is null by default.
534+
// Simulates setting the system to dark mode.
535+
await tester.pumpWidget(
536+
MediaQuery(
537+
data: const MediaQueryData(platformBrightness: Brightness.dark),
538+
child: CupertinoApp(
539+
builder: (BuildContext context, Widget? child) {
540+
return const Placeholder();
541+
},
542+
),
543+
),
544+
);
545+
546+
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.light);
547+
},
548+
);
549+
484550
testWidgets('Text color is correctly resolved when CupertinoThemeData.brightness is null', (
485551
WidgetTester tester,
486552
) async {

packages/flutter/test/material/material_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ library;
99

1010
import 'package:flutter/material.dart';
1111
import 'package:flutter/rendering.dart';
12+
import 'package:flutter/services.dart';
1213
import 'package:flutter_test/flutter_test.dart';
1314
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
1415
import '../widgets/multi_view_testing.dart';
@@ -284,6 +285,30 @@ void main() {
284285
expect(pressed, isTrue);
285286
});
286287

288+
testWidgets('Material uses the dark SystemUIOverlayStyle when the background is light', (
289+
WidgetTester tester,
290+
) async {
291+
final ThemeData lightTheme = ThemeData();
292+
await tester.pumpWidget(
293+
MaterialApp(theme: lightTheme, home: const Scaffold(body: Center(child: Text('test')))),
294+
);
295+
296+
expect(lightTheme.colorScheme.brightness, Brightness.light);
297+
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.dark);
298+
});
299+
300+
testWidgets('Material uses the light SystemUIOverlayStyle when the background is dark', (
301+
WidgetTester tester,
302+
) async {
303+
final ThemeData darkTheme = ThemeData.dark();
304+
await tester.pumpWidget(
305+
MaterialApp(theme: darkTheme, home: const Scaffold(body: Center(child: Text('test')))),
306+
);
307+
308+
expect(darkTheme.colorScheme.brightness, Brightness.dark);
309+
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.light);
310+
});
311+
287312
group('Surface Tint Overlay', () {
288313
testWidgets(
289314
'applyElevationOverlayColor does not effect anything with useMaterial3 set to true',

0 commit comments

Comments
 (0)