Commit 72ae4b2e authored by Adam Barth's avatar Adam Barth

Merge pull request #568 from abarth/system_routing

Add support for system-level routing
parents b152791f 860547c1
...@@ -59,7 +59,7 @@ class MaterialApp extends StatefulComponent { ...@@ -59,7 +59,7 @@ class MaterialApp extends StatefulComponent {
_MaterialAppState createState() => new _MaterialAppState(); _MaterialAppState createState() => new _MaterialAppState();
} }
class _MaterialAppState extends State<MaterialApp> { class _MaterialAppState extends State<MaterialApp> implements BindingObserver {
GlobalObjectKey _navigator; GlobalObjectKey _navigator;
...@@ -68,28 +68,25 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -68,28 +68,25 @@ class _MaterialAppState extends State<MaterialApp> {
void initState() { void initState() {
super.initState(); super.initState();
_navigator = new GlobalObjectKey(this); _navigator = new GlobalObjectKey(this);
WidgetFlutterBinding.instance.addEventListener(_backHandler);
_size = ui.window.size; _size = ui.window.size;
FlutterBinding.instance.addMetricListener(_metricHandler); FlutterBinding.instance.addObserver(this);
} }
void dispose() { void dispose() {
WidgetFlutterBinding.instance.removeEventListener(_backHandler); FlutterBinding.instance.removeObserver(this);
FlutterBinding.instance.removeMetricListener(_metricHandler);
super.dispose(); super.dispose();
} }
void _backHandler(InputEvent event) { bool didPopRoute() {
assert(mounted); assert(mounted);
if (event.type == 'back') {
NavigatorState navigator = _navigator.currentState; NavigatorState navigator = _navigator.currentState;
assert(navigator != null); assert(navigator != null);
if (!navigator.pop()) if (!navigator.pop())
activity.finishCurrentActivity(); activity.finishCurrentActivity();
} return true;
} }
void _metricHandler(Size size) => setState(() { _size = size; }); void didChangeSize(Size size) => setState(() { _size = size; });
final HeroController _heroController = new HeroController(); final HeroController _heroController = new HeroController();
...@@ -116,6 +113,7 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -116,6 +113,7 @@ class _MaterialAppState extends State<MaterialApp> {
title: config.title, title: config.title,
child: new Navigator( child: new Navigator(
key: _navigator, key: _navigator,
initialRoute: ui.window.defaultRouteName,
onGenerateRoute: _generateRoute, onGenerateRoute: _generateRoute,
observer: _heroController observer: _heroController
) )
......
...@@ -132,6 +132,11 @@ class _PointerEventConverter { ...@@ -132,6 +132,11 @@ class _PointerEventConverter {
} }
} }
class BindingObserver {
bool didPopRoute() => false;
void didChangeSize(Size size) { }
}
/// The glue between the render tree and the Flutter engine /// The glue between the render tree and the Flutter engine
class FlutterBinding extends HitTestTarget { class FlutterBinding extends HitTestTarget {
...@@ -139,9 +144,9 @@ class FlutterBinding extends HitTestTarget { ...@@ -139,9 +144,9 @@ class FlutterBinding extends HitTestTarget {
assert(_instance == null); assert(_instance == null);
_instance = this; _instance = this;
ui.window.onEvent = _handleEvent;
ui.window.onPointerPacket = _handlePointerPacket; ui.window.onPointerPacket = _handlePointerPacket;
ui.window.onMetricsChanged = _handleMetricsChanged; ui.window.onMetricsChanged = _handleMetricsChanged;
ui.window.onPopRoute = _handlePopRoute;
if (renderViewOverride == null) { if (renderViewOverride == null) {
_renderView = new RenderView(child: root); _renderView = new RenderView(child: root);
...@@ -165,19 +170,16 @@ class FlutterBinding extends HitTestTarget { ...@@ -165,19 +170,16 @@ class FlutterBinding extends HitTestTarget {
RenderView get renderView => _renderView; RenderView get renderView => _renderView;
RenderView _renderView; RenderView _renderView;
final List<MetricListener> _metricListeners = new List<MetricListener>(); final List<BindingObserver> _observers = new List<BindingObserver>();
/// Calls listener for every event that isn't localized to a given view coordinate
void addMetricListener(MetricListener listener) => _metricListeners.add(listener);
/// Stops calling listener for every event that isn't localized to a given view coordinate void addObserver(BindingObserver observer) => _observers.add(observer);
bool removeMetricListener(MetricListener listener) => _metricListeners.remove(listener); bool removeObserver(BindingObserver observer) => _observers.remove(observer);
void _handleMetricsChanged() { void _handleMetricsChanged() {
Size size = ui.window.size; Size size = ui.window.size;
_renderView.rootConstraints = new ViewConstraints(size: size); _renderView.rootConstraints = new ViewConstraints(size: size);
for (MetricListener listener in _metricListeners) for (BindingObserver observer in _observers)
listener(size); observer.didChangeSize(size);
} }
void _handlePersistentFrameCallback(Duration timeStamp) { void _handlePersistentFrameCallback(Duration timeStamp) {
...@@ -192,23 +194,11 @@ class FlutterBinding extends HitTestTarget { ...@@ -192,23 +194,11 @@ class FlutterBinding extends HitTestTarget {
_renderView.compositeFrame(); _renderView.compositeFrame();
} }
final List<EventListener> _eventListeners = new List<EventListener>(); void _handlePopRoute() {
for (BindingObserver observer in _observers) {
/// Calls listener for every event that isn't localized to a given view coordinate if (observer.didPopRoute())
void addEventListener(EventListener listener) => _eventListeners.add(listener); return;
}
/// Stops calling listener for every event that isn't localized to a given view coordinate
bool removeEventListener(EventListener listener) => _eventListeners.remove(listener);
// TODO(abarth): The engine should give us the timeStamp in Durations.
void _handleEvent(String eventType, double timeStamp) {
assert(eventType == 'back');
InputEvent ourEvent = new InputEvent(
type: eventType,
timeStamp: new Duration(microseconds: timeStamp.round())
);
for (EventListener listener in _eventListeners)
listener(ourEvent);
} }
void _handlePointerPacket(ByteData serializedPacket) { void _handlePointerPacket(ByteData serializedPacket) {
......
...@@ -51,6 +51,7 @@ class NavigatorObserver { ...@@ -51,6 +51,7 @@ class NavigatorObserver {
class Navigator extends StatefulComponent { class Navigator extends StatefulComponent {
Navigator({ Navigator({
Key key, Key key,
this.initialRoute,
this.onGenerateRoute, this.onGenerateRoute,
this.onUnknownRoute, this.onUnknownRoute,
this.observer this.observer
...@@ -58,6 +59,7 @@ class Navigator extends StatefulComponent { ...@@ -58,6 +59,7 @@ class Navigator extends StatefulComponent {
assert(onGenerateRoute != null); assert(onGenerateRoute != null);
} }
final String initialRoute;
final RouteFactory onGenerateRoute; final RouteFactory onGenerateRoute;
final RouteFactory onUnknownRoute; final RouteFactory onUnknownRoute;
final NavigatorObserver observer; final NavigatorObserver observer;
...@@ -77,7 +79,9 @@ class NavigatorState extends State<Navigator> { ...@@ -77,7 +79,9 @@ class NavigatorState extends State<Navigator> {
super.initState(); super.initState();
assert(config.observer == null || config.observer.navigator == null); assert(config.observer == null || config.observer.navigator == null);
config.observer?._navigator = this; config.observer?._navigator = this;
push(config.onGenerateRoute(new NamedRouteSettings(name: Navigator.defaultRouteName))); push(config.onGenerateRoute(new NamedRouteSettings(
name: config.initialRoute ?? Navigator.defaultRouteName
)));
} }
void didUpdateConfig(Navigator oldConfig) { void didUpdateConfig(Navigator oldConfig) {
......
...@@ -59,7 +59,7 @@ class ListenCommand extends FlutterCommand { ...@@ -59,7 +59,7 @@ class ListenCommand extends FlutterCommand {
if (package == null || !device.isConnected()) if (package == null || !device.isConnected())
continue; continue;
if (device is AndroidDevice) { if (device is AndroidDevice) {
device.startBundle(package, localBundlePath, true, argResults['checked']); device.startBundle(package, localBundlePath, poke: true, checked: argResults['checked']);
} else if (device is IOSDevice) { } else if (device is IOSDevice) {
device.pushFile(package, localBundlePath, _remoteFlutterBundle); device.pushFile(package, localBundlePath, _remoteFlutterBundle);
} else if (device is IOSSimulator) { } else if (device is IOSSimulator) {
......
...@@ -33,6 +33,7 @@ class StartCommand extends FlutterCommand { ...@@ -33,6 +33,7 @@ class StartCommand extends FlutterCommand {
defaultsTo: '', defaultsTo: '',
abbr: 't', abbr: 't',
help: 'Target app path or filename to start.'); help: 'Target app path or filename to start.');
argParser.addOption('route', help: 'Which route to load when starting the app.');
argParser.addFlag('boot', argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.'); help: 'Boot the iOS Simulator if it isn\'t already running.');
} }
...@@ -88,7 +89,10 @@ class StartCommand extends FlutterCommand { ...@@ -88,7 +89,10 @@ class StartCommand extends FlutterCommand {
await builder.buildInTempDir( await builder.buildInTempDir(
mainPath: mainPath, mainPath: mainPath,
onBundleAvailable: (String localBundlePath) { onBundleAvailable: (String localBundlePath) {
if (device.startBundle(package, localBundlePath, poke, argResults['checked'])) if (device.startBundle(package, localBundlePath,
poke: poke,
checked: argResults['checked'],
route: argResults['route']))
startedSomething = true; startedSomething = true;
} }
); );
......
...@@ -768,7 +768,11 @@ class AndroidDevice extends Device { ...@@ -768,7 +768,11 @@ class AndroidDevice extends Device {
runCheckedSync(adbCommandForDevice(['forward', portString, portString])); runCheckedSync(adbCommandForDevice(['forward', portString, portString]));
} }
bool startBundle(AndroidApk apk, String bundlePath, bool poke, bool checked) { bool startBundle(AndroidApk apk, String bundlePath, {
bool poke,
bool checked,
String route
}) {
if (!FileSystemEntity.isFileSync(bundlePath)) { if (!FileSystemEntity.isFileSync(bundlePath)) {
_logging.severe('Cannot find $bundlePath'); _logging.severe('Cannot find $bundlePath');
return false; return false;
...@@ -786,6 +790,8 @@ class AndroidDevice extends Device { ...@@ -786,6 +790,8 @@ class AndroidDevice extends Device {
]); ]);
if (checked) if (checked)
cmd.addAll(['--ez', 'enable-checked-mode', 'true']); cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
if (route != null)
cmd.addAll(['--es', 'route', route]);
cmd.add(apk.launchActivity); cmd.add(apk.launchActivity);
runCheckedSync(cmd); runCheckedSync(cmd);
return true; return true;
......
...@@ -21,7 +21,7 @@ defineTests() { ...@@ -21,7 +21,7 @@ defineTests() {
when(mockDevices.android.isConnected()).thenReturn(true); when(mockDevices.android.isConnected()).thenReturn(true);
when(mockDevices.android.isAppInstalled(any)).thenReturn(false); when(mockDevices.android.isAppInstalled(any)).thenReturn(false);
when(mockDevices.android.installApp(any)).thenReturn(true); when(mockDevices.android.installApp(any)).thenReturn(true);
when(mockDevices.android.startBundle(any, any, any, any)).thenReturn(true); when(mockDevices.android.startBundle(any, any)).thenReturn(true);
when(mockDevices.android.stopApp(any)).thenReturn(true); when(mockDevices.android.stopApp(any)).thenReturn(true);
when(mockDevices.iOS.isConnected()).thenReturn(false); when(mockDevices.iOS.isConnected()).thenReturn(false);
...@@ -49,7 +49,7 @@ defineTests() { ...@@ -49,7 +49,7 @@ defineTests() {
when(mockDevices.android.isConnected()).thenReturn(false); when(mockDevices.android.isConnected()).thenReturn(false);
when(mockDevices.android.isAppInstalled(any)).thenReturn(false); when(mockDevices.android.isAppInstalled(any)).thenReturn(false);
when(mockDevices.android.installApp(any)).thenReturn(false); when(mockDevices.android.installApp(any)).thenReturn(false);
when(mockDevices.android.startBundle(any, any, any, any)).thenReturn(false); when(mockDevices.android.startBundle(any, any)).thenReturn(false);
when(mockDevices.android.stopApp(any)).thenReturn(false); when(mockDevices.android.stopApp(any)).thenReturn(false);
when(mockDevices.iOS.isConnected()).thenReturn(true); when(mockDevices.iOS.isConnected()).thenReturn(true);
......
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