Commit 8c98ea37 authored by Hans Muller's avatar Hans Muller

Merge pull request #269 from HansMuller/bottom_sheet_drag

Refactor bottom sheet support, add one to the stocks demo

Factored OverlayRoute out of the modal and persistent bottom sheet classes, since the bottom sheet classes need to drive the performance.

Added a bottom sheet to the stocks demo: long-press on a stock shows a modal bottom sheet.

Made AnimatedModalBarrier public.
parents 0f2a9c40 5e08b98e
......@@ -81,7 +81,7 @@ class StocksAppState extends State<StocksApp> {
if (path.length != 3)
return null;
if (_stocks.containsKey(path[2]))
return (RouteArguments args) => new StockSymbolViewer(_stocks[path[2]]);
return (RouteArguments args) => new StockSymbolViewer(stock: _stocks[path[2]]);
return null;
}
return null;
......
......@@ -186,6 +186,10 @@ class StockHomeState extends State<StockHome> {
stock.percentChange = 100.0 * (1.0 / stock.lastSale);
stock.lastSale += 1.0;
});
showModalBottomSheet(
context: context,
child: new StockSymbolViewer(stock: stock, showToolBar: false)
);
},
onOpen: (Stock stock, Key arrowKey) {
Set<Key> mostValuableKeys = new Set<Key>();
......
......@@ -5,16 +5,54 @@
part of stocks;
class StockSymbolViewer extends StatelessComponent {
StockSymbolViewer(this.stock);
StockSymbolViewer({ this.stock, this.showToolBar: true });
final Stock stock;
final bool showToolBar;
Widget build(BuildContext context) {
String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}";
String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
if (stock.percentChange > 0)
changeInPrice = "+" + changeInPrice;
TextStyle headings = Theme.of(context).text.body2;
Widget body = new Block(<Widget>[
new Container(
margin: new EdgeDims.all(20.0),
child: new Card(
child: new Container(
padding: new EdgeDims.all(20.0),
child: new Column(<Widget>[
new Row(<Widget>[
new Text(
'${stock.symbol}',
style: Theme.of(context).text.display2
),
new Hero(
tag: StockRowPartKind.arrow,
turns: 2,
child: new StockArrow(percentChange: stock.percentChange)
),
],
justifyContent: FlexJustifyContent.spaceBetween
),
new Text('Last Sale', style: headings),
new Text('$lastSale ($changeInPrice)'),
new Container(
height: 8.0
),
new Text('Market Cap', style: headings),
new Text('${stock.marketCap}'),
])
)
)
)
]);
if (!showToolBar)
return body;
return new Scaffold(
toolBar: new ToolBar(
left: new IconButton(
......@@ -25,39 +63,7 @@ class StockSymbolViewer extends StatelessComponent {
),
center: new Text(stock.name)
),
body: new Block(<Widget>[
new Container(
margin: new EdgeDims.all(20.0),
child: new Card(
child: new Container(
padding: new EdgeDims.all(20.0),
child: new Column(<Widget>[
new Row(<Widget>[
new Text(
'${stock.symbol}',
style: Theme.of(context).text.display2
),
new Hero(
tag: StockRowPartKind.arrow,
turns: 2,
child: new StockArrow(percentChange: stock.percentChange)
),
],
justifyContent: FlexJustifyContent.spaceBetween
),
new Text('Last Sale', style: headings),
new Text('$lastSale ($changeInPrice)'),
new Container(
height: 8.0
),
new Text('Market Cap', style: headings),
new Text('${stock.marketCap}'),
])
)
)
)
]
)
body: body
);
}
......
......@@ -15,6 +15,8 @@ import 'material.dart';
const Duration _kBottomSheetDuration = const Duration(milliseconds: 200);
const double _kMinFlingVelocity = 700.0;
const double _kCloseProgressThreshold = 0.5;
const Color _kTransparent = const Color(0x00000000);
const Color _kBarrierColor = Colors.black54;
class _BottomSheetDragController extends StatelessComponent {
_BottomSheetDragController({
......@@ -61,6 +63,29 @@ class _BottomSheetDragController extends StatelessComponent {
}
}
class _BottomSheetRoute extends OverlayRoute {
_BottomSheetRoute({ this.completer, this.child });
final Completer completer;
final Widget child;
Performance performance;
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
performance = new Performance(duration: _kBottomSheetDuration, debugLabel: debugLabel)
..forward();
super.didPush(overlay, insertionPoint);
}
void didPop(dynamic result) {
completer.complete(result);
performance.reverse().then((_) {
super.didPop(result); // clear the overlay entries
});
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $performance)';
}
class _ModalBottomSheet extends StatefulComponent {
_ModalBottomSheet({ Key key, this.route }) : super(key: key);
......@@ -97,7 +122,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
return new GestureDetector(
onTap: () { Navigator.of(context).pop(); },
child: new BuilderTransition(
performance: config.route._performance,
performance: config.route.performance,
variables: <AnimatedValue<double>>[_layout.childTop],
builder: (BuildContext context) {
return new ClipRect(
......@@ -105,7 +130,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
delegate: _layout,
token: _layout.childTop.value,
child: new _BottomSheetDragController(
performance: config.route._performance,
performance: config.route.performance,
child: new Material(child: config.route.child),
childHeight: _layout.childTop.end
)
......@@ -117,29 +142,28 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
}
}
class _ModalBottomSheetRoute extends ModalRoute {
_ModalBottomSheetRoute({ this.completer, this.child });
final Completer completer;
final Widget child;
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
Performance _performance;
class _ModalBottomSheetRoute extends _BottomSheetRoute {
_ModalBottomSheetRoute({ Completer completer, Widget child })
: super(completer: completer, child: child);
Performance createPerformance() {
_performance = super.createPerformance();
return _performance;
Widget _buildModalBarrier(BuildContext context) {
return new AnimatedModalBarrier(
color: new AnimatedColorValue(_kTransparent, end: _kBarrierColor, curve: Curves.ease),
performance: performance
);
}
Color get barrierColor => Colors.black54;
Widget buildModalWidget(BuildContext context) => new _ModalBottomSheet(route: this);
void didPop([dynamic result]) {
completer.complete(result);
super.didPop(result);
Widget _buildBottomSheet(BuildContext context) {
return new Focus(
key: new GlobalObjectKey(this),
child: new _ModalBottomSheet(route: this)
);
}
List<WidgetBuilder> get builders => <WidgetBuilder>[
_buildModalBarrier,
_buildBottomSheet,
];
}
Future showModalBottomSheet({ BuildContext context, Widget child }) {
......@@ -155,7 +179,7 @@ Future showModalBottomSheet({ BuildContext context, Widget child }) {
class _PersistentBottomSheet extends StatefulComponent {
_PersistentBottomSheet({ Key key, this.route }) : super(key: key);
final _PersistentBottomSheetRoute route;
final _BottomSheetRoute route;
_PersistentBottomSheetState createState() => new _PersistentBottomSheetState();
}
......@@ -175,7 +199,7 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> {
alignment: new AnimatedValue<FractionalOffset>(const FractionalOffset(0.0, 0.0)),
heightFactor: new AnimatedValue<double>(0.0, end: 1.0),
child: new _BottomSheetDragController(
performance: config.route._performance,
performance: config.route.performance,
childHeight: _childHeight,
child: new Material(
child: new SizeObserver(child: config.route.child, onSizeChanged: _updateChildHeight)
......@@ -185,32 +209,11 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> {
}
}
class _PersistentBottomSheetRoute extends TransitionRoute {
_PersistentBottomSheetRoute({ this.completer, this.child });
final Widget child;
final Completer completer;
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
Performance _performance;
Performance createPerformance() {
_performance = super.createPerformance();
return _performance;
}
void didPop([dynamic result]) {
completer.complete(result);
super.didPop(result);
}
}
Future showBottomSheet({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget child }) {
assert(child != null);
assert(placeholderKey != null);
final Completer completer = new Completer();
_PersistentBottomSheetRoute route = new _PersistentBottomSheetRoute(child: child, completer: completer);
_BottomSheetRoute route = new _BottomSheetRoute(child: child, completer: completer);
placeholderKey.currentState.child = new _PersistentBottomSheet(route: route);
Navigator.of(context).pushEphemeral(route);
return completer.future;
......
......@@ -39,8 +39,8 @@ class ModalBarrier extends StatelessComponent {
}
}
class _AnimatedModalBarrier extends StatelessComponent {
_AnimatedModalBarrier({
class AnimatedModalBarrier extends StatelessComponent {
AnimatedModalBarrier({
Key key,
this.color,
this.performance
......@@ -108,7 +108,7 @@ abstract class ModalRoute extends TransitionRoute {
Widget buildModalWidget(BuildContext context);
Widget _buildModalBarrier(BuildContext context) {
return new _AnimatedModalBarrier(
return new AnimatedModalBarrier(
color: new AnimatedColorValue(_kTransparent, end: barrierColor, curve: Curves.ease),
performance: performance
);
......
......@@ -29,6 +29,7 @@ void main() {
tester.tap(tester.findText('BottomSheet'));
tester.pump(); // bottom sheet dismiss animation starts
tester.pump(new Duration(seconds: 1)); // animation done
tester.pump(new Duration(seconds: 2)); // rebuild frame
expect(tester.findText('BottomSheet'), isNull);
showModalBottomSheet(context: context, child: new Text('BottomSheet'));
......@@ -40,6 +41,7 @@ void main() {
tester.tapAt(new Point(20.0, 20.0));
tester.pump(); // bottom sheet dismiss animation starts
tester.pump(new Duration(seconds: 1)); // animation done
tester.pump(new Duration(seconds: 2)); // rebuild frame
expect(tester.findText('BottomSheet'), isNull);
});
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment