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 with sufficient padding to avoid intrusions
15/// by the operating system.
16///
17/// {@youtube 560 315 https://www.youtube.com/watch?v=lkF0TQJO0bA}
18///
19/// When a [minimum] padding is specified, the greater of the minimum padding
20/// or the safe area padding will be applied.
21///
22/// {@tool dartpad}
23/// This example shows how `SafeArea` can apply padding within a mobile device's
24/// screen to make the relevant content completely visible.
25///
26/// ** See code in examples/api/lib/widgets/safe_area/safe_area.0.dart **
27/// {@end-tool}
28///
29/// {@tool snippet}
30///
31/// This example creates a blue box containing text that is sufficiently padded
32/// to avoid intrusions by the operating system.
33///
34/// ```dart
35/// SafeArea(
36/// child: Container(
37/// constraints: const BoxConstraints.expand(),
38/// alignment: Alignment.center,
39/// color: Colors.blue,
40/// child: const Text('Hello, World!'),
41/// ),
42/// )
43/// ```
44/// {@end-tool}
45///
46/// ### [MediaQuery] impact
47///
48/// The padding on the [MediaQuery] for the [child] will be suitably adjusted
49/// to zero out any sides that were avoided by this widget.
50///
51/// {@youtube 560 315 https://www.youtube.com/watch?v=ceCo8U0XHqw}
52///
53/// See also:
54///
55/// * [SliverSafeArea], for insetting slivers to avoid operating system
56/// intrusions.
57/// * [Padding], for insetting widgets in general.
58/// * [MediaQuery], from which the view padding is obtained.
59/// * [dart:ui.FlutterView.padding], which reports the padding from the operating
60/// system.
61class SafeArea extends StatelessWidget {
62 /// Creates a widget that avoids operating system interfaces.
63 const SafeArea({
64 super.key,
65 this.left = true,
66 this.top = true,
67 this.right = true,
68 this.bottom = true,
69 this.minimum = EdgeInsets.zero,
70 this.maintainBottomViewPadding = false,
71 required this.child,
72 });
73
74 /// Whether to avoid system intrusions on the left.
75 final bool left;
76
77 /// Whether to avoid system intrusions at the top of the screen, typically the
78 /// system status bar.
79 final bool top;
80
81 /// Whether to avoid system intrusions on the right.
82 final bool right;
83
84 /// Whether to avoid system intrusions on the bottom side of the screen.
85 final bool bottom;
86
87 /// This minimum padding to apply.
88 ///
89 /// The greater of the minimum insets and the media padding will be applied.
90 final EdgeInsets minimum;
91
92 /// Specifies whether the [SafeArea] should maintain the bottom
93 /// [MediaQueryData.viewPadding] instead of the bottom [MediaQueryData.padding],
94 /// defaults to false.
95 ///
96 /// For example, if there is an onscreen keyboard displayed above the
97 /// SafeArea, the padding can be maintained below the obstruction rather than
98 /// being consumed. This can be helpful in cases where your layout contains
99 /// flexible widgets, which could visibly move when opening a software
100 /// keyboard due to the change in the padding value. Setting this to true will
101 /// avoid the UI shift.
102 final bool maintainBottomViewPadding;
103
104 /// The widget below this widget in the tree.
105 ///
106 /// The padding on the [MediaQuery] for the [child] will be suitably adjusted
107 /// to zero out any sides that were avoided by this widget.
108 ///
109 /// {@macro flutter.widgets.ProxyWidget.child}
110 final Widget child;
111
112 @override
113 Widget build(BuildContext context) {
114 assert(debugCheckHasMediaQuery(context));
115 EdgeInsets padding = MediaQuery.paddingOf(context);
116 // Bottom padding has been consumed - i.e. by the keyboard
117 if (maintainBottomViewPadding) {
118 padding = padding.copyWith(bottom: MediaQuery.viewPaddingOf(context).bottom);
119 }
120
121 return Padding(
122 padding: EdgeInsets.only(
123 left: math.max(left ? padding.left : 0.0, minimum.left),
124 top: math.max(top ? padding.top : 0.0, minimum.top),
125 right: math.max(right ? padding.right : 0.0, minimum.right),
126 bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
127 ),
128 child: MediaQuery.removePadding(
129 context: context,
130 removeLeft: left,
131 removeTop: top,
132 removeRight: right,
133 removeBottom: bottom,
134 child: child,
135 ),
136 );
137 }
138
139 @override
140 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
141 super.debugFillProperties(properties);
142 properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
143 properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
144 properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
145 properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
146 }
147}
148
149/// A sliver that insets another sliver by sufficient padding to avoid
150/// intrusions by the operating system.
151///
152/// For example, this will indent the sliver by enough to avoid the status bar
153/// at the top of the screen.
154///
155/// It will also indent the sliver by the amount necessary to avoid The Notch
156/// on the iPhone X, or other similar creative physical features of the
157/// display.
158///
159/// When a [minimum] padding is specified, the greater of the minimum padding
160/// or the safe area padding will be applied.
161///
162/// See also:
163///
164/// * [SafeArea], for insetting box widgets to avoid operating system intrusions.
165/// * [SliverPadding], for insetting slivers in general.
166/// * [MediaQuery], from which the window padding is obtained.
167/// * [dart:ui.FlutterView.padding], which reports the padding from the operating
168/// system.
169class SliverSafeArea extends StatelessWidget {
170 /// Creates a sliver that avoids operating system interfaces.
171 const SliverSafeArea({
172 super.key,
173 this.left = true,
174 this.top = true,
175 this.right = true,
176 this.bottom = true,
177 this.minimum = EdgeInsets.zero,
178 required this.sliver,
179 });
180
181 /// Whether to avoid system intrusions on the left.
182 final bool left;
183
184 /// Whether to avoid system intrusions at the top of the screen, typically the
185 /// system status bar.
186 final bool top;
187
188 /// Whether to avoid system intrusions on the right.
189 final bool right;
190
191 /// Whether to avoid system intrusions on the bottom side of the screen.
192 final bool bottom;
193
194 /// This minimum padding to apply.
195 ///
196 /// The greater of the minimum padding and the media padding is be applied.
197 final EdgeInsets minimum;
198
199 /// The sliver below this sliver in the tree.
200 ///
201 /// The padding on the [MediaQuery] for the [sliver] will be suitably adjusted
202 /// to zero out any sides that were avoided by this sliver.
203 final Widget sliver;
204
205 @override
206 Widget build(BuildContext context) {
207 assert(debugCheckHasMediaQuery(context));
208 final EdgeInsets padding = MediaQuery.paddingOf(context);
209 return SliverPadding(
210 padding: EdgeInsets.only(
211 left: math.max(left ? padding.left : 0.0, minimum.left),
212 top: math.max(top ? padding.top : 0.0, minimum.top),
213 right: math.max(right ? padding.right : 0.0, minimum.right),
214 bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
215 ),
216 sliver: MediaQuery.removePadding(
217 context: context,
218 removeLeft: left,
219 removeTop: top,
220 removeRight: right,
221 removeBottom: bottom,
222 child: sliver,
223 ),
224 );
225 }
226
227 @override
228 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
229 super.debugFillProperties(properties);
230 properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
231 properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
232 properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
233 properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
234 }
235}
236