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 { ...@@ -57,7 +57,7 @@ class CupertinoPageScaffold extends StatelessWidget {
/// scaffold, the body can be resized to avoid overlapping the keyboard, which /// scaffold, the body can be resized to avoid overlapping the keyboard, which
/// prevents widgets inside the body from being obscured by the keyboard. /// prevents widgets inside the body from being obscured by the keyboard.
/// ///
/// Defaults to true. /// Defaults to true and cannot be null.
final bool resizeToAvoidBottomInset; final bool resizeToAvoidBottomInset;
@override @override
...@@ -78,6 +78,12 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -78,6 +78,12 @@ class CupertinoPageScaffold extends StatelessWidget {
? existingMediaQuery.viewInsets.bottom ? existingMediaQuery.viewInsets.bottom
: 0.0; : 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 = final bool fullObstruction =
navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF; navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;
...@@ -85,9 +91,14 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -85,9 +91,14 @@ class CupertinoPageScaffold extends StatelessWidget {
// down. If translucent, let main content draw behind navigation bar but hint the // down. If translucent, let main content draw behind navigation bar but hint the
// obstructed area. // obstructed area.
if (fullObstruction) { if (fullObstruction) {
paddedContent = Padding( paddedContent = MediaQuery(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding), data: existingMediaQuery.copyWith(
child: child, viewInsets: newViewInsets,
),
child: Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: child,
),
); );
} else { } else {
paddedContent = MediaQuery( paddedContent = MediaQuery(
...@@ -95,6 +106,7 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -95,6 +106,7 @@ class CupertinoPageScaffold extends StatelessWidget {
padding: existingMediaQuery.padding.copyWith( padding: existingMediaQuery.padding.copyWith(
top: topPadding, top: topPadding,
), ),
viewInsets: newViewInsets,
), ),
child: Padding( child: Padding(
padding: EdgeInsets.only(bottom: bottomPadding), padding: EdgeInsets.only(bottom: bottomPadding),
......
...@@ -97,6 +97,8 @@ class CupertinoTabScaffold extends StatefulWidget { ...@@ -97,6 +97,8 @@ class CupertinoTabScaffold extends StatefulWidget {
Key key, Key key,
@required this.tabBar, @required this.tabBar,
@required this.tabBuilder, @required this.tabBuilder,
this.backgroundColor,
this.resizeToAvoidBottomInset = true,
}) : assert(tabBar != null), }) : assert(tabBar != null),
assert(tabBuilder != null), assert(tabBuilder != null),
super(key: key); super(key: key);
...@@ -138,6 +140,20 @@ class CupertinoTabScaffold extends StatefulWidget { ...@@ -138,6 +140,20 @@ class CupertinoTabScaffold extends StatefulWidget {
/// Must not be null. /// Must not be null.
final IndexedWidgetBuilder tabBuilder; 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 @override
_CupertinoTabScaffoldState createState() => _CupertinoTabScaffoldState(); _CupertinoTabScaffoldState createState() => _CupertinoTabScaffoldState();
} }
...@@ -163,15 +179,30 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -163,15 +179,30 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> stacked = <Widget>[]; final List<Widget> stacked = <Widget>[];
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
MediaQueryData newMediaQuery = MediaQuery.of(context);
Widget content = _TabSwitchingView( Widget content = _TabSwitchingView(
currentTabIndex: _currentPage, currentTabIndex: _currentPage,
tabNumber: widget.tabBar.items.length, tabNumber: widget.tabBar.items.length,
tabBuilder: widget.tabBuilder, tabBuilder: widget.tabBuilder,
); );
if (widget.tabBar != null) { if (widget.resizeToAvoidBottomInset) {
final MediaQueryData existingMediaQuery = MediaQuery.of(context); // 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. // TODO(xster): Use real size after partial layout instead of preferred size.
// https://github.com/flutter/flutter/issues/12912 // https://github.com/flutter/flutter/issues/12912
final double bottomPadding = final double bottomPadding =
...@@ -186,17 +217,19 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -186,17 +217,19 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
child: content, child: content,
); );
} else { } else {
content = MediaQuery( newMediaQuery = newMediaQuery.copyWith(
data: existingMediaQuery.copyWith( padding: newMediaQuery.padding.copyWith(
padding: existingMediaQuery.padding.copyWith( bottom: bottomPadding,
bottom: bottomPadding,
),
), ),
child: content,
); );
} }
} }
content = MediaQuery(
data: newMediaQuery,
child: content,
);
// The main content being at the bottom is added to the stack first. // The main content being at the bottom is added to the stack first.
stacked.add(content); stacked.add(content);
...@@ -222,7 +255,7 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -222,7 +255,7 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: CupertinoTheme.of(context).scaffoldBackgroundColor color: widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
), ),
child: Stack( child: Stack(
children: stacked, children: stacked,
......
...@@ -42,6 +42,7 @@ void main() { ...@@ -42,6 +42,7 @@ void main() {
expect(tester.getSize(find.byType(Container)).height, 600.0 - 44.0 - 100.0); expect(tester.getSize(find.byType(Container)).height, 600.0 - 44.0 - 100.0);
BuildContext childContext;
await tester.pumpWidget(Directionality( await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: MediaQuery( child: MediaQuery(
...@@ -50,12 +51,20 @@ void main() { ...@@ -50,12 +51,20 @@ void main() {
navigationBar: const CupertinoNavigationBar( navigationBar: const CupertinoNavigationBar(
middle: Text('Transparent'), 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); 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( await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
......
...@@ -312,6 +312,89 @@ void main() { ...@@ -312,6 +312,89 @@ void main() {
)); ));
expect(tab2.text.style.color, CupertinoColors.destructiveRed); 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 }) { 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