diff --git a/packages/flutter/lib/src/cupertino/app.dart b/packages/flutter/lib/src/cupertino/app.dart index b663160d2f..0b7ce65d21 100644 --- a/packages/flutter/lib/src/cupertino/app.dart +++ b/packages/flutter/lib/src/cupertino/app.dart @@ -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 { 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( diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index 80faf6e2ec..9e3cc9d054 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -1009,6 +1009,10 @@ class _MaterialAppState extends State { theme = widget.highContrastTheme; } theme ??= widget.theme ?? ThemeData.light(); + SystemChrome.setSystemUIOverlayStyle( + theme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, + ); + return theme; } diff --git a/packages/flutter/lib/src/services/system_chrome.dart b/packages/flutter/lib/src/services/system_chrome.dart index 128e27e7fb..bc3c4050fa 100644 --- a/packages/flutter/lib/src/services/system_chrome.dart +++ b/packages/flutter/lib/src/services/system_chrome.dart @@ -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 diff --git a/packages/flutter/test/cupertino/app_test.dart b/packages/flutter/test/cupertino/app_test.dart index 6e99392591..b308e77a57 100644 --- a/packages/flutter/test/cupertino/app_test.dart +++ b/packages/flutter/test/cupertino/app_test.dart @@ -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 { diff --git a/packages/flutter/test/material/material_test.dart b/packages/flutter/test/material/material_test.dart index 0dcc91d7c5..9c9dfa3ebf 100644 --- a/packages/flutter/test/material/material_test.dart +++ b/packages/flutter/test/material/material_test.dart @@ -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',