Jaime Blasco
Committed by GitHub

Merge pull request #37 from jamesblasco/scroll_top

Support scroll to top when tapped on the status bar
@@ -98,7 +98,8 @@ class _MyHomePageState extends State<MyHomePage> { @@ -98,7 +98,8 @@ 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( 101 + child: Scaffold(
  102 + body: CupertinoPageScaffold(
102 backgroundColor: Colors.white, 103 backgroundColor: Colors.white,
103 navigationBar: CupertinoNavigationBar( 104 navigationBar: CupertinoNavigationBar(
104 transitionBetweenRoutes: false, 105 transitionBetweenRoutes: false,
@@ -110,6 +111,7 @@ class _MyHomePageState extends State<MyHomePage> { @@ -110,6 +111,7 @@ class _MyHomePageState extends State<MyHomePage> {
110 ), 111 ),
111 child: SizedBox.expand( 112 child: SizedBox.expand(
112 child: SingleChildScrollView( 113 child: SingleChildScrollView(
  114 + primary: true,
113 child: SafeArea( 115 child: SafeArea(
114 bottom: false, 116 bottom: false,
115 child: Column( 117 child: Column(
@@ -238,12 +240,16 @@ class _MyHomePageState extends State<MyHomePage> { @@ -238,12 +240,16 @@ class _MyHomePageState extends State<MyHomePage> {
238 NestedScrollModal( 240 NestedScrollModal(
239 scrollController: scrollController), 241 scrollController: scrollController),
240 )), 242 )),
  243 + SizedBox(
  244 + height: 60,
  245 + )
241 ], 246 ],
242 ), 247 ),
243 ), 248 ),
244 ), 249 ),
245 ), 250 ),
246 ), 251 ),
  252 + ),
247 ); 253 );
248 } 254 }
249 255
@@ -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;
@@ -334,7 +335,9 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -334,7 +335,9 @@ 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 + return PrimaryScrollStatusBarHandler(
  339 + scrollController: _scrollController,
  340 + child: AnimatedBuilder(
338 animation: widget.animationController, 341 animation: widget.animationController,
339 builder: (context, _) => ClipRect( 342 builder: (context, _) => ClipRect(
340 child: CustomSingleChildLayout( 343 child: CustomSingleChildLayout(
@@ -347,19 +350,21 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -347,19 +350,21 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
347 child: AnimatedBuilder( 350 child: AnimatedBuilder(
348 animation: bounceAnimation, 351 animation: bounceAnimation,
349 builder: (context, _) => CustomSingleChildLayout( 352 builder: (context, _) => CustomSingleChildLayout(
350 - delegate: _CustomBottomSheetLayout(bounceAnimation.value), 353 + delegate:
  354 + _CustomBottomSheetLayout(bounceAnimation.value),
351 child: GestureDetector( 355 child: GestureDetector(
352 onVerticalDragUpdate: (details) => 356 onVerticalDragUpdate: (details) =>
353 _handleDragUpdate(details.primaryDelta), 357 _handleDragUpdate(details.primaryDelta),
354 onVerticalDragEnd: (details) => 358 onVerticalDragEnd: (details) =>
355 _handleDragEnd(details.primaryVelocity), 359 _handleDragEnd(details.primaryVelocity),
356 child: NotificationListener<ScrollNotification>( 360 child: NotificationListener<ScrollNotification>(
357 - onNotification: (ScrollNotification notification) { 361 + onNotification:
  362 + (ScrollNotification notification) {
358 _handleScrollUpdate(notification); 363 _handleScrollUpdate(notification);
359 return false; 364 return false;
360 }, 365 },
361 - child: child,  
362 - ), 366 + child: RepaintBoundary(child: child),
  367 + )),
363 ), 368 ),
364 ), 369 ),
365 ), 370 ),
@@ -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,8 +99,9 @@ Future<T> showCupertinoModalBottomSheet<T>({ @@ -99,8 +99,9 @@ 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>( 102 + final result =
  103 + await Navigator.of(context, rootNavigator: useRootNavigator).push(
  104 + CupertinoModalBottomSheetRoute<T>(
104 builder: builder, 105 builder: builder,
105 containerBuilder: (context, _, child) => _CupertinoBottomSheetContainer( 106 containerBuilder: (context, _, child) => _CupertinoBottomSheetContainer(
106 child: child, 107 child: child,
@@ -122,8 +123,8 @@ Future<T> showCupertinoModalBottomSheet<T>({ @@ -122,8 +123,8 @@ Future<T> showCupertinoModalBottomSheet<T>({
122 previousRouteAnimationCurve: previousRouteAnimationCurve, 123 previousRouteAnimationCurve: previousRouteAnimationCurve,
123 duration: duration, 124 duration: duration,
124 settings: settings, 125 settings: settings,
125 - transitionBackgroundColor: transitionBackgroundColor ?? Colors.black  
126 - )); 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,
@@ -195,6 +198,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> { @@ -195,6 +198,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
195 ), 198 ),
196 child: child, 199 child: child,
197 animation: secondaryAnimation, 200 animation: secondaryAnimation,
  201 +
198 ); 202 );
199 } 203 }
200 204
@@ -266,14 +270,16 @@ class _CupertinoModalTransition extends StatelessWidget { @@ -266,14 +270,16 @@ class _CupertinoModalTransition extends StatelessWidget {
266 borderRadius: BorderRadius.circular(radius), 270 borderRadius: BorderRadius.circular(radius),
267 child: child), 271 child: child),
268 ), 272 ),
269 - ) 273 + ),
270 ], 274 ],
271 ); 275 );
272 }, 276 },
273 - )); 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 +}