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
This commit is contained in:
jesswrd 2025-02-07 12:37:56 -08:00 committed by GitHub
parent 41c3008afb
commit 75df59e62f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 125 additions and 0 deletions

View File

@ -10,6 +10,7 @@
library;
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'button.dart';
@ -652,6 +653,14 @@ class _CupertinoAppState extends State<CupertinoApp> {
final CupertinoThemeData effectiveThemeData = (widget.theme ?? const CupertinoThemeData())
.resolveFrom(context);
// Prefer theme brightness if set, otherwise check system brightness.
final Brightness brightness =
effectiveThemeData.brightness ?? MediaQuery.platformBrightnessOf(context);
SystemChrome.setSystemUIOverlayStyle(
brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
);
return ScrollConfiguration(
behavior: widget.scrollBehavior ?? const CupertinoScrollBehavior(),
child: CupertinoUserInterfaceLevel(

View File

@ -1009,6 +1009,10 @@ class _MaterialAppState extends State<MaterialApp> {
theme = widget.highContrastTheme;
}
theme ??= widget.theme ?? ThemeData.light();
SystemChrome.setSystemUIOverlayStyle(
theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark,
);
return theme;
}

View File

@ -18,6 +18,13 @@ export 'dart:ui' show Brightness, Color;
export 'binding.dart' show SystemUiChangeCallback;
// Examples can assume:
// import 'dart:ui' as ui;
// import 'package:flutter/services.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter/widgets.dart';
// late BuildContext context;
/// Specifies a particular device orientation.
///
/// To determine which values correspond to which orientations, first position
@ -644,6 +651,20 @@ abstract final class SystemChrome {
/// ** See code in examples/api/lib/services/system_chrome/system_chrome.set_system_u_i_overlay_style.1.dart **
/// {@end-tool}
///
/// To imperatively set the style of the system overlays, use [SystemChrome.setSystemUIOverlayStyle].
///
/// {@tool snippet}
/// The following example uses SystemChrome to set the status bar icon brightness based on system brightness.
/// ```dart
/// final Brightness brightness = MediaQuery.platformBrightnessOf(context);
/// SystemChrome.setSystemUIOverlayStyle(
/// SystemUiOverlayStyle(
/// statusBarIconBrightness: brightness == Brightness.dark ? Brightness.light : Brightness.dark,
/// ),
/// );
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [AppBar.systemOverlayStyle], a convenient property for declaratively setting

View File

@ -481,6 +481,72 @@ void main() {
},
);
testWidgets('CupertinoApp uses the dark SystemUIOverlayStyle when the background is light', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.light),
home: CupertinoPageScaffold(child: Text('Hello')),
),
);
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.dark);
});
testWidgets('CupertinoApp uses the light SystemUIOverlayStyle when the background is dark', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoPageScaffold(child: Text('Hello')),
),
);
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.light);
});
testWidgets(
'CupertinoApp uses the dark SystemUIOverlayStyle when theme brightness is null and the system is in light mode',
(WidgetTester tester) async {
// The theme brightness is null by default.
// The system is in light mode by default.
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: CupertinoApp(
builder: (BuildContext context, Widget? child) {
return const Placeholder();
},
),
),
);
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.dark);
},
);
testWidgets(
'CupertinoApp uses the light SystemUIOverlayStyle when theme brightness is null and the system is in dark mode',
(WidgetTester tester) async {
// The theme brightness is null by default.
// Simulates setting the system to dark mode.
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(platformBrightness: Brightness.dark),
child: CupertinoApp(
builder: (BuildContext context, Widget? child) {
return const Placeholder();
},
),
),
);
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.light);
},
);
testWidgets('Text color is correctly resolved when CupertinoThemeData.brightness is null', (
WidgetTester tester,
) async {

View File

@ -9,6 +9,7 @@ library;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/multi_view_testing.dart';
@ -284,6 +285,30 @@ void main() {
expect(pressed, isTrue);
});
testWidgets('Material uses the dark SystemUIOverlayStyle when the background is light', (
WidgetTester tester,
) async {
final ThemeData lightTheme = ThemeData();
await tester.pumpWidget(
MaterialApp(theme: lightTheme, home: const Scaffold(body: Center(child: Text('test')))),
);
expect(lightTheme.colorScheme.brightness, Brightness.light);
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.dark);
});
testWidgets('Material uses the light SystemUIOverlayStyle when the background is dark', (
WidgetTester tester,
) async {
final ThemeData darkTheme = ThemeData.dark();
await tester.pumpWidget(
MaterialApp(theme: darkTheme, home: const Scaffold(body: Center(child: Text('test')))),
);
expect(darkTheme.colorScheme.brightness, Brightness.dark);
expect(SystemChrome.latestStyle, SystemUiOverlayStyle.light);
});
group('Surface Tint Overlay', () {
testWidgets(
'applyElevationOverlayColor does not effect anything with useMaterial3 set to true',