Unverified Commit d1ec126a authored by xster's avatar xster Committed by GitHub

Let CupertinoTabScaffold handle keyboard insets too (#25593)

parent 7a88fbc5
......@@ -57,7 +57,7 @@ class CupertinoPageScaffold extends StatelessWidget {
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
/// prevents widgets inside the body from being obscured by the keyboard.
///
/// Defaults to true.
/// Defaults to true and cannot be null.
final bool resizeToAvoidBottomInset;
@override
......@@ -78,6 +78,12 @@ class CupertinoPageScaffold extends StatelessWidget {
? existingMediaQuery.viewInsets.bottom
: 0.0;
final EdgeInsets newViewInsets = resizeToAvoidBottomInset
// The insets are consumed by the scaffolds and no longer exposed to
// the descendant subtree.
? existingMediaQuery.viewInsets.copyWith(bottom: 0.0)
: existingMediaQuery.viewInsets;
final bool fullObstruction =
navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;
......@@ -85,9 +91,14 @@ class CupertinoPageScaffold extends StatelessWidget {
// down. If translucent, let main content draw behind navigation bar but hint the
// obstructed area.
if (fullObstruction) {
paddedContent = Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: child,
paddedContent = MediaQuery(
data: existingMediaQuery.copyWith(
viewInsets: newViewInsets,
),
child: Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: child,
),
);
} else {
paddedContent = MediaQuery(
......@@ -95,6 +106,7 @@ class CupertinoPageScaffold extends StatelessWidget {
padding: existingMediaQuery.padding.copyWith(
top: topPadding,
),
viewInsets: newViewInsets,
),
child: Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
......
......@@ -97,6 +97,8 @@ class CupertinoTabScaffold extends StatefulWidget {
Key key,
@required this.tabBar,
@required this.tabBuilder,
this.backgroundColor,
this.resizeToAvoidBottomInset = true,
}) : assert(tabBar != null),
assert(tabBuilder != null),
super(key: key);
......@@ -138,6 +140,20 @@ class CupertinoTabScaffold extends StatefulWidget {
/// Must not be null.
final IndexedWidgetBuilder tabBuilder;
/// The color of the widget that underlies the entire scaffold.
///
/// By default uses [CupertinoTheme]'s `scaffoldBackgroundColor` when null.
final Color backgroundColor;
/// Whether the [child] should size itself to avoid the window's bottom inset.
///
/// For example, if there is an onscreen keyboard displayed above the
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
/// prevents widgets inside the body from being obscured by the keyboard.
///
/// Defaults to true and cannot be null.
final bool resizeToAvoidBottomInset;
@override
_CupertinoTabScaffoldState createState() => _CupertinoTabScaffoldState();
}
......@@ -163,15 +179,30 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
Widget build(BuildContext context) {
final List<Widget> stacked = <Widget>[];
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
MediaQueryData newMediaQuery = MediaQuery.of(context);
Widget content = _TabSwitchingView(
currentTabIndex: _currentPage,
tabNumber: widget.tabBar.items.length,
tabBuilder: widget.tabBuilder,
);
if (widget.tabBar != null) {
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
if (widget.resizeToAvoidBottomInset) {
// Remove the view inset and add it back as a padding in the inner content.
newMediaQuery = newMediaQuery.removeViewInsets(removeBottom: true);
content = Padding(
padding: EdgeInsets.only(bottom: existingMediaQuery.viewInsets.bottom),
child: content,
);
}
if (widget.tabBar != null &&
// Only pad the content with the height of the tab bar if the tab
// isn't already entirely obstructed by a keyboard or other view insets.
// Don't double pad.
(!widget.resizeToAvoidBottomInset ||
widget.tabBar.preferredSize.height > existingMediaQuery.viewInsets.bottom)) {
// TODO(xster): Use real size after partial layout instead of preferred size.
// https://github.com/flutter/flutter/issues/12912
final double bottomPadding =
......@@ -186,17 +217,19 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
child: content,
);
} else {
content = MediaQuery(
data: existingMediaQuery.copyWith(
padding: existingMediaQuery.padding.copyWith(
bottom: bottomPadding,
),
newMediaQuery = newMediaQuery.copyWith(
padding: newMediaQuery.padding.copyWith(
bottom: bottomPadding,
),
child: content,
);
}
}
content = MediaQuery(
data: newMediaQuery,
child: content,
);
// The main content being at the bottom is added to the stack first.
stacked.add(content);
......@@ -222,7 +255,7 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
return DecoratedBox(
decoration: BoxDecoration(
color: CupertinoTheme.of(context).scaffoldBackgroundColor
color: widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
),
child: Stack(
children: stacked,
......
......@@ -42,6 +42,7 @@ void main() {
expect(tester.getSize(find.byType(Container)).height, 600.0 - 44.0 - 100.0);
BuildContext childContext;
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
......@@ -50,12 +51,20 @@ void main() {
navigationBar: const CupertinoNavigationBar(
middle: Text('Transparent'),
),
child: Container(),
child: Builder(
builder: (BuildContext context) {
childContext = context;
return Container();
},
),
),
),
));
expect(tester.getSize(find.byType(Container)).height, 600.0 - 100.0);
// The shouldn't see a media query view inset because it was consumed by
// the scaffold.
expect(MediaQuery.of(childContext).viewInsets.bottom, 0);
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
......
......@@ -312,6 +312,89 @@ void main() {
));
expect(tab2.text.style.color, CupertinoColors.destructiveRed);
});
testWidgets('Tab contents are padded when there are view insets', (WidgetTester tester) async {
BuildContext innerContext;
await tester.pumpWidget(
CupertinoApp(
home: MediaQuery(
data: const MediaQueryData(
viewInsets: EdgeInsets.only(bottom: 200),
),
child: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
innerContext = context;
return const Placeholder();
},
),
),
),
);
expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 400));
// Don't generate more media query padding from the translucent bottom
// tab since the tab is behind the keyboard now.
expect(MediaQuery.of(innerContext).padding.bottom, 0);
});
testWidgets('Tab contents are not inset when resizeToAvoidBottomInset overriden', (WidgetTester tester) async {
BuildContext innerContext;
await tester.pumpWidget(
CupertinoApp(
home: MediaQuery(
data: const MediaQueryData(
viewInsets: EdgeInsets.only(bottom: 200),
),
child: CupertinoTabScaffold(
resizeToAvoidBottomInset: false,
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
innerContext = context;
return const Placeholder();
}
),
),
),
);
expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 600));
// Media query padding shows up in the inner content because it wasn't masked
// by the view inset.
expect(MediaQuery.of(innerContext).padding.bottom, 50);
});
testWidgets('Tab and page scaffolds do not double stack view insets', (WidgetTester tester) async {
BuildContext innerContext;
await tester.pumpWidget(
CupertinoApp(
home: MediaQuery(
data: const MediaQueryData(
viewInsets: EdgeInsets.only(bottom: 200),
),
child: CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return CupertinoPageScaffold(
child: Builder(
builder: (BuildContext context) {
innerContext = context;
return const Placeholder();
},
),
);
},
),
),
),
);
expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 400));
expect(MediaQuery.of(innerContext).padding.bottom, 0);
});
}
CupertinoTabBar _buildTabBar({ int selectedTab = 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