Implement resizeToAvoidBottomPadding in CupertinoPageScaffold (#20929)

diff --git a/packages/flutter/lib/src/cupertino/page_scaffold.dart b/packages/flutter/lib/src/cupertino/page_scaffold.dart
index efa8458..88f095a 100644
--- a/packages/flutter/lib/src/cupertino/page_scaffold.dart
+++ b/packages/flutter/lib/src/cupertino/page_scaffold.dart
@@ -22,8 +22,10 @@
     Key key,
     this.navigationBar,
     this.backgroundColor = CupertinoColors.white,
+    this.resizeToAvoidBottomInset = true,
     @required this.child,
   }) : assert(child != null),
+       assert(resizeToAvoidBottomInset != null),
        super(key: key);
 
   /// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the
@@ -49,6 +51,15 @@
   /// By default uses [CupertinoColors.white] color.
   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.
+  final bool resizeToAvoidBottomInset;
+
   @override
   Widget build(BuildContext context) {
     final List<Widget> stacked = <Widget>[];
@@ -59,15 +70,20 @@
 
       // TODO(xster): Use real size after partial layout instead of preferred size.
       // https://github.com/flutter/flutter/issues/12912
-      final double topPadding = navigationBar.preferredSize.height
-          + existingMediaQuery.padding.top;
+      final double topPadding =
+          navigationBar.preferredSize.height + existingMediaQuery.padding.top;
+
+      // Propagate bottom padding and include viewInsets if appropriate
+      final double bottomPadding = resizeToAvoidBottomInset
+          ? existingMediaQuery.viewInsets.bottom
+          : 0.0;
 
       // 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 (navigationBar.fullObstruction) {
         paddedContent = new Padding(
-          padding: new EdgeInsets.only(top: topPadding),
+          padding: new EdgeInsets.only(top: topPadding, bottom: bottomPadding),
           child: child,
         );
       } else {
@@ -77,7 +93,10 @@
               top: topPadding,
             ),
           ),
-          child: child,
+          child: new Padding(
+            padding: new EdgeInsets.only(bottom: bottomPadding),
+            child: child,
+          ),
         );
       }
     }
diff --git a/packages/flutter/test/cupertino/scaffold_test.dart b/packages/flutter/test/cupertino/scaffold_test.dart
index 31ef37a..b9be032 100644
--- a/packages/flutter/test/cupertino/scaffold_test.dart
+++ b/packages/flutter/test/cupertino/scaffold_test.dart
@@ -25,6 +25,55 @@
     expect(tester.getTopLeft(find.byType(Center)), const Offset(0.0, 0.0));
   });
 
+  testWidgets('Contents padding from viewInsets', (WidgetTester tester) async {
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: MediaQuery(
+        data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)),
+        child: CupertinoPageScaffold(
+          navigationBar: const CupertinoNavigationBar(
+            middle: Text('Opaque'),
+            backgroundColor: Color(0xFFF8F8F8),
+          ),
+          child: Container(),
+        ),
+      ),
+    ));
+
+    expect(tester.getSize(find.byType(Container)).height, 600.0 - 44.0 - 100.0);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: MediaQuery(
+        data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)),
+        child: CupertinoPageScaffold(
+          navigationBar: const CupertinoNavigationBar(
+            middle: Text('Transparent'),
+          ),
+          child: Container(),
+        ),
+      ),
+    ));
+
+    expect(tester.getSize(find.byType(Container)).height, 600.0 - 100.0);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: MediaQuery(
+        data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)),
+        child: CupertinoPageScaffold(
+          navigationBar: const CupertinoNavigationBar(
+            middle: Text('Title'),
+          ),
+          resizeToAvoidBottomInset: false,
+          child: Container(),
+        ),
+      ),
+    ));
+
+    expect(tester.getSize(find.byType(Container)).height, 600.0);
+  });
+
   testWidgets('Contents are between opaque bars', (WidgetTester tester) async {
     const Center page1Center = Center();