blob: acd99fad984b618d5ec2170f15c08d1ffd38acef [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.
#import "GoogleMapController.h"
#pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations.
static id positionToJson(GMSCameraPosition* position);
static double toDouble(id json);
static CLLocationCoordinate2D toLocation(id json);
static GMSCameraPosition* toOptionalCameraPosition(id json);
static GMSCoordinateBounds* toOptionalBounds(id json);
static GMSCameraUpdate* toCameraUpdate(id json);
static void interpretMapOptions(id json, id<FLTGoogleMapOptionsSink> sink);
static void interpretMarkerOptions(id json, id<FLTGoogleMapMarkerOptionsSink> sink,
NSObject<FlutterPluginRegistrar>* registrar);
@implementation FLTGoogleMapFactory {
NSObject<FlutterPluginRegistrar>* _registrar;
}
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
self = [super init];
if (self) {
_registrar = registrar;
}
return self;
}
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
return [FlutterStandardMessageCodec sharedInstance];
}
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[FLTGoogleMapController alloc] initWithFrame:frame
viewIdentifier:viewId
arguments:args
registrar:_registrar];
}
@end
@implementation FLTGoogleMapController {
GMSMapView* _mapView;
int64_t _viewId;
NSMutableDictionary* _markers;
FlutterMethodChannel* _channel;
BOOL _trackCameraPosition;
NSObject<FlutterPluginRegistrar>* _registrar;
}
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
registrar:(NSObject<FlutterPluginRegistrar>*)registrar {
if ([super init]) {
_viewId = viewId;
GMSCameraPosition* camera = toOptionalCameraPosition(args[@"initialCameraPosition"]);
_mapView = [GMSMapView mapWithFrame:frame camera:camera];
_markers = [NSMutableDictionary dictionaryWithCapacity:1];
_trackCameraPosition = NO;
interpretMapOptions(args[@"options"], self);
NSString* channelName =
[NSString stringWithFormat:@"plugins.flutter.io/google_maps_%lld", viewId];
_channel = [FlutterMethodChannel methodChannelWithName:channelName
binaryMessenger:registrar.messenger];
__weak __typeof__(self) weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if (weakSelf) {
[weakSelf onMethodCall:call result:result];
}
}];
_mapView.delegate = weakSelf;
_registrar = registrar;
}
return self;
}
- (UIView*)view {
return _mapView;
}
- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([call.method isEqualToString:@"map#show"]) {
[self showAtX:toDouble(call.arguments[@"x"]) Y:toDouble(call.arguments[@"y"])];
result(nil);
} else if ([call.method isEqualToString:@"map#hide"]) {
[self hide];
result(nil);
} else if ([call.method isEqualToString:@"camera#animate"]) {
[self animateWithCameraUpdate:toCameraUpdate(call.arguments[@"cameraUpdate"])];
result(nil);
} else if ([call.method isEqualToString:@"camera#move"]) {
[self moveWithCameraUpdate:toCameraUpdate(call.arguments[@"cameraUpdate"])];
result(nil);
} else if ([call.method isEqualToString:@"map#update"]) {
interpretMapOptions(call.arguments[@"options"], self);
result(positionToJson([self cameraPosition]));
} else if ([call.method isEqualToString:@"map#waitForMap"]) {
result(nil);
} else if ([call.method isEqualToString:@"marker#add"]) {
NSDictionary* options = call.arguments[@"options"];
NSString* markerId = [self addMarkerWithPosition:toLocation(options[@"position"])];
interpretMarkerOptions(options, [self markerWithId:markerId], _registrar);
result(markerId);
} else if ([call.method isEqualToString:@"marker#update"]) {
interpretMarkerOptions(call.arguments[@"options"],
[self markerWithId:call.arguments[@"marker"]], _registrar);
result(nil);
} else if ([call.method isEqualToString:@"marker#remove"]) {
[self removeMarkerWithId:call.arguments[@"marker"]];
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
}
- (void)showAtX:(CGFloat)x Y:(CGFloat)y {
_mapView.frame =
CGRectMake(x, y, CGRectGetWidth(_mapView.frame), CGRectGetHeight(_mapView.frame));
_mapView.hidden = NO;
}
- (void)hide {
_mapView.hidden = YES;
}
- (void)animateWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate {
[_mapView animateWithCameraUpdate:cameraUpdate];
}
- (void)moveWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate {
[_mapView moveCamera:cameraUpdate];
}
- (GMSCameraPosition*)cameraPosition {
if (_trackCameraPosition) {
return _mapView.camera;
} else {
return nil;
}
}
- (NSString*)addMarkerWithPosition:(CLLocationCoordinate2D)position {
FLTGoogleMapMarkerController* markerController =
[[FLTGoogleMapMarkerController alloc] initWithPosition:position mapView:_mapView];
_markers[markerController.markerId] = markerController;
return markerController.markerId;
}
- (FLTGoogleMapMarkerController*)markerWithId:(NSString*)markerId {
return _markers[markerId];
}
- (void)removeMarkerWithId:(NSString*)markerId {
FLTGoogleMapMarkerController* markerController = _markers[markerId];
if (markerController) {
[markerController setVisible:NO];
[_markers removeObjectForKey:markerId];
}
}
#pragma mark - FLTGoogleMapOptionsSink methods
- (void)setCamera:(GMSCameraPosition*)camera {
_mapView.camera = camera;
}
- (void)setCameraTargetBounds:(GMSCoordinateBounds*)bounds {
_mapView.cameraTargetBounds = bounds;
}
- (void)setCompassEnabled:(BOOL)enabled {
_mapView.settings.compassButton = enabled;
}
- (void)setMapType:(GMSMapViewType)mapType {
_mapView.mapType = mapType;
}
- (void)setMinZoom:(float)minZoom maxZoom:(float)maxZoom {
[_mapView setMinZoom:minZoom maxZoom:maxZoom];
}
- (void)setRotateGesturesEnabled:(BOOL)enabled {
_mapView.settings.rotateGestures = enabled;
}
- (void)setScrollGesturesEnabled:(BOOL)enabled {
_mapView.settings.scrollGestures = enabled;
}
- (void)setTiltGesturesEnabled:(BOOL)enabled {
_mapView.settings.tiltGestures = enabled;
}
- (void)setTrackCameraPosition:(BOOL)enabled {
_trackCameraPosition = enabled;
}
- (void)setZoomGesturesEnabled:(BOOL)enabled {
_mapView.settings.zoomGestures = enabled;
}
- (void)setMyLocationEnabled:(BOOL)enabled {
_mapView.myLocationEnabled = enabled;
_mapView.settings.myLocationButton = enabled;
}
#pragma mark - GMSMapViewDelegate methods
- (void)mapView:(GMSMapView*)mapView willMove:(BOOL)gesture {
[_channel invokeMethod:@"camera#onMoveStarted" arguments:@{@"isGesture" : @(gesture)}];
}
- (void)mapView:(GMSMapView*)mapView didChangeCameraPosition:(GMSCameraPosition*)position {
if (_trackCameraPosition) {
[_channel invokeMethod:@"camera#onMove" arguments:@{@"position" : positionToJson(position)}];
}
}
- (void)mapView:(GMSMapView*)mapView idleAtCameraPosition:(GMSCameraPosition*)position {
[_channel invokeMethod:@"camera#onIdle" arguments:@{}];
}
- (BOOL)mapView:(GMSMapView*)mapView didTapMarker:(GMSMarker*)marker {
NSString* markerId = marker.userData[0];
[_channel invokeMethod:@"marker#onTap" arguments:@{@"marker" : markerId}];
return [marker.userData[1] boolValue];
}
- (void)mapView:(GMSMapView*)mapView didTapInfoWindowOfMarker:(GMSMarker*)marker {
NSString* markerId = marker.userData[0];
[_channel invokeMethod:@"infoWindow#onTap" arguments:@{@"marker" : markerId}];
}
@end
#pragma mark - Implementations of JSON conversion functions.
static id locationToJson(CLLocationCoordinate2D position) {
return @[ @(position.latitude), @(position.longitude) ];
}
static id positionToJson(GMSCameraPosition* position) {
if (!position) {
return nil;
}
return @{
@"target" : locationToJson([position target]),
@"zoom" : @([position zoom]),
@"bearing" : @([position bearing]),
@"tilt" : @([position viewingAngle]),
};
}
static bool toBool(id json) {
NSNumber* data = json;
return data.boolValue;
}
static int toInt(id json) {
NSNumber* data = json;
return data.intValue;
}
static double toDouble(id json) {
NSNumber* data = json;
return data.doubleValue;
}
static float toFloat(id json) {
NSNumber* data = json;
return data.floatValue;
}
static CLLocationCoordinate2D toLocation(id json) {
NSArray* data = json;
return CLLocationCoordinate2DMake(toDouble(data[0]), toDouble(data[1]));
}
static CGPoint toPoint(id json) {
NSArray* data = json;
return CGPointMake(toDouble(data[0]), toDouble(data[1]));
}
static GMSCameraPosition* toCameraPosition(id json) {
NSDictionary* data = json;
return [GMSCameraPosition cameraWithTarget:toLocation(data[@"target"])
zoom:toFloat(data[@"zoom"])
bearing:toDouble(data[@"bearing"])
viewingAngle:toDouble(data[@"tilt"])];
}
static GMSCameraPosition* toOptionalCameraPosition(id json) {
return json ? toCameraPosition(json) : nil;
}
static GMSCoordinateBounds* toBounds(id json) {
NSArray* data = json;
return [[GMSCoordinateBounds alloc] initWithCoordinate:toLocation(data[0])
coordinate:toLocation(data[1])];
}
static GMSCoordinateBounds* toOptionalBounds(id json) {
NSArray* data = json;
return (data[0] == [NSNull null]) ? nil : toBounds(data[0]);
}
static GMSMapViewType toMapViewType(id json) {
int value = toInt(json);
return (GMSMapViewType)(value == 0 ? 5 : value);
}
static GMSCameraUpdate* toCameraUpdate(id json) {
NSArray* data = json;
NSString* update = data[0];
if ([update isEqualToString:@"newCameraPosition"]) {
return [GMSCameraUpdate setCamera:toCameraPosition(data[1])];
} else if ([update isEqualToString:@"newLatLng"]) {
return [GMSCameraUpdate setTarget:toLocation(data[1])];
} else if ([update isEqualToString:@"newLatLngBounds"]) {
return [GMSCameraUpdate fitBounds:toBounds(data[1]) withPadding:toDouble(data[2])];
} else if ([update isEqualToString:@"newLatLngZoom"]) {
return [GMSCameraUpdate setTarget:toLocation(data[1]) zoom:toFloat(data[2])];
} else if ([update isEqualToString:@"scrollBy"]) {
return [GMSCameraUpdate scrollByX:toDouble(data[1]) Y:toDouble(data[2])];
} else if ([update isEqualToString:@"zoomBy"]) {
if (data.count == 2) {
return [GMSCameraUpdate zoomBy:toFloat(data[1])];
} else {
return [GMSCameraUpdate zoomBy:toFloat(data[1]) atPoint:toPoint(data[2])];
}
} else if ([update isEqualToString:@"zoomIn"]) {
return [GMSCameraUpdate zoomIn];
} else if ([update isEqualToString:@"zoomOut"]) {
return [GMSCameraUpdate zoomOut];
} else if ([update isEqualToString:@"zoomTo"]) {
return [GMSCameraUpdate zoomTo:toFloat(data[1])];
}
return nil;
}
static void interpretMapOptions(id json, id<FLTGoogleMapOptionsSink> sink) {
NSDictionary* data = json;
id cameraTargetBounds = data[@"cameraTargetBounds"];
if (cameraTargetBounds) {
[sink setCameraTargetBounds:toOptionalBounds(cameraTargetBounds)];
}
id compassEnabled = data[@"compassEnabled"];
if (compassEnabled) {
[sink setCompassEnabled:toBool(compassEnabled)];
}
id mapType = data[@"mapType"];
if (mapType) {
[sink setMapType:toMapViewType(mapType)];
}
id minMaxZoomPreference = data[@"minMaxZoomPreference"];
if (minMaxZoomPreference) {
NSArray* zoomData = minMaxZoomPreference;
float minZoom = (zoomData[0] == [NSNull null]) ? kGMSMinZoomLevel : toFloat(zoomData[0]);
float maxZoom = (zoomData[1] == [NSNull null]) ? kGMSMaxZoomLevel : toFloat(zoomData[1]);
[sink setMinZoom:minZoom maxZoom:maxZoom];
}
id rotateGesturesEnabled = data[@"rotateGesturesEnabled"];
if (rotateGesturesEnabled) {
[sink setRotateGesturesEnabled:toBool(rotateGesturesEnabled)];
}
id scrollGesturesEnabled = data[@"scrollGesturesEnabled"];
if (scrollGesturesEnabled) {
[sink setScrollGesturesEnabled:toBool(scrollGesturesEnabled)];
}
id tiltGesturesEnabled = data[@"tiltGesturesEnabled"];
if (tiltGesturesEnabled) {
[sink setTiltGesturesEnabled:toBool(tiltGesturesEnabled)];
}
id trackCameraPosition = data[@"trackCameraPosition"];
if (trackCameraPosition) {
[sink setTrackCameraPosition:toBool(trackCameraPosition)];
}
id zoomGesturesEnabled = data[@"zoomGesturesEnabled"];
if (zoomGesturesEnabled) {
[sink setZoomGesturesEnabled:toBool(zoomGesturesEnabled)];
}
id myLocationEnabled = data[@"myLocationEnabled"];
if (myLocationEnabled) {
[sink setMyLocationEnabled:toBool(myLocationEnabled)];
}
}
static void interpretMarkerOptions(id json, id<FLTGoogleMapMarkerOptionsSink> sink,
NSObject<FlutterPluginRegistrar>* registrar) {
NSDictionary* data = json;
id alpha = data[@"alpha"];
if (alpha) {
[sink setAlpha:toFloat(alpha)];
}
id anchor = data[@"anchor"];
if (anchor) {
[sink setAnchor:toPoint(anchor)];
}
id draggable = data[@"draggable"];
if (draggable) {
[sink setDraggable:toBool(draggable)];
}
id icon = data[@"icon"];
if (icon) {
NSArray* iconData = icon;
UIImage* image;
if ([iconData[0] isEqualToString:@"defaultMarker"]) {
CGFloat hue = (iconData.count == 1) ? 0.0f : toDouble(iconData[1]);
image = [GMSMarker markerImageWithColor:[UIColor colorWithHue:hue / 360.0
saturation:1.0
brightness:0.7
alpha:1.0]];
} else if ([iconData[0] isEqualToString:@"fromAsset"]) {
if (iconData.count == 2) {
image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]];
} else {
image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]
fromPackage:iconData[2]]];
}
}
[sink setIcon:image];
}
id flat = data[@"flat"];
if (flat) {
[sink setFlat:toBool(flat)];
}
id infoWindowAnchor = data[@"infoWindowAnchor"];
if (infoWindowAnchor) {
[sink setInfoWindowAnchor:toPoint(infoWindowAnchor)];
}
id infoWindowText = data[@"infoWindowText"];
if (infoWindowText) {
NSArray* infoWindowTextData = infoWindowText;
NSString* title = (infoWindowTextData[0] == [NSNull null]) ? nil : infoWindowTextData[0];
NSString* snippet = (infoWindowTextData[1] == [NSNull null]) ? nil : infoWindowTextData[1];
[sink setInfoWindowTitle:title snippet:snippet];
}
id position = data[@"position"];
if (position) {
[sink setPosition:toLocation(position)];
}
id rotation = data[@"rotation"];
if (rotation) {
[sink setRotation:toDouble(rotation)];
}
id visible = data[@"visible"];
if (visible) {
[sink setVisible:toBool(visible)];
}
id zIndex = data[@"zIndex"];
if (zIndex) {
[sink setZIndex:toInt(zIndex)];
}
}