Use the navigator to stack dialogs.
This removes the need to manually include the dialog builder in the main window's build() function. It also removes the need to track if a dialog is visible. Other changes: - I made dialog.dart a bit more readable. - I renamed transitionFinished to fullyOpaque since that's what actually matters. - I made Routes track if they're opaque. Eventually this should probably be more configurable when creating the route. Directions for Future Research: - Use this for focus management somehow. - The popup menu should use something like this. - We should factor the following out into a showDialog() function that returns a future for the dialog's exit result: navigator.push(new DialogRoute(builder: (navigator, route) { ... })); - Maybe navigator.pop() should take a value to return to that Future.
This commit is contained in:
parent
c6fe01eda3
commit
ac6342ab8a
@ -68,10 +68,30 @@ class AddressBookApp extends App {
|
||||
child: new Icon(type: 'image/photo_camera', size: 24),
|
||||
backgroundColor: Theme.of(this).accentColor,
|
||||
onPressed: () {
|
||||
showDialog = true;
|
||||
navigator.pushState(this, (_) {
|
||||
showDialog = false;
|
||||
});
|
||||
navigator.push(new DialogRoute(builder: (navigator, route) {
|
||||
return new Dialog(
|
||||
title: new Text("Describe your picture"),
|
||||
content: new ScrollableBlock([
|
||||
new Field(inputKey: fillKey, icon: "editor/format_color_fill", placeholder: "Color"),
|
||||
new Field(inputKey: emoticonKey, icon: "editor/insert_emoticon", placeholder: "Emotion"),
|
||||
]),
|
||||
onDismiss: navigator.pop,
|
||||
actions: [
|
||||
new FlatButton(
|
||||
child: new Text('DISCARD'),
|
||||
onPressed: () {
|
||||
navigator.pop();
|
||||
}
|
||||
),
|
||||
new FlatButton(
|
||||
child: new Text('SAVE'),
|
||||
onPressed: () {
|
||||
navigator.pop();
|
||||
}
|
||||
),
|
||||
]
|
||||
);
|
||||
}));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -104,47 +124,15 @@ class AddressBookApp extends App {
|
||||
);
|
||||
}
|
||||
|
||||
bool showDialog = false;
|
||||
|
||||
Widget buildMain(Navigator navigator) {
|
||||
List<Widget> layers = [
|
||||
new Focus(
|
||||
initialFocus: nameKey,
|
||||
child: new Scaffold(
|
||||
toolbar: buildToolBar(navigator),
|
||||
body: buildBody(navigator),
|
||||
floatingActionButton: buildFloatingActionButton(navigator)
|
||||
)
|
||||
return new Focus(
|
||||
initialFocus: nameKey,
|
||||
child: new Scaffold(
|
||||
toolbar: buildToolBar(navigator),
|
||||
body: buildBody(navigator),
|
||||
floatingActionButton: buildFloatingActionButton(navigator)
|
||||
)
|
||||
];
|
||||
if (showDialog) {
|
||||
layers.add(new Focus(
|
||||
initialFocus: fillKey,
|
||||
child: new Dialog(
|
||||
title: new Text("Describe your picture"),
|
||||
content: new ScrollableBlock([
|
||||
new Field(inputKey: fillKey, icon: "editor/format_color_fill", placeholder: "Color"),
|
||||
new Field(inputKey: emoticonKey, icon: "editor/insert_emoticon", placeholder: "Emotion"),
|
||||
]),
|
||||
onDismiss: navigator.pop,
|
||||
actions: [
|
||||
new FlatButton(
|
||||
child: new Text('DISCARD'),
|
||||
onPressed: () {
|
||||
navigator.pop();
|
||||
}
|
||||
),
|
||||
new FlatButton(
|
||||
child: new Text('SAVE'),
|
||||
onPressed: () {
|
||||
navigator.pop();
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
));
|
||||
}
|
||||
return new Stack(layers);
|
||||
);
|
||||
}
|
||||
|
||||
NavigationState _navigationState;
|
||||
|
@ -18,8 +18,6 @@ class StockSettings extends StatefulComponent {
|
||||
BackupMode backup;
|
||||
SettingsUpdater updater;
|
||||
|
||||
bool _showModeDialog = false;
|
||||
|
||||
void syncFields(StockSettings source) {
|
||||
navigator = source.navigator;
|
||||
optimism = source.optimism;
|
||||
@ -47,10 +45,26 @@ class StockSettings extends StatefulComponent {
|
||||
_handleOptimismChanged(false);
|
||||
break;
|
||||
case StockMode.pessimistic:
|
||||
_showModeDialog = true;
|
||||
navigator.pushState(this, (_) {
|
||||
_showModeDialog = false;
|
||||
});
|
||||
navigator.push(new DialogRoute(builder: (navigator, route) {
|
||||
return new Dialog(
|
||||
title: new Text("Change mode?"),
|
||||
content: new Text("Optimistic mode means everything is awesome. Are you sure you can handle that?"),
|
||||
onDismiss: navigator.pop,
|
||||
actions: [
|
||||
new FlatButton(
|
||||
child: new Text('NO THANKS'),
|
||||
onPressed: navigator.pop
|
||||
),
|
||||
new FlatButton(
|
||||
child: new Text('AGREE'),
|
||||
onPressed: () {
|
||||
_handleOptimismChanged(true);
|
||||
navigator.pop();
|
||||
}
|
||||
),
|
||||
]
|
||||
);
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -104,32 +118,9 @@ class StockSettings extends StatefulComponent {
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
List<Widget> layers = [
|
||||
new Scaffold(
|
||||
toolbar: buildToolBar(),
|
||||
body: buildSettingsPane()
|
||||
)
|
||||
];
|
||||
if (_showModeDialog) {
|
||||
layers.add(new Dialog(
|
||||
title: new Text("Change mode?"),
|
||||
content: new Text("Optimistic mode means everything is awesome. Are you sure you can handle that?"),
|
||||
onDismiss: navigator.pop,
|
||||
actions: [
|
||||
new FlatButton(
|
||||
child: new Text('NO THANKS'),
|
||||
onPressed: navigator.pop
|
||||
),
|
||||
new FlatButton(
|
||||
child: new Text('AGREE'),
|
||||
onPressed: () {
|
||||
_handleOptimismChanged(true);
|
||||
navigator.pop();
|
||||
}
|
||||
),
|
||||
]
|
||||
));
|
||||
}
|
||||
return new Stack(layers);
|
||||
return new Scaffold(
|
||||
toolbar: buildToolBar(),
|
||||
body: buildSettingsPane()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -46,14 +46,11 @@ class Dialog extends Component {
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
Container mask = new Container(
|
||||
decoration: const BoxDecoration(
|
||||
backgroundColor: const Color(0x7F000000)));
|
||||
|
||||
List<Widget> children = new List<Widget>();
|
||||
List<Widget> dialogBody = new List<Widget>();
|
||||
|
||||
if (title != null) {
|
||||
children.add(new Padding(
|
||||
dialogBody.add(new Padding(
|
||||
padding: new EdgeDims(24.0, 24.0, content == null ? 20.0 : 0.0, 24.0),
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(this).text.title,
|
||||
@ -63,7 +60,7 @@ class Dialog extends Component {
|
||||
}
|
||||
|
||||
if (content != null) {
|
||||
children.add(new Padding(
|
||||
dialogBody.add(new Padding(
|
||||
padding: const EdgeDims(20.0, 24.0, 24.0, 24.0),
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(this).text.subhead,
|
||||
@ -73,11 +70,15 @@ class Dialog extends Component {
|
||||
}
|
||||
|
||||
if (actions != null)
|
||||
children.add(new Flex(actions, justifyContent: FlexJustifyContent.end));
|
||||
dialogBody.add(new Flex(actions, justifyContent: FlexJustifyContent.end));
|
||||
|
||||
return new Stack([
|
||||
new Listener(
|
||||
child: mask,
|
||||
child: new Container(
|
||||
decoration: const BoxDecoration(
|
||||
backgroundColor: const Color(0x7F000000)
|
||||
)
|
||||
),
|
||||
onGestureTap: (_) => onDismiss()
|
||||
),
|
||||
new Center(
|
||||
@ -89,12 +90,13 @@ class Dialog extends Component {
|
||||
level: 4,
|
||||
color: _color,
|
||||
child: new ShrinkWrapWidth(
|
||||
child: new ScrollableBlock(children)
|
||||
child: new ScrollableBlock(dialogBody)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -9,22 +9,40 @@ import 'package:sky/widgets/animated_component.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:vector_math/vector_math.dart';
|
||||
|
||||
typedef Widget Builder(Navigator navigator, RouteBase route);
|
||||
typedef Widget RouteBuilder(Navigator navigator, RouteBase route);
|
||||
|
||||
abstract class RouteBase {
|
||||
Widget build(Navigator navigator, RouteBase route);
|
||||
bool get isOpaque;
|
||||
void popState() { }
|
||||
}
|
||||
|
||||
class Route extends RouteBase {
|
||||
Route({ this.name, this.builder });
|
||||
|
||||
final String name;
|
||||
final Builder builder;
|
||||
final RouteBuilder builder;
|
||||
|
||||
Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
|
||||
bool get isOpaque => true;
|
||||
}
|
||||
|
||||
class DialogRoute extends RouteBase {
|
||||
DialogRoute({ this.builder, this.callback });
|
||||
|
||||
final RouteBuilder builder;
|
||||
Function callback;
|
||||
|
||||
Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
|
||||
bool get isOpaque => false;
|
||||
|
||||
void popState() {
|
||||
if (callback != null)
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
class RouteState extends RouteBase {
|
||||
|
||||
RouteState({ this.callback, this.route, this.owner });
|
||||
|
||||
Function callback;
|
||||
@ -32,6 +50,7 @@ class RouteState extends RouteBase {
|
||||
StatefulComponent owner;
|
||||
|
||||
Widget build(Navigator navigator, RouteBase route) => null;
|
||||
bool get isOpaque => false;
|
||||
|
||||
void popState() {
|
||||
if (callback != null)
|
||||
@ -52,7 +71,7 @@ class Transition extends AnimatedComponent {
|
||||
this.onDismissed,
|
||||
this.onCompleted,
|
||||
this.interactive
|
||||
}) : super(key: key);
|
||||
}): super(key: key);
|
||||
Widget content;
|
||||
TransitionDirection direction;
|
||||
bool interactive;
|
||||
@ -145,7 +164,7 @@ class Transition extends AnimatedComponent {
|
||||
class HistoryEntry {
|
||||
HistoryEntry({ this.route });
|
||||
final RouteBase route;
|
||||
bool transitionFinished = false;
|
||||
bool fullyOpaque = false;
|
||||
// TODO(jackson): Keep track of the requested transition
|
||||
}
|
||||
|
||||
@ -182,7 +201,7 @@ class NavigationState {
|
||||
if (historyIndex > 0) {
|
||||
HistoryEntry entry = history[historyIndex];
|
||||
entry.route.popState();
|
||||
entry.transitionFinished = false;
|
||||
entry.fullyOpaque = false;
|
||||
historyIndex--;
|
||||
}
|
||||
}
|
||||
@ -231,7 +250,7 @@ class Navigator extends StatefulComponent {
|
||||
List<Widget> visibleRoutes = new List<Widget>();
|
||||
for (int i = 0; i < state.history.length; i++) {
|
||||
// Avoid building routes that are not visible
|
||||
if (i + 1 < state.history.length && state.history[i + 1].transitionFinished)
|
||||
if (i + 1 < state.history.length && state.history[i + 1].fullyOpaque)
|
||||
continue;
|
||||
HistoryEntry historyEntry = state.history[i];
|
||||
Widget content = historyEntry.route.build(this, historyEntry.route);
|
||||
@ -253,7 +272,7 @@ class Navigator extends StatefulComponent {
|
||||
},
|
||||
onCompleted: () {
|
||||
setState(() {
|
||||
historyEntry.transitionFinished = true;
|
||||
historyEntry.fullyOpaque = historyEntry.route.isOpaque;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user