bierbaumtim

Merge branch 'jamesblasco-master'

@@ -12,4 +12,10 @@ @@ -12,4 +12,10 @@
12 ## [0.1.6] - New custom params 12 ## [0.1.6] - New custom params
13 - Use `duration` to define the opening duration of the modal 13 - Use `duration` to define the opening duration of the modal
14 - Change the top radius of the cupertino bottom sheet 14 - Change the top radius of the cupertino bottom sheet
15 -Thanks to @bierbaumtim @troyanskiy @rodineijf for the contributions  
  15 +Thanks to @bierbaumtim @troyanskiy @rodineijf for the contributions
  16 +
  17 +
  18 +## [0.2.0] - New Cool Features
  19 +- Added support for scroll-to-top by tapping the status bar on iOS devices.
  20 +- Use `curveAnimation` to define a custom curve animation for the modal transition
  21 +- Bug fixes releated to horizontal scroll, clamping physics and othes.
1 -<a href="https://jamesblasco.github.io/modal_bottom_sheet/#/"><img src="https://github.com/jamesblasco/modal_bottom_sheet/blob/master/screenshots/preview.png?raw=true"></a> 1 +<a href="https://jamesblasco.github.io/modal_bottom_sheet/#/"><img src="https://github.com/jamesblasco/modal_bottom_sheet/blob/screenshots/preview.png?raw=true"></a>
2 2
3 # Flutter Modal Bottom Sheet 3 # Flutter Modal Bottom Sheet
4 4
@@ -9,16 +9,22 @@ Create awesome and powerful modal bottom sheets. @@ -9,16 +9,22 @@ Create awesome and powerful modal bottom sheets.
9 9
10 | Cupertino Modal | Multiple Modals | Material Modal | Bar Modal | Create your own | 10 | Cupertino Modal | Multiple Modals | Material Modal | Bar Modal | Create your own |
11 |---|---|---|---|---| 11 |---|---|---|---|---|
12 -|<img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/master/screenshots/cupertino_shared_view.gif?raw=true">| <img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/master/screenshots/modal_inside_modal.gif?raw=true">| <img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/master/screenshots/material_fit.png?raw=true">|<img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/master/screenshots/bar_modal.png?raw=true">| <img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/master/screenshots/avatar_modal.png?raw=true">| 12 +|<img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/screenshots/cupertino_shared_view.gif?raw=true">| <img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/screenshots/modal_inside_modal.gif?raw=true">| <img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/screenshots/material_fit.png?raw=true">|<img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/screenshots/bar_modal.png?raw=true">| <img height="300" src="https://github.com/jamesblasco/modal_bottom_sheet/blob/screenshots/avatar_modal.png?raw=true">|
13 13
14 ## Try it 14 ## Try it
15 15
16 Explore the [Web Demo](https://jamesblasco.github.io/modal_bottom_sheet/#/) or clone the repository. 16 Explore the [Web Demo](https://jamesblasco.github.io/modal_bottom_sheet/#/) or clone the repository.
17 17
18 -Known problems on web demo:  
19 -- Web demo can run very slow on mobile devides. 18 +Why not `showModalBottomSheet`?
  19 +
  20 +Inspired by `showModalBottomSheet`, it completes with some must-need features:
  21 +
  22 +- Support for inside scrollview + dragging down to close (`showModalBottomSheet` won't work correctly with scrollviews.
  23 +- Support for `WillPopScope` to prevent closing the dialog.
  24 +- Support for scroll to top when tapping status bar (iOS only)
  25 +- Cupertino modal bottom sheet
  26 +- Create custom modal bottom sheet
20 27
21 -- Fake status bar doesn't change color as the iOS, Android app  
22 28
23 ## First Steps 29 ## First Steps
24 30
@@ -33,11 +39,6 @@ showMaterialModalBottomSheet( @@ -33,11 +39,6 @@ showMaterialModalBottomSheet(
33 builder: (context, scrollController) => Container(), 39 builder: (context, scrollController) => Container(),
34 ) 40 )
35 ``` 41 ```
36 -What to use this over flutter `showModalBottomSheet`?  
37 -  
38 -`showMaterialModalBottomSheet` supports closing bottoms sheets by dragging down even if there is a scrollview inside.  
39 -`showModalBottomSheet` won't work correctly with scrollviews.  
40 -Also it supports `WillPopScope` to prevent closing the dialog  
41 42
42 #### Generic params for all modal bottom sheets 43 #### Generic params for all modal bottom sheets
43 44
@@ -78,15 +79,17 @@ Useful if you want a blurred transparent background as the example Cupertino Pho @@ -78,15 +79,17 @@ Useful if you want a blurred transparent background as the example Cupertino Pho
78 > **Why?** 79 > **Why?**
79 > `MaterialPageRoute` and `CupertinoPageRoute` do not allow animated translation to/from routes that are not the same type. 80 > `MaterialPageRoute` and `CupertinoPageRoute` do not allow animated translation to/from routes that are not the same type.
80 81
81 -There are two options:  
82 -  
83 -### OPTION 1. Recommended.  
84 82
85 Replace your current route class with `MaterialWithModalsPageRoute`. 83 Replace your current route class with `MaterialWithModalsPageRoute`.
86 84
  85 +
87 Notice this route type behaves the same as `MaterialPageRoute` and supports custom `PageTransitionsBuilder` and `PageTransitionsTheme`. 86 Notice this route type behaves the same as `MaterialPageRoute` and supports custom `PageTransitionsBuilder` and `PageTransitionsTheme`.
88 87
89 -How can I change my route class? See cases: 88 +
  89 +
  90 +<details><summary>
  91 + How can I replace my current route? </summary>
  92 +
90 93
91 <details><summary> 1. 94 <details><summary> 1.
92 95
@@ -145,9 +148,12 @@ Unfortunately this parameter uses `MaterialPageRoute` and `CupertinoPageRoute` r @@ -145,9 +148,12 @@ Unfortunately this parameter uses `MaterialPageRoute` and `CupertinoPageRoute` r
145 You can modify the way you call the previous route with one of the previous methods or try option 2 148 You can modify the way you call the previous route with one of the previous methods or try option 2
146 149
147 </details> 150 </details>
148 - 151 + </details>
  152 +
  153 +Is there an alternative in case I can't change my current route? **Yes!**
  154 +<details><summary>
  155 + Learn how to animate previous route with CupertinoScaffold: </summary>
149 156
150 -### OPTION 2.  
151 157
152 1. Wrap previous route inside a `CupertinoScaffold`. 158 1. Wrap previous route inside a `CupertinoScaffold`.
153 Example with `routes` parameter from `MaterialApp` or `CupertinoApp` 159 Example with `routes` parameter from `MaterialApp` or `CupertinoApp`
@@ -162,13 +168,15 @@ You can modify the way you call the previous route with one of the previous meth @@ -162,13 +168,15 @@ You can modify the way you call the previous route with one of the previous meth
162 CupertinoScaffold.showCupertinoModalBottomSheet(context:context, builder: (context) => Container()) 168 CupertinoScaffold.showCupertinoModalBottomSheet(context:context, builder: (context) => Container())
163 ``` 169 ```
164 170
165 -These two options won't work correctly together. 171 +Don't use this solution at the same time as `MaterialWithModalsPageRoute`
  172 +
  173 + </details>
166 174
167 It supports native features as bouncing, blurred background, dark mode, stacking modals and inside navigation. 175 It supports native features as bouncing, blurred background, dark mode, stacking modals and inside navigation.
168 176
169 ## Push new views inside the modal bottom sheet 177 ## Push new views inside the modal bottom sheet
170 178
171 -a. If you want to push a new modal bottom sheet just call `showCupertinoModalBottomSheet` again (works with two option) 179 +a. If you want to push a new modal bottom sheet just call `showCupertinoModalBottomSheet` again (works with both options)
172 180
173 b. For inside navigaton add a new `Navigator` or `CupertinoTabScaffold` inside 181 b. For inside navigaton add a new `Navigator` or `CupertinoTabScaffold` inside
174 182
@@ -193,7 +201,7 @@ Check in the example project `showAvatarModalBottomSheet` for how to create your @@ -193,7 +201,7 @@ Check in the example project `showAvatarModalBottomSheet` for how to create your
193 201
194 - [X] Support closing by dragging fast on a modal with a scroll view. 202 - [X] Support closing by dragging fast on a modal with a scroll view.
195 203
196 -- [ ] Improve animation curves when user is not dragging. 204 +- [X] Improve animation curves when user is not dragging.
197 205
198 - [ ] Allow to set the initial size of the bottom sheet 206 - [ ] Allow to set the initial size of the bottom sheet
199 207
@@ -11,6 +11,7 @@ import 'modals/modal_fit.dart'; @@ -11,6 +11,7 @@ import 'modals/modal_fit.dart';
11 import 'modals/modal_inside_modal.dart'; 11 import 'modals/modal_inside_modal.dart';
12 import 'modals/modal_will_scope.dart'; 12 import 'modals/modal_will_scope.dart';
13 import 'modals/modal_with_navigator.dart'; 13 import 'modals/modal_with_navigator.dart';
  14 +import 'modals/modal_with_nested_scroll.dart';
14 import 'modals/modal_with_scroll.dart'; 15 import 'modals/modal_with_scroll.dart';
15 16
16 import 'examples/cupertino_share.dart'; 17 import 'examples/cupertino_share.dart';
@@ -97,124 +98,153 @@ class _MyHomePageState extends State<MyHomePage> { @@ -97,124 +98,153 @@ class _MyHomePageState extends State<MyHomePage> {
97 Widget build(BuildContext context) { 98 Widget build(BuildContext context) {
98 print(MediaQuery.of(context).size.height); 99 print(MediaQuery.of(context).size.height);
99 return Material( 100 return Material(
100 - child: CupertinoPageScaffold(  
101 - backgroundColor: Colors.white,  
102 - navigationBar: CupertinoNavigationBar(  
103 - transitionBetweenRoutes: false,  
104 - middle: Text('iOS13 Modal Presentation'),  
105 - trailing: GestureDetector(  
106 - child: Icon(Icons.arrow_forward),  
107 - 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 + ),
108 ), 111 ),
109 - ),  
110 - child: SizedBox.expand(  
111 - child: SingleChildScrollView(  
112 - child: SafeArea(  
113 - bottom: false,  
114 - child: Column(  
115 - mainAxisSize: MainAxisSize.min,  
116 - children: <Widget>[  
117 - ListTile(  
118 - title: Text('Cupertino Photo Share Example'),  
119 - onTap: () => Navigator.of(context).push(  
120 - MaterialWithModalsPageRoute(  
121 - builder: (context) => CupertinoSharePage()))),  
122 - ListTile(  
123 - title: Text('Material fit'),  
124 - onTap: () => showMaterialModalBottomSheet(  
125 - expand: false,  
126 - context: context,  
127 - backgroundColor: Colors.transparent,  
128 - builder: (context, scrollController) =>  
129 - ModalFit(scrollController: scrollController),  
130 - )),  
131 - ListTile(  
132 - title: Text('Bar Modal'),  
133 - onTap: () => showBarModalBottomSheet(  
134 - expand: true,  
135 - context: context,  
136 - backgroundColor: Colors.transparent,  
137 - builder: (context, scrollController) =>  
138 - ModalInsideModal(  
139 - scrollController: scrollController),  
140 - )),  
141 - ListTile(  
142 - title: Text('Avatar Modal'),  
143 - onTap: () => showAvatarModalBottomSheet(  
144 - expand: true,  
145 - context: context,  
146 - backgroundColor: Colors.transparent,  
147 - builder: (context, scrollController) =>  
148 - ModalInsideModal(  
149 - scrollController: scrollController),  
150 - )),  
151 - ListTile(  
152 - title: Text('Float Modal'),  
153 - onTap: () => showFloatingModalBottomSheet(  
154 - context: context,  
155 - builder: (context, scrollController) =>  
156 - ModalFit(scrollController: scrollController),  
157 - )),  
158 - ListTile(  
159 - title: Text('Cupertino Modal fit'),  
160 - onTap: () => showCupertinoModalBottomSheet(  
161 - expand: false,  
162 - context: context,  
163 - backgroundColor: Colors.transparent,  
164 - builder: (context, scrollController) =>  
165 - ModalFit(scrollController: scrollController),  
166 - )),  
167 - ListTile(  
168 - title: Text('Cupertino Small Modal forced to expand'),  
169 - onTap: () => showCupertinoModalBottomSheet(  
170 - expand: true,  
171 - context: context,  
172 - backgroundColor: Colors.transparent,  
173 - builder: (context, scrollController) =>  
174 - ModalFit(scrollController: scrollController),  
175 - )),  
176 - ListTile(  
177 - title: Text('Cupertino Modal inside modal'),  
178 - onTap: () => showCupertinoModalBottomSheet(  
179 - expand: true,  
180 - context: context,  
181 - backgroundColor: Colors.transparent,  
182 - builder: (context, scrollController) =>  
183 - ModalInsideModal(  
184 - scrollController: scrollController),  
185 - )),  
186 - ListTile(  
187 - title: Text('Cupertino Modal with inside navigation'),  
188 - onTap: () => showCupertinoModalBottomSheet(  
189 - expand: true,  
190 - context: context,  
191 - backgroundColor: Colors.transparent,  
192 - builder: (context, scrollController) =>  
193 - ModalWithNavigator(  
194 - scrollController: scrollController),  
195 - )),  
196 - ListTile(  
197 - title:  
198 - Text('Cupertino Navigator + Scroll + WillPopScope'),  
199 - onTap: () => showCupertinoModalBottomSheet(  
200 - expand: true,  
201 - context: context,  
202 - backgroundColor: Colors.transparent,  
203 - builder: (context, scrollController) =>  
204 - ComplexModal(  
205 - scrollController: scrollController),  
206 - )),  
207 - ListTile(  
208 - title: Text('Cupertino Modal with WillPopScope'),  
209 - onTap: () => showCupertinoModalBottomSheet(  
210 - expand: true,  
211 - context: context,  
212 - backgroundColor: Colors.transparent,  
213 - builder: (context, scrollController) =>  
214 - ModalWillScope(  
215 - scrollController: scrollController),  
216 - )),  
217 - ], 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 + ),
218 ), 248 ),
219 ), 249 ),
220 ), 250 ),
@@ -222,4 +252,13 @@ class _MyHomePageState extends State<MyHomePage> { @@ -222,4 +252,13 @@ class _MyHomePageState extends State<MyHomePage> {
222 ), 252 ),
223 ); 253 );
224 } 254 }
  255 +
  256 + Widget section(String title) {
  257 + return Padding(
  258 + padding: EdgeInsets.fromLTRB(16, 20, 16, 8),
  259 + child: Text(
  260 + title,
  261 + style: Theme.of(context).textTheme.caption,
  262 + ));
  263 + }
