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