bierbaumtim

Merge branch 'master' of https://github.com/jamesblasco/modal_bottom_sheet into jamesblasco-master

... ... @@ -12,4 +12,10 @@
## [0.1.6] - New custom params
- Use `duration` to define the opening duration of the modal
- Change the top radius of the cupertino bottom sheet
Thanks to @bierbaumtim @troyanskiy @rodineijf for the contributions
\ No newline at end of file
Thanks to @bierbaumtim @troyanskiy @rodineijf for the contributions
## [0.2.0] - New Cool Features
- Added support for scroll-to-top by tapping the status bar on iOS devices.
- Use `curveAnimation` to define a custom curve animation for the modal transition
- Bug fixes releated to horizontal scroll, clamping physics and othes.
\ No newline at end of file
... ...
<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>
<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>
# Flutter Modal Bottom Sheet
... ... @@ -9,16 +9,22 @@ Create awesome and powerful modal bottom sheets.
| Cupertino Modal | Multiple Modals | Material Modal | Bar Modal | Create your own |
|---|---|---|---|---|
|<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">|
|<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">|
## Try it
Explore the [Web Demo](https://jamesblasco.github.io/modal_bottom_sheet/#/) or clone the repository.
Known problems on web demo:
- Web demo can run very slow on mobile devides.
Why not `showModalBottomSheet`?
Inspired by `showModalBottomSheet`, it completes with some must-need features:
- Support for inside scrollview + dragging down to close (`showModalBottomSheet` won't work correctly with scrollviews.
- Support for `WillPopScope` to prevent closing the dialog.
- Support for scroll to top when tapping status bar (iOS only)
- Cupertino modal bottom sheet
- Create custom modal bottom sheet
- Fake status bar doesn't change color as the iOS, Android app
## First Steps
... ... @@ -33,11 +39,6 @@ showMaterialModalBottomSheet(
builder: (context, scrollController) => Container(),
)
```
What to use this over flutter `showModalBottomSheet`?
`showMaterialModalBottomSheet` supports closing bottoms sheets by dragging down even if there is a scrollview inside.
`showModalBottomSheet` won't work correctly with scrollviews.
Also it supports `WillPopScope` to prevent closing the dialog
#### Generic params for all modal bottom sheets
... ... @@ -78,15 +79,17 @@ Useful if you want a blurred transparent background as the example Cupertino Pho
> **Why?**
> `MaterialPageRoute` and `CupertinoPageRoute` do not allow animated translation to/from routes that are not the same type.
There are two options:
### OPTION 1. Recommended.
Replace your current route class with `MaterialWithModalsPageRoute`.
Notice this route type behaves the same as `MaterialPageRoute` and supports custom `PageTransitionsBuilder` and `PageTransitionsTheme`.
How can I change my route class? See cases:
<details><summary>
How can I replace my current route? </summary>
<details><summary> 1.
... ... @@ -145,9 +148,12 @@ Unfortunately this parameter uses `MaterialPageRoute` and `CupertinoPageRoute` r
You can modify the way you call the previous route with one of the previous methods or try option 2
</details>
</details>
Is there an alternative in case I can't change my current route? **Yes!**
<details><summary>
Learn how to animate previous route with CupertinoScaffold: </summary>
### OPTION 2.
1. Wrap previous route inside a `CupertinoScaffold`.
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
CupertinoScaffold.showCupertinoModalBottomSheet(context:context, builder: (context) => Container())
```
These two options won't work correctly together.
Don't use this solution at the same time as `MaterialWithModalsPageRoute`
</details>
It supports native features as bouncing, blurred background, dark mode, stacking modals and inside navigation.
## Push new views inside the modal bottom sheet
a. If you want to push a new modal bottom sheet just call `showCupertinoModalBottomSheet` again (works with two option)
a. If you want to push a new modal bottom sheet just call `showCupertinoModalBottomSheet` again (works with both options)
b. For inside navigaton add a new `Navigator` or `CupertinoTabScaffold` inside
... ... @@ -193,7 +201,7 @@ Check in the example project `showAvatarModalBottomSheet` for how to create your
- [X] Support closing by dragging fast on a modal with a scroll view.
- [ ] Improve animation curves when user is not dragging.
- [X] Improve animation curves when user is not dragging.
- [ ] Allow to set the initial size of the bottom sheet
... ...
... ... @@ -11,6 +11,7 @@ import 'modals/modal_fit.dart';
import 'modals/modal_inside_modal.dart';
import 'modals/modal_will_scope.dart';
import 'modals/modal_with_navigator.dart';
import 'modals/modal_with_nested_scroll.dart';
import 'modals/modal_with_scroll.dart';
import 'examples/cupertino_share.dart';
... ... @@ -97,124 +98,153 @@ class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
print(MediaQuery.of(context).size.height);
return Material(
child: CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
middle: Text('iOS13 Modal Presentation'),
trailing: GestureDetector(
child: Icon(Icons.arrow_forward),
onTap: () => Navigator.of(context).pushNamed('ss'),
child: Scaffold(
body: CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
middle: Text('iOS13 Modal Presentation'),
trailing: GestureDetector(
child: Icon(Icons.arrow_forward),
onTap: () => Navigator.of(context).pushNamed('ss'),
),
),
),
child: SizedBox.expand(
child: SingleChildScrollView(
child: SafeArea(
bottom: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text('Cupertino Photo Share Example'),
onTap: () => Navigator.of(context).push(
MaterialWithModalsPageRoute(
builder: (context) => CupertinoSharePage()))),
ListTile(
title: Text('Material fit'),
onTap: () => showMaterialModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
ListTile(
title: Text('Bar Modal'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Avatar Modal'),
onTap: () => showAvatarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Float Modal'),
onTap: () => showFloatingModalBottomSheet(
context: context,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
ListTile(
title: Text('Cupertino Modal fit'),
onTap: () => showCupertinoModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
ListTile(
title: Text('Cupertino Small Modal forced to expand'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
ListTile(
title: Text('Cupertino Modal inside modal'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Cupertino Modal with inside navigation'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalWithNavigator(
scrollController: scrollController),
)),
ListTile(
title:
Text('Cupertino Navigator + Scroll + WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ComplexModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Cupertino Modal with WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalWillScope(
scrollController: scrollController),
)),
],
child: SizedBox.expand(
child: SingleChildScrollView(
primary: true,
child: SafeArea(
bottom: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text('Cupertino Photo Share Example'),
onTap: () => Navigator.of(context).push(
MaterialWithModalsPageRoute(
builder: (context) => CupertinoSharePage()))),
section('STYLES'),
ListTile(
title: Text('Material fit'),
onTap: () => showMaterialModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
ListTile(
title: Text('Bar Modal'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Avatar Modal'),
onTap: () => showAvatarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Float Modal'),
onTap: () => showFloatingModalBottomSheet(
context: context,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
ListTile(
title: Text('Cupertino Modal fit'),
onTap: () => showCupertinoModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
section('COMPLEX CASES'),
ListTile(
title: Text('Cupertino Small Modal forced to expand'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
)),
ListTile(
title: Text('Reverse list'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController,
reverse: true),
)),
ListTile(
title: Text('Cupertino Modal inside modal'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Cupertino Modal with inside navigation'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalWithNavigator(
scrollController: scrollController),
)),
ListTile(
title:
Text('Cupertino Navigator + Scroll + WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ComplexModal(
scrollController: scrollController),
)),
ListTile(
title: Text('Modal with WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalWillScope(
scrollController: scrollController),
)),
ListTile(
title: Text('Modal with Nested Scroll'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context, scrollController) =>
NestedScrollModal(
scrollController: scrollController),
)),
SizedBox(
height: 60,
)
],
),
),
),
),
... ... @@ -222,4 +252,13 @@ class _MyHomePageState extends State<MyHomePage> {
),
);
}
Widget section(String title) {
return Padding(
padding: EdgeInsets.fromLTRB(16, 20, 16, 8),
child: Text(
title,
style: Theme.of(context).textTheme.caption,
));
}
}
... ...
... ... @@ -4,8 +4,10 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class ModalInsideModal extends StatelessWidget {
final ScrollController scrollController;
final bool reverse;
const ModalInsideModal({Key key, this.scrollController}) : super(key: key);
const ModalInsideModal({Key key, this.scrollController, this.reverse = false})
: super(key: key);
@override
Widget build(BuildContext context) {
... ... @@ -16,15 +18,16 @@ class ModalInsideModal extends StatelessWidget {
child: SafeArea(
bottom: false,
child: ListView(
reverse: reverse,
shrinkWrap: true,
controller: scrollController,
physics: BouncingScrollPhysics(),
physics: ClampingScrollPhysics(),
children: ListTile.divideTiles(
context: context,
tiles: List.generate(
100,
(index) => ListTile(
title: Text('Item'),
title: Text('Item $index'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
isDismissible: false,
... ... @@ -32,7 +35,8 @@ class ModalInsideModal extends StatelessWidget {
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalInsideModal(
scrollController: scrollController),
scrollController: scrollController,
reverse: reverse),
)),
)).toList(),
),
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class NestedScrollModal extends StatelessWidget {
final ScrollController scrollController;
const NestedScrollModal({Key key, this.scrollController}) : super(key: key);
@override
Widget build(BuildContext context) {
return NestedScrollView(
controller: ScrollController(),
physics: ScrollPhysics(parent: PageScrollPhysics()),
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverList(
delegate: SliverChildListDelegate(
[
Container(height: 300, color: Colors.blue),
],
),
),
];
},
body: ListView.builder(
controller: scrollController,
itemBuilder: (context, index) {
return Container(
height: 100,
color: index.isOdd ? Colors.green : Colors.orange,
);
},
itemCount: 12,
),
);
}
}
... ...
... ... @@ -10,10 +10,16 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:modal_bottom_sheet/src/utils/primary_scroll_status_bar.dart';
import 'package:modal_bottom_sheet/src/utils/bottom_sheet_suspended_curve.dart';
const Curve _decelerateEasing = Cubic(0.0, 0.0, 0.2, 1.0);
const Curve _modalBottomSheetCurve = _decelerateEasing;
const Duration _bottomSheetDuration = Duration(milliseconds: 400);
const double _minFlingVelocity = 500.0;
const double _closeProgressThreshold = 0.5;
const double _closeProgressThreshold = 0.6;
const double _willPopThreshold = 0.8;
typedef ScrollWidgetBuilder = Widget Function(
... ... @@ -61,7 +67,7 @@ class ModalBottomSheet extends StatefulWidget {
/// The curve used by the animation showing and dismissing the bottom sheet.
///
/// If no curve is provided it falls back to `Curves.easeOutSine`.
/// If no curve is provided it falls back to `decelerateEasing`.
final Curve animationCurve;
/// Allows the bottom sheet to go beyond the top bound of the content,
... ... @@ -69,6 +75,8 @@ class ModalBottomSheet extends StatefulWidget {
/// the top bound.
final bool bounce;
// Force the widget to fill the maximum size of the viewport
// or if false it will fit to the content of the widget
final bool expanded;
final WidgetWithChildBuilder containerBuilder;
... ... @@ -174,7 +182,10 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
return result;
}
ParametricCurve<double> animationCurve;
void _handleDragUpdate(double primaryDelta) async {
animationCurve = Curves.linear;
assert(widget.enableDrag, 'Dragging is disabled');
if (_dismissUnderway) return;
... ... @@ -208,6 +219,11 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
void _handleDragEnd(double velocity) async {
assert(widget.enableDrag, 'Dragging is disabled');
animationCurve = BottomSheetSuspendedCurve(
widget.animationController.value,
curve: _defaultCurve,
);
if (_dismissUnderway || !isDragging) return;
isDragging = false;
_bounceDragController.reverse();
... ... @@ -242,18 +258,45 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
DateTime _startTime;
void _handleScrollUpdate(ScrollNotification notification) {
if (notification.metrics.pixels <= notification.metrics.minScrollExtent) {
//Check if listener is same from scrollController
if (!_scrollController.hasClients) return;
final scrollPosition = _scrollController.position;
if (scrollPosition.axis == Axis.horizontal) return;
//Check if scrollController is used
if (!_scrollController.hasClients) return;
final isScrollReversed = scrollPosition.axisDirection == AxisDirection.down;
final offset = isScrollReversed
? scrollPosition.pixels
: scrollPosition.maxScrollExtent - scrollPosition.pixels;
if (_scrollController.position.pixels != notification.metrics.pixels) {
if (offset <= 0) {
// Check if listener is same from scrollController.
// TODO: Improve the way it checks if it the same view controller
// Use PrimaryScrollController
if (_scrollController.position.pixels != notification.metrics.pixels &&
!(_scrollController.position.pixels == 0 &&
notification.metrics.pixels >= 0)) {
return;
}
DragUpdateDetails dragDetails;
if (notification is ScrollStartNotification) {
// Clamping Scroll Physics end with a ScrollEndNotification with a DragEndDetail class
// while Bouncing Scroll Physics or other physics that Overflow don't return a drag end info
// We use the velocity from DragEndDetail in case it is available
if (notification is ScrollEndNotification &&
notification.dragDetails != null) {
_handleDragEnd(notification.dragDetails.primaryVelocity);
_velocityTracker = null;
_startTime = null;
return;
}
// Otherwise the calculate the velocity with a VelocityTracker
if (_velocityTracker == null) {
_velocityTracker = VelocityTracker();
_startTime = DateTime.now();
}
DragUpdateDetails dragDetails;
if (notification is ScrollUpdateNotification) {
dragDetails = notification.dragDetails;
}
... ... @@ -262,18 +305,23 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
}
if (dragDetails != null) {
final duration = _startTime.difference(DateTime.now());
final offset = Offset(0, _scrollController.offset);
_velocityTracker.addPosition(duration, offset);
_velocityTracker.addPosition(duration, Offset(0, offset));
_handleDragUpdate(dragDetails.primaryDelta);
} else if (isDragging) {
final velocity = _velocityTracker.getVelocity().pixelsPerSecond.dy;
_velocityTracker = null;
_startTime = null;
_handleDragEnd(velocity);
}
}
}
ParametricCurve<double> get _defaultCurve =>
widget.animationCurve ?? _modalBottomSheetCurve;
@override
void initState() {
animationCurve = _defaultCurve;
_bounceDragController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_scrollController = widget.scrollController ?? ScrollController();
... ... @@ -285,7 +333,7 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
Widget build(BuildContext context) {
final bounceAnimation = CurvedAnimation(
parent: _bounceDragController,
curve: widget.animationCurve ?? Curves.easeOutSine,
curve: Curves.easeOutSine,
);
var child = widget.builder(context, _scrollController);
... ... @@ -298,45 +346,57 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
);
}
// Todo: Add curved Animation when push and pop without gesture
/* final Animation<double> containerAnimation = CurvedAnimation(
parent: widget.animationController,
curve: Curves.easeOut,
);*/
final mediaQuery = MediaQuery.of(context);
return AnimatedBuilder(
child = AnimatedBuilder(
animation: widget.animationController,
builder: (context, _) => ClipRect(
child: CustomSingleChildLayout(
delegate: _ModalBottomSheetLayout(
widget.animationController.value, widget.expanded),
child: !widget.enableDrag
? child
: KeyedSubtree(
key: _childKey,
child: AnimatedBuilder(
animation: bounceAnimation,
builder: (context, _) => CustomSingleChildLayout(
delegate: _CustomBottomSheetLayout(bounceAnimation.value),
child: GestureDetector(
onVerticalDragUpdate: (details) =>
_handleDragUpdate(details.primaryDelta),
onVerticalDragEnd: (details) =>
_handleDragEnd(details.primaryVelocity),
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
_handleScrollUpdate(notification);
return false;
},
child: child,
),
builder: (context, child) {
final animationValue = animationCurve.transform(
mediaQuery.accessibleNavigation
? 1.0
: widget.animationController.value);
final draggableChild = !widget.enableDrag
? child
: KeyedSubtree(
key: _childKey,
child: AnimatedBuilder(
animation: bounceAnimation,
builder: (context, _) => CustomSingleChildLayout(
delegate: _CustomBottomSheetLayout(bounceAnimation.value),
child: GestureDetector(
onVerticalDragUpdate: (details) {
_handleDragUpdate(details.primaryDelta);
},
onVerticalDragEnd: (details) {
_handleDragEnd(details.primaryVelocity);
},
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
_handleScrollUpdate(notification);
return false;
},
child: child,
),
),
),
),
),
),
);
return ClipRect(
child: CustomSingleChildLayout(
delegate: _ModalBottomSheetLayout(
animationValue,
widget.expanded,
),
child: draggableChild,
),
);
},
child: RepaintBoundary(child: child),
);
return PrimaryScrollStatusBarHandler(
scrollController: _scrollController, child: child);
}
}
... ...
... ... @@ -39,6 +39,9 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
final platform = Theme.of(context)?.platform ?? defaultTargetPlatform;
switch (platform) {
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return '';
case TargetPlatform.android:
case TargetPlatform.fuchsia:
... ... @@ -99,6 +102,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
builder: widget.route.builder,
enableDrag: widget.enableDrag,
bounce: widget.bounce,
scrollController: widget.scrollController,
animationCurve: widget.animationCurve,
),
);
... ...
... ... @@ -10,8 +10,8 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'
show
Colors,
Theme,
MaterialLocalizations,
Theme,
debugCheckHasMaterialLocalizations;
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
... ... @@ -83,6 +83,8 @@ Future<T> showCupertinoModalBottomSheet<T>({
bool enableDrag = true,
Radius topRadius = _default_top_radius,
Duration duration,
RouteSettings settings,
Color transitionBackgroundColor,
}) async {
assert(context != null);
assert(builder != null);
... ... @@ -97,29 +99,32 @@ Future<T> showCupertinoModalBottomSheet<T>({
? MaterialLocalizations.of(context).modalBarrierDismissLabel
: '';
final result = await Navigator.of(context, rootNavigator: useRootNavigator)
.push(CupertinoModalBottomSheetRoute<T>(
builder: builder,
containerBuilder: (context, _, child) => _CupertinoBottomSheetContainer(
child: child,
backgroundColor: backgroundColor,
topRadius: topRadius,
),
secondAnimationController: secondAnimation,
expanded: expand,
barrierLabel: barrierLabel,
elevation: elevation,
bounce: bounce,
shape: shape,
clipBehavior: clipBehavior,
isDismissible: isDismissible ?? expand == false ? true : false,
modalBarrierColor: barrierColor ?? Colors.black12,
enableDrag: enableDrag,
topRadius: topRadius,
animationCurve: animationCurve,
previousRouteAnimationCurve: previousRouteAnimationCurve,
duration: duration,
));
final result =
await Navigator.of(context, rootNavigator: useRootNavigator).push(
CupertinoModalBottomSheetRoute<T>(
builder: builder,
containerBuilder: (context, _, child) => _CupertinoBottomSheetContainer(
child: child,
backgroundColor: backgroundColor,
topRadius: topRadius,
),
secondAnimationController: secondAnimation,
expanded: expand,
barrierLabel: barrierLabel,
elevation: elevation,
bounce: bounce,
shape: shape,
clipBehavior: clipBehavior,
isDismissible: isDismissible ?? expand == false ? true : false,
modalBarrierColor: barrierColor ?? Colors.black12,
enableDrag: enableDrag,
topRadius: topRadius,
animationCurve: animationCurve,
previousRouteAnimationCurve: previousRouteAnimationCurve,
duration: duration,
settings: settings,
transitionBackgroundColor: transitionBackgroundColor ?? Colors.black),
);
return result;
}
... ... @@ -127,6 +132,10 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
final Radius topRadius;
final Curve previousRouteAnimationCurve;
// Background color behind all routes
// Black by default
final Color transitionBackgroundColor;
CupertinoModalBottomSheetRoute({
ScrollWidgetBuilder builder,
WidgetWithChildBuilder containerBuilder,
... ... @@ -143,12 +152,15 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
@required bool expanded,
Duration duration,
RouteSettings settings,
ScrollController scrollController,
this.transitionBackgroundColor,
this.topRadius = _default_top_radius,
this.previousRouteAnimationCurve,
}) : assert(expanded != null),
assert(isDismissible != null),
assert(enableDrag != null),
super(
scrollController: scrollController,
containerBuilder: containerBuilder,
builder: builder,
bounce: bounce,
... ... @@ -197,6 +209,7 @@ class CupertinoModalBottomSheetRoute<T> extends ModalBottomSheetRoute<T> {
body: child,
animationCurve: previousRouteAnimationCurve,
topRadius: topRadius,
backgroundColor: transitionBackgroundColor ?? Colors.black,
);
}
}
... ... @@ -205,6 +218,7 @@ class _CupertinoModalTransition extends StatelessWidget {
final Animation<double> secondaryAnimation;
final Radius topRadius;
final Curve animationCurve;
final Color backgroundColor;
final Widget body;
... ... @@ -213,6 +227,7 @@ class _CupertinoModalTransition extends StatelessWidget {
@required this.secondaryAnimation,
@required this.body,
@required this.topRadius,
this.backgroundColor = Colors.black,
this.animationCurve,
}) : super(key: key);
... ... @@ -231,39 +246,41 @@ class _CupertinoModalTransition extends StatelessWidget {
);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light,
child: AnimatedBuilder(
animation: curvedAnimation,
child: body,
builder: (context, child) {
final progress = curvedAnimation.value;
final yOffset = progress * paddingTop;
final scale = 1 - progress / 10;
final radius = progress == 0
? 0.0
: (1 - progress) * startRoundCorner + progress * topRadius.x;
return Stack(
children: <Widget>[
Container(color: Colors.black),
Transform.translate(
offset: Offset(0, yOffset),
child: Transform.scale(
scale: scale,
alignment: Alignment.topCenter,
child: ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: child),
),
)
],
);
},
));
value: SystemUiOverlayStyle.light,
child: AnimatedBuilder(
animation: curvedAnimation,
child: body,
builder: (context, child) {
final progress = curvedAnimation.value;
final yOffset = progress * paddingTop;
final scale = 1 - progress / 10;
final radius = progress == 0
? 0.0
: (1 - progress) * startRoundCorner + progress * topRadius.x;
return Stack(
children: <Widget>[
Container(color: backgroundColor),
Transform.translate(
offset: Offset(0, yOffset),
child: Transform.scale(
scale: scale,
alignment: Alignment.topCenter,
child: ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: child),
),
),
],
);
},
),
);
}
}
class _CupertinoScaffold extends InheritedWidget {
final AnimationController animation;
final Radius topRadius;
@override
... ... @@ -286,10 +303,14 @@ class CupertinoScaffold extends StatefulWidget {
final Widget body;
final Radius topRadius;
final Color transitionBackgroundColor;
const CupertinoScaffold(
{Key key, this.body, this.topRadius = _default_top_radius})
: super(key: key);
const CupertinoScaffold({
Key key,
this.body,
this.topRadius = _default_top_radius,
this.transitionBackgroundColor = Colors.black,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _CupertinoScaffoldState();
... ... @@ -307,6 +328,7 @@ class CupertinoScaffold extends StatefulWidget {
bool isDismissible,
bool enableDrag = true,
Duration duration,
RouteSettings settings,
}) async {
assert(context != null);
assert(builder != null);
... ... @@ -315,7 +337,7 @@ class CupertinoScaffold extends StatefulWidget {
assert(enableDrag != null);
assert(debugCheckHasMediaQuery(context));
final isCupertinoApp = Theme.of(context, shadowThemeOnly: true) == null;
String barrierLabel = '';
var barrierLabel = '';
if (!isCupertinoApp) {
assert(debugCheckHasMaterialLocalizations(context));
barrierLabel = MaterialLocalizations.of(context).modalBarrierDismissLabel;
... ... @@ -340,6 +362,7 @@ class CupertinoScaffold extends StatefulWidget {
animationCurve: animationCurve,
previousRouteAnimationCurve: previousRouteAnimationCurve,
duration: duration,
settings: settings,
));
return result;
}
... ... @@ -372,6 +395,7 @@ class _CupertinoScaffoldState extends State<CupertinoScaffold>
secondaryAnimation: animationController,
body: widget.body,
topRadius: widget.topRadius,
backgroundColor: widget.transitionBackgroundColor,
),
);
}
... ...
import 'dart:ui';
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
// Copied from bottom_sheet.dart as is a private class
// https://github.com/flutter/flutter/issues/51627
// TODO(guidezpl): Look into making this public. A copy of this class is in
// scaffold.dart, for now, https://github.com/flutter/flutter/issues/51627
/// A curve that progresses linearly until a specified [startingPoint], at which
/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
/// but will use [startingPoint] as the Y position.
///
/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
/// straight line, and the top-right quarter will contain the entire contents of
/// [Curves.easeOut].
///
/// This is useful in situations where a widget must track the user's finger
/// (which requires a linear animation), and afterwards can be flung using a
/// curve specified with the [curve] argument, after the finger is released. In
/// such a case, the value of [startingPoint] would be the progress of the
/// animation at the time when the finger was released.
///
/// The [startingPoint] and [curve] arguments must not be null.
class BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// Creates a suspended curve.
const BottomSheetSuspendedCurve(
this.startingPoint, {
this.curve = Curves.easeOutCubic,
}) : assert(startingPoint != null),
assert(curve != null);
/// The progress value at which [curve] should begin.
///
/// This defaults to [Curves.easeOutCubic].
final double startingPoint;
/// The curve to use when [startingPoint] is reached.
final Curve curve;
@override
double transform(double t) {
assert(t >= 0.0 && t <= 1.0);
assert(startingPoint >= 0.0 && startingPoint <= 1.0);
if (t < startingPoint) {
return t;
}
if (t == 1.0) {
return t;
}
final curveProgress = (t - startingPoint) / (1 - startingPoint);
final transformed = curve.transform(curveProgress);
return lerpDouble(startingPoint, 1, transformed);
}
@override
String toString() {
return '${describeIdentity(this)}($startingPoint, $curve)';
}
}
... ...
import 'package:flutter/widgets.dart';
/// Creates a primary scroll controller that will
/// scroll to the top when tapped on the status bar
///
class PrimaryScrollStatusBarHandler extends StatefulWidget {
final ScrollController scrollController;
final Widget child;
const PrimaryScrollStatusBarHandler(
{Key key, this.child, this.scrollController})
: super(key: key);
@override
_PrimaryScrollWidgetState createState() => _PrimaryScrollWidgetState();
}
class _PrimaryScrollWidgetState extends State<PrimaryScrollStatusBarHandler> {
ScrollController controller;
@override
void initState() {
controller = widget.scrollController ?? ScrollController();
super.initState();
}
@override
Widget build(BuildContext context) {
return PrimaryScrollController(
controller: controller,
child: Stack(
fit: StackFit.expand,
children: [
widget.child,
Positioned(
top: 0,
left: 0,
right: 0,
height: MediaQuery.of(context).padding.top,
child: Builder(
builder: (context) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _handleStatusBarTap(context),
// iOS accessibility automatically adds scroll-to-top to the clock in the status bar
excludeFromSemantics: true,
),
),
),
],
),
);
}
void _handleStatusBarTap(BuildContext context) {
final controller = PrimaryScrollController.of(context);
if (controller.hasClients) {
controller.animateTo(
0.0,
duration: const Duration(milliseconds: 300),
curve: Curves.linear, // TODO(ianh): Use a more appropriate curve.
);
}
}
}
... ...
name: modal_bottom_sheet
description: 'Create awesome and powerful modal bottom sheets. Material, Cupertino iOS 13 or create your own style'
version: 0.1.6
version: 0.2.0
homepage: 'https://github.com/jamesblasco/modal_bottom_sheet'
environment:
... ...
This file is too large to display.