Skip to content

Commit

Permalink
Let CupertinoTabScaffold handle keyboard insets too (#25593)
Browse files Browse the repository at this point in the history
  • Loading branch information
xster committed Dec 20, 2018
1 parent 7a88fbc commit d1ec126
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 14 deletions.
20 changes: 16 additions & 4 deletions packages/flutter/lib/src/cupertino/page_scaffold.dart
Expand Up @@ -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
Expand All @@ -78,23 +78,35 @@ 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;

// If navigation bar is opaquely obstructing, directly shift the main content
// 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(
data: existingMediaQuery.copyWith(
padding: existingMediaQuery.padding.copyWith(
top: topPadding,
),
viewInsets: newViewInsets,
),
child: Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
Expand Down
51 changes: 42 additions & 9 deletions packages/flutter/lib/src/cupertino/tab_scaffold.dart
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
Expand All @@ -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 =
Expand All @@ -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);

Expand All @@ -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,
Expand Down
11 changes: 10 additions & 1 deletion packages/flutter/test/cupertino/scaffold_test.dart
Expand Up @@ -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(
Expand All @@ -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,
Expand Down
83 changes: 83 additions & 0 deletions packages/flutter/test/cupertino/tab_scaffold_test.dart
Expand Up @@ -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 }) {
Expand Down

0 comments on commit d1ec126

Please sign in to comment.