225 } 264 }
@@ -4,8 +4,10 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; @@ -4,8 +4,10 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
4 4
5 class ModalInsideModal extends StatelessWidget { 5 class ModalInsideModal extends StatelessWidget {
6 final ScrollController scrollController; 6 final ScrollController scrollController;
  7 + final bool reverse;
7 8
8 - const ModalInsideModal({Key key, this.scrollController}) : super(key: key); 9 + const ModalInsideModal({Key key, this.scrollController, this.reverse = false})
  10 + : super(key: key);
9 11
10 @override 12 @override
11 Widget build(BuildContext context) { 13 Widget build(BuildContext context) {
@@ -16,15 +18,16 @@ class ModalInsideModal extends StatelessWidget { @@ -16,15 +18,16 @@ class ModalInsideModal extends StatelessWidget {
16 child: SafeArea( 18 child: SafeArea(
17 bottom: false, 19 bottom: false,
18 child: ListView( 20 child: ListView(
  21 + reverse: reverse,
19 shrinkWrap: true, 22 shrinkWrap: true,
20 controller: scrollController, 23 controller: scrollController,
21 - physics: BouncingScrollPhysics(), 24 + physics: ClampingScrollPhysics(),
22 children: ListTile.divideTiles( 25 children: ListTile.divideTiles(
23 context: context, 26 context: context,
24 tiles: List.generate( 27 tiles: List.generate(
25 100, 28 100,
26 (index) => ListTile( 29 (index) => ListTile(
27 - title: Text('Item'), 30 + title: Text('Item $index'),
28 onTap: () => showCupertinoModalBottomSheet( 31 onTap: () => showCupertinoModalBottomSheet(
29 expand: true, 32 expand: true,
30 isDismissible: false, 33 isDismissible: false,
@@ -32,7 +35,8 @@ class ModalInsideModal extends StatelessWidget { @@ -32,7 +35,8 @@ class ModalInsideModal extends StatelessWidget {
32 backgroundColor: Colors.transparent, 35 backgroundColor: Colors.transparent,
33 builder: (context, scrollController) => 36 builder: (context, scrollController) =>
34 ModalInsideModal( 37 ModalInsideModal(
35 - scrollController: scrollController), 38 + scrollController: scrollController,
  39 + reverse: reverse),
36 )), 40 )),
37 )).toList(), 41 )).toList(),
38 ), 42 ),
  1 +import 'package:flutter/cupertino.dart';
  2 +import 'package:flutter/material.dart';
  3 +
  4 +class NestedScrollModal extends StatelessWidget {
  5 + final ScrollController scrollController;
  6 +
  7 + const NestedScrollModal({Key key, this.scrollController}) : super(key: key);
  8 +
  9 + @override
  10 + Widget build(BuildContext context) {
  11 + return NestedScrollView(
  12 + controller: ScrollController(),
  13 + physics: ScrollPhysics(parent: PageScrollPhysics()),
  14 + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  15 + return <Widget>[
  16 + SliverList(
  17 + delegate: SliverChildListDelegate(
  18 + [
  19 + Container(height: 300, color: Colors.blue),
  20 + ],
  21 + ),
  22 + ),
  23 + ];
  24 + },
  25 + body: ListView.builder(
  26 + controller: scrollController,
  27 + itemBuilder: (context, index) {
  28 + return Container(
  29 + height: 100,
  30 + color: index.isOdd ? Colors.green : Colors.orange,
  31 + );
  32 + },
  33 + itemCount: 12,
  34 + ),
  35 + );
  36 + }
  37 +}
@@ -10,10 +10,16 @@ import 'package:flutter/gestures.dart'; @@ -10,10 +10,16 @@ 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
  15 +import 'package:modal_bottom_sheet/src/utils/bottom_sheet_suspended_curve.dart';
  16 +
  17 +
  18 +const Curve _decelerateEasing = Cubic(0.0, 0.0, 0.2, 1.0);
  19 +const Curve _modalBottomSheetCurve = _decelerateEasing;
14 const Duration _bottomSheetDuration = Duration(milliseconds: 400); 20 const Duration _bottomSheetDuration = Duration(milliseconds: 400);
15 const double _minFlingVelocity = 500.0; 21 const double _minFlingVelocity = 500.0;
16 -const double _closeProgressThreshold = 0.5; 22 +const double _closeProgressThreshold = 0.6;
17 const double _willPopThreshold = 0.8; 23 const double _willPopThreshold = 0.8;
18 24
19 typedef ScrollWidgetBuilder = Widget Function( 25 typedef ScrollWidgetBuilder = Widget Function(
@@ -61,7 +67,7 @@ class ModalBottomSheet extends StatefulWidget { @@ -61,7 +67,7 @@ class ModalBottomSheet extends StatefulWidget {
61 67
62 /// The curve used by the animation showing and dismissing the bottom sheet. 68 /// The curve used by the animation showing and dismissing the bottom sheet.
63 /// 69 ///
64 - /// If no curve is provided it falls back to `Curves.easeOutSine`. 70 + /// If no curve is provided it falls back to `decelerateEasing`.
65 final Curve animationCurve; 71 final Curve animationCurve;
66 72
67 /// Allows the bottom sheet to go beyond the top bound of the content, 73 /// Allows the bottom sheet to go beyond the top bound of the content,
@@ -69,6 +75,8 @@ class ModalBottomSheet extends StatefulWidget { @@ -69,6 +75,8 @@ class ModalBottomSheet extends StatefulWidget {
69 /// the top bound. 75 /// the top bound.
70 final bool bounce; 76 final bool bounce;
71 77
  78 + // Force the widget to fill the maximum size of the viewport
  79 + // or if false it will fit to the content of the widget
72 final bool expanded; 80 final bool expanded;
73 81
74 final WidgetWithChildBuilder containerBuilder; 82 final WidgetWithChildBuilder containerBuilder;
@@ -174,7 +182,10 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -174,7 +182,10 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
174 return result; 182 return result;
175 } 183 }
176 184
  185 + ParametricCurve<double> animationCurve;
  186 +
177 void _handleDragUpdate(double primaryDelta) async { 187 void _handleDragUpdate(double primaryDelta) async {
  188 + animationCurve = Curves.linear;
178 assert(widget.enableDrag, 'Dragging is disabled'); 189 assert(widget.enableDrag, 'Dragging is disabled');
179 190
180 if (_dismissUnderway) return; 191 if (_dismissUnderway) return;
@@ -208,6 +219,11 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -208,6 +219,11 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
208 void _handleDragEnd(double velocity) async { 219 void _handleDragEnd(double velocity) async {
209 assert(widget.enableDrag, 'Dragging is disabled'); 220 assert(widget.enableDrag, 'Dragging is disabled');
210 221
  222 + animationCurve = BottomSheetSuspendedCurve(
  223 + widget.animationController.value,
  224 + curve: _defaultCurve,
  225 + );
  226 +
211 if (_dismissUnderway || !isDragging) return; 227 if (_dismissUnderway || !isDragging) return;
212 isDragging = false; 228 isDragging = false;
213 _bounceDragController.reverse(); 229 _bounceDragController.reverse();
@@ -242,18 +258,45 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -242,18 +258,45 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
242 DateTime _startTime; 258 DateTime _startTime;
243 259
244 void _handleScrollUpdate(ScrollNotification notification) { 260 void _handleScrollUpdate(ScrollNotification notification) {
245 - if (notification.metrics.pixels <= notification.metrics.minScrollExtent) {  
246 - //Check if listener is same from scrollController  
247 - if (!_scrollController.hasClients) return; 261 + final scrollPosition = _scrollController.position;
  262 +
  263 + if (scrollPosition.axis == Axis.horizontal) return;
  264 +
  265 + //Check if scrollController is used
  266 + if (!_scrollController.hasClients) return;
  267 +
  268 + final isScrollReversed = scrollPosition.axisDirection == AxisDirection.down;
  269 + final offset = isScrollReversed
  270 + ? scrollPosition.pixels
  271 + : scrollPosition.maxScrollExtent - scrollPosition.pixels;
248 272
249 - if (_scrollController.position.pixels != notification.metrics.pixels) { 273 + if (offset <= 0) {
  274 + // Check if listener is same from scrollController.
  275 + // TODO: Improve the way it checks if it the same view controller
  276 + // Use PrimaryScrollController
  277 + if (_scrollController.position.pixels != notification.metrics.pixels &&
  278 + !(_scrollController.position.pixels == 0 &&
  279 + notification.metrics.pixels >= 0)) {
250 return; 280 return;
251 } 281 }
252 - DragUpdateDetails dragDetails;  
253 - if (notification is ScrollStartNotification) { 282 + // Clamping Scroll Physics end with a ScrollEndNotification with a DragEndDetail class
  283 + // while Bouncing Scroll Physics or other physics that Overflow don't return a drag end info
  284 +
  285 + // We use the velocity from DragEndDetail in case it is available
  286 + if (notification is ScrollEndNotification &&
  287 + notification.dragDetails != null) {
  288 + _handleDragEnd(notification.dragDetails.primaryVelocity);
  289 + _velocityTracker = null;
  290 + _startTime = null;
  291 + return;
  292 + }
  293 +
  294 +// Otherwise the calculate the velocity with a VelocityTracker
  295 + if (_velocityTracker == null) {
254 _velocityTracker = VelocityTracker(); 296 _velocityTracker = VelocityTracker();
255 _startTime = DateTime.now(); 297 _startTime = DateTime.now();
256 } 298 }
  299 + DragUpdateDetails dragDetails;
257 if (notification is ScrollUpdateNotification) { 300 if (notification is ScrollUpdateNotification) {
258 dragDetails = notification.dragDetails; 301 dragDetails = notification.dragDetails;
259 } 302 }
@@ -262,18 +305,23 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -262,18 +305,23 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
262 } 305 }
263 if (dragDetails != null) { 306 if (dragDetails != null) {
264 final duration = _startTime.difference(DateTime.now()); 307 final duration = _startTime.difference(DateTime.now());
265 - final offset = Offset(0, _scrollController.offset);  
266 - _velocityTracker.addPosition(duration, offset); 308 + _velocityTracker.addPosition(duration, Offset(0, offset));
267 _handleDragUpdate(dragDetails.primaryDelta); 309 _handleDragUpdate(dragDetails.primaryDelta);
268 } else if (isDragging) { 310 } else if (isDragging) {
269 final velocity = _velocityTracker.getVelocity().pixelsPerSecond.dy; 311 final velocity = _velocityTracker.getVelocity().pixelsPerSecond.dy;
  312 + _velocityTracker = null;
  313 + _startTime = null;
270 _handleDragEnd(velocity); 314 _handleDragEnd(velocity);
271 } 315 }
272 } 316 }
273 } 317 }
274 318
  319 + ParametricCurve<double> get _defaultCurve =>
  320 + widget.animationCurve ?? _modalBottomSheetCurve;
  321 +
275 @override 322 @override
276 void initState() { 323 void initState() {
  324 + animationCurve = _defaultCurve;
277 _bounceDragController = 325 _bounceDragController =
278 AnimationController(vsync: this, duration: Duration(milliseconds: 300)); 326 AnimationController(vsync: this, duration: Duration(milliseconds: 300));
279 _scrollController = widget.scrollController ?? ScrollController(); 327 _scrollController = widget.scrollController ?? ScrollController();
@@ -285,7 +333,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -285,7 +333,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
285 Widget build(BuildContext context) { 333 Widget build(BuildContext context) {
286 final bounceAnimation = CurvedAnimation( 334 final bounceAnimation = CurvedAnimation(
287 parent: _bounceDragController, 335 parent: _bounceDragController,
288 - curve: widget.animationCurve ?? Curves.easeOutSine, 336 + curve: Curves.easeOutSine,
289 ); 337 );
290 338
291 var child = widget.builder(context, _scrollController); 339 var child = widget.builder(context, _scrollController);
@@ -298,45 +346,57 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -298,45 +346,57 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
298 ); 346 );
299 } 347 }
300 348
301 - // Todo: Add curved Animation when push and pop without gesture  
302 - /* final Animation<double> containerAnimation = CurvedAnimation(  
303 - parent: widget.animationController,  
304 - curve: Curves.easeOut,  
305 - );*/ 349 + final mediaQuery = MediaQuery.of(context);
306 350
307 - return AnimatedBuilder( 351 + child = AnimatedBuilder(
308 animation: widget.animationController, 352 animation: widget.animationController,
309 - builder: (context, _) => ClipRect(  
310 - child: CustomSingleChildLayout(  
311 - delegate: _ModalBottomSheetLayout(  
312 - widget.animationController.value, widget.expanded),  
313 - child: !widget.enableDrag  
314 - ? child  
315 - : KeyedSubtree(  
316 - key: _childKey,  
317 - child: AnimatedBuilder(  
318 - animation: bounceAnimation,  
319 - builder: (context, _) => CustomSingleChildLayout(  
320 - delegate: _CustomBottomSheetLayout(bounceAnimation.value),  
321 - child: GestureDetector(  
322 - onVerticalDragUpdate: (details) =>  
323 - _handleDragUpdate(details.primaryDelta),  
324 - onVerticalDragEnd: (details) =>  
325 - _handleDragEnd(details.primaryVelocity),  
326 - child: NotificationListener<ScrollNotification>(  
327 - onNotification: (ScrollNotification notification) {  
328 - _handleScrollUpdate(notification);  
329 - return false;  
330 - },  
331 - child: child,  
332 - ), 353 + builder: (context, child) {
  354 + final animationValue = animationCurve.transform(
  355 + mediaQuery.accessibleNavigation
  356 + ? 1.0
  357 + : widget.animationController.value);
  358 +
  359 + final draggableChild = !widget.enableDrag
  360 + ? child
  361 + : KeyedSubtree(
  362 + key: _childKey,
  363 + child: AnimatedBuilder(
  364 + animation: bounceAnimation,
  365 + builder: (context, _) => CustomSingleChildLayout(
  366 + delegate: _CustomBottomSheetLayout(bounceAnimation.value),
  367 + child: GestureDetector(
  368 + onVerticalDragUpdate: (details) {
  369 + _handleDragUpdate(details.primaryDelta);
  370 + },
  371 + onVerticalDragEnd: (details) {
  372 + _handleDragEnd(details.primaryVelocity);
  373 + },
  374 + child: NotificationListener<ScrollNotification>(
  375 + onNotification: (ScrollNotification notification) {
  376 + _handleScrollUpdate(notification);
  377 + return false;
  378 + },
  379 + child: child,
333 ), 380 ),
334 ), 381 ),
335 ), 382 ),
336 ), 383 ),
337 - ),  
338 - ), 384 + );
  385 + return ClipRect(
  386 + child: CustomSingleChildLayout(
  387 + delegate: _ModalBottomSheetLayout(
  388 + animationValue,
  389 + widget.expanded,
  390 + ),
  391 + child: draggableChild,
  392 + ),
  393 + );
  394 + },
  395 + child: RepaintBoundary(child: child),
339 ); 396 );
  397 +
  398 + return PrimaryScrollStatusBarHandler(
  399 + scrollController: _scrollController, child: child);
340 } 400 }
341 } 401 }
342 402
@@ -39,6 +39,9 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { @@ -39,6 +39,9 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
39 final platform = Theme.of(context)?.platform ?? defaultTargetPlatform; 39 final platform = Theme.of(context)?.platform ?? defaultTargetPlatform;
40 switch (platform) { 40 switch (platform) {
41 case TargetPlatform.iOS: 41 case TargetPlatform.iOS:
  42 + case TargetPlatform.linux:
  43 + case TargetPlatform.macOS:
  44 + case TargetPlatform.windows:
42 return ''; 45 return '';
43 case TargetPlatform.android: 46 case TargetPlatform.android:
44 case TargetPlatform.fuchsia: 47 case TargetPlatform.fuchsia:
@@ -99,6 +102,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { @@ -99,6 +102,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
99 builder: widget.route.builder, 102 builder: widget.route.builder,
100 enableDrag: widget.enableDrag, 103 enableDrag: widget.enableDrag,
101 bounce: widget.bounce, 104 bounce: widget.bounce,
  105 + scrollController: widget.scrollController,
102 animationCurve: widget.animationCurve, 106 animationCurve: widget.animationCurve,
103 ), 107 ),
104 ); 108 );
@@ -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';
@@ -83,6 +83,8 @@ Future<T> showCupertinoModalBottomSheet<T>({ @@ -83,6 +83,8 @@ Future<T> showCupertinoModalBottomSheet<T>({
83 bool enableDrag = true, 83 bool enableDrag = true,
84 Radius topRadius = _default_top_radius, 84 Radius topRadius = _default_top_radius,
85 Duration duration, 85 Duration duration,
  86 + RouteSettings settings,
  87 + Color transitionBackgroundColor,
86 }) async { 88 }) async {
87 assert(context != null); 89 assert(context != null);
88 assert(builder != null); 90 assert(builder != null);
@@ -97,29 +99,32 @@ Future<T> showCupertinoModalBottomSheet<T>({ @@ -97,29 +99,32 @@ Future<T> showCupertinoModalBottomSheet<T>({
97 ? MaterialLocalizations.of(context).modalBarrierDismissLabel 99 ? MaterialLocalizations.of(context).modalBarrierDismissLabel
98 : ''; 100 : '';
99 101
100 - final result = await Navigator.of(context, rootNavigator: useRootNavigator)  
101 - .push(CupertinoModalBottomSheetRoute<T>(  
102 - builder: builder,  
103 - containerBuilder: (context, _, child) => _CupertinoBottomSheetContainer(  
104 - child: child,  
105 - backgroundColor: backgroundColor,  
106 - topRadius: topRadius,  
107 - ),  
108 - secondAnimationController: secondAnimation,  
109 - expanded: expand,  
110 - barrierLabel: barrierLabel,  
111 - elevation: elevation,  
112 - bounce: bounce,  
113 - shape: shape,  
114 - clipBehavior: clipBehavior,  
115 - isDismissible: isDismissible ?? expand == false ? true : false,  
116 - modalBarrierColor: barrierColor ?? Colors.black12,  
117 - enableDrag: enableDrag,  
118 - topRadius: topRadius,  
119 - animationCurve: animationCurve,  
120 - previousRouteAnimationCurve: previousRouteAnimationCurve,  
121 - duration: duration,  
122 - )); 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 + );
123 return result; 128 return result;
124 } 129 }
125 130
@@ -127,6 +132,10 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> { @@ -127,6 +132,10 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
127 final Radius topRadius; 132 final Radius topRadius;
128 final Curve previousRouteAnimationCurve; 133 final Curve previousRouteAnimationCurve;
129 134
  135 + // Background color behind all routes
  136 + // Black by default
  137 + final Color transitionBackgroundColor;
  138 +
130 CupertinoModalBottomSheetRoute({ 139 CupertinoModalBottomSheetRoute({
131 ScrollWidgetBuilder builder, 140 ScrollWidgetBuilder builder,
132 WidgetWithChildBuilder containerBuilder, 141 WidgetWithChildBuilder containerBuilder,
@@ -143,12 +152,15 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> { @@ -143,12 +152,15 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
143 @required bool expanded, 152 @required bool expanded,
144 Duration duration, 153 Duration duration,
145 RouteSettings settings, 154 RouteSettings settings,
  155 + ScrollController scrollController,
  156 + this.transitionBackgroundColor,
146 this.topRadius = _default_top_radius, 157 this.topRadius = _default_top_radius,
147 this.previousRouteAnimationCurve, 158 this.previousRouteAnimationCurve,
148 }) : assert(expanded != null), 159 }) : assert(expanded != null),
149 assert(isDismissible != null), 160 assert(isDismissible != null),
150 assert(enableDrag != null), 161 assert(enableDrag != null),
151 super( 162 super(
  163 + scrollController: scrollController,
152 containerBuilder: containerBuilder, 164 containerBuilder: containerBuilder,
153 builder: builder, 165 builder: builder,
154 bounce: bounce, 166 bounce: bounce,
@@ -197,6 +209,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> { @@ -197,6 +209,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
197 body: child, 209 body: child,
198 animationCurve: previousRouteAnimationCurve, 210 animationCurve: previousRouteAnimationCurve,
199 topRadius: topRadius, 211 topRadius: topRadius,
  212 + backgroundColor: transitionBackgroundColor ?? Colors.black,
200 ); 213 );
201 } 214 }
202 } 215 }
@@ -205,6 +218,7 @@ class _CupertinoModalTransition extends StatelessWidget { @@ -205,6 +218,7 @@ class _CupertinoModalTransition extends StatelessWidget {
205 final Animation<double> secondaryAnimation; 218 final Animation<double> secondaryAnimation;
206 final Radius topRadius; 219 final Radius topRadius;
207 final Curve animationCurve; 220 final Curve animationCurve;
  221 + final Color backgroundColor;
208 222
209 final Widget body; 223 final Widget body;
210 224
@@ -213,6 +227,7 @@ class _CupertinoModalTransition extends StatelessWidget { @@ -213,6 +227,7 @@ class _CupertinoModalTransition extends StatelessWidget {
213 @required this.secondaryAnimation, 227 @required this.secondaryAnimation,
214 @required this.body, 228 @required this.body,
215 @required this.topRadius, 229 @required this.topRadius,
  230 + this.backgroundColor = Colors.black,
216 this.animationCurve, 231 this.animationCurve,
217 }) : super(key: key); 232 }) : super(key: key);
218 233
@@ -231,39 +246,41 @@ class _CupertinoModalTransition extends StatelessWidget { @@ -231,39 +246,41 @@ class _CupertinoModalTransition extends StatelessWidget {
231 ); 246 );
232 247
233 return AnnotatedRegion<SystemUiOverlayStyle>( 248 return AnnotatedRegion<SystemUiOverlayStyle>(
234 - value: SystemUiOverlayStyle.light,  
235 - child: AnimatedBuilder(  
236 - animation: curvedAnimation,  
237 - child: body,  
238 - builder: (context, child) {  
239 - final progress = curvedAnimation.value;  
240 - final yOffset = progress * paddingTop;  
241 - final scale = 1 - progress / 10;  
242 - final radius = progress == 0  
243 - ? 0.0  
244 - : (1 - progress) * startRoundCorner + progress * topRadius.x;  
245 - return Stack(  
246 - children: <Widget>[  
247 - Container(color: Colors.black),  
248 - Transform.translate(  
249 - offset: Offset(0, yOffset),  
250 - child: Transform.scale(  
251 - scale: scale,  
252 - alignment: Alignment.topCenter,  
253 - child: ClipRRect(  
254 - borderRadius: BorderRadius.circular(radius),  
255 - child: child),  
256 - ),  
257 - )  
258 - ],  
259 - );  
260 - },  
261 - )); 249 + value: SystemUiOverlayStyle.light,
  250 + child: AnimatedBuilder(
  251 + animation: curvedAnimation,
  252 + child: body,
  253 + builder: (context, child) {
  254 + final progress = curvedAnimation.value;
  255 + final yOffset = progress * paddingTop;
  256 + final scale = 1 - progress / 10;
  257 + final radius = progress == 0
  258 + ? 0.0
  259 + : (1 - progress) * startRoundCorner + progress * topRadius.x;
  260 + return Stack(
  261 + children: <Widget>[
  262 + Container(color: backgroundColor),
  263 + Transform.translate(
  264 + offset: Offset(0, yOffset),
  265 + child: Transform.scale(
  266 + scale: scale,
  267 + alignment: Alignment.topCenter,
  268 + child: ClipRRect(
  269 + borderRadius: BorderRadius.circular(radius),
  270 + child: child),
  271 + ),
  272 + ),
  273 + ],
  274 + );
  275 + },
  276 + ),
  277 + );
262 } 278 }
263 } 279 }
264 280
265 class _CupertinoScaffold extends InheritedWidget { 281 class _CupertinoScaffold extends InheritedWidget {
266 final AnimationController animation; 282 final AnimationController animation;
  283 +
267 final Radius topRadius; 284 final Radius topRadius;
268 285
269 @override 286 @override
@@ -286,10 +303,14 @@ class CupertinoScaffold extends StatefulWidget { @@ -286,10 +303,14 @@ class CupertinoScaffold extends StatefulWidget {
286 303
287 final Widget body; 304 final Widget body;
288 final Radius topRadius; 305 final Radius topRadius;
  306 + final Color transitionBackgroundColor;
289 307
290 - const CupertinoScaffold(  
291 - {Key key, this.body, this.topRadius = _default_top_radius})  
292 - : super(key: key); 308 + const CupertinoScaffold({
  309 + Key key,
  310 + this.body,
  311 + this.topRadius = _default_top_radius,
  312 + this.transitionBackgroundColor = Colors.black,
  313 + }) : super(key: key);
293 314
294 @override 315 @override
295 State<StatefulWidget> createState() => _CupertinoScaffoldState(); 316 State<StatefulWidget> createState() => _CupertinoScaffoldState();
@@ -307,6 +328,7 @@ class CupertinoScaffold extends StatefulWidget { @@ -307,6 +328,7 @@ class CupertinoScaffold extends StatefulWidget {
307 bool isDismissible, 328 bool isDismissible,
308 bool enableDrag = true, 329 bool enableDrag = true,
309 Duration duration, 330 Duration duration,
  331 + RouteSettings settings,
310 }) async { 332 }) async {
311 assert(context != null); 333 assert(context != null);
312 assert(builder != null); 334 assert(builder != null);
@@ -315,7 +337,7 @@ class CupertinoScaffold extends StatefulWidget { @@ -315,7 +337,7 @@ class CupertinoScaffold extends StatefulWidget {
315 assert(enableDrag != null); 337 assert(enableDrag != null);
316 assert(debugCheckHasMediaQuery(context)); 338 assert(debugCheckHasMediaQuery(context));
317 final isCupertinoApp = Theme.of(context, shadowThemeOnly: true) == null; 339 final isCupertinoApp = Theme.of(context, shadowThemeOnly: true) == null;
318 - String barrierLabel = ''; 340 + var barrierLabel = '';
319 if (!isCupertinoApp) { 341 if (!isCupertinoApp) {
320 assert(debugCheckHasMaterialLocalizations(context)); 342 assert(debugCheckHasMaterialLocalizations(context));
321 barrierLabel = MaterialLocalizations.of(context).modalBarrierDismissLabel; 343 barrierLabel = MaterialLocalizations.of(context).modalBarrierDismissLabel;
@@ -340,6 +362,7 @@ class CupertinoScaffold extends StatefulWidget { @@ -340,6 +362,7 @@ class CupertinoScaffold extends StatefulWidget {
340 animationCurve: animationCurve, 362 animationCurve: animationCurve,
341 previousRouteAnimationCurve: previousRouteAnimationCurve, 363 previousRouteAnimationCurve: previousRouteAnimationCurve,
342 duration: duration, 364 duration: duration,
  365 + settings: settings,
343 )); 366 ));
344 return result; 367 return result;
345 } 368 }
@@ -372,6 +395,7 @@ class _CupertinoScaffoldState extends State<CupertinoScaffold> @@ -372,6 +395,7 @@ class _CupertinoScaffoldState extends State<CupertinoScaffold>
372 secondaryAnimation: animationController, 395 secondaryAnimation: animationController,
373 body: widget.body, 396 body: widget.body,
374 topRadius: widget.topRadius, 397 topRadius: widget.topRadius,
  398 + backgroundColor: widget.transitionBackgroundColor,
375 ), 399 ),
376 ); 400 );
377 } 401 }
  1 +import 'dart:ui';
  2 +
  3 +import 'package:flutter/animation.dart';
  4 +import 'package:flutter/foundation.dart';
  5 +
  6 +// Copied from bottom_sheet.dart as is a private class
  7 +// https://github.com/flutter/flutter/issues/51627
  8 +
  9 +// TODO(guidezpl): Look into making this public. A copy of this class is in
  10 +// scaffold.dart, for now, https://github.com/flutter/flutter/issues/51627
  11 +
  12 +/// A curve that progresses linearly until a specified [startingPoint], at which
  13 +/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
  14 +/// but will use [startingPoint] as the Y position.
  15 +///
  16 +/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
  17 +/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
  18 +/// straight line, and the top-right quarter will contain the entire contents of
  19 +/// [Curves.easeOut].
  20 +///
  21 +/// This is useful in situations where a widget must track the user's finger
  22 +/// (which requires a linear animation), and afterwards can be flung using a
  23 +/// curve specified with the [curve] argument, after the finger is released. In
  24 +/// such a case, the value of [startingPoint] would be the progress of the
  25 +/// animation at the time when the finger was released.
  26 +///
  27 +/// The [startingPoint] and [curve] arguments must not be null.
  28 +class BottomSheetSuspendedCurve extends ParametricCurve<double> {
  29 + /// Creates a suspended curve.
  30 + const BottomSheetSuspendedCurve(
  31 + this.startingPoint, {
  32 + this.curve = Curves.easeOutCubic,
  33 + }) : assert(startingPoint != null),
  34 + assert(curve != null);
  35 +
  36 + /// The progress value at which [curve] should begin.
  37 + ///
  38 + /// This defaults to [Curves.easeOutCubic].
  39 + final double startingPoint;
  40 +
  41 + /// The curve to use when [startingPoint] is reached.
  42 + final Curve curve;
  43 +
  44 + @override
  45 + double transform(double t) {
  46 + assert(t >= 0.0 && t <= 1.0);
  47 + assert(startingPoint >= 0.0 && startingPoint <= 1.0);
  48 +
  49 + if (t < startingPoint) {
  50 + return t;
  51 + }
  52 +
  53 + if (t == 1.0) {
  54 + return t;
  55 + }
  56 +
  57 + final curveProgress = (t - startingPoint) / (1 - startingPoint);
  58 + final transformed = curve.transform(curveProgress);
  59 + return lerpDouble(startingPoint, 1, transformed);
  60 + }
  61 +
  62 + @override
  63 + String toString() {
  64 + return '${describeIdentity(this)}($startingPoint, $curve)';
  65 + }
  66 +}
  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 +}
1 name: modal_bottom_sheet 1 name: modal_bottom_sheet
2 description: 'Create awesome and powerful modal bottom sheets. Material, Cupertino iOS 13 or create your own style' 2 description: 'Create awesome and powerful modal bottom sheets. Material, Cupertino iOS 13 or create your own style'
3 -version: 0.1.6 3 +version: 0.2.0
4 homepage: 'https://github.com/jamesblasco/modal_bottom_sheet' 4 homepage: 'https://github.com/jamesblasco/modal_bottom_sheet'
5 5
6 environment: 6 environment:
This file is too large to display.