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

Commit f1cdfa2

Browse files
authored
Use AppBar.systemOverlayStyle to style system navigation bar (#104827)
* Use upper AnnotatedRegion properties when no bottom one found (and vice versa) * Update comments Co-authored-by: Bruno Leroux <[email protected]>
1 parent 5628ebf commit f1cdfa2

File tree

5 files changed

+172
-12
lines changed

5 files changed

+172
-12
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,13 @@ class _AppBarState extends State<AppBar> {
879879
final SystemUiOverlayStyle style = brightness == Brightness.dark
880880
? SystemUiOverlayStyle.light
881881
: SystemUiOverlayStyle.dark;
882-
return style.copyWith(statusBarColor: backgroundColor);
882+
// For backward compatibility, create an overlay style without system navigation bar settings.
883+
return SystemUiOverlayStyle(
884+
statusBarColor: backgroundColor,
885+
statusBarBrightness: style.statusBarBrightness,
886+
statusBarIconBrightness: style.statusBarIconBrightness,
887+
systemStatusBarContrastEnforced: style.systemStatusBarContrastEnforced,
888+
);
883889
}
884890

885891
@override

packages/flutter/lib/src/rendering/view.dart

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
112112
/// and the hit-test result from the bottom of the screen provides the system
113113
/// nav bar settings.
114114
///
115+
/// If there is no [AnnotatedRegionLayer] on the bottom, the hit-test result
116+
/// from the top provides the system nav bar settings. If there is no
117+
/// [AnnotatedRegionLayer] on the top, the hit-test result from the bottom
118+
/// provides the system status bar settings.
119+
///
115120
/// Setting this to false does not cause previous automatic adjustments to be
116121
/// reset, nor does setting it to true cause the app to update immediately.
117122
///
@@ -312,20 +317,47 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
312317
case TargetPlatform.windows:
313318
break;
314319
}
315-
// If there are no overlay styles in the UI don't bother updating.
316-
if (upperOverlayStyle != null || lowerOverlayStyle != null) {
320+
// If there are no overlay style in the UI don't bother updating.
321+
if (upperOverlayStyle == null && lowerOverlayStyle == null) {
322+
return;
323+
}
324+
325+
// If both are not null, the upper provides the status bar properties and the lower provides
326+
// the system navigation bar properties. This is done for advanced use cases where a widget
327+
// on the top (for instance an app bar) will create an annotated region to set the status bar
328+
// style and another widget on the bottom will create an annotated region to set the system
329+
// navigation bar style.
330+
if (upperOverlayStyle != null && lowerOverlayStyle != null) {
317331
final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle(
318-
statusBarBrightness: upperOverlayStyle?.statusBarBrightness,
319-
statusBarIconBrightness: upperOverlayStyle?.statusBarIconBrightness,
320-
statusBarColor: upperOverlayStyle?.statusBarColor,
321-
systemStatusBarContrastEnforced: upperOverlayStyle?.systemStatusBarContrastEnforced,
322-
systemNavigationBarColor: lowerOverlayStyle?.systemNavigationBarColor,
323-
systemNavigationBarDividerColor: lowerOverlayStyle?.systemNavigationBarDividerColor,
324-
systemNavigationBarIconBrightness: lowerOverlayStyle?.systemNavigationBarIconBrightness,
325-
systemNavigationBarContrastEnforced: lowerOverlayStyle?.systemNavigationBarContrastEnforced,
332+
statusBarBrightness: upperOverlayStyle.statusBarBrightness,
333+
statusBarIconBrightness: upperOverlayStyle.statusBarIconBrightness,
334+
statusBarColor: upperOverlayStyle.statusBarColor,
335+
systemStatusBarContrastEnforced: upperOverlayStyle.systemStatusBarContrastEnforced,
336+
systemNavigationBarColor: lowerOverlayStyle.systemNavigationBarColor,
337+
systemNavigationBarDividerColor: lowerOverlayStyle.systemNavigationBarDividerColor,
338+
systemNavigationBarIconBrightness: lowerOverlayStyle.systemNavigationBarIconBrightness,
339+
systemNavigationBarContrastEnforced: lowerOverlayStyle.systemNavigationBarContrastEnforced,
326340
);
327341
SystemChrome.setSystemUIOverlayStyle(overlayStyle);
342+
return;
328343
}
344+
// If only one of the upper or the lower overlay style is not null, it provides all properties.
345+
// This is done for developer convenience as it allows setting both status bar style and
346+
// navigation bar style using only one annotated region layer (for instance the one
347+
// automatically created by an [AppBar]).
348+
final bool isAndroid = defaultTargetPlatform == TargetPlatform.android;
349+
final SystemUiOverlayStyle definedOverlayStyle = (upperOverlayStyle ?? lowerOverlayStyle)!;
350+
final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle(
351+
statusBarBrightness: definedOverlayStyle.statusBarBrightness,
352+
statusBarIconBrightness: definedOverlayStyle.statusBarIconBrightness,
353+
statusBarColor: definedOverlayStyle.statusBarColor,
354+
systemStatusBarContrastEnforced: definedOverlayStyle.systemStatusBarContrastEnforced,
355+
systemNavigationBarColor: isAndroid ? definedOverlayStyle.systemNavigationBarColor : null,
356+
systemNavigationBarDividerColor: isAndroid ? definedOverlayStyle.systemNavigationBarDividerColor : null,
357+
systemNavigationBarIconBrightness: isAndroid ? definedOverlayStyle.systemNavigationBarIconBrightness : null,
358+
systemNavigationBarContrastEnforced: isAndroid ? definedOverlayStyle.systemNavigationBarContrastEnforced : null,
359+
);
360+
SystemChrome.setSystemUIOverlayStyle(overlayStyle);
329361
}
330362

