blob: a29184019cf4336077f1d985dce7348eb1ef7cd1 [file] [log] [blame]
// Copyright 2018 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.
part of google_mobile_maps;
final MethodChannel _channel =
const MethodChannel('plugins.flutter.io/google_mobile_maps');
/// Controller for a single GoogleMap instance.
///
/// Used for programmatically controlling a platform-specific GoogleMap view.
///
/// Change listeners are notified upon changes to any of
///
/// * the [options] property,
/// * the collection of [Marker]s added to this map
/// * the [cameraPosition] property,
///
/// Listeners are notified when changes have been applied on the platform side.
///
/// Marker tap events can be received by adding callbacks to [onMarkerTapped].
class GoogleMapController extends ChangeNotifier {
GoogleMapController._({
this.id,
GoogleMapOptions options,
}) : _options = options {
id.then((int id) {
_controllers[id] = this;
});
if (options.trackCameraPosition) {
_cameraPosition = options.cameraPosition;
}
}
/// An ID identifying the GoogleMaps instance, once created.
final Future<int> id;
final ArgumentCallbacks<Marker> onMarkerTapped =
new ArgumentCallbacks<Marker>();
/// The configuration options most recently applied via controller
/// initialization or [updateMapOptions].
GoogleMapOptions get options => _options;
GoogleMapOptions _options;
Set<Marker> get markers => new Set<Marker>.from(_markers.values);
final Map<String, Marker> _markers = <String, Marker>{};
bool get isCameraMoving => _isCameraMoving;
bool _isCameraMoving = false;
/// Returns the most recent camera position reported by the platform side.
/// Will be null, if camera position tracking is not enabled via
/// [GoogleMapOptions].
CameraPosition get cameraPosition => _cameraPosition;
CameraPosition _cameraPosition;
static Map<int, GoogleMapController> _controllers =
<int, GoogleMapController>{};
static Future<void> init() async {
await _channel.invokeMethod('init');
_channel.setMethodCallHandler((MethodCall call) {
final int mapId = call.arguments['map'];
final GoogleMapController controller = _controllers[mapId];
if (controller != null) {
controller._handleMethodCall(call);
}
});
}
void _handleMethodCall(MethodCall call) {
switch (call.method) {
case "marker#onTap":
final String markerId = call.arguments['marker'];
final Marker marker = _markers[markerId];
if (marker != null) {
onMarkerTapped(marker);
}
break;
case "map#onCameraMoveStarted":
_isCameraMoving = true;
notifyListeners();
break;
case "map#onCameraMove":
_cameraPosition = CameraPosition._fromJson(call.arguments['position']);
notifyListeners();
break;
case "map#onCameraIdle":
_isCameraMoving = false;
notifyListeners();
break;
default:
throw new MissingPluginException();
}
}
Future<void> updateMapOptions(GoogleMapOptions options) async {
assert(options != null);
final int id = await this.id;
await _channel.invokeMethod('setMapOptions', <String, dynamic>{
'map': id,
'options': options._toJson(),
});
_options = _options._updateWith(options);
if (!_options.trackCameraPosition) {
_cameraPosition = null;
}
notifyListeners();
}
Future<void> animateCamera(CameraUpdate cameraUpdate) async {
final int id = await this.id;
await _channel.invokeMethod('animateCamera', <String, dynamic>{
'map': id,
'cameraUpdate': cameraUpdate._toJson(),
});
}
Future<void> moveCamera(CameraUpdate cameraUpdate) async {
final int id = await this.id;
await _channel.invokeMethod('moveCamera', <String, dynamic>{
'map': id,
'cameraUpdate': cameraUpdate._toJson(),
});
}
Future<Marker> addMarker(MarkerOptions options) async {
assert(options != null);
assert(options.position != null);
final int id = await this.id;
final MarkerOptions effectiveOptions =
MarkerOptions.defaultOptions._updateWith(options);
final String markerId = await _channel.invokeMethod(
'addMarker',
<String, dynamic>{
'map': id,
'options': effectiveOptions._toJson(),
},
);
final Marker marker = new Marker._(this, markerId, effectiveOptions);
_markers[markerId] = marker;
notifyListeners();
return marker;
}
Future<void> _updateMarker(Marker marker, MarkerOptions changes) async {
assert(_markers[marker.id] == marker);
assert(changes != null);
final int id = await this.id;
await _channel.invokeMethod('marker#update', <String, dynamic>{
'map': id,
'marker': marker.id,
'options': changes._toJson(),
});
marker._options = marker._options._updateWith(changes);
notifyListeners();
}
Future<void> _removeMarker(Marker marker) async {
assert(_markers[marker.id] == marker);
final int id = await this.id;
await _channel.invokeMethod('marker#remove', <String, dynamic>{
'map': id,
'marker': marker.id,
});
_markers.remove(marker.id);
notifyListeners();
}
}
/// Controller for a GoogleMap instance that is integrated as a
/// platform overlay.
///
/// *Warning*: Platform overlays cannot be freely composed with
/// other widgets. See [PlatformOverlayController] for caveats and
/// limitations.
class GoogleMapOverlayController {
GoogleMapOverlayController._(this.mapController, this.overlayController);
/// Creates a controller for a GoogleMaps of the specified size in
/// logical pixels.
factory GoogleMapOverlayController.fromSize({
@required double width,
@required double height,
GoogleMapOptions options = const GoogleMapOptions(),
}) {
assert(width != null);
assert(height != null);
assert(options != null);
final GoogleMapOptions effectiveOptions =
GoogleMapOptions.defaultOptions._updateWith(options);
final _GoogleMapsPlatformOverlay overlay =
new _GoogleMapsPlatformOverlay(effectiveOptions);
return new GoogleMapOverlayController._(
new GoogleMapController._(
id: overlay._textureId.future,
options: effectiveOptions,
),
new PlatformOverlayController(width, height, overlay),
);
}
/// The controller of the GoogleMaps instance.
final GoogleMapController mapController;
/// The controller of the platform overlay.
final PlatformOverlayController overlayController;
void dispose() {
overlayController.dispose();
}
}
class _GoogleMapsPlatformOverlay extends PlatformOverlay {
_GoogleMapsPlatformOverlay(this.options);
final GoogleMapOptions options;
Completer<int> _textureId = new Completer<int>();
@override
Future<int> create(Size physicalSize) {
_textureId.complete(_channel.invokeMethod('createMap', <String, dynamic>{
'width': physicalSize.width,
'height': physicalSize.height,
'options': options._toJson(),
}).then<int>((dynamic value) => value));
return _textureId.future;
}
@override
Future<void> show(Offset physicalOffset) async {
final int id = await _textureId.future;
_channel.invokeMethod('showMapOverlay', <String, dynamic>{
'map': id,
'x': physicalOffset.dx,
'y': physicalOffset.dy,
});
}
@override
Future<void> hide() async {
final int id = await _textureId.future;
_channel.invokeMethod('hideMapOverlay', <String, dynamic>{
'map': id,
});
}
@override
Future<void> dispose() async {
final int id = await _textureId.future;
_channel.invokeMethod('disposeMap', <String, dynamic>{
'map': id,
});
}
}
/// A Widget covered by a GoogleMaps platform overlay.
class GoogleMapOverlay extends StatefulWidget {
final GoogleMapOverlayController controller;
GoogleMapOverlay({Key key, @required this.controller}) : super(key: key);
@override
State<StatefulWidget> createState() => new _GoogleMapOverlayState();
}
class _GoogleMapOverlayState extends State<GoogleMapOverlay> {
@override
void initState() {
super.initState();
widget.controller.overlayController.attachTo(context);
}
@override
void dispose() {
widget.controller.overlayController.detach();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new SizedBox(
child: new FutureBuilder<int>(
future: widget.controller.mapController.id,
builder: (_, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return new Texture(textureId: snapshot.data);
} else {
return new Container();
}
},
),
width: widget.controller.overlayController.width,
height: widget.controller.overlayController.height,
);
}
}