Showing
17 changed files
with
552 additions
and
241 deletions
@@ -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 | +} |
lib/src/utils/primary_scroll_status_bar.dart
0 → 100644
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: |
screenshots/avatar_modal.png
deleted
100644 → 0

210 KB
screenshots/bar_modal.png
deleted
100644 → 0

124 KB
This file is too large to display.
screenshots/material_fit.png
deleted
100644 → 0

164 KB
screenshots/modal_inside_modal.gif
deleted
100644 → 0

1.87 MB
screenshots/preview.png
deleted
100644 → 0

225 KB
-
Please register or login to post a comment