331363
@override

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,13 @@ class SystemChrome {
558558
/// it can be hit-tested by the framework. On every frame, the framework will
559559
/// hit-test and select the annotated region it finds under the status and
560560
/// navigation bar and synthesize them into a single style. This can be used
561-
/// to configure the system styles when an app bar is not used.
561+
/// to configure the system styles when an app bar is not used. When an app
562+
/// bar is used, apps should not enclose the app bar in an annotated region
563+
/// because one is automatically created. If an app bar is used and the app
564+
/// bar is enclosed in an annotated region, the app bar overlay style supercedes
565+
/// the status bar properties defined in the enclosing annotated region overlay
566+
/// style and the enclosing annotated region overlay style supercedes the app bar
567+
/// overlay style navigation bar properties.
562568
///
563569
/// {@tool sample}
564570
/// The following example creates a widget that changes the status bar color

packages/flutter/test/material/app_bar_test.dart

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2421,6 +2421,56 @@ void main() {
24212421
}
24222422
});
24232423

2424+
testWidgets('Default status bar color', (WidgetTester tester) async {
2425+
Future<void> pumpBoilerplate({required bool useMaterial3, required bool backwardsCompatibility}) async {
2426+
await tester.pumpWidget(
2427+
MaterialApp(
2428+
key: GlobalKey(),
2429+
theme: ThemeData.light().copyWith(
2430+
useMaterial3: useMaterial3,
2431+
appBarTheme: AppBarTheme(
2432+
backwardsCompatibility: backwardsCompatibility,
2433+
),
2434+
),
2435+
home: Scaffold(
2436+
appBar: AppBar(
2437+
title: const Text('title'),
2438+
),
2439+
),
2440+
),
2441+
);
2442+
}
2443+
2444+
await pumpBoilerplate(useMaterial3: false, backwardsCompatibility: false);
2445+
expect(SystemChrome.latestStyle!.statusBarColor, null);
2446+
await pumpBoilerplate(useMaterial3: false, backwardsCompatibility: true);
2447+
expect(SystemChrome.latestStyle!.statusBarColor, null);
2448+
await pumpBoilerplate(useMaterial3: true, backwardsCompatibility: false);
2449+
expect(SystemChrome.latestStyle!.statusBarColor, Colors.transparent);
2450+
await pumpBoilerplate(useMaterial3: true, backwardsCompatibility: true);
2451+
expect(SystemChrome.latestStyle!.statusBarColor, null);
2452+
});
2453+
2454+
testWidgets('AppBar systemOverlayStyle is use to style status bar and navigation bar', (WidgetTester tester) async {
2455+
final SystemUiOverlayStyle systemOverlayStyle = SystemUiOverlayStyle.light.copyWith(
2456+
statusBarColor: Colors.red,
2457+
systemNavigationBarColor: Colors.green,
2458+
);
2459+
await tester.pumpWidget(
2460+
MaterialApp(
2461+
home: Scaffold(
2462+
appBar: AppBar(
2463+
title: const Text('test'),
2464+
systemOverlayStyle: systemOverlayStyle,
2465+
),
2466+
),
2467+
),
2468+
);
2469+
2470+
expect(SystemChrome.latestStyle!.statusBarColor, Colors.red);
2471+
expect(SystemChrome.latestStyle!.systemNavigationBarColor, Colors.green);
2472+
});
2473+
24242474
testWidgets('Changing SliverAppBar snap from true to false', (WidgetTester tester) async {
24252475
// Regression test for https://github.com/flutter/flutter/issues/17598
24262476
const double appBarHeight = 256.0;

packages/flutter/test/rendering/view_chrome_style_test.dart

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,72 @@ void main() {
228228
variant: TargetPlatformVariant.only(TargetPlatform.android),
229229
);
230230
});
231+
232+
testWidgets('Top AnnotatedRegion provides status bar overlay style and bottom AnnotatedRegion provides navigation bar overlay style', (WidgetTester tester) async {
233+
setupTestDevice();
234+
await tester.pumpWidget(
235+
Column(children: const <Widget>[
236+
Expanded(child: AnnotatedRegion<SystemUiOverlayStyle>(
237+
value: SystemUiOverlayStyle(
238+
systemNavigationBarColor: Colors.blue,
239+
statusBarColor: Colors.blue
240+
),
241+
child: SizedBox.expand(),
242+
)),
243+
Expanded(child: AnnotatedRegion<SystemUiOverlayStyle>(
244+
value: SystemUiOverlayStyle(
245+
systemNavigationBarColor: Colors.green,
246+
statusBarColor: Colors.green,
247+
),
248+
child: SizedBox.expand(),
249+
)),
250+
]),
251+
);
252+
await tester.pumpAndSettle();
253+
254+
expect(SystemChrome.latestStyle?.statusBarColor, Colors.blue);
255+
expect(SystemChrome.latestStyle?.systemNavigationBarColor, Colors.green);
256+
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
257+
258+
testWidgets('Top only AnnotatedRegion provides status bar and navigation bar style properties', (WidgetTester tester) async {
259+
setupTestDevice();
260+
await tester.pumpWidget(
261+
Column(children: const <Widget>[
262+
Expanded(child: AnnotatedRegion<SystemUiOverlayStyle>(
263+
value: SystemUiOverlayStyle(
264+
systemNavigationBarColor: Colors.blue,
265+
statusBarColor: Colors.blue
266+
),
267+
child: SizedBox.expand(),
268+
)),
269+
Expanded(child: SizedBox.expand()),
270+
]),
271+
);
272+
await tester.pumpAndSettle();
273+
274+
expect(SystemChrome.latestStyle?.statusBarColor, Colors.blue);
275+
expect(SystemChrome.latestStyle?.systemNavigationBarColor, Colors.blue);
276+
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
277+
278+
testWidgets('Bottom only AnnotatedRegion provides status bar and navigation bar style properties', (WidgetTester tester) async {
279+
setupTestDevice();
280+
await tester.pumpWidget(
281+
Column(children: const <Widget>[
282+
Expanded(child: SizedBox.expand()),
283+
Expanded(child: AnnotatedRegion<SystemUiOverlayStyle>(
284+
value: SystemUiOverlayStyle(
285+
systemNavigationBarColor: Colors.green,
286+
statusBarColor: Colors.green
287+
),
288+
child: SizedBox.expand(),
289+
)),
290+
]),
291+
);
292+
await tester.pumpAndSettle();
293+
294+
expect(SystemChrome.latestStyle?.statusBarColor, Colors.green);
295+
expect(SystemChrome.latestStyle?.systemNavigationBarColor, Colors.green);
296+
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
231297
});
232298
}
233299

0 commit comments

Comments
 (0)