Jaime Blasco
Committed by GitHub

Merge pull request #37 from jamesblasco/scroll_top

Support scroll to top when tapped on the status bar
@@ -98,147 +98,153 @@ class _MyHomePageState extends State<MyHomePage> { @@ -98,147 +98,153 @@ class _MyHomePageState extends State<MyHomePage> {
98 Widget build(BuildContext context) { 98 Widget build(BuildContext context) {
99 print(MediaQuery.of(context).size.height); 99 print(MediaQuery.of(context).size.height);
100 return Material( 100 return Material(
101 - child: CupertinoPageScaffold(  
102 - backgroundColor: Colors.white,  
103 - navigationBar: CupertinoNavigationBar(  
104 - transitionBetweenRoutes: false,  
105 - middle: Text('iOS13 Modal Presentation'),  
106 - trailing: GestureDetector(  
107 - child: Icon(Icons.arrow_forward),  
108 - onTap: () => Navigator.of(context).pushNamed('ss'), 101 + child: Scaffold(
  102 + body: CupertinoPageScaffold(
  103 + backgroundColor: Colors.white,
  104 + navigationBar: CupertinoNavigationBar(
  105 + transitionBetweenRoutes: false,
  106 + middle: Text('iOS13 Modal Presentation'),
  107 + trailing: GestureDetector(
  108 + child: Icon(Icons.arrow_forward),
  109 + onTap: () => Navigator.of(context).pushNamed('ss'),
  110 + ),
109 ), 111 ),
110 - ),  
111 - child: SizedBox.expand(  
112 - child: SingleChildScrollView(  
113 - child: SafeArea(  
114 - bottom: false,  
115 - child: Column(  
116 - crossAxisAlignment: CrossAxisAlignment.stretch,  
117 - mainAxisSize: MainAxisSize.min,  
118 - children: <Widget>[  
119 - ListTile(  
120 - title: Text('Cupertino Photo Share Example'),  
121 - onTap: () => Navigator.of(context).push(  
122 - MaterialWithModalsPageRoute(  
123 - builder: (context) => CupertinoSharePage()))),  
124 - section('STYLES'),  
125 - ListTile(  
126 - title: Text('Material fit'),  
127 - onTap: () => showMaterialModalBottomSheet(  
128 - expand: false,  
129 - context: context,  
130 - backgroundColor: Colors.transparent,  
131 - builder: (context, scrollController) =>  
132 - ModalFit(scrollController: scrollController),  
133 - )),  
134 - ListTile(  
135 - title: Text('Bar Modal'),  
136 - onTap: () => showBarModalBottomSheet(  
137 - expand: true,  
138 - context: context,  
139 - backgroundColor: Colors.transparent,  
140 - builder: (context, scrollController) =>  
141 - ModalInsideModal(  
142 - scrollController: scrollController),  
143 - )),  
144 - ListTile(  
145 - title: Text('Avatar Modal'),  
146 - onTap: () => showAvatarModalBottomSheet(  
147 - expand: true,  
148 - context: context,  
149 - backgroundColor: Colors.transparent,  
150 - builder: (context, scrollController) =>  
151 - ModalInsideModal(  
152 - scrollController: scrollController),  
153 - )),  
154 - ListTile(  
155 - title: Text('Float Modal'),  
156 - onTap: () => showFloatingModalBottomSheet(  
157 - context: context,  
158 - builder: (context, scrollController) =>  
159 - ModalFit(scrollController: scrollController),  
160 - )),  
161 - ListTile(  
162 - title: Text('Cupertino Modal fit'),  
163 - onTap: () => showCupertinoModalBottomSheet(  
164 - expand: false,  
165 - context: context,  
166 - backgroundColor: Colors.transparent,  
167 - builder: (context, scrollController) =>  
168 - ModalFit(scrollController: scrollController),  
169 - )),  
170 - section('COMPLEX CASES'),  
171 - ListTile(  
172 - title: Text('Cupertino Small Modal forced to expand'),  
173 - onTap: () => showCupertinoModalBottomSheet(  
174 - expand: true,  
175 - context: context,  
176 - backgroundColor: Colors.transparent,  
177 - builder: (context, scrollController) =>  
178 - ModalFit(scrollController: scrollController),  
179 - )),  
180 - ListTile(  
181 - title: Text('Reverse list'),  
182 - onTap: () => showBarModalBottomSheet(  
183 - expand: true,  
184 - context: context,  
185 - backgroundColor: Colors.transparent,  
186 - builder: (context, scrollController) =>  
187 - ModalInsideModal(  
188 - scrollController: scrollController,  
189 - reverse: true),  
190 - )),  
191 - ListTile(  
192 - title: Text('Cupertino Modal inside modal'),  
193 - onTap: () => showCupertinoModalBottomSheet(  
194 - expand: true,  
195 - context: context,  
196 - backgroundColor: Colors.transparent,  
197 - builder: (context, scrollController) =>  
198 - ModalInsideModal(  
199 - scrollController: scrollController),  
200 - )),  
201 - ListTile(  
202 - title: Text('Cupertino Modal with inside navigation'),  
203 - onTap: () => showCupertinoModalBottomSheet(  
204 - expand: true,  
205 - context: context,  
206 - backgroundColor: Colors.transparent,  
207 - builder: (context, scrollController) =>  
208 - ModalWithNavigator(  
209 - scrollController: scrollController),  
210 - )),  
211 - ListTile(  
212 - title:  
213 - Text('Cupertino Navigator + Scroll + WillPopScope'),  
214 - onTap: () => showCupertinoModalBottomSheet(  
215 - expand: true,  
216 - context: context,  
217 - backgroundColor: Colors.transparent,  
218 - builder: (context, scrollController) =>  
219 - ComplexModal(  
220 - scrollController: scrollController),  
221 - )),  
222 - ListTile(  
223 - title: Text('Modal with WillPopScope'),  
224 - onTap: () => showCupertinoModalBottomSheet(  
225 - expand: true,  
226 - context: context,  
227 - backgroundColor: Colors.transparent,  
228 - builder: (context, scrollController) =>  
229 - ModalWillScope(  
230 - scrollController: scrollController),  
231 - )),  
232 - ListTile(  
233 - title: Text('Modal with Nested Scroll'),  
234 - onTap: () => showCupertinoModalBottomSheet(  
235 - expand: true,  
236 - context: context,  
237 - builder: (context, scrollController) =>  
238 - NestedScrollModal(  
239 - scrollController: scrollController),  
240 - )),  
241 - ], 112 + child: SizedBox.expand(
  113 + child: SingleChildScrollView(
  114 + primary: true,
  115 + child: SafeArea(
  116 + bottom: false,
  117 + child: Column(
  118 + crossAxisAlignment: CrossAxisAlignment.stretch,
  119 + mainAxisSize: MainAxisSize.min,
  120 + children: <Widget>[
  121 + ListTile(
  122 + title: Text('Cupertino Photo Share Example'),
  123 + onTap: () => Navigator.of(context).push(
  124 + MaterialWithModalsPageRoute(
  125 + builder: (context) => CupertinoSharePage()))),
  126 + section('STYLES'),
  127 + ListTile(
  128 + title: Text('Material fit'),
  129 + onTap: () => showMaterialModalBottomSheet(
  130 + expand: false,
  131 + context: context,
  132 + backgroundColor: Colors.transparent,
  133 + builder: (context, scrollController) =>
  134 + ModalFit(scrollController: scrollController),
  135 + )),
  136 + ListTile(
  137 + title: Text('Bar Modal'),
  138 + onTap: () => showBarModalBottomSheet(
  139 + expand: true,
  140 + context: context,
  141 + backgroundColor: Colors.transparent,
  142 + builder: (context, scrollController) =>
  143 + ModalInsideModal(
  144 + scrollController: scrollController),
  145 + )),
  146 + ListTile(
  147 + title: Text('Avatar Modal'),
  148 + onTap: () => showAvatarModalBottomSheet(
  149 + expand: true,
  150 + context: context,
  151 + backgroundColor: Colors.transparent,
  152 + builder: (context, scrollController) =>
  153 + ModalInsideModal(
  154 + scrollController: scrollController),
  155 + )),
  156 + ListTile(
  157 + title: Text('Float Modal'),
  158 + onTap: () => showFloatingModalBottomSheet(
  159 + context: context,
  160 + builder: (context, scrollController) =>
  161 + ModalFit(scrollController: scrollController),
  162 + )),
  163 + ListTile(
  164 + title: Text('Cupertino Modal fit'),
  165 + onTap: () => showCupertinoModalBottomSheet(
  166 + expand: false,
  167 + context: context,
  168 + backgroundColor: Colors.transparent,
  169 + builder: (context, scrollController) =>
  170 + ModalFit(scrollController: scrollController),
  171 + )),
  172 + section('COMPLEX CASES'),
  173 + ListTile(
  174 + title: Text('Cupertino Small Modal forced to expand'),
  175 + onTap: () => showCupertinoModalBottomSheet(
  176 + expand: true,
  177 + context: context,
  178 + backgroundColor: Colors.transparent,
  179 + builder: (context, scrollController) =>
  180 + ModalFit(scrollController: scrollController),
  181 + )),
  182 + ListTile(
  183 + title: Text('Reverse list'),
  184 + onTap: () => showBarModalBottomSheet(
  185 + expand: true,
  186 + context: context,
  187 + backgroundColor: Colors.transparent,
  188 + builder: (context, scrollController) =>
  189 + ModalInsideModal(
  190 + scrollController: scrollController,
  191 + reverse: true),
  192 + )),
  193 + ListTile(
  194 + title: Text('Cupertino Modal inside modal'),
  195 + onTap: () => showCupertinoModalBottomSheet(
  196 + expand: true,
  197 + context: context,
  198 + backgroundColor: Colors.transparent,
  199 + builder: (context, scrollController) =>
  200 + ModalInsideModal(
  201 + scrollController: scrollController),
  202 + )),
  203 + ListTile(
  204 + title: Text('Cupertino Modal with inside navigation'),
  205 + onTap: () => showCupertinoModalBottomSheet(
  206 + expand: true,
  207 + context: context,
  208 + backgroundColor: Colors.transparent,
  209 + builder: (context, scrollController) =>
  210 + ModalWithNavigator(
  211 + scrollController: scrollController),
  212 + )),
  213 + ListTile(
  214 + title:
  215 + Text('Cupertino Navigator + Scroll + WillPopScope'),
  216 + onTap: () => showCupertinoModalBottomSheet(
  217 + expand: true,
  218 + context: context,
  219 + backgroundColor: Colors.transparent,
  220 + builder: (context, scrollController) =>
  221 + ComplexModal(
  222 + scrollController: scrollController),
  223 + )),
  224 + ListTile(
  225 + title: Text('Modal with WillPopScope'),
  226 + onTap: () => showCupertinoModalBottomSheet(
  227 + expand: true,
  228 + context: context,
  229 + backgroundColor: Colors.transparent,
  230 + builder: (context, scrollController) =>
  231 + ModalWillScope(
  232 + scrollController: scrollController),
  233 + )),
  234 + ListTile(
  235 + title: Text('Modal with Nested Scroll'),
  236 + onTap: () => showCupertinoModalBottomSheet(
  237 + expand: true,
  238 + context: context,
  239 + builder: (context, scrollController) =>
  240 + NestedScrollModal(
  241 + scrollController: scrollController),
  242 + )),
  243 + SizedBox(
  244 + height: 60,
  245 + )
  246 + ],
  247 + ),
242 ), 248 ),
243 ), 249 ),
244 ), 250 ),
@@ -10,6 +10,7 @@ import 'package:flutter/gestures.dart'; @@ -10,6 +10,7 @@ import 'package:flutter/gestures.dart';
10 import 'package:flutter/material.dart'; 10 import 'package:flutter/material.dart';
11 import 'package:flutter/scheduler.dart'; 11 import 'package:flutter/scheduler.dart';
12 import 'package:flutter/widgets.dart'; 12 import 'package:flutter/widgets.dart';
  13 +import 'package:modal_bottom_sheet/src/utils/primary_scroll_status_bar.dart';
13 14
14 const Duration _bottomSheetDuration = Duration(milliseconds: 400); 15 const Duration _bottomSheetDuration = Duration(milliseconds: 400);
15 const double _minFlingVelocity = 500.0; 16 const double _minFlingVelocity = 500.0;
@@ -249,7 +250,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -249,7 +250,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
249 if (scrollPosition.axis == Axis.horizontal) return; 250 if (scrollPosition.axis == Axis.horizontal) return;
250 251
251 //Check if scrollController is used 252 //Check if scrollController is used
252 - if (!_scrollController.hasClients) return; 253 + if (!_scrollController.hasClients) return;
253 254
254 final isScrollReversed = scrollPosition.axisDirection == AxisDirection.down; 255 final isScrollReversed = scrollPosition.axisDirection == AxisDirection.down;
255 final offset = isScrollReversed 256 final offset = isScrollReversed
@@ -334,36 +335,40 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -334,36 +335,40 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
334 curve: widget.animationCurve ?? Curves.linear, 335 curve: widget.animationCurve ?? Curves.linear,
335 ); 336 );
336 337
337 - return AnimatedBuilder(  
338 - animation: widget.animationController,  
339 - builder: (context, _) => ClipRect(  
340 - child: CustomSingleChildLayout(  
341 - delegate: _ModalBottomSheetLayout(  
342 - containerAnimation.value, widget.expanded),  
343 - child: !widget.enableDrag  
344 - ? child  
345 - : KeyedSubtree(  
346 - key: _childKey,  
347 - child: AnimatedBuilder(  
348 - animation: bounceAnimation,  
349 - builder: (context, _) => CustomSingleChildLayout(  
350 - delegate: _CustomBottomSheetLayout(bounceAnimation.value),  
351 - child: GestureDetector(  
352 - onVerticalDragUpdate: (details) =>  
353 - _handleDragUpdate(details.primaryDelta),  
354 - onVerticalDragEnd: (details) =>  
355 - _handleDragEnd(details.primaryVelocity),  
356 - child: NotificationListener<ScrollNotification>(  
357 - onNotification: (ScrollNotification notification) {  
358 - _handleScrollUpdate(notification);  
359 - return false;  
360 - },  
361 - child: child,  
362 - ), 338 + return PrimaryScrollStatusBarHandler(
  339 + scrollController: _scrollController,
  340 + child: AnimatedBuilder(
  341 + animation: widget.animationController,
  342 + builder: (context, _) => ClipRect(
  343 + child: CustomSingleChildLayout(
  344 + delegate: _ModalBottomSheetLayout(
  345 + containerAnimation.value, widget.expanded),
  346 + child: !widget.enableDrag
  347 + ? child
  348 + : KeyedSubtree(
  349 + key: _childKey,
  350 + child: AnimatedBuilder(
  351 + animation: bounceAnimation,
  352 + builder: (context, _) => CustomSingleChildLayout(
  353 + delegate:
  354 + _CustomBottomSheetLayout(bounceAnimation.value),
  355 + child: GestureDetector(
  356 + onVerticalDragUpdate: (details) =>
  357 + _handleDragUpdate(details.primaryDelta),
  358 + onVerticalDragEnd: (details) =>
  359 + _handleDragEnd(details.primaryVelocity),
  360 + child: NotificationListener<ScrollNotification>(
  361 + onNotification:
  362 + (ScrollNotification notification) {
  363 + _handleScrollUpdate(notification);
  364 + return false;
  365 + },
  366 + child: RepaintBoundary(child: child),
  367 + )),
363 ), 368 ),
364 ), 369 ),
365 ), 370 ),
366 - ), 371 + ),
367 ), 372 ),
368 ), 373 ),
369 ); 374 );
@@ -99,6 +99,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { @@ -99,6 +99,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
99 builder: widget.route.builder, 99 builder: widget.route.builder,
100 enableDrag: widget.enableDrag, 100 enableDrag: widget.enableDrag,
101 bounce: widget.bounce, 101 bounce: widget.bounce,
  102 + scrollController: widget.scrollController,
102 animationCurve: widget.animationCurve, 103 animationCurve: widget.animationCurve,
103 ), 104 ),
104 ); 105 );
@@ -10,8 +10,8 @@ import 'package:flutter/gestures.dart'; @@ -10,8 +10,8 @@ import 'package:flutter/gestures.dart';
10 import 'package:flutter/material.dart' 10 import 'package:flutter/material.dart'
11 show 11 show
12 Colors, 12 Colors,
13 - Theme,  
14 MaterialLocalizations, 13 MaterialLocalizations,
  14 + Theme,
15 debugCheckHasMaterialLocalizations; 15 debugCheckHasMaterialLocalizations;
16 import 'package:flutter/services.dart'; 16 import 'package:flutter/services.dart';
17 import 'package:flutter/widgets.dart'; 17 import 'package:flutter/widgets.dart';
@@ -99,31 +99,32 @@ Future<T> showCupertinoModalBottomSheet<T>({ @@ -99,31 +99,32 @@ Future<T> showCupertinoModalBottomSheet<T>({
99 ? MaterialLocalizations.of(context).modalBarrierDismissLabel 99 ? MaterialLocalizations.of(context).modalBarrierDismissLabel
100 : ''; 100 : '';
101 101
102 - final result = await Navigator.of(context, rootNavigator: useRootNavigator)  
103 - .push(CupertinoModalBottomSheetRoute<T>(  
104 - builder: builder,  
105 - containerBuilder: (context, _, child) => _CupertinoBottomSheetContainer(  
106 - child: child,  
107 - backgroundColor: backgroundColor,  
108 - topRadius: topRadius,  
109 - ),  
110 - secondAnimationController: secondAnimation,  
111 - expanded: expand,  
112 - barrierLabel: barrierLabel,  
113 - elevation: elevation,  
114 - bounce: bounce,  
115 - shape: shape,  
116 - clipBehavior: clipBehavior,  
117 - isDismissible: isDismissible ?? expand == false ? true : false,  
118 - modalBarrierColor: barrierColor ?? Colors.black12,  
119 - enableDrag: enableDrag,  
120 - topRadius: topRadius,  
121 - animationCurve: animationCurve,  
122 - previousRouteAnimationCurve: previousRouteAnimationCurve,  
123 - duration: duration,  
124 - settings: settings,  
125 - transitionBackgroundColor: transitionBackgroundColor ?? Colors.black  
126 - )); 102 + final result =
  103 + await Navigator.of(context, rootNavigator: useRootNavigator).push(
  104 + CupertinoModalBottomSheetRoute<T>(
  105 + builder: builder,
  106 + containerBuilder: (context, _, child) => _CupertinoBottomSheetContainer(
  107 + child: child,
  108 + backgroundColor: backgroundColor,
  109 + topRadius: topRadius,
  110 + ),
  111 + secondAnimationController: secondAnimation,
  112 + expanded: expand,
  113 + barrierLabel: barrierLabel,
  114 + elevation: elevation,
  115 + bounce: bounce,
  116 + shape: shape,
  117 + clipBehavior: clipBehavior,
  118 + isDismissible: isDismissible ?? expand == false ? true : false,
  119 + modalBarrierColor: barrierColor ?? Colors.black12,
  120 + enableDrag: enableDrag,
  121 + topRadius: topRadius,
  122 + animationCurve: animationCurve,
  123 + previousRouteAnimationCurve: previousRouteAnimationCurve,
  124 + duration: duration,
  125 + settings: settings,
  126 + transitionBackgroundColor: transitionBackgroundColor ?? Colors.black),
  127 + );
127 return result; 128 return result;
128 } 129 }
129 130
@@ -151,6 +152,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> { @@ -151,6 +152,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
151 @required bool expanded, 152 @required bool expanded,
152 Duration duration, 153 Duration duration,
153 RouteSettings settings, 154 RouteSettings settings,
  155 + ScrollController scrollController,
154 this.transitionBackgroundColor, 156 this.transitionBackgroundColor,
155 this.topRadius = _default_top_radius, 157 this.topRadius = _default_top_radius,
156 this.previousRouteAnimationCurve, 158 this.previousRouteAnimationCurve,
@@ -158,6 +160,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> { @@ -158,6 +160,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
158 assert(isDismissible != null), 160 assert(isDismissible != null),
159 assert(enableDrag != null), 161 assert(enableDrag != null),
160 super( 162 super(
  163 + scrollController: scrollController,
161 containerBuilder: containerBuilder, 164 containerBuilder: containerBuilder,
162 builder: builder, 165 builder: builder,
163 bounce: bounce, 166 bounce: bounce,
@@ -184,17 +187,18 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> { @@ -184,17 +187,18 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
184 (paddingTop + _behind_widget_visible_height) * 0.9; 187 (paddingTop + _behind_widget_visible_height) * 0.9;
185 final offsetY = secondaryAnimation.value * (paddingTop - distanceWithScale); 188 final offsetY = secondaryAnimation.value * (paddingTop - distanceWithScale);
186 final scale = 1 - secondaryAnimation.value / 10; 189 final scale = 1 - secondaryAnimation.value / 10;
187 - return AnimatedBuilder(  
188 - builder: (context, child) => Transform.translate(  
189 - offset: Offset(0, offsetY),  
190 - child: Transform.scale(  
191 - scale: scale,  
192 - child: child,  
193 - alignment: Alignment.topCenter, 190 + return AnimatedBuilder(
  191 + builder: (context, child) => Transform.translate(
  192 + offset: Offset(0, offsetY),
  193 + child: Transform.scale(
  194 + scale: scale,
  195 + child: child,
  196 + alignment: Alignment.topCenter,
  197 + ),
194 ), 198 ),
195 - ),  
196 - child: child,  
197 - animation: secondaryAnimation, 199 + child: child,
  200 + animation: secondaryAnimation,
  201 +
198 ); 202 );
199 } 203 }
200 204
@@ -243,37 +247,39 @@ class _CupertinoModalTransition extends StatelessWidget { @@ -243,37 +247,39 @@ class _CupertinoModalTransition extends StatelessWidget {
243 ); 247 );
244 248
245 return AnnotatedRegion<SystemUiOverlayStyle>( 249 return AnnotatedRegion<SystemUiOverlayStyle>(
246 - value: SystemUiOverlayStyle.light,  
247 - child: AnimatedBuilder(  
248 - animation: curvedAnimation,  
249 - child: body,  
250 - builder: (context, child) {  
251 - final progress = curvedAnimation.value;  
252 - final yOffset = progress * paddingTop;  
253 - final scale = 1 - progress / 10;  
254 - final radius = progress == 0  
255 - ? 0.0  
256 - : (1 - progress) * startRoundCorner + progress * topRadius.x;  
257 - return Stack(  
258 - children: <Widget>[  
259 - Container(color: backgroundColor),  
260 - Transform.translate(  
261 - offset: Offset(0, yOffset),  
262 - child: Transform.scale(  
263 - scale: scale,  
264 - alignment: Alignment.topCenter,  
265 - child: ClipRRect(  
266 - borderRadius: BorderRadius.circular(radius),  
267 - child: child),  
268 - ),  
269 - )  
270 - ],  
271 - );  
272 - },  
273 - )); 250 + value: SystemUiOverlayStyle.light,
  251 + child: AnimatedBuilder(
  252 + animation: curvedAnimation,
  253 + child: body,
  254 + builder: (context, child) {
  255 + final progress = curvedAnimation.value;
  256 + final yOffset = progress * paddingTop;
  257 + final scale = 1 - progress / 10;
  258 + final radius = progress == 0
  259 + ? 0.0
  260 + : (1 - progress) * startRoundCorner + progress * topRadius.x;
  261 + return Stack(
  262 + children: <Widget>[
  263 + Container(color: backgroundColor),
  264 + Transform.translate(
  265 + offset: Offset(0, yOffset),
  266 + child: Transform.scale(
  267 + scale: scale,
  268 + alignment: Alignment.topCenter,
  269 + child: ClipRRect(
  270 + borderRadius: BorderRadius.circular(radius),
  271 + child: child),
  272 + ),
  273 + ),
  274 + ],
  275 + );
  276 + },
  277 + ),
  278 + );
274 } 279 }
275 } 280 }
276 281
  282 +
277 class _CupertinoScaffold extends InheritedWidget { 283 class _CupertinoScaffold extends InheritedWidget {
278 final AnimationController animation; 284 final AnimationController animation;
279 285
  1 +import 'package:flutter/widgets.dart';
  2 +
  3 +/// Creates a primary scroll controller that will
  4 +/// scroll to the top when tapped on the status bar
  5 +///
  6 +class PrimaryScrollStatusBarHandler extends StatefulWidget {
  7 + final ScrollController scrollController;
  8 + final Widget child;
  9 +
  10 + const PrimaryScrollStatusBarHandler(
  11 + {Key key, this.child, this.scrollController})
  12 + : super(key: key);
  13 + @override
  14 + _PrimaryScrollWidgetState createState() => _PrimaryScrollWidgetState();
  15 +}
  16 +
  17 +class _PrimaryScrollWidgetState extends State<PrimaryScrollStatusBarHandler> {
  18 + ScrollController controller;
  19 +
  20 + @override
  21 + void initState() {
  22 + controller = widget.scrollController ?? ScrollController();
  23 + super.initState();
  24 + }
  25 +
  26 + @override
  27 + Widget build(BuildContext context) {
  28 + return PrimaryScrollController(
  29 + controller: controller,
  30 + child: Stack(
  31 + fit: StackFit.expand,
  32 + children: [
  33 + widget.child,
  34 + Positioned(
  35 + top: 0,
  36 + left: 0,
  37 + right: 0,
  38 + height: MediaQuery.of(context).padding.top,
  39 + child: Builder(
  40 + builder: (context) => GestureDetector(
  41 + behavior: HitTestBehavior.opaque,
  42 + onTap: () => _handleStatusBarTap(context),
  43 + // iOS accessibility automatically adds scroll-to-top to the clock in the status bar
  44 + excludeFromSemantics: true,
  45 + ),
  46 + ),
  47 + ),
  48 + ],
  49 + ),
  50 + );
  51 + }
  52 +
  53 + void _handleStatusBarTap(BuildContext context) {
  54 + final controller = PrimaryScrollController.of(context);
  55 + if (controller.hasClients) {
  56 + controller.animateTo(
  57 + 0.0,
  58 + duration: const Duration(milliseconds: 300),
  59 + curve: Curves.linear, // TODO(ianh): Use a more appropriate curve.
  60 + );
  61 + }
  62 + }
  63 +}