Unverified Commit 35c2267f authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Add SliverSafeArea widget (#14499)

A SafeArea-like widget that applies a SliverPadding instead of a
Padding.
parent 66febf26
......@@ -20,6 +20,8 @@ import 'media_query.dart';
///
/// See also:
///
/// * [SliverSafeArea], for insetting slivers to avoid operating system
/// intrusions.
/// * [Padding], for insetting widgets in general.
/// * [MediaQuery], from which the window padding is obtained.
/// * [dart:ui.Window.padding], which reports the padding from the operating
......@@ -93,3 +95,88 @@ class SafeArea extends StatelessWidget {
description.add(new FlagProperty('bottom', value: left, ifTrue: 'avoid bottom padding'));
}
}
/// A sliver that insets another sliver by sufficient padding to avoid
/// intrusions by the operating system.
///
/// For example, this will indent the sliver by enough to avoid the status bar
/// at the top of the screen.
///
/// It will also indent the sliver by the amount necessary to avoid The Notch
/// on the iPhone X, or other similar creative physical features of the
/// display.
///
/// See also:
///
/// * [SafeArea], for insetting widgets to avoid operating system intrusions.
/// * [SliverPadding], for insetting slivers in general.
/// * [MediaQuery], from which the window padding is obtained.
/// * [dart:ui.Window.padding], which reports the padding from the operating
/// system.
class SliverSafeArea extends StatelessWidget {
/// Creates a sliver that avoids operating system interfaces.
///
/// The [left], [top], [right], and [bottom] arguments must not be null.
const SliverSafeArea({
Key key,
this.left: true,
this.top: true,
this.right: true,
this.bottom: true,
@required this.sliver,
}) : assert(left != null),
assert(top != null),
assert(right != null),
assert(bottom != null),
super(key: key);
/// Whether to avoid system intrusions on the left.
final bool left;
/// Whether to avoid system intrusions at the top of the screen, typically the
/// system status bar.
final bool top;
/// Whether to avoid system intrusions on the right.
final bool right;
/// Whether to avoid system intrusions on the bottom side of the screen.
final bool bottom;
/// The sliver below this sliver in the tree.
///
/// The padding on the [MediaQuery] for the [sliver] will be suitably adjusted
/// to zero out any sides that were avoided by this sliver.
final Widget sliver;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final EdgeInsets padding = MediaQuery.of(context).padding;
return new SliverPadding(
padding: new EdgeInsets.only(
left: left ? padding.left : 0.0,
top: top ? padding.top : 0.0,
right: right ? padding.right : 0.0,
bottom: bottom ? padding.bottom : 0.0,
),
sliver: new MediaQuery.removePadding(
context: context,
removeLeft: left,
removeTop: top,
removeRight: right,
removeBottom: bottom,
child: sliver,
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
description.add(new FlagProperty('top', value: left, ifTrue: 'avoid top padding'));
description.add(new FlagProperty('right', value: left, ifTrue: 'avoid right padding'));
description.add(new FlagProperty('bottom', value: left, ifTrue: 'avoid bottom padding'));
}
}
......@@ -7,66 +7,175 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('SafeArea - basic', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
group('SafeArea', () {
testWidgets('SafeArea - basic', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: const SafeArea(
left: false,
child: const Placeholder(),
),
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
});
testWidgets('SafeArea - nested', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: const SafeArea(
top: false,
child: const SafeArea(
right: false,
child: const Placeholder(),
),
),
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
});
testWidgets('SafeArea - changing', (WidgetTester tester) async {
const Widget child = const SafeArea(
bottom: false,
child: const SafeArea(
left: false,
bottom: false,
child: const Placeholder(),
),
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
);
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: child,
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 600.0));
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.only(
left: 100.0,
top: 30.0,
right: 0.0,
bottom: 40.0,
)),
child: child,
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(100.0, 30.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
});
testWidgets('SafeArea - nested', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: const SafeArea(
top: false,
child: const SafeArea(
right: false,
child: const Placeholder(),
group('SliverSafeArea', () {
Widget buildWidget(EdgeInsets mediaPadding, Widget sliver) {
return new MediaQuery(
data: new MediaQueryData(padding: mediaPadding),
child: new Directionality(
textDirection: TextDirection.ltr,
child: new Viewport(
offset: new ViewportOffset.fixed(0.0),
axisDirection: AxisDirection.down,
slivers: <Widget>[
const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('before'))),
sliver,
const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('after'))),
],
),
),
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
});
);
}
void verify(WidgetTester tester, List<Rect> expectedRects) {
final List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Rect>(
(RenderBox target) {
final Offset topLeft = target.localToGlobal(Offset.zero);
final Offset bottomRight = target.localToGlobal(target.size.bottomRight(Offset.zero));
return new Rect.fromPoints(topLeft, bottomRight);
}
).toList();
expect(testAnswers, equals(expectedRects));
}
testWidgets('SliverSafeArea - basic', (WidgetTester tester) async {
await tester.pumpWidget(
buildWidget(
const EdgeInsets.all(20.0),
const SliverSafeArea(
left: false,
sliver: const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('padded'))),
),
),
);
verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
new Rect.fromLTWH(0.0, 120.0, 780.0, 100.0),
new Rect.fromLTWH(0.0, 240.0, 800.0, 100.0),
]);
});
testWidgets('SliverSafeArea - nested', (WidgetTester tester) async {
await tester.pumpWidget(
buildWidget(
const EdgeInsets.all(20.0),
const SliverSafeArea(
top: false,
sliver: const SliverSafeArea(
right: false,
sliver: const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('padded'))),
),
),
),
);
verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
new Rect.fromLTWH(20.0, 120.0, 760.0, 100.0),
new Rect.fromLTWH(0.0, 240.0, 800.0, 100.0),
]);
});
testWidgets('SafeArea - changing', (WidgetTester tester) async {
const Widget child = const SafeArea(
bottom: false,
child: const SafeArea(
left: false,
testWidgets('SliverSafeArea - changing', (WidgetTester tester) async {
const Widget sliver = const SliverSafeArea(
bottom: false,
child: const Placeholder(),
),
);
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: child,
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 600.0));
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.only(
left: 100.0,
top: 30.0,
right: 0.0,
bottom: 40.0,
)),
child: child,
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(100.0, 30.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
sliver: const SliverSafeArea(
left: false,
bottom: false,
sliver: const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('padded'))),
),
);
await tester.pumpWidget(
buildWidget(
const EdgeInsets.all(20.0),
sliver,
),
);
verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
new Rect.fromLTWH(20.0, 120.0, 760.0, 100.0),
new Rect.fromLTWH(0.0, 220.0, 800.0, 100.0),
]);
await tester.pumpWidget(
buildWidget(
const EdgeInsets.only(
left: 100.0,
top: 30.0,
right: 0.0,
bottom: 40.0,
),
sliver,
),
);
verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
new Rect.fromLTWH(100.0, 130.0, 700.0, 100.0),
new Rect.fromLTWH(0.0, 230.0, 800.0, 100.0),
]);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment