Commit 9f176fc8 authored by Adam Barth's avatar Adam Barth

Port widgets that depend on scrolling to fn3

parent 042f49a7
This diff is collapsed.
// 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:sky/animation.dart';
import 'package:sky/material.dart';
import 'package:sky/src/fn3/basic.dart';
import 'package:sky/src/fn3/focus.dart';
import 'package:sky/src/fn3/framework.dart';
import 'package:sky/src/fn3/gesture_detector.dart';
import 'package:sky/src/fn3/material.dart';
import 'package:sky/src/fn3/navigator.dart';
import 'package:sky/src/fn3/scrollable.dart';
import 'package:sky/src/fn3/theme.dart';
import 'package:sky/src/fn3/transitions.dart';
typedef Widget DialogBuilder(Navigator navigator);
/// A material design dialog
///
/// <https://www.google.com/design/spec/components/dialogs.html>
class Dialog extends StatelessComponent {
Dialog({
Key key,
this.title,
this.titlePadding,
this.content,
this.contentPadding,
this.actions,
this.onDismiss
}): super(key: key);
/// The (optional) title of the dialog is displayed in a large font at the top
/// of the dialog.
final Widget title;
// Padding around the title; uses material design default if none is supplied
// If there is no title, no padding will be provided
final EdgeDims titlePadding;
/// The (optional) content of the dialog is displayed in the center of the
/// dialog in a lighter font.
final Widget content;
// Padding around the content; uses material design default if none is supplied
final EdgeDims contentPadding;
/// The (optional) set of actions that are displayed at the bottom of the
/// dialog.
final List<Widget> actions;
/// An (optional) callback that is called when the dialog is dismissed.
final Function onDismiss;
Color _getColor(BuildContext context) {
switch (Theme.of(context).brightness) {
case ThemeBrightness.light:
return Colors.white;
case ThemeBrightness.dark:
return Colors.grey[800];
}
}
Widget build(BuildContext context) {
List<Widget> dialogBody = new List<Widget>();
if (title != null) {
EdgeDims padding = titlePadding;
if (padding == null)
padding = new EdgeDims(24.0, 24.0, content == null ? 20.0 : 0.0, 24.0);
dialogBody.add(new Padding(
padding: padding,
child: new DefaultTextStyle(
style: Theme.of(context).text.title,
child: title
)
));
}
if (content != null) {
EdgeDims padding = contentPadding;
if (padding == null)
padding = const EdgeDims(20.0, 24.0, 24.0, 24.0);
dialogBody.add(new Padding(
padding: padding,
child: new DefaultTextStyle(
style: Theme.of(context).text.subhead,
child: content
)
));
}
if (actions != null) {
dialogBody.add(new Container(
child: new Row(actions,
justifyContent: FlexJustifyContent.end
)
));
}
return new Stack([
new GestureDetector(
onTap: onDismiss,
child: new Container(
decoration: const BoxDecoration(
backgroundColor: const Color(0x7F000000)
)
)
),
new Center(
child: new Container(
margin: new EdgeDims.symmetric(horizontal: 40.0, vertical: 24.0),
child: new ConstrainedBox(
constraints: new BoxConstraints(minWidth: 280.0),
child: new Material(
level: 4,
color: _getColor(context),
child: new IntrinsicWidth(
child: new Block(dialogBody)
)
)
)
)
)
]);
}
}
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
class DialogRoute extends RouteBase {
DialogRoute({ this.completer, this.builder });
final Completer completer;
final RouteBuilder builder;
Duration get transitionDuration => _kTransitionDuration;
bool get isOpaque => false;
Widget build(Key key, NavigatorState navigator, WatchableAnimationPerformance performance) {
return new FadeTransition(
performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
child: builder(navigator, this)
);
}
void popState([dynamic result]) {
completer.complete(result);
}
}
Future showDialog(NavigatorState navigator, DialogBuilder builder) {
Completer completer = new Completer();
navigator.push(new DialogRoute(
completer: completer,
builder: (navigator, route) {
return new Focus(
key: new GlobalObjectKey(route),
autofocus: true,
child: builder(navigator)
);
}
));
return completer.future;
}
// 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:sky/animation.dart';
import 'package:sky/material.dart';
import 'package:sky/src/fn3/framework.dart';
import 'package:sky/src/fn3/basic.dart';
import 'package:sky/src/fn3/gesture_detector.dart';
import 'package:sky/src/fn3/navigator.dart';
import 'package:sky/src/fn3/scrollable.dart';
import 'package:sky/src/fn3/theme.dart';
import 'package:sky/src/fn3/transitions.dart';
// TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav
// Mobile:
// Width = Screen width − 56 dp
// Maximum width: 320dp
// Maximum width applies only when using a left nav. When using a right nav,
// the panel can cover the full width of the screen.
// Desktop/Tablet:
// Maximum width for a left nav is 400dp.
// The right nav can vary depending on content.
const double _kWidth = 304.0;
const double _kMinFlingVelocity = 365.0;
const double _kFlingVelocityScale = 1.0 / 300.0;
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);
const Duration _kThemeChangeDuration = const Duration(milliseconds: 200);
const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
typedef void DrawerDismissedCallback();
class Drawer extends StatefulComponent {
Drawer({
Key key,
this.children,
this.showing: false,
this.level: 0,
this.onDismissed,
this.navigator
}) : super(key: key);
final List<Widget> children;
final bool showing;
final int level;
final DrawerDismissedCallback onDismissed;
final NavigatorState navigator;
DrawerState createState() => new DrawerState(this);
}
class DrawerState extends ComponentState<Drawer> {
DrawerState(Drawer config) : super(config) {
_performance = new AnimationPerformance(duration: _kBaseSettleDuration);
_performance.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.dismissed)
_handleDismissed();
});
// Use a spring force for animating the drawer. We can't use curves for
// this because we need a linear curve in order to track the user's finger
// while dragging.
_performance.attachedForce = kDefaultSpringForce;
if (config.navigator != null) {
// TODO(ianh): This is crazy. We should convert drawer to use a pattern like openDialog().
// https://github.com/domokit/sky_engine/pull/1186
scheduleMicrotask(() {
config.navigator.pushState(this, (_) => _performance.reverse());
});
}
_performance.play(_direction);
}
AnimationPerformance _performance;
Direction get _direction => config.showing ? Direction.forward : Direction.reverse;
void didUpdateConfig(Drawer oldConfig) {
if (config.showing != oldConfig.showing)
_performance.play(_direction);
}
Widget build(BuildContext context) {
var mask = new GestureDetector(
child: new ColorTransition(
performance: _performance.view,
color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)),
child: new Container()
),
onTap: () {
_performance.reverse();
}
);
Widget content = new SlideTransition(
performance: _performance.view,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
// TODO(abarth): Use AnimatedContainer
child: new Container(
// behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
decoration: new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
boxShadow: shadows[config.level]),
width: _kWidth,
child: new Block(config.children)
)
);
return new GestureDetector(
onHorizontalDragStart: _performance.stop,
onHorizontalDragUpdate: _handleDragUpdate,
onHorizontalDragEnd: _handleDragEnd,
child: new Stack([ mask, content ])
);
}
void _handleDismissed() {
if (config.navigator != null &&
config.navigator.currentRoute is RouteState &&
(config.navigator.currentRoute as RouteState).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
config.navigator.pop();
if (config.onDismissed != null)
config.onDismissed();
}
bool get _isMostlyClosed => _performance.progress < 0.5;
void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); }
void _handleDragUpdate(double delta) {
_performance.progress += delta / _kWidth;
}
void _handleDragEnd(Offset velocity) {
if (velocity.dx.abs() >= _kMinFlingVelocity) {
_performance.fling(velocity: velocity.dx * _kFlingVelocityScale);
} else {
_settle();
}
}
}
......@@ -90,7 +90,7 @@ class RouteState extends RouteBase {
Function callback;
RouteBase route;
StatefulComponent owner;
ComponentState owner;
bool get isOpaque => false;
......@@ -160,7 +160,7 @@ class NavigatorState extends ComponentState<Navigator> {
RouteBase get currentRoute => config.history.currentRoute;
void pushState(StatefulComponent owner, Function callback) {
void pushState(ComponentState owner, Function callback) {
RouteBase route = new RouteState(
owner: owner,
callback: callback,
......
// 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 'package:sky/animation.dart';
import 'package:sky/painting.dart';
import 'package:sky/material.dart';
import 'package:sky/src/fn3/animated_component.dart';
import 'package:sky/src/fn3/basic.dart';
import 'package:sky/src/fn3/framework.dart';
import 'package:sky/src/fn3/gesture_detector.dart';
import 'package:sky/src/fn3/material.dart';
import 'package:sky/src/fn3/theme.dart';
import 'package:sky/src/fn3/transitions.dart';
typedef void SnackBarDismissedCallback();
const Duration _kSlideInDuration = const Duration(milliseconds: 200);
// TODO(ianh): factor out some of the constants below
class SnackBarAction extends StatelessComponent {
SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) {
assert(label != null);
}
final String label;
final Function onPressed;
Widget build(BuildContext) {
return new GestureDetector(
onTap: onPressed,
child: new Container(
margin: const EdgeDims.only(left: 24.0),
padding: const EdgeDims.only(top: 14.0, bottom: 14.0),
child: new Text(label)
)
);
}
}
class SnackBar extends AnimatedComponent {
SnackBar({
Key key,
this.transitionKey,
this.content,
this.actions,
bool showing,
this.onDismissed
}) : super(key: key, direction: showing ? Direction.forward : Direction.reverse, duration: _kSlideInDuration) {
assert(content != null);
}
final Key transitionKey;
final Widget content;
final List<SnackBarAction> actions;
final SnackBarDismissedCallback onDismissed;
SnackBarState createState() => new SnackBarState(this);
}
class SnackBarState extends AnimatedComponentState<SnackBar> {
SnackBarState(SnackBar config) : super(config);
void handleDismissed() {
if (config.onDismissed != null)
config.onDismissed();
}
Widget build(BuildContext context) {
List<Widget> children = [
new Flexible(
child: new Container(
margin: const EdgeDims.symmetric(vertical: 14.0),
child: new DefaultTextStyle(
style: Typography.white.subhead,
child: config.content
)
)
)
];
if (config.actions != null)
children.addAll(config.actions);
return new SlideTransition(
key: config.transitionKey,
performance: performance.view,
position: new AnimatedValue<Point>(
Point.origin,
end: const Point(0.0, -52.0),
curve: easeIn,
reverseCurve: easeOut
),
child: new Material(
level: 2,
color: const Color(0xFF323232),
type: MaterialType.canvas,
child: new Container(
margin: const EdgeDims.symmetric(horizontal: 24.0),
child: new DefaultTextStyle(
style: new TextStyle(color: Theme.of(context).accentColor),
child: new Row(children)
)
)
)
);
}
}
This diff is collapsed.
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