1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6
7import 'package:flutter/foundation.dart';
8
9import 'basic.dart';
10import 'debug.dart';
11import 'framework.dart';
12import 'media_query.dart';
13
14/// A widget that insets its child by sufficient padding to avoid intrusions by
15/// the operating system.
16///
17/// For example, this will indent the child by enough to avoid the status bar at
18/// the top of the screen.
19///
20/// It will also indent the child by the amount necessary to avoid The Notch on
21/// the iPhone X, or other similar creative physical features of the display.
22///
23/// When a [minimum] padding is specified, the greater of the minimum padding
24/// or the safe area padding will be applied.
25///
26/// {@youtube 560 315 https://www.youtube.com/watch?v=lkF0TQJO0bA}
27///
28/// See also:
29///
30/// * [SliverSafeArea], for insetting slivers to avoid operating system
31/// intrusions.
32/// * [Padding], for insetting widgets in general.
33/// * [MediaQuery], from which the window padding is obtained.
34/// * [dart:ui.FlutterView.padding], which reports the padding from the operating
35/// system.
36class SafeArea extends StatelessWidget {
37 /// Creates a widget that avoids operating system interfaces.
38 ///
39 /// The [left], [top], [right], [bottom], and [minimum] arguments must not be
40 /// null.
41 const SafeArea({
42 super.key,
43 this.left = true,
44 this.top = true,
45 this.right = true,
46 this.bottom = true,
47 this.minimum = EdgeInsets.zero,
48 this.maintainBottomViewPadding = false,
49 required this.child,
50 });
51
52 /// Whether to avoid system intrusions on the left.
53 final bool left;
54
55 /// Whether to avoid system intrusions at the top of the screen, typically the
56 /// system status bar.
57 final bool top;
58
59 /// Whether to avoid system intrusions on the right.
60 final bool right;
61
62 /// Whether to avoid system intrusions on the bottom side of the screen.
63 final bool bottom;
64
65 /// This minimum padding to apply.
66 ///
67 /// The greater of the minimum insets and the media padding will be applied.
68 final EdgeInsets minimum;
69
70 /// Specifies whether the [SafeArea] should maintain the bottom
71 /// [MediaQueryData.viewPadding] instead of the bottom [MediaQueryData.padding],
72 /// defaults to false.
73 ///
74 /// For example, if there is an onscreen keyboard displayed above the
75 /// SafeArea, the padding can be maintained below the obstruction rather than
76 /// being consumed. This can be helpful in cases where your layout contains
77 /// flexible widgets, which could visibly move when opening a software
78 /// keyboard due to the change in the padding value. Setting this to true will
79 /// avoid the UI shift.
80 final bool maintainBottomViewPadding;
81
82 /// The widget below this widget in the tree.
83 ///
84 /// The padding on the [MediaQuery] for the [child] will be suitably adjusted
85 /// to zero out any sides that were avoided by this widget.
86 ///
87 /// {@macro flutter.widgets.ProxyWidget.child}
88 final Widget child;
89
90 @override
91 Widget build(BuildContext context) {
92 assert(debugCheckHasMediaQuery(context));
93 EdgeInsets padding = MediaQuery.paddingOf(context);
94 // Bottom padding has been consumed - i.e. by the keyboard
95 if (maintainBottomViewPadding) {
96 padding = padding.copyWith(bottom: MediaQuery.viewPaddingOf(context).bottom);
97 }
98
99 return Padding(
100 padding: EdgeInsets.only(
101 left: math.max(left ? padding.left : 0.0, minimum.left),
102 top: math.max(top ? padding.top : 0.0, minimum.top),
103 right: math.max(right ? padding.right : 0.0, minimum.right),
104 bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
105 ),
106 child: MediaQuery.removePadding(
107 context: context,
108 removeLeft: left,
109 removeTop: top,
110 removeRight: right,
111 removeBottom: bottom,
112 child: child,
113 ),
114 );
115 }
116
117 @override
118 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
119 super.debugFillProperties(properties);
120 properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
121 properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
122 properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
123 properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
124 }
125}
126
127/// A sliver that insets another sliver by sufficient padding to avoid
128/// intrusions by the operating system.
129///
130/// For example, this will indent the sliver by enough to avoid the status bar
131/// at the top of the screen.
132///
133/// It will also indent the sliver by the amount necessary to avoid The Notch
134/// on the iPhone X, or other similar creative physical features of the
135/// display.
136///
137/// When a [minimum] padding is specified, the greater of the minimum padding
138/// or the safe area padding will be applied.
139///
140/// See also:
141///
142/// * [SafeArea], for insetting box widgets to avoid operating system intrusions.
143/// * [SliverPadding], for insetting slivers in general.
144/// * [MediaQuery], from which the window padding is obtained.
145/// * [dart:ui.FlutterView.padding], which reports the padding from the operating
146/// system.
147class SliverSafeArea extends StatelessWidget {
148 /// Creates a sliver that avoids operating system interfaces.
149 const SliverSafeArea({
150 super.key,
151 this.left = true,
152 this.top = true,
153 this.right = true,
154 this.bottom = true,
155 this.minimum = EdgeInsets.zero,
156 required this.sliver,
157 });
158
159 /// Whether to avoid system intrusions on the left.
160 final bool left;
161
162 /// Whether to avoid system intrusions at the top of the screen, typically the
163 /// system status bar.
164 final bool top;
165
166 /// Whether to avoid system intrusions on the right.
167 final bool right;
168
169 /// Whether to avoid system intrusions on the bottom side of the screen.
170 final bool bottom;
171
172 /// This minimum padding to apply.
173 ///
174 /// The greater of the minimum padding and the media padding is be applied.
175 final EdgeInsets minimum;
176
177 /// The sliver below this sliver in the tree.
178 ///
179 /// The padding on the [MediaQuery] for the [sliver] will be suitably adjusted
180 /// to zero out any sides that were avoided by this sliver.
181 final Widget sliver;
182
183 @override
184 Widget build(BuildContext context) {
185 assert(debugCheckHasMediaQuery(context));
186 final EdgeInsets padding = MediaQuery.paddingOf(context);
187 return SliverPadding(
188 padding: EdgeInsets.only(
189 left: math.max(left ? padding.left : 0.0, minimum.left),
190 top: math.max(top ? padding.top : 0.0, minimum.top),
191 right: math.max(right ? padding.right : 0.0, minimum.right),
192 bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
193 ),
194 sliver: MediaQuery.removePadding(
195 context: context,
196 removeLeft: left,
197 removeTop: top,
198 removeRight: right,
199 removeBottom: bottom,
200 child: sliver,
201 ),
202 );
203 }
204
205 @override
206 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
207 super.debugFillProperties(properties);
208 properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
209 properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
210 properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
211 properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
212 }
213}
214