diff --git a/.idea/misc.xml b/.idea/misc.xml index 6ab96c3..5a3f2a5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ + + - + \ No newline at end of file diff --git a/.idea/react-native-gmaps.iml b/.idea/react-native-gmaps.iml index d6ebd48..3a3afd8 100644 --- a/.idea/react-native-gmaps.iml +++ b/.idea/react-native-gmaps.iml @@ -1,7 +1,6 @@ - - + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ee96105..0f2597e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,11 +2,11 @@ - - + + - - + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -56,15 +74,20 @@ - - + + + + true + - @@ -87,10 +110,23 @@ + + - - + + + + + + + + + @@ -234,100 +270,18 @@ - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -403,24 +358,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 3771546..7c16018 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,30 @@ let RNGMap = require('react-native-gmaps'); style={ { height: 500, width: 500 } } markers={ [ { coordinates: {lng: 0.1, lat: 51.0} }, - { coordinates: {lng: -0.1, lat: 51.0}, title: "Click marker to see this title!" } + { + coordinates: {lng: -0.1, lat: 51.0}, + title: "Click marker to see this title!", + snippet: "Subtitle", + id: 0, + /* + * Able to use "my_icon" or {uri: 'my_icon', width: 100, height: 100 } here as well + */ + icon: require('image!my_icon'), + /* + * color is only working with default icon + */ + color: 120, + } ] } zoomLevel={10} onMapChange={(e) => console.log(e)} onMapError={(e) => console.log('Map error --> ', e)} - center={ { lng: 0.1, lat: 51.0 } } /> + center={ { lng: 0.1, lat: 51.0 } } + /* + * clickMarker shows Info Window of Marker with id: 0, + * hides Info Window if given null + */ + clickMarker={0}/> ``` ##### onMapChange diff --git a/android/app/build.gradle b/android/app/build.gradle index 852bf0e..f913fe4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -10,6 +10,12 @@ android { versionCode 1 versionName "1.0" } + buildTypes { + release { + minifyEnabled false // Set this to true to enable Proguard + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } lintOptions { abortOnError false } @@ -22,7 +28,7 @@ repositories { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.0' - compile 'com.facebook.react:react-native:0.12.0' - compile 'com.google.android.gms:play-services:8.1.0' + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.facebook.react:react-native:0.16.+' + compile 'com.google.android.gms:play-services-maps:8.1.0' } diff --git a/android/app/src/main/java/com/rota/rngmaps/RNGMapsModule.java b/android/app/src/main/java/com/rota/rngmaps/RNGMapsModule.java index a9cba92..221e19f 100644 --- a/android/app/src/main/java/com/rota/rngmaps/RNGMapsModule.java +++ b/android/app/src/main/java/com/rota/rngmaps/RNGMapsModule.java @@ -1,52 +1,113 @@ package com.rota.rngmaps; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import javax.annotation.Nullable; import android.util.Log; import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.uimanager.CatalystStylesDiffMap; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.UIProp; +import com.facebook.react.uimanager.ReactProp; import com.google.android.gms.maps.*; -import com.google.android.gms.maps.model.CameraPosition; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.LatLngBounds; -import com.google.android.gms.maps.model.Marker; -import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.*; import java.util.ArrayList; -import java.util.Map; +import java.util.HashMap; -/** - * Created by Henry on 08/10/2015. - */ public class RNGMapsModule extends SimpleViewManager { public static final String REACT_CLASS = "RNGMaps"; + // Unique Name for Log TAG + public static final String TAG = RNGMapsModule.class.getSimpleName(); private MapView mView; private GoogleMap map; private ReactContext reactContext; private ArrayList mapMarkers = new ArrayList(); + private HashMap markerLookup = new HashMap(); + private WritableMap properties = Arguments.createMap(); + + public static final String[] markerProps = { + "title", + "coordinates", + "color", + "snippet", + "icon", + "id" + }; - @UIProp(UIProp.Type.MAP) public static final String PROP_CENTER = "center"; - - @UIProp(UIProp.Type.NUMBER) public static final String PROP_ZOOM_LEVEL = "zoomLevel"; - - @UIProp(UIProp.Type.ARRAY) public static final String PROP_MARKERS = "markers"; - - @UIProp(UIProp.Type.BOOLEAN) public static final String PROP_ZOOM_ON_MARKERS = "zoomOnMarkers"; + public static final String PROP_CLICK_MARKER = "clickMarker"; + + @ReactProp(name = PROP_CENTER) + public void setPropCenter(MapView view, @Nullable ReadableMap center) { + if (center != null) { + WritableMap centerLatLng = Arguments.createMap(); + WritableMap centerMap = Arguments.createMap(); + centerLatLng.putDouble("lat", center.getDouble("lat")); + centerLatLng.putDouble("lng", center.getDouble("lng")); + + centerMap.putMap("center", centerLatLng); + properties.merge(centerMap); + updateCenter(); + } + } + + @ReactProp(name = PROP_ZOOM_LEVEL, defaultInt = 10) + public void setPropZoomLevel(MapView view, int zoomLevel) { + properties.putInt(PROP_ZOOM_LEVEL, zoomLevel); + updateCenter(); + } + + @ReactProp(name = PROP_MARKERS) + public void setPropMarkers(MapView view, @Nullable ReadableArray markersArray) { + if (markersArray != null) { + updateMarkers(markersArray); + } + } + + @ReactProp(name = PROP_ZOOM_ON_MARKERS, defaultBoolean = false) + public void setPropZoomOnMarkers(MapView view, Boolean shallZoomOnMarkers) { + properties.putBoolean(PROP_ZOOM_ON_MARKERS, shallZoomOnMarkers); + if (shallZoomOnMarkers) { + zoomOnMarkers(); + } + } + + @ReactProp(name = PROP_CLICK_MARKER) + public void setPropClickMarker(MapView view, @Nullable Integer clickMarker) { + String key = String.valueOf(clickMarker); + Log.i(TAG, key); + if (clickMarker == null) { + if (properties.hasKey(PROP_CLICK_MARKER)) { + if (markerLookup.containsKey(String.valueOf(properties.getInt(PROP_CLICK_MARKER)))) { + Marker marker = mapMarkers.get(Integer.parseInt(markerLookup.get(String.valueOf(properties.getInt(PROP_CLICK_MARKER)))) ); + marker.hideInfoWindow(); + Log.i(TAG, "hideInfoWindow"); + } + } + } else { + properties.putInt(PROP_CLICK_MARKER, clickMarker); + if (markerLookup.containsKey(key)) { + Marker marker = mapMarkers.get( Integer.parseInt(markerLookup.get(key)) ); + marker.showInfoWindow(); + Log.i(TAG, "showInfoWindow" + String.valueOf(marker)); + } + } + } + + protected int mlastZoom = 10; @Override public String getName() { @@ -54,7 +115,7 @@ public String getName() { } @Override - protected MapView createViewInstance(ThemedReactContext context) { + public MapView createViewInstance(ThemedReactContext context) { reactContext = context; mView = new MapView(context); mView.onCreate(null); @@ -62,31 +123,41 @@ protected MapView createViewInstance(ThemedReactContext context) { map = mView.getMap(); if (map == null) { - sendMapError("Map is null", "map_null"); + sendMapError("Map is null", "map_null"); } else { - map.getUiSettings().setMyLocationButtonEnabled(false); - map.setMyLocationEnabled(true); - - try { - MapsInitializer.initialize(context.getApplicationContext()); - map.setOnCameraChangeListener(getCameraChangeListener()); - } catch (Exception e) { - e.printStackTrace(); - sendMapError("Map initialize error", "map_init_error"); - } + map.getUiSettings().setMyLocationButtonEnabled(true); + map.setMyLocationEnabled(true); + + try { + MapsInitializer.initialize(context.getApplicationContext()); + map.setOnCameraChangeListener(getCameraChangeListener()); + map.setOnMarkerClickListener(getMarkerClickListener()); + //add location button click listener + map.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() { + @Override + public boolean onMyLocationButtonClick() { + CameraPosition position = map.getCameraPosition(); + mlastZoom = (int) position.zoom; + return false; + } + }); + } catch (Exception e) { + e.printStackTrace(); + sendMapError("Map initialize error", "map_init_error"); + } } return mView; } private void sendMapError (String message, String type) { - WritableMap error = Arguments.createMap(); - error.putString("message", message); - error.putString("type", type); + WritableMap error = Arguments.createMap(); + error.putString("message", message); + error.putString("type", type); - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("mapError", error); + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("mapError", error); } private GoogleMap.OnCameraChangeListener getCameraChangeListener() { @@ -99,7 +170,8 @@ public void onCameraChange(CameraPosition position) { latLng.putDouble("lng", position.target.longitude); params.putMap("latLng", latLng); - params.putDouble("zoomLevel", position.zoom); + params.putInt("zoomLevel",(int) position.zoom); + mlastZoom = (int) position.zoom; reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -108,34 +180,72 @@ public void onCameraChange(CameraPosition position) { }; } - private Boolean updateCenter (CatalystStylesDiffMap props) { - try { - CameraUpdate cameraUpdate; - Double lng = props.getMap(PROP_CENTER).getDouble("lng"); - Double lat = props.getMap(PROP_CENTER).getDouble("lat"); - - if(props.hasKey(PROP_ZOOM_LEVEL)) { - int zoomLevel = props.getInt(PROP_ZOOM_LEVEL, 10); - cameraUpdate = CameraUpdateFactory - .newLatLngZoom( - new LatLng(lat, lng), - props.getInt(PROP_ZOOM_LEVEL, 10) - ); - } else { - cameraUpdate = CameraUpdateFactory.newLatLng(new LatLng(lat, lng)); + private GoogleMap.OnMarkerClickListener getMarkerClickListener() { + return new GoogleMap.OnMarkerClickListener() { + @Override + public boolean onMarkerClick(Marker marker) { + String id = marker.getId(); + Log.i(TAG, "onMarkerClick " + id); + if (markerLookup.containsKey(id)) { + WritableMap event = Arguments.createMap(); + event.putString("id", markerLookup.get(id)); + + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("markerClick", event); + } + + return false; } + }; + } - map.animateCamera(cameraUpdate); - - return true; - } catch (Exception e) { - // ERROR! - e.printStackTrace(); + private Boolean updateCenter () { + if (properties.hasKey(PROP_CENTER)) { + try { + CameraUpdate cameraUpdate; + + Double lng = properties.getMap(PROP_CENTER).getDouble("lng"); + Double lat = properties.getMap(PROP_CENTER).getDouble("lat"); + + if (properties.hasKey(PROP_ZOOM_LEVEL)) { + int zoomLevel = properties.getInt(PROP_ZOOM_LEVEL); + mlastZoom = zoomLevel; + Log.i(TAG, "Zoom: " + Integer.toString(properties.getInt(PROP_ZOOM_LEVEL))); + cameraUpdate = CameraUpdateFactory + .newLatLngZoom( + new LatLng(lat, lng), + zoomLevel + ); + } else { + Log.i(TAG, "Default Zoom."); + /* + * Changed from cameraUpdate = CameraUpdateFactory.newLatLng(new LatLng(lat, lng)); + * as it gave me "zoom" Bugs (defaulted to zoom factor 2) as soon as I put in + * "real" LNG and LAT values... + */ + cameraUpdate = CameraUpdateFactory + .newLatLngZoom( + new LatLng(lat, lng), + mlastZoom + ); + } + + + map.animateCamera(cameraUpdate); + + return true; + } catch (Exception e) { + // ERROR! + e.printStackTrace(); + return false; + } + } else { return false; } } - private Boolean updateMarkers (CatalystStylesDiffMap props) { + private Boolean updateMarkers (ReadableArray markerArray) { try { // First clear all markers from the map @@ -143,23 +253,64 @@ private Boolean updateMarkers (CatalystStylesDiffMap props) { marker.remove(); } mapMarkers.clear(); + markerLookup.clear(); // All markers to map - for (int i = 0; i < props.getArray(PROP_MARKERS).size(); i++) { + for (int i = 0; i < markerArray.size(); i++) { MarkerOptions options = new MarkerOptions(); - ReadableMap marker = props.getArray(PROP_MARKERS).getMap(i); - if(marker.hasKey("coordinates")) { + ReadableMap markerJson = markerArray.getMap(i); + if(markerJson.hasKey("coordinates")) { options.position(new LatLng( - marker.getMap("coordinates").getDouble("lat"), - marker.getMap("coordinates").getDouble("lng") + markerJson.getMap("coordinates").getDouble("lat"), + markerJson.getMap("coordinates").getDouble("lng") ) ); - if(marker.hasKey("title")) { - options.title(marker.getString("title")); + if(markerJson.hasKey("title")) { + options.title(markerJson.getString("title")); + } + if(markerJson.hasKey("color")) { + options.icon(BitmapDescriptorFactory.defaultMarker((float) markerJson.getDouble("color"))); + } + if (markerJson.hasKey("snippet")) { + options.snippet(markerJson.getString("snippet")); + } + if(markerJson.hasKey("icon")) { + String varName = ""; + ReadableType iconType = markerJson.getType("icon"); + if (iconType.compareTo(ReadableType.Map) >= 0) { + ReadableMap icon = markerJson.getMap("icon"); + try { + int resId = getResourceDrawableId(icon.getString("uri")); + Bitmap image = BitmapFactory.decodeResource(reactContext.getResources(), resId); + + options.icon(BitmapDescriptorFactory.fromBitmap( + Bitmap.createScaledBitmap(image, icon.getInt("width"), icon.getInt("height"), true) + )); + } catch (Exception e) { + varName = icon.getString("uri"); + } + } else if (iconType.compareTo(ReadableType.String) >= 0) { + varName = markerJson.getString("icon"); + } + if (!varName.equals("")) { + // Changing marker icon to use resource + int resourceValue = getResourceDrawableId(varName); + Log.i(TAG, varName + markerJson.toString()); + options.icon(BitmapDescriptorFactory.fromResource(resourceValue)); + } + } + + Marker marker = map.addMarker(options); + + if (markerJson.hasKey("id")) { + // As we have to lookup it either way, switch it around + markerLookup.put(marker.getId(), markerJson.getString("id")); + markerLookup.put(markerJson.getString("id"), marker.getId().replace("m","")); } - mapMarkers.add(map.addMarker(options)); + + mapMarkers.add(marker); } else break; } @@ -191,15 +342,18 @@ private Boolean zoomOnMarkers () { } } - @Override - public void updateView(MapView view, CatalystStylesDiffMap props) { - super.updateView(view, props); - if (props.hasKey(PROP_CENTER)) updateCenter(props); - if (props.hasKey(PROP_ZOOM_LEVEL)) updateCenter(props); - if (props.hasKey(PROP_MARKERS)) updateMarkers(props); - if (props.hasKey(PROP_ZOOM_ON_MARKERS)&&props.getBoolean(PROP_ZOOM_ON_MARKERS, false)) { - zoomOnMarkers(); + private int getResourceDrawableId(String name) { + try { + return reactContext.getResources().getIdentifier( + name.toLowerCase().replace("-", "_"), + "drawable", + reactContext.getPackageName() + ); + } catch (Exception e) { + Log.e(TAG, "Failure to get drawable id.", e); + return 0; } - } + + } diff --git a/index.android.js b/index.android.js index f6d449e..258d165 100644 --- a/index.android.js +++ b/index.android.js @@ -15,10 +15,12 @@ let { var gmaps = { name: 'RNGMaps', propTypes: { + ...View.propTypes, center: PropTypes.object, zoomLevel: PropTypes.number, markers: PropTypes.array, zoomOnMarkers: PropTypes.bool, + clickMarker: PropTypes.number, /* Hackedy hack hack hack */ scaleX: React.PropTypes.number, @@ -34,31 +36,46 @@ let MapView = requireNativeComponent('RNGMaps', gmaps); class RNGMaps extends Component { constructor (props) { super(props); - this._event = null; + + this._listeners = { + mapError: null, + mapChange: null, + markerClick: null, + }; + + // Look up markers by id + this._markersLookup = {}; this.state = { zoomOnMarkers: false, markers: [] } } - componentDidMount () { - this._event = DeviceEventEmitter.addListener('mapChange', (e: Event) => { - this.props.onMapChange&&this.props.onMapChange(e); +componentDidMount () { + this._listeners.mapError = DeviceEventEmitter.addListener('mapError', (e: Event) => { + console.log(`[GMAP_ERROR]: ${e.message}`); + this.props.onMapError && this.props.onMapError(e); }); - this._error = DeviceEventEmitter.addListener('mapError', (e: Event) => { - console.log(`[GMAP_ERROR]: ${e.message}`); - this.props.onMapError&&this.props.onMapError(e); + this._listeners.mapChange = DeviceEventEmitter.addListener('mapChange', (e: Event) => { + this.props.onMapChange && this.props.onMapChange(e); + }); + + this._listeners.markerClick = DeviceEventEmitter.addListener('markerClick', (e: Event) => { + let marker = this._markersLookup[e.id]; + marker && this.props.onMarkerClick && this.props.onMarkerClick(marker); }); this.updateMarkers(this.props.markers); } componentWillUnmount () { - this._event&&this._event.remove(); - this._error&&this._error.remove(); + this._listeners.mapError && this._listeners.mapError.remove(); + this._listeners.mapChange && this._listeners.mapChange.remove(); + this._listeners.markerClick && this._listeners.markerClick.remove(); } + zoomOnMarkers (bool) { // HACK: Bleurgh, forcing the change on zoomOnMarkers. this.setState({ zoomOnMarkers: null }, () => { @@ -68,7 +85,11 @@ class RNGMaps extends Component { updateMarkers (markers) { let newMarkers = []; - for (var i = 0; i < markers.length; i++) newMarkers.push(markers[i]); + for (var i = 0; i < markers.length; i++) { + let marker = markers[i]; + this._markersLookup[marker.id] = marker; + newMarkers.push(marker); + } this.setState({ markers: newMarkers }); } @@ -88,6 +109,7 @@ class RNGMaps extends Component { if(this._diffMarkers(nextProps.markers, this.state.markers)) { this.updateMarkers(nextProps.markers); } + console.log('clickMarker:' + nextProps.clickMarker); } render () { diff --git a/package.json b/package.json index dd8ec20..9f2acc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-gmaps", - "version": "1.0.4", + "version": "1.0.5", "description": "React Native Android Google Maps implementation.", "scripts": { "test": "echo \"Error: no test specified\" && exit 1"