Commit 5e08b98e authored by Hans Muller's avatar Hans Muller

Refactor bottom sheet support, add a bottom sheet to the stocks demo

Factored OverlayRoute out of the modal and persistent bottom sheet clases, 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.
parent 0f2a9c40
...@@ -81,7 +81,7 @@ class StocksAppState extends State<StocksApp> { ...@@ -81,7 +81,7 @@ class StocksAppState extends State<StocksApp> {
if (path.length != 3) if (path.length != 3)
return null; return null;
if (_stocks.containsKey(path[2])) 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;
} }
return null; return null;
......
...@@ -186,6 +186,10 @@ class StockHomeState extends State<StockHome> { ...@@ -186,6 +186,10 @@ class StockHomeState extends State<StockHome> {
stock.percentChange = 100.0 * (1.0 / stock.lastSale); stock.percentChange = 100.0 * (1.0 / stock.lastSale);
stock.lastSale += 1.0; stock.lastSale += 1.0;
}); });
showModalBottomSheet(
context: context,
child: new StockSymbolViewer(stock: stock, showToolBar: false)
);
}, },
onOpen: (Stock stock, Key arrowKey) { onOpen: (Stock stock, Key arrowKey) {
Set<Key> mostValuableKeys = new Set<Key>(); Set<Key> mostValuableKeys = new Set<Key>();
......
...@@ -5,16 +5,54 @@ ...@@ -5,16 +5,54 @@
part of stocks; part of stocks;
class StockSymbolViewer extends StatelessComponent { class StockSymbolViewer extends StatelessComponent {
StockSymbolViewer(this.stock); StockSymbolViewer({ this.stock, this.showToolBar: true });
final Stock stock; final Stock stock;
final bool showToolBar;
Widget build(BuildContext context) { Widget build(BuildContext context) {
String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}"; String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}";
String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%"; String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
if (stock.percentChange > 0) if (stock.percentChange > 0)
changeInPrice = "+" + changeInPrice; changeInPrice = "+" + changeInPrice;
TextStyle headings = Theme.of(context).text.body2; 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( return new Scaffold(
toolBar: new ToolBar( toolBar: new ToolBar(
left: new IconButton( left: new IconButton(
...@@ -25,39 +63,7 @@ class StockSymbolViewer extends StatelessComponent { ...@@ -25,39 +63,7 @@ class StockSymbolViewer extends StatelessComponent {
), ),
center: new Text(stock.name) center: new Text(stock.name)
), ),
body: new Block(<Widget>[ body: body
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}'),
])
)
)
)
]
)
); );
} }
......
...@@ -15,6 +15,8 @@ import 'material.dart'; ...@@ -15,6 +15,8 @@ import 'material.dart';
const Duration _kBottomSheetDuration = const Duration(milliseconds: 200); const Duration _kBottomSheetDuration = const Duration(milliseconds: 200);
const double _kMinFlingVelocity = 700.0; const double _kMinFlingVelocity = 700.0;
const double _kCloseProgressThreshold = 0.5; const double _kCloseProgressThreshold = 0.5;
const Color _kTransparent = const Color(0x00000000);
const Color _kBarrierColor = Colors.black54;
class _BottomSheetDragController extends StatelessComponent { class _BottomSheetDragController extends StatelessComponent {
_BottomSheetDragController({ _BottomSheetDragController({
...@@ -61,6 +63,29 @@ class _BottomSheetDragController extends StatelessComponent { ...@@ -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 { class _ModalBottomSheet extends StatefulComponent {
_ModalBottomSheet({ Key key, this.route }) : super(key: key); _ModalBottomSheet({ Key key, this.route }) : super(key: key);
...@@ -97,7 +122,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { ...@@ -97,7 +122,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
return new GestureDetector( return new GestureDetector(
onTap: () { Navigator.of(context).pop(); }, onTap: () { Navigator.of(context).pop(); },
child: new BuilderTransition( child: new BuilderTransition(
performance: config.route._performance, performance: config.route.performance,
variables: <AnimatedValue<double>>[_layout.childTop], variables: <AnimatedValue<double>>[_layout.childTop],
builder: (BuildContext context) { builder: (BuildContext context) {
return new ClipRect( return new ClipRect(
...@@ -105,7 +130,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { ...@@ -105,7 +130,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
delegate: _layout, delegate: _layout,
token: _layout.childTop.value, token: _layout.childTop.value,
child: new _BottomSheetDragController( child: new _BottomSheetDragController(
performance: config.route._performance, performance: config.route.performance,
child: new Material(child: config.route.child), child: new Material(child: config.route.child),
childHeight: _layout.childTop.end childHeight: _layout.childTop.end
) )
...@@ -117,29 +142,28 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { ...@@ -117,29 +142,28 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
} }
} }
class _ModalBottomSheetRoute extends ModalRoute { class _ModalBottomSheetRoute extends _BottomSheetRoute {
_ModalBottomSheetRoute({ this.completer, this.child }); _ModalBottomSheetRoute({ Completer completer, Widget child })
: super(completer: completer, child: child);
final Completer completer;
final Widget child;
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
Performance _performance;
Performance createPerformance() { Widget _buildModalBarrier(BuildContext context) {
_performance = super.createPerformance(); return new AnimatedModalBarrier(
return _performance; color: new AnimatedColorValue(_kTransparent, end: _kBarrierColor, curve: Curves.ease),
performance: performance
);
} }
Color get barrierColor => Colors.black54; Widget _buildBottomSheet(BuildContext context) {
Widget buildModalWidget(BuildContext context) => new _ModalBottomSheet(route: this); return new Focus(
key: new GlobalObjectKey(this),
void didPop([dynamic result]) { child: new _ModalBottomSheet(route: this)
completer.complete(result); );
super.didPop(result);
} }
List<WidgetBuilder> get builders => <WidgetBuilder>[
_buildModalBarrier,
_buildBottomSheet,
];
} }
Future showModalBottomSheet({ BuildContext context, Widget child }) { Future showModalBottomSheet({ BuildContext context, Widget child }) {
...@@ -155,7 +179,7 @@ Future showModalBottomSheet({ BuildContext context, Widget child }) { ...@@ -155,7 +179,7 @@ Future showModalBottomSheet({ BuildContext context, Widget child }) {
class _PersistentBottomSheet extends StatefulComponent { class _PersistentBottomSheet extends StatefulComponent {
_PersistentBottomSheet({ Key key, this.route }) : super(key: key); _PersistentBottomSheet({ Key key, this.route }) : super(key: key);
final _PersistentBottomSheetRoute route; final _BottomSheetRoute route;
_PersistentBottomSheetState createState() => new _PersistentBottomSheetState(); _PersistentBottomSheetState createState() => new _PersistentBottomSheetState();
} }
...@@ -175,7 +199,7 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> { ...@@ -175,7 +199,7 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> {
alignment: new AnimatedValue<FractionalOffset>(const FractionalOffset(0.0, 0.0)), alignment: new AnimatedValue<FractionalOffset>(const FractionalOffset(0.0, 0.0)),
heightFactor: new AnimatedValue<double>(0.0, end: 1.0), heightFactor: new AnimatedValue<double>(0.0, end: 1.0),
child: new _BottomSheetDragController( child: new _BottomSheetDragController(
performance: config.route._performance, performance: config.route.performance,
childHeight: _childHeight, childHeight: _childHeight,
child: new Material( child: new Material(
child: new SizeObserver(child: config.route.child, onSizeChanged: _updateChildHeight) child: new SizeObserver(child: config.route.child, onSizeChanged: _updateChildHeight)
...@@ -185,32 +209,11 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> { ...@@ -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 }) { Future showBottomSheet({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget child }) {
assert(child != null); assert(child != null);
assert(placeholderKey != null); assert(placeholderKey != null);
final Completer completer = new Completer(); 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); placeholderKey.currentState.child = new _PersistentBottomSheet(route: route);
Navigator.of(context).pushEphemeral(route); Navigator.of(context).pushEphemeral(route);
return completer.future; return completer.future;
......
...@@ -39,8 +39,8 @@ class ModalBarrier extends StatelessComponent { ...@@ -39,8 +39,8 @@ class ModalBarrier extends StatelessComponent {
} }
} }
class _AnimatedModalBarrier extends StatelessComponent { class AnimatedModalBarrier extends StatelessComponent {
_AnimatedModalBarrier({ AnimatedModalBarrier({
Key key, Key key,
this.color, this.color,
this.performance this.performance
...@@ -108,7 +108,7 @@ abstract class ModalRoute extends TransitionRoute { ...@@ -108,7 +108,7 @@ abstract class ModalRoute extends TransitionRoute {
Widget buildModalWidget(BuildContext context); Widget buildModalWidget(BuildContext context);
Widget _buildModalBarrier(BuildContext context) { Widget _buildModalBarrier(BuildContext context) {
return new _AnimatedModalBarrier( return new AnimatedModalBarrier(
color: new AnimatedColorValue(_kTransparent, end: barrierColor, curve: Curves.ease), color: new AnimatedColorValue(_kTransparent, end: barrierColor, curve: Curves.ease),
performance: performance performance: performance
); );
......
...@@ -29,6 +29,7 @@ void main() { ...@@ -29,6 +29,7 @@ void main() {
tester.tap(tester.findText('BottomSheet')); tester.tap(tester.findText('BottomSheet'));
tester.pump(); // bottom sheet dismiss animation starts tester.pump(); // bottom sheet dismiss animation starts
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
tester.pump(new Duration(seconds: 2)); // rebuild frame
expect(tester.findText('BottomSheet'), isNull); expect(tester.findText('BottomSheet'), isNull);
showModalBottomSheet(context: context, child: new Text('BottomSheet')); showModalBottomSheet(context: context, child: new Text('BottomSheet'));
...@@ -40,6 +41,7 @@ void main() { ...@@ -40,6 +41,7 @@ void main() {
tester.tapAt(new Point(20.0, 20.0)); tester.tapAt(new Point(20.0, 20.0));
tester.pump(); // bottom sheet dismiss animation starts tester.pump(); // bottom sheet dismiss animation starts
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
tester.pump(new Duration(seconds: 2)); // rebuild frame
expect(tester.findText('BottomSheet'), isNull); 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