blob: 7e81c30750d68236ee9c9f9ffde26931335e302d [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/animation.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'material.dart';
const Duration _kBottomSheetDuration = const Duration(milliseconds: 200);
const double _kMinFlingVelocity = 700.0;
const double _kFlingVelocityScale = 1.0 / 300.0;
class _ModalBottomSheet extends StatefulComponent {
_ModalBottomSheet({ Key key, this.route }) : super(key: key);
final _ModalBottomSheetRoute route;
_ModalBottomSheetState createState() => new _ModalBottomSheetState();
}
class _ModalBottomSheetLayout extends OneChildLayoutDelegate {
// The distance from the bottom of the parent to the top of the BottomSheet child.
AnimatedValue<double> childTop = new AnimatedValue<double>(0.0);
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return new BoxConstraints(
minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
maxHeight: constraints.maxHeight * 9.0 / 16.0
);
}
Point getPositionForChild(Size size, Size childSize) {
childTop.end = childSize.height;
return new Point(0.0, size.height - childTop.value);
}
}
class _ModalBottomSheetState extends State<_ModalBottomSheet> {
final _ModalBottomSheetLayout _layout = new _ModalBottomSheetLayout();
bool _dragEnabled = false;
void _handleDragStart(Point position) {
_dragEnabled = !config.route._performance.isAnimating;
}
void _handleDragUpdate(double delta) {
if (!_dragEnabled)
return;
config.route._performance.progress -= delta / _layout.childTop.end;
}
void _handleDragEnd(Offset velocity) {
if (!_dragEnabled)
return;
if (velocity.dy > _kMinFlingVelocity)
config.route._performance.fling(velocity: -velocity.dy * _kFlingVelocityScale);
else
config.route._performance.forward();
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () { Navigator.of(context).pop(); },
child: new BuilderTransition(
performance: config.route._performance,
variables: <AnimatedValue<double>>[_layout.childTop],
builder: (BuildContext context) {
return new ClipRect(
child: new CustomOneChildLayout(
delegate: _layout,
token: _layout.childTop.value,
child: new GestureDetector(
onVerticalDragStart: _handleDragStart,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: new Material(child: config.route.child)
)
)
);
}
)
);
}
}
class _ModalBottomSheetRoute extends ModalRoute {
_ModalBottomSheetRoute({ this.completer, this.child }) {
_performance = new Performance(duration: transitionDuration, debugLabel: 'ModalBottomSheet');
}
final Completer completer;
final Widget child;
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
Performance _performance;
Performance createPerformance() {
_performance = super.createPerformance();
return _performance;
}
Color get barrierColor => Colors.black54;
Widget buildModalWidget(BuildContext context) => new _ModalBottomSheet(route: this);
void didPop([dynamic result]) {
completer.complete(result);
super.didPop(result);
}
}
Future showModalBottomSheet({ BuildContext context, Widget child }) {
assert(child != null);
final Completer completer = new Completer();
Navigator.of(context).pushEphemeral(new _ModalBottomSheetRoute(
completer: completer,
child: child
));
return completer.future;
}
class _PersistentBottomSheet extends StatelessComponent {
_PersistentBottomSheet({
Key key,
this.child,
this.route
}) : super(key: key);
final TransitionRoute route;
final Widget child;
Widget build(BuildContext context) {
return new AlignTransition(
performance: route.performance,
alignment: new AnimatedValue<FractionalOffset>(const FractionalOffset(0.0, 0.0)),
heightFactor: new AnimatedValue<double>(0.0, end: 1.0),
child: child
);
}
}
class _PersistentBottomSheetRoute extends TransitionRoute {
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
}
void showBottomSheet({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget child }) {
assert(child != null);
assert(placeholderKey != null);
_PersistentBottomSheetRoute route = new _PersistentBottomSheetRoute();
placeholderKey.currentState.child = new _PersistentBottomSheet(route: route, child: child);
Navigator.of(context).pushEphemeral(route);
}