Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let CupertinoTabScaffold handle keyboard insets too #25593

Merged
merged 2 commits into from
Dec 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 16 additions & 4 deletions packages/flutter/lib/src/cupertino/page_scaffold.dart
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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