From 1dffe2871ba76c054b80bbb84bedec3ffcabe6ff Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 17 Mar 2016 11:40:47 -0700 Subject: [PATCH 01/37] Initial steps to cleanup plugin and encapsulate map instances. --- plugin.xml | 1 + src/android/MapInstance.java | 169 +++++++++ src/android/Mapbox.java | 679 ++++++++++++++++------------------- src/android/mapbox.gradle | 4 +- 4 files changed, 474 insertions(+), 379 deletions(-) create mode 100644 src/android/MapInstance.java diff --git a/plugin.xml b/plugin.xml index f2d6d31..f5c6ef0 100755 --- a/plugin.xml +++ b/plugin.xml @@ -48,6 +48,7 @@ <framework src="src/android/mapbox.gradle" custom="true" type="gradleReference"/> <source-file src="src/android/Mapbox.java" target-dir="src/com/telerik/plugins/mapbox"/> + <source-file src="src/android/MapInstance.java" target-dir="src/com/telerik/plugins/mapbox"/> <!-- This leads to trouble in AppBuilder when compiling for Cordova-Android 4 --> <!--source-file src="src/android/res/values/mapboxstrings.xml" target-dir="res/values" /> diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java new file mode 100644 index 0000000..f388fcd --- /dev/null +++ b/src/android/MapInstance.java @@ -0,0 +1,169 @@ +package com.telerik.plugins.mapbox; + +import android.content.Context; +import android.webkit.WebView; +import android.widget.FrameLayout; + +import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; +import com.mapbox.mapboxsdk.maps.UiSettings; + +import org.apache.cordova.CordovaWebView; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; + +public class MapInstance { + + public interface MapCreatedCallback { + void onMapReady(MapInstance map); + } + + public static MapInstance createMap(Context context, String accessToken, MapCreatedCallback callback) { + MapView mapView = new MapView(context); + mapView.setAccessToken(accessToken); + MapInstance map = new MapInstance(mapView, callback); + maps.put(map.getId(), map); + return map; + } + + public static MapInstance getMap(int id) { + return maps.get(id); + } + + private static HashMap<Integer, MapInstance> maps = new HashMap<Integer, MapInstance>(); + + private static int ids = 0; + + private int id; + + private MapView mapView; + + private MapboxMap mapboxMap; + + private MapCreatedCallback constructorCallback; + + private MapInstance(MapView mapView, MapCreatedCallback callback) { + this.id = this.ids++; + this.constructorCallback = callback; + this.mapView = mapView; + + mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(MapboxMap mMap) { + mapboxMap = mMap; + constructorCallback.onMapReady(MapInstance.this); + } + }); + } + + public int getId() { + return this.id; + } + + public MapView getMapView() { + return this.mapView; + } + + public MapboxMap getMapboxMap() { + return this.mapboxMap; + } + + public void configure(JSONObject options) throws JSONException { + UiSettings uiSettings = mapboxMap.getUiSettings(); + uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); + uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); + uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); + uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); + uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); + } + + public void show(CordovaWebView webView, float retinaFactor, JSONObject options) throws JSONException { + final String style = getStyle(options.optString("style")); + final JSONObject center = options.isNull("center") ? null : options.getJSONObject("center"); + + final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); + final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); + final int right = (int) (retinaFactor * (margins == null || margins.isNull("right") ? 0 : margins.getInt("right"))); + final int top = (int) (retinaFactor * (margins == null || margins.isNull("top") ? 0 : margins.getInt("top"))); + final int bottom = (int) (retinaFactor * (margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"))); + + // need to do this to register a receiver which onPause later needs + mapView.onResume(); + mapView.onCreate(null); + + // position the mapView overlay + int webViewWidth = webView.getView().getWidth(); + int webViewHeight = webView.getView().getHeight(); + final FrameLayout layout = (FrameLayout) webView.getView().getParent(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(webViewWidth - left - right, webViewHeight - top - bottom); + params.setMargins(left, top, right, bottom); + mapView.setLayoutParams(params); + + layout.addView(mapView); + } + + private static String getStyle(final String requested) { + if ("light".equalsIgnoreCase(requested)) { + return Style.LIGHT; + } else if ("dark".equalsIgnoreCase(requested)) { + return Style.DARK; + } else if ("emerald".equalsIgnoreCase(requested)) { + return Style.EMERALD; + } else if ("satellite".equalsIgnoreCase(requested)) { + return Style.SATELLITE; + // TODO not currently supported on Android + //} else if ("hybrid".equalsIgnoreCase(requested)) { + // return Style.HYBRID; + } else if ("streets".equalsIgnoreCase(requested)) { + return Style.MAPBOX_STREETS; + } else { + return requested; + } + } + +// public void show(JSONObject options, CallbackContext callbackContext) { +// +// +// try { +// +// +// // placing these offscreen in case the user wants to hide them +// if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { +// mapView.setAttributionMargins(-300, 0, 0, 0); +// } +// if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { +// mapView.setLogoMargins(-300, 0, 0, 0); +// } +// +// if (showUserLocation) { +// showUserLocation(); +// } +// +// Double zoom = options.isNull("zoomLevel") ? 10 : options.getDouble("zoomLevel"); +// float zoomLevel = zoom.floatValue(); +// if (center != null) { +// final double lat = center.getDouble("lat"); +// final double lng = center.getDouble("lng"); +// mapView.setLatLng(new LatLngZoom(lat, lng, zoomLevel)); +// } else { +// if (zoomLevel > 18.0) { +// zoomLevel = 18.0f; +// } +// mapView.setZoom(zoomLevel); +// } +// +// if (options.has("markers")) { +// addMarkers(options.getJSONArray("markers")); +// } +// } catch (JSONException e) { +// callbackContext.error(e.getMessage()); +// return; +// } +// +// mapView.setStyleUrl(style); +// } +} diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 029fe92..150a24f 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -1,24 +1,14 @@ package com.telerik.plugins.mapbox; import android.Manifest; +import android.app.Activity; +import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Build; -import android.support.annotation.NonNull; + import android.support.v4.app.ActivityCompat; import android.util.DisplayMetrics; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import com.mapbox.mapboxsdk.camera.CameraPosition; -import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; -import com.mapbox.mapboxsdk.constants.Style; -import com.mapbox.mapboxsdk.annotations.Marker; -import com.mapbox.mapboxsdk.annotations.MarkerOptions; -import com.mapbox.mapboxsdk.annotations.PolygonOptions; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.geometry.LatLngZoom; -import com.mapbox.mapboxsdk.views.MapView; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; @@ -26,14 +16,10 @@ import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaWebView; import org.apache.cordova.PluginResult; -import org.json.JSONArray; + import org.json.JSONException; import org.json.JSONObject; -import java.util.HashMap; -import java.util.Map; - - // TODO for screen rotation, see https://www.mapbox.com/mapbox-android-sdk/#screen-rotation // TODO fox Xwalk compat, see nativepagetransitions plugin // TODO look at demo app: https://github.com/mapbox/mapbox-gl-native/blob/master/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -61,387 +47,303 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_SET_TILT = "setTilt"; private static final String ACTION_ANIMATE_CAMERA = "animateCamera"; - public static MapView mapView; private static float retinaFactor; private String accessToken; private CallbackContext callback; - private CallbackContext markerCallbackContext; - private boolean showUserLocation; +// private boolean showUserLocation; @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); - DisplayMetrics metrics = new DisplayMetrics(); - cordova.getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); - retinaFactor = metrics.density; - - try { - int mapboxAccesstokenResourceId = cordova.getActivity().getResources().getIdentifier(MAPBOX_ACCESSTOKEN_RESOURCE_KEY, "string", cordova.getActivity().getPackageName()); - accessToken = cordova.getActivity().getString(mapboxAccesstokenResourceId); - } catch (Resources.NotFoundException e) { - // we'll deal with this when the accessToken property is read, but for now let's dump the error: - e.printStackTrace(); - } + this.retinaFactor = this.getRetinaFactor(); + this.accessToken = this.getAccessToken(); } + @Override public boolean execute(final String action, final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { - - this.callback = callbackContext; - - try { - if (ACTION_SHOW.equals(action)) { - final JSONObject options = args.getJSONObject(0); - final String style = getStyle(options.optString("style")); - - final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); - final int left = applyRetinaFactor(margins == null || margins.isNull("left") ? 0 : margins.getInt("left")); - final int right = applyRetinaFactor(margins == null || margins.isNull("right") ? 0 : margins.getInt("right")); - final int top = applyRetinaFactor(margins == null || margins.isNull("top") ? 0 : margins.getInt("top")); - final int bottom = applyRetinaFactor(margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom")); - - final JSONObject center = options.isNull("center") ? null : options.getJSONObject("center"); - - this.showUserLocation = !options.isNull("showUserLocation") && options.getBoolean("showUserLocation"); - - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (accessToken == null) { - callbackContext.error(MAPBOX_ACCESSTOKEN_RESOURCE_KEY + " not set in strings.xml"); - return; - } - mapView = new MapView(webView.getContext(), accessToken); - - // need to do this to register a receiver which onPause later needs - mapView.onResume(); - mapView.onCreate(null); - - try { - mapView.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); - mapView.setRotateEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); - mapView.setScrollEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); - mapView.setZoomEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); - mapView.setTiltEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - - // placing these offscreen in case the user wants to hide them - if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { - mapView.setAttributionMargins(-300, 0, 0, 0); - } - if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { - mapView.setLogoMargins(-300, 0, 0, 0); - } - - if (showUserLocation) { - showUserLocation(); - } - - Double zoom = options.isNull("zoomLevel") ? 10 : options.getDouble("zoomLevel"); - float zoomLevel = zoom.floatValue(); - if (center != null) { - final double lat = center.getDouble("lat"); - final double lng = center.getDouble("lng"); - mapView.setLatLng(new LatLngZoom(lat, lng, zoomLevel)); - } else { - if (zoomLevel > 18.0) { - zoomLevel = 18.0f; - } - mapView.setZoom(zoomLevel); - } - - if (options.has("markers")) { - addMarkers(options.getJSONArray("markers")); - } - } catch (JSONException e) { - callbackContext.error(e.getMessage()); - return; - } - - mapView.setStyleUrl(style); - - // position the mapView overlay - int webViewWidth = webView.getView().getWidth(); - int webViewHeight = webView.getView().getHeight(); - final FrameLayout layout = (FrameLayout) webView.getView().getParent(); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(webViewWidth - left - right, webViewHeight - top - bottom); - params.setMargins(left, top, right, bottom); - mapView.setLayoutParams(params); - - layout.addView(mapView); - callbackContext.success(); - } - }); - - } else if (ACTION_HIDE.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - ViewGroup vg = (ViewGroup) mapView.getParent(); - if (vg != null) { - vg.removeView(mapView); - } - callbackContext.success(); - } - }); - } - - } else if (ACTION_GET_ZOOMLEVEL.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - final double zoomLevel = mapView.getZoom(); - callbackContext.success("" + zoomLevel); - } - }); - } - - } else if (ACTION_SET_ZOOMLEVEL.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - try { - final JSONObject options = args.getJSONObject(0); - final double zoom = options.getDouble("level"); - if (zoom >= 0 && zoom <= 20) { - final boolean animated = !options.isNull("animated") && options.getBoolean("animated"); - mapView.setZoom(zoom, animated); - callbackContext.success(); - } else { - callbackContext.error("invalid zoomlevel, use any double value from 0 to 20 (like 8.3)"); - } - } catch (JSONException e) { - callbackContext.error(e.getMessage()); - } - } - }); - } - - } else if (ACTION_GET_CENTER.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - final LatLng center = mapView.getLatLng(); - Map<String, Double> result = new HashMap<String, Double>(); - result.put("lat", center.getLatitude()); - result.put("lng", center.getLongitude()); - callbackContext.success(new JSONObject(result)); - } - }); - } - - } else if (ACTION_SET_CENTER.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - try { - final JSONObject options = args.getJSONObject(0); - final boolean animated = !options.isNull("animated") && options.getBoolean("animated"); - final double lat = options.getDouble("lat"); - final double lng = options.getDouble("lng"); - mapView.setLatLng(new LatLng(lat, lng), animated); - callbackContext.success(); - } catch (JSONException e) { - callbackContext.error(e.getMessage()); - } - } - }); - } - - } else if (ACTION_GET_TILT.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - final double tilt = mapView.getTilt(); - callbackContext.success("" + tilt); - } - }); + callback = callbackContext; + + if (ACTION_SHOW.equals(action)) { + final JSONObject options = args.getJSONObject(0); + this.show(options, callbackContext); + } else if (ACTION_HIDE.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// ViewGroup vg = (ViewGroup) mapView.getParent(); +// if (vg != null) { +// vg.removeView(mapView); +// } +// callbackContext.success(); +// } +// }); +// } + + } else if (ACTION_GET_ZOOMLEVEL.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// final double zoomLevel = mapView.getZoom(); +// callbackContext.success("" + zoomLevel); +// } +// }); +// } + + } else if (ACTION_SET_ZOOMLEVEL.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// try { +// final JSONObject options = args.getJSONObject(0); +// final double zoom = options.getDouble("level"); +// if (zoom >= 0 && zoom <= 20) { +// final boolean animated = !options.isNull("animated") && options.getBoolean("animated"); +// mapView.setZoom(zoom, animated); +// callbackContext.success(); +// } else { +// callbackContext.error("invalid zoomlevel, use any double value from 0 to 20 (like 8.3)"); +// } +// } catch (JSONException e) { +// callbackContext.error(e.getMessage()); +// } +// } +// }); +// } + + } else if (ACTION_GET_CENTER.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// final LatLng center = mapView.getLatLng(); +// Map<String, Double> result = new HashMap<String, Double>(); +// result.put("lat", center.getLatitude()); +// result.put("lng", center.getLongitude()); +// callbackContext.success(new JSONObject(result)); +// } +// }); +// } + + } else if (ACTION_SET_CENTER.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// try { +// final JSONObject options = args.getJSONObject(0); +// final boolean animated = !options.isNull("animated") && options.getBoolean("animated"); +// final double lat = options.getDouble("lat"); +// final double lng = options.getDouble("lng"); +// mapView.setLatLng(new LatLng(lat, lng), animated); +// callbackContext.success(); +// } catch (JSONException e) { +// callbackContext.error(e.getMessage()); +// } +// } +// }); +// } + + } else if (ACTION_GET_TILT.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// final double tilt = mapView.getTilt(); +// callbackContext.success("" + tilt); +// } +// }); +// } + + } else if (ACTION_SET_TILT.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// try { +// final JSONObject options = args.getJSONObject(0); +// mapView.setTilt( +// options.optDouble("pitch", 20), // default 20 +// options.optLong("duration", 5000)); // default 5s +// callbackContext.success(); +// } catch (JSONException e) { +// callbackContext.error(e.getMessage()); +// } +// } +// }); +// } + + } else if (ACTION_ANIMATE_CAMERA.equals(action)) { +// if (mapView != null) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// try { +// // TODO check mandatory elements +// final JSONObject options = args.getJSONObject(0); +// +// final JSONObject target = options.getJSONObject("target"); +// final double lat = target.getDouble("lat"); +// final double lng = target.getDouble("lng"); +// +// final CameraPosition.Builder builder = +// new CameraPosition.Builder() +// .target(new LatLng(lat, lng)); +// +// if (options.has("bearing")) { +// builder.bearing(((Double)options.getDouble("bearing")).floatValue()); +// } +// if (options.has("tilt")) { +// builder.tilt(((Double)options.getDouble("tilt")).floatValue()); +// } +// if (options.has("zoomLevel")) { +// builder.zoom(((Double)options.getDouble("zoomLevel")).floatValue()); +// } +// +// mapView.animateCamera( +// CameraUpdateFactory.newCameraPosition(builder.build()), +// (options.optInt("duration", 15)) * 1000, // default 15 seconds +// null); +// +// callbackContext.success(); +// } catch (JSONException e) { +// callbackContext.error(e.getMessage()); +// } +// } +// }); +// } + + } else if (ACTION_ADD_POLYGON.equals(action)) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// try { +// final PolygonOptions polygon = new PolygonOptions(); +// final JSONObject options = args.getJSONObject(0); +// final JSONArray points = options.getJSONArray("points"); +// for (int i = 0; i < points.length(); i++) { +// final JSONObject marker = points.getJSONObject(i); +// final double lat = marker.getDouble("lat"); +// final double lng = marker.getDouble("lng"); +// polygon.add(new LatLng(lat, lng)); +// } +// mapView.addPolygon(polygon); +// +// callbackContext.success(); +// } catch (JSONException e) { +// callbackContext.error(e.getMessage()); +// } +// } +// }); + + } else if (ACTION_ADD_GEOJSON.equals(action)) { + cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + // TODO implement + callbackContext.success(); } + }); + + } else if (ACTION_ADD_MARKERS.equals(action)) { +// cordova.getActivity().runOnUiThread(new Runnable() { +// @Override +// public void run() { +// try { +// addMarkers(args.getJSONArray(0)); +// callbackContext.success(); +// } catch (JSONException e) { +// callbackContext.error(e.getMessage()); +// } +// } +// }); + + } else if (ACTION_ADD_MARKER_CALLBACK.equals(action)) { +// this.markerCallbackContext = callbackContext; +// mapView.setOnInfoWindowClickListener(new MarkerClickListener()); - } else if (ACTION_SET_TILT.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - try { - final JSONObject options = args.getJSONObject(0); - mapView.setTilt( - options.optDouble("pitch", 20), // default 20 - options.optLong("duration", 5000)); // default 5s - callbackContext.success(); - } catch (JSONException e) { - callbackContext.error(e.getMessage()); - } - } - }); - } - - } else if (ACTION_ANIMATE_CAMERA.equals(action)) { - if (mapView != null) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - try { - // TODO check mandatory elements - final JSONObject options = args.getJSONObject(0); - - final JSONObject target = options.getJSONObject("target"); - final double lat = target.getDouble("lat"); - final double lng = target.getDouble("lng"); - - final CameraPosition.Builder builder = - new CameraPosition.Builder() - .target(new LatLng(lat, lng)); - - if (options.has("bearing")) { - builder.bearing(((Double)options.getDouble("bearing")).floatValue()); - } - if (options.has("tilt")) { - builder.tilt(((Double)options.getDouble("tilt")).floatValue()); - } - if (options.has("zoomLevel")) { - builder.zoom(((Double)options.getDouble("zoomLevel")).floatValue()); - } - - mapView.animateCamera( - CameraUpdateFactory.newCameraPosition(builder.build()), - (options.optInt("duration", 15)) * 1000, // default 15 seconds - null); - - callbackContext.success(); - } catch (JSONException e) { - callbackContext.error(e.getMessage()); - } - } - }); - } + } else { + return false; + } + return true; + } - } else if (ACTION_ADD_POLYGON.equals(action)) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - try { - final PolygonOptions polygon = new PolygonOptions(); - final JSONObject options = args.getJSONObject(0); - final JSONArray points = options.getJSONArray("points"); - for (int i = 0; i < points.length(); i++) { - final JSONObject marker = points.getJSONObject(i); - final double lat = marker.getDouble("lat"); - final double lng = marker.getDouble("lng"); - polygon.add(new LatLng(lat, lng)); - } - mapView.addPolygon(polygon); - - callbackContext.success(); - } catch (JSONException e) { - callbackContext.error(e.getMessage()); - } - } - }); + private void show(final JSONObject options, final CallbackContext callback) { +// this.showUserLocation = !options.isNull("showUserLocation") && options.getBoolean("showUserLocation"); - } else if (ACTION_ADD_GEOJSON.equals(action)) { - cordova.getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - // TODO implement - callbackContext.success(); - } - }); + if (accessToken == null) { + callback.error(MAPBOX_ACCESSTOKEN_RESOURCE_KEY + " not set in strings.xml"); + return; + } - } else if (ACTION_ADD_MARKERS.equals(action)) { + MapInstance.createMap(this.webView.getContext(), accessToken, new MapInstance.MapCreatedCallback() { + @Override + public void onMapReady(final MapInstance map) { cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { + JSONObject resp = new JSONObject(); try { - addMarkers(args.getJSONArray(0)); - callbackContext.success(); + map.configure(options); + map.show(webView, retinaFactor, options); + resp.put("id", map.getId()); + callback.success(resp); + return; } catch (JSONException e) { - callbackContext.error(e.getMessage()); + e.printStackTrace(); + callback.error("Failed to create map."); + return; } } }); - - } else if (ACTION_ADD_MARKER_CALLBACK.equals(action)) { - this.markerCallbackContext = callbackContext; - mapView.setOnInfoWindowClickListener(new MarkerClickListener()); - - } else { - return false; } - } catch (Throwable t) { - t.printStackTrace(); - callbackContext.error(t.getMessage()); - } - return true; + }); } - private void addMarkers(JSONArray markers) throws JSONException { - for (int i=0; i<markers.length(); i++) { - final JSONObject marker = markers.getJSONObject(i); - final MarkerOptions mo = new MarkerOptions(); - mo.title(marker.isNull("title") ? null : marker.getString("title")); - mo.snippet(marker.isNull("subtitle") ? null : marker.getString("subtitle")); - mo.position(new LatLng(marker.getDouble("lat"), marker.getDouble("lng"))); - mapView.addMarker(mo); - } - } - private class MarkerClickListener implements MapView.OnInfoWindowClickListener { - - @Override - public boolean onMarkerClick(@NonNull Marker marker) { - // callback - if (markerCallbackContext != null) { - final JSONObject json = new JSONObject(); - try { - json.put("title", marker.getTitle()); - json.put("subtitle", marker.getSnippet()); - json.put("lat", marker.getPosition().getLatitude()); - json.put("lng", marker.getPosition().getLongitude()); - } catch (JSONException e) { - PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, - "Error in callback of " + ACTION_ADD_MARKER_CALLBACK + ": " + e.getMessage()); - pluginResult.setKeepCallback(true); - markerCallbackContext.sendPluginResult(pluginResult); - } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, json); - pluginResult.setKeepCallback(true); - markerCallbackContext.sendPluginResult(pluginResult); - return true; - } - return false; - } - } - private static int applyRetinaFactor(int i) { - return (int) (i * retinaFactor); - } - - private static String getStyle(final String requested) { - if ("light".equalsIgnoreCase(requested)) { - return Style.LIGHT; - } else if ("dark".equalsIgnoreCase(requested)) { - return Style.DARK; - } else if ("emerald".equalsIgnoreCase(requested)) { - return Style.EMERALD; - } else if ("satellite".equalsIgnoreCase(requested)) { - return Style.SATELLITE; - // TODO not currently supported on Android -// } else if ("hybrid".equalsIgnoreCase(requested)) { -// return Style.HYBRID; - } else if ("streets".equalsIgnoreCase(requested)) { - return Style.MAPBOX_STREETS; - } else { - return requested; - } - } +// private void addMarkers(JSONArray markers) throws JSONException { +// for (int i=0; i<markers.length(); i++) { +// final JSONObject marker = markers.getJSONObject(i); +// final MarkerOptions mo = new MarkerOptions(); +// mo.title(marker.isNull("title") ? null : marker.getString("title")); +// mo.snippet(marker.isNull("subtitle") ? null : marker.getString("subtitle")); +// mo.position(new LatLng(marker.getDouble("lat"), marker.getDouble("lng"))); +// mapView.addMarker(mo); +// } +// } +// +// private class MarkerClickListener implements MapView.OnInfoWindowClickListener { +// +// @Override +// public boolean onMarkerClick(@NonNull Marker marker) { +// // callback +// if (markerCallbackContext != null) { +// final JSONObject json = new JSONObject(); +// try { +// json.put("title", marker.getTitle()); +// json.put("subtitle", marker.getSnippet()); +// json.put("lat", marker.getPosition().getLatitude()); +// json.put("lng", marker.getPosition().getLongitude()); +// } catch (JSONException e) { +// PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, +// "Error in callback of " + ACTION_ADD_MARKER_CALLBACK + ": " + e.getMessage()); +// pluginResult.setKeepCallback(true); +// markerCallbackContext.sendPluginResult(pluginResult); +// } +// PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, json); +// pluginResult.setKeepCallback(true); +// markerCallbackContext.sendPluginResult(pluginResult); +// return true; +// } +// return false; +// } +// } private boolean permissionGranted(String... types) { if (Build.VERSION.SDK_INT < 23) { @@ -458,7 +360,7 @@ private boolean permissionGranted(String... types) { protected void showUserLocation() { if (permissionGranted(COARSE_LOCATION, FINE_LOCATION)) { //noinspection MissingPermission - mapView.setMyLocationEnabled(showUserLocation); +// mapView.setMyLocationEnabled(showUserLocation); } else { requestPermission(COARSE_LOCATION, FINE_LOCATION); } @@ -487,15 +389,40 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int } } - public void onPause(boolean multitasking) { - mapView.onPause(); +// public void onPause(boolean multitasking) { +// mapView.onPause(); +// } +// +// public void onResume(boolean multitasking) { +// mapView.onResume(); +// } +// +// public void onDestroy() { +// mapView.onDestroy(); +// } + + private float getRetinaFactor() { + DisplayMetrics metrics = new DisplayMetrics(); + this.cordova.getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); + return metrics.density; } - public void onResume(boolean multitasking) { - mapView.onResume(); - } + private String getAccessToken() { + Activity activity = cordova.getActivity(); + Resources res = activity.getResources(); + String packageName = activity.getPackageName(); + int resourceId; + String accessToken; + + try { + resourceId = res.getIdentifier(MAPBOX_ACCESSTOKEN_RESOURCE_KEY, "string", packageName); + accessToken = activity.getString(resourceId); + } catch (Resources.NotFoundException e) { + // we'll deal with this when the accessToken property is read, but for now let's dump the error: + e.printStackTrace(); + throw e; + } - public void onDestroy() { - mapView.onDestroy(); + return accessToken; } } diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index af6df92..4bbe68b 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -2,13 +2,11 @@ ext.cdvMinSdkVersion = 15 repositories { mavenCentral() - maven { url "http://oss.sonatype.org/content/repositories/snapshots/" } } - dependencies { compile 'com.android.support:appcompat-v7:23.0.1' - compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:3.2.0@aar'){ + compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-beta.1@aar'){ transitive=true } } From 34a5a548760a08df7a193ae17ec4370c9a47e242 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 17 Mar 2016 16:34:15 -0700 Subject: [PATCH 02/37] Added js bindings for new map instance. Rearranged MapView & MapboxMap creation sequence. --- plugin.xml | 4 +++- src/android/MapInstance.java | 28 +++-------------------- src/android/Mapbox.java | 44 ++++++++++++++++++++++++++++++++---- www/Mapbox.js | 17 +++++++++----- www/map-instance.js | 5 ++++ 5 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 www/map-instance.js diff --git a/plugin.xml b/plugin.xml index f5c6ef0..499a824 100755 --- a/plugin.xml +++ b/plugin.xml @@ -24,8 +24,10 @@ <engine name="cordova-plugman" version=">=4.2.0"/><!-- needed for gradleReference support --> </engines> + <js-module src="www/map-instance.js" name="MapInstance" /> + <js-module src="www/Mapbox.js" name="Mapbox"> - <clobbers target="Mapbox" /> + <clobbers target="window.Mapbox" /> </js-module> <!-- android --> diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index f388fcd..b4ca5d3 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -22,9 +22,7 @@ public interface MapCreatedCallback { void onMapReady(MapInstance map); } - public static MapInstance createMap(Context context, String accessToken, MapCreatedCallback callback) { - MapView mapView = new MapView(context); - mapView.setAccessToken(accessToken); + public static MapInstance createMap(MapView mapView, MapCreatedCallback callback) { MapInstance map = new MapInstance(mapView, callback); maps.put(map.getId(), map); return map; @@ -55,6 +53,7 @@ private MapInstance(MapView mapView, MapCreatedCallback callback) { @Override public void onMapReady(MapboxMap mMap) { mapboxMap = mMap; + mapboxMap.setMyLocationEnabled(false); constructorCallback.onMapReady(MapInstance.this); } }); @@ -82,28 +81,7 @@ public void configure(JSONObject options) throws JSONException { } public void show(CordovaWebView webView, float retinaFactor, JSONObject options) throws JSONException { - final String style = getStyle(options.optString("style")); - final JSONObject center = options.isNull("center") ? null : options.getJSONObject("center"); - - final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); - final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); - final int right = (int) (retinaFactor * (margins == null || margins.isNull("right") ? 0 : margins.getInt("right"))); - final int top = (int) (retinaFactor * (margins == null || margins.isNull("top") ? 0 : margins.getInt("top"))); - final int bottom = (int) (retinaFactor * (margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"))); - - // need to do this to register a receiver which onPause later needs - mapView.onResume(); - mapView.onCreate(null); - - // position the mapView overlay - int webViewWidth = webView.getView().getWidth(); - int webViewHeight = webView.getView().getHeight(); - final FrameLayout layout = (FrameLayout) webView.getView().getParent(); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(webViewWidth - left - right, webViewHeight - top - bottom); - params.setMargins(left, top, right, bottom); - mapView.setLayoutParams(params); - - layout.addView(mapView); + } private static String getStyle(final String requested) { diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 150a24f..8b78095 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -2,13 +2,14 @@ import android.Manifest; import android.app.Activity; -import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Build; - import android.support.v4.app.ActivityCompat; import android.util.DisplayMetrics; +import android.widget.FrameLayout; + +import com.mapbox.mapboxsdk.maps.MapView; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; @@ -274,14 +275,18 @@ public void run() { } private void show(final JSONObject options, final CallbackContext callback) { -// this.showUserLocation = !options.isNull("showUserLocation") && options.getBoolean("showUserLocation"); + if (!this.permissionGranted(COARSE_LOCATION, FINE_LOCATION)) { + this.requestPermission(COARSE_LOCATION, FINE_LOCATION); + return; + } if (accessToken == null) { callback.error(MAPBOX_ACCESSTOKEN_RESOURCE_KEY + " not set in strings.xml"); return; } - MapInstance.createMap(this.webView.getContext(), accessToken, new MapInstance.MapCreatedCallback() { + MapView mapView = this.createMapView(accessToken, options); + MapInstance.createMap(mapView, new MapInstance.MapCreatedCallback() { @Override public void onMapReady(final MapInstance map) { cordova.getActivity().runOnUiThread(new Runnable() { @@ -305,7 +310,37 @@ public void run() { }); } + private MapView createMapView(String accessToken, JSONObject options) { + MapView mapView = new MapView(this.webView.getContext()); + mapView.setAccessToken(accessToken); + try { + // final String style = getStyle(options.optString("style")); + // final JSONObject center = options.isNull("center") ? null : options.getJSONObject("center"); + final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); + final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); + final int right = (int) (retinaFactor * (margins == null || margins.isNull("right") ? 0 : margins.getInt("right"))); + final int top = (int) (retinaFactor * (margins == null || margins.isNull("top") ? 0 : margins.getInt("top"))); + final int bottom = (int) (retinaFactor * (margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"))); + + // need to do this to register a receiver which onPause later needs + mapView.onResume(); + mapView.onCreate(null); + + // position the mapView overlay + int webViewWidth = webView.getView().getWidth(); + int webViewHeight = webView.getView().getHeight(); + final FrameLayout layout = (FrameLayout) webView.getView().getParent(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(webViewWidth - left - right, webViewHeight - top - bottom); + params.setMargins(left, top, right, bottom); + mapView.setLayoutParams(params); + + layout.addView(mapView); + } catch (JSONException e) { + e.printStackTrace(); + } + return mapView; + } // private void addMarkers(JSONArray markers) throws JSONException { // for (int i=0; i<markers.length(); i++) { @@ -384,7 +419,6 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int } switch (requestCode) { case LOCATION_REQ_CODE: - showUserLocation(); break; } } diff --git a/www/Mapbox.js b/www/Mapbox.js index 2360cd2..ea8b459 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -1,9 +1,15 @@ -var exec = require("cordova/exec"); +var exec = require("cordova/exec"), + MapInstance = require("./MapInstance"); module.exports = { - show: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "show", [options]); - }, + show: function (options, successCallback, errorCallback) { + console.log('Mapbox.js show()'); + cordova.exec(function(resp) { + console.log('Mapbox.js show()', resp); + var map = new MapInstance(resp.id); + successCallback(map); + }, errorCallback, "Mapbox", "show", [options]); + }, hide: function (options, successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, "Mapbox", "hide", [options]); @@ -60,5 +66,4 @@ module.exports = { convertPoint: function(options, successCallback, errorCallback){ cordova.exec(successCallback, errorCallback, "Mapbox", "convertPoint", [options]); } - -}; \ No newline at end of file +}; diff --git a/www/map-instance.js b/www/map-instance.js new file mode 100644 index 0000000..34ea06f --- /dev/null +++ b/www/map-instance.js @@ -0,0 +1,5 @@ +function MapInstance(id) { + this._id = id; +} + +module.exports = MapInstance; From 20cf01b494dbcc40c45e5f713ae5f86d3d04e5d5 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 17 Mar 2016 17:12:02 -0700 Subject: [PATCH 03/37] Added service MapboxSDK depends on. Added a Mapbox.create API --- plugin.xml | 4 ++++ www/Mapbox.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin.xml b/plugin.xml index 499a824..37cc5ee 100755 --- a/plugin.xml +++ b/plugin.xml @@ -48,6 +48,10 @@ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> </config-file> + <config-file target="AndroidManifest.xml" parent="/manifest/application"> + <service android:name="com.mapbox.mapboxsdk.telemetry.TelemetryService" /> + </config-file> + <framework src="src/android/mapbox.gradle" custom="true" type="gradleReference"/> <source-file src="src/android/Mapbox.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/MapInstance.java" target-dir="src/com/telerik/plugins/mapbox"/> diff --git a/www/Mapbox.js b/www/Mapbox.js index ea8b459..358e715 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -2,7 +2,7 @@ var exec = require("cordova/exec"), MapInstance = require("./MapInstance"); module.exports = { - show: function (options, successCallback, errorCallback) { + create: function (options, successCallback, errorCallback) { console.log('Mapbox.js show()'); cordova.exec(function(resp) { console.log('Mapbox.js show()', resp); From 881cb4ce1ff2ab3dcdbb4afb9e31b6c4dbeb58be Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 17 Mar 2016 17:20:04 -0700 Subject: [PATCH 04/37] fixing issue with accessing view from non-ui thread. --- src/android/Mapbox.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 8b78095..b95bd9a 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -284,14 +284,13 @@ private void show(final JSONObject options, final CallbackContext callback) { callback.error(MAPBOX_ACCESSTOKEN_RESOURCE_KEY + " not set in strings.xml"); return; } - - MapView mapView = this.createMapView(accessToken, options); - MapInstance.createMap(mapView, new MapInstance.MapCreatedCallback() { + cordova.getActivity().runOnUiThread(new Runnable() { @Override - public void onMapReady(final MapInstance map) { - cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + MapView mapView = createMapView(accessToken, options); + MapInstance.createMap(mapView, new MapInstance.MapCreatedCallback() { @Override - public void run() { + public void onMapReady(final MapInstance map) { JSONObject resp = new JSONObject(); try { map.configure(options); From d1a25e6c834f1b8e869682fc577217beb2c75ee0 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Fri, 18 Mar 2016 13:55:17 -0700 Subject: [PATCH 05/37] Fleshing out new instance API. --- src/android/MapInstance.java | 62 +++++++++-- src/android/Mapbox.java | 201 ++++++++++++++--------------------- www/Mapbox.js | 37 +++---- www/map-instance.js | 39 +++++++ 4 files changed, 191 insertions(+), 148 deletions(-) diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index b4ca5d3..e140ad3 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -1,16 +1,20 @@ package com.telerik.plugins.mapbox; -import android.content.Context; -import android.webkit.WebView; -import android.widget.FrameLayout; - +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.annotations.MarkerOptions; +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.UiSettings; +import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaWebView; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -80,8 +84,54 @@ public void configure(JSONObject options) throws JSONException { uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); } - public void show(CordovaWebView webView, float retinaFactor, JSONObject options) throws JSONException { + public JSONArray getCenter() throws JSONException { + CameraPosition cameraPosition = mapboxMap.getCameraPosition(); + double lat = cameraPosition.target.getLatitude(); + double lng = cameraPosition.target.getLongitude(); + double alt = cameraPosition.target.getAltitude(); + return new JSONArray().put(lat).put(lng).put(alt); + } + + public void setCenter(JSONArray coords) throws JSONException { + double lat = coords.getDouble(0); + double lng = coords.getDouble(1); + double alt = coords.getDouble(2); + + mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition( + new CameraPosition.Builder() + .target(new LatLng(lat, lng, alt)) + .build() + )); + } + + public double getZoom() { + CameraPosition cameraPosition = mapboxMap.getCameraPosition(); + return cameraPosition.zoom; + } + + public void setZoom(double zoom) { + mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition( + new CameraPosition.Builder() + .zoom(zoom) + .build() + )); + } + + public void addMarkers(JSONArray markers) throws JSONException { + for (int i = 0; i < markers.length(); i++) { + final JSONObject marker = markers.getJSONObject(i); + final MarkerOptions mo = new MarkerOptions(); + + mo.title(marker.isNull("title") ? null : marker.getString("title")); + mo.snippet(marker.isNull("subtitle") ? null : marker.getString("subtitle")); + mo.position(new LatLng(marker.getDouble("lat"), marker.getDouble("lng"))); + + mapboxMap.addMarker(mo); + } + } + public void addMarkerListener(MapboxMap.OnInfoWindowClickListener listener) { + mapboxMap.setOnInfoWindowClickListener(listener); } private static String getStyle(final String requested) { @@ -144,4 +194,4 @@ private static String getStyle(final String requested) { // // mapView.setStyleUrl(style); // } -} +} \ No newline at end of file diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index b95bd9a..d319f57 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -9,7 +9,9 @@ import android.util.DisplayMetrics; import android.widget.FrameLayout; +import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; @@ -18,6 +20,7 @@ import org.apache.cordova.CordovaWebView; import org.apache.cordova.PluginResult; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -34,7 +37,7 @@ public class Mapbox extends CordovaPlugin { private static final String MAPBOX_ACCESSTOKEN_RESOURCE_KEY = "mapbox_accesstoken"; - private static final String ACTION_SHOW = "show"; + private static final String ACTION_CREATE = "create"; private static final String ACTION_HIDE = "hide"; private static final String ACTION_ADD_MARKERS = "addMarkers"; private static final String ACTION_ADD_MARKER_CALLBACK = "addMarkerCallback"; @@ -67,90 +70,84 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) { public boolean execute(final String action, final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { callback = callbackContext; - if (ACTION_SHOW.equals(action)) { + if (ACTION_CREATE.equals(action)) { final JSONObject options = args.getJSONObject(0); - this.show(options, callbackContext); - } else if (ACTION_HIDE.equals(action)) { -// if (mapView != null) { -// cordova.getActivity().runOnUiThread(new Runnable() { -// @Override -// public void run() { -// ViewGroup vg = (ViewGroup) mapView.getParent(); -// if (vg != null) { -// vg.removeView(mapView); -// } -// callbackContext.success(); -// } -// }); -// } + this.create(options, callbackContext); + } - } else if (ACTION_GET_ZOOMLEVEL.equals(action)) { -// if (mapView != null) { -// cordova.getActivity().runOnUiThread(new Runnable() { -// @Override -// public void run() { -// final double zoomLevel = mapView.getZoom(); -// callbackContext.success("" + zoomLevel); -// } -// }); -// } + else if (ACTION_GET_CENTER.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + try { + callbackContext.success(map.getCenter()); + } catch (JSONException e) { + callbackContext.error(e.getMessage()); + } + } - } else if (ACTION_SET_ZOOMLEVEL.equals(action)) { -// if (mapView != null) { -// cordova.getActivity().runOnUiThread(new Runnable() { -// @Override -// public void run() { -// try { -// final JSONObject options = args.getJSONObject(0); -// final double zoom = options.getDouble("level"); -// if (zoom >= 0 && zoom <= 20) { -// final boolean animated = !options.isNull("animated") && options.getBoolean("animated"); -// mapView.setZoom(zoom, animated); -// callbackContext.success(); -// } else { -// callbackContext.error("invalid zoomlevel, use any double value from 0 to 20 (like 8.3)"); -// } -// } catch (JSONException e) { -// callbackContext.error(e.getMessage()); -// } -// } -// }); -// } + else if (ACTION_SET_CENTER.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + final JSONArray center = args.getJSONArray(1); + try { + map.setCenter(center); + callbackContext.success(); + } catch (JSONException e) { + callbackContext.error(e.getMessage()); + } + } - } else if (ACTION_GET_CENTER.equals(action)) { -// if (mapView != null) { -// cordova.getActivity().runOnUiThread(new Runnable() { -// @Override -// public void run() { -// final LatLng center = mapView.getLatLng(); -// Map<String, Double> result = new HashMap<String, Double>(); -// result.put("lat", center.getLatitude()); -// result.put("lng", center.getLongitude()); -// callbackContext.success(new JSONObject(result)); -// } -// }); -// } + else if (ACTION_GET_ZOOMLEVEL.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + callbackContext.success("" + map.getZoom()); + } - } else if (ACTION_SET_CENTER.equals(action)) { -// if (mapView != null) { -// cordova.getActivity().runOnUiThread(new Runnable() { -// @Override -// public void run() { -// try { -// final JSONObject options = args.getJSONObject(0); -// final boolean animated = !options.isNull("animated") && options.getBoolean("animated"); -// final double lat = options.getDouble("lat"); -// final double lng = options.getDouble("lng"); -// mapView.setLatLng(new LatLng(lat, lng), animated); -// callbackContext.success(); -// } catch (JSONException e) { -// callbackContext.error(e.getMessage()); -// } -// } -// }); -// } + else if (ACTION_SET_ZOOMLEVEL.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + final double zoom = args.getDouble(1); + map.setZoom(zoom); + callbackContext.success(); + } + + else if (ACTION_ADD_MARKERS.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + try { + map.addMarkers(args.getJSONArray(1)); + callbackContext.success(); + } catch (JSONException e) { + callbackContext.error(e.getMessage()); + } + } - } else if (ACTION_GET_TILT.equals(action)) { + else if (ACTION_ADD_MARKER_CALLBACK.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + map.addMarkerListener( + new MapboxMap.OnInfoWindowClickListener() { + @Override + public boolean onInfoWindowClick(Marker marker) { + try { + callbackContext.success( + new JSONObject() + .put("title", marker.getTitle()) + .put("subtitle", marker.getSnippet()) + .put("lat", marker.getPosition().getLatitude()) + .put("lng", marker.getPosition().getLongitude()) + ); + return true; + } catch (JSONException e) { + String message = "Error in callback of " + ACTION_ADD_MARKER_CALLBACK + ": " + e.getMessage(); + callbackContext.error(message); + return false; + } + } + } + ); + } + else if (ACTION_GET_TILT.equals(action)) { // if (mapView != null) { // cordova.getActivity().runOnUiThread(new Runnable() { // @Override @@ -251,30 +248,13 @@ public void run() { } }); - } else if (ACTION_ADD_MARKERS.equals(action)) { -// cordova.getActivity().runOnUiThread(new Runnable() { -// @Override -// public void run() { -// try { -// addMarkers(args.getJSONArray(0)); -// callbackContext.success(); -// } catch (JSONException e) { -// callbackContext.error(e.getMessage()); -// } -// } -// }); - - } else if (ACTION_ADD_MARKER_CALLBACK.equals(action)) { -// this.markerCallbackContext = callbackContext; -// mapView.setOnInfoWindowClickListener(new MarkerClickListener()); - } else { return false; } return true; } - private void show(final JSONObject options, final CallbackContext callback) { + private void create(final JSONObject options, final CallbackContext callback) { if (!this.permissionGranted(COARSE_LOCATION, FINE_LOCATION)) { this.requestPermission(COARSE_LOCATION, FINE_LOCATION); return; @@ -294,7 +274,6 @@ public void onMapReady(final MapInstance map) { JSONObject resp = new JSONObject(); try { map.configure(options); - map.show(webView, retinaFactor, options); resp.put("id", map.getId()); callback.success(resp); return; @@ -352,32 +331,6 @@ private MapView createMapView(String accessToken, JSONObject options) { // } // } // -// private class MarkerClickListener implements MapView.OnInfoWindowClickListener { -// -// @Override -// public boolean onMarkerClick(@NonNull Marker marker) { -// // callback -// if (markerCallbackContext != null) { -// final JSONObject json = new JSONObject(); -// try { -// json.put("title", marker.getTitle()); -// json.put("subtitle", marker.getSnippet()); -// json.put("lat", marker.getPosition().getLatitude()); -// json.put("lng", marker.getPosition().getLongitude()); -// } catch (JSONException e) { -// PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, -// "Error in callback of " + ACTION_ADD_MARKER_CALLBACK + ": " + e.getMessage()); -// pluginResult.setKeepCallback(true); -// markerCallbackContext.sendPluginResult(pluginResult); -// } -// PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, json); -// pluginResult.setKeepCallback(true); -// markerCallbackContext.sendPluginResult(pluginResult); -// return true; -// } -// return false; -// } -// } private boolean permissionGranted(String... types) { if (Build.VERSION.SDK_INT < 23) { diff --git a/www/Mapbox.js b/www/Mapbox.js index 358e715..8804509 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -3,34 +3,35 @@ var exec = require("cordova/exec"), module.exports = { create: function (options, successCallback, errorCallback) { - console.log('Mapbox.js show()'); + console.log('Mapbox.js create()'); cordova.exec(function(resp) { - console.log('Mapbox.js show()', resp); + console.log('Mapbox.js create()', resp); var map = new MapInstance(resp.id); successCallback(map); - }, errorCallback, "Mapbox", "show", [options]); + }, errorCallback, "Mapbox", "create", [options]); }, - hide: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "hide", [options]); - }, + // hide: function (options, successCallback, errorCallback) { + // cordova.exec(successCallback, errorCallback, "Mapbox", "hide", [options]); + // }, - addMarkers: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "addMarkers", [options]); - }, + // animateCamera: function (options, successCallback, errorCallback) { + // cordova.exec(successCallback, errorCallback, "Mapbox", "animateCamera", [options]); + // }, - addMarkerCallback: function (callback) { - cordova.exec(callback, null, "Mapbox", "addMarkerCallback", []); - }, + // addGeoJSON: function (options, successCallback, errorCallback) { + // cordova.exec(successCallback, errorCallback, "Mapbox", "addGeoJSON", [options]); + // }, - animateCamera: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "animateCamera", [options]); - }, + // setTilt: function (options, successCallback, errorCallback) { + // cordova.exec(successCallback, errorCallback, "Mapbox", "setTilt", [options]); + // }, - addGeoJSON: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "addGeoJSON", [options]); - }, + // getTilt: function (successCallback, errorCallback) { + // cordova.exec(successCallback, errorCallback, "Mapbox", "getTilt", []); + // }, +<<<<<<< 881cb4ce1ff2ab3dcdbb4afb9e31b6c4dbeb58be setCenter: function (options, successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, "Mapbox", "setCenter", [options]); }, diff --git a/www/map-instance.js b/www/map-instance.js index 34ea06f..122dfc6 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -1,5 +1,44 @@ +var exec = require("cordova/exec"); + function MapInstance(id) { this._id = id; } +MapInstance.prototype._exec = function (successCallback, errorCallback, method, args) { + args = [this._id].concat(args || []); + exec(successCallback, errorCallback, "Mapbox", method, args); +}; + +MapInstance.prototype.setCenter = function (options, successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "setCenter", [options]); +}; + +MapInstance.prototype.getCenter = function (successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "getCenter"); +}; + +MapInstance.prototype.addMarkers = function (options, successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "addMarkers", [options]); +}; + +MapInstance.prototype.addMarkerCallback = function (callback) { + this._exec(callback, null, "addMarkerCallback"); +}; + +MapInstance.prototype.setCenter = function (center, successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "setCenter", [center]); +}; + +MapInstance.prototype.getCenter = function (successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "getCenter"); +}; + +MapInstance.prototype.getZoomLevel: function (successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "getZoomLevel"); +}; + +MapInstance.prototype.setZoomLevel: function (zoom, successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "setZoomLevel", [zoom]); +}; + module.exports = MapInstance; From 0fb3d2a801530c58775d69a7874a8a7e17c43bfa Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Fri, 18 Mar 2016 14:46:20 -0700 Subject: [PATCH 06/37] Cleaning up map creation API, particularly options processing/handling. --- src/android/MapInstance.java | 98 +++++++++++++++--------------------- src/android/Mapbox.java | 20 ++------ www/Mapbox.js | 2 +- www/map-instance.js | 4 +- 4 files changed, 47 insertions(+), 77 deletions(-) diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index e140ad3..468f0d5 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -1,6 +1,5 @@ package com.telerik.plugins.mapbox; -import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; @@ -11,9 +10,6 @@ import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.UiSettings; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaWebView; -import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -26,8 +22,8 @@ public interface MapCreatedCallback { void onMapReady(MapInstance map); } - public static MapInstance createMap(MapView mapView, MapCreatedCallback callback) { - MapInstance map = new MapInstance(mapView, callback); + public static MapInstance createMap(MapView mapView, JSONObject options, MapCreatedCallback callback) { + MapInstance map = new MapInstance(mapView, options, callback); maps.put(map.getId(), map); return map; } @@ -48,7 +44,7 @@ public static MapInstance getMap(int id) { private MapCreatedCallback constructorCallback; - private MapInstance(MapView mapView, MapCreatedCallback callback) { + private MapInstance(final MapView mapView, final JSONObject options, final MapCreatedCallback callback) { this.id = this.ids++; this.constructorCallback = callback; this.mapView = mapView; @@ -58,6 +54,7 @@ private MapInstance(MapView mapView, MapCreatedCallback callback) { public void onMapReady(MapboxMap mMap) { mapboxMap = mMap; mapboxMap.setMyLocationEnabled(false); + applyOptions(options); constructorCallback.onMapReady(MapInstance.this); } }); @@ -75,15 +72,6 @@ public MapboxMap getMapboxMap() { return this.mapboxMap; } - public void configure(JSONObject options) throws JSONException { - UiSettings uiSettings = mapboxMap.getUiSettings(); - uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); - uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); - uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); - uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); - uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - } - public JSONArray getCenter() throws JSONException { CameraPosition cameraPosition = mapboxMap.getCameraPosition(); double lat = cameraPosition.target.getLatitude(); @@ -153,45 +141,41 @@ private static String getStyle(final String requested) { } } -// public void show(JSONObject options, CallbackContext callbackContext) { -// -// -// try { -// -// -// // placing these offscreen in case the user wants to hide them -// if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { -// mapView.setAttributionMargins(-300, 0, 0, 0); -// } -// if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { -// mapView.setLogoMargins(-300, 0, 0, 0); -// } -// -// if (showUserLocation) { -// showUserLocation(); -// } -// -// Double zoom = options.isNull("zoomLevel") ? 10 : options.getDouble("zoomLevel"); -// float zoomLevel = zoom.floatValue(); -// if (center != null) { -// final double lat = center.getDouble("lat"); -// final double lng = center.getDouble("lng"); -// mapView.setLatLng(new LatLngZoom(lat, lng, zoomLevel)); -// } else { -// if (zoomLevel > 18.0) { -// zoomLevel = 18.0f; -// } -// mapView.setZoom(zoomLevel); -// } -// -// if (options.has("markers")) { -// addMarkers(options.getJSONArray("markers")); -// } -// } catch (JSONException e) { -// callbackContext.error(e.getMessage()); -// return; -// } -// -// mapView.setStyleUrl(style); -// } + private void applyOptions(JSONObject options) { + try { + UiSettings uiSettings = mapboxMap.getUiSettings(); + uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); + uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); + uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); + uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); + uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); + + if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { + uiSettings.setAttributionMargins(-300, 0, 0, 0); + } + + if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { + uiSettings.setLogoMargins(-300, 0, 0, 0); + } + + if (!options.isNull("center")) { + this.setCenter(options.getJSONArray("center")); + } + + if (!options.isNull("zoomLevel")) { + this.setZoom(options.getDouble("zoom")); + } + + if (options.has("markers")) { + this.addMarkers(options.getJSONArray("markers")); + } + + if (options.has("style")) { + this.mapView.setStyleUrl(this.getStyle(options.optString("style"))); + } + } + catch (JSONException e) { + e.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index d319f57..899f0bb 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -268,16 +268,16 @@ private void create(final JSONObject options, final CallbackContext callback) { @Override public void run() { MapView mapView = createMapView(accessToken, options); - MapInstance.createMap(mapView, new MapInstance.MapCreatedCallback() { + MapInstance.createMap(mapView, options, new MapInstance.MapCreatedCallback() { @Override public void onMapReady(final MapInstance map) { JSONObject resp = new JSONObject(); try { - map.configure(options); resp.put("id", map.getId()); callback.success(resp); return; - } catch (JSONException e) { + } + catch (JSONException e) { e.printStackTrace(); callback.error("Failed to create map."); return; @@ -293,8 +293,6 @@ private MapView createMapView(String accessToken, JSONObject options) { mapView.setAccessToken(accessToken); try { - // final String style = getStyle(options.optString("style")); - // final JSONObject center = options.isNull("center") ? null : options.getJSONObject("center"); final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); final int right = (int) (retinaFactor * (margins == null || margins.isNull("right") ? 0 : margins.getInt("right"))); @@ -320,18 +318,6 @@ private MapView createMapView(String accessToken, JSONObject options) { return mapView; } -// private void addMarkers(JSONArray markers) throws JSONException { -// for (int i=0; i<markers.length(); i++) { -// final JSONObject marker = markers.getJSONObject(i); -// final MarkerOptions mo = new MarkerOptions(); -// mo.title(marker.isNull("title") ? null : marker.getString("title")); -// mo.snippet(marker.isNull("subtitle") ? null : marker.getString("subtitle")); -// mo.position(new LatLng(marker.getDouble("lat"), marker.getDouble("lng"))); -// mapView.addMarker(mo); -// } -// } -// - private boolean permissionGranted(String... types) { if (Build.VERSION.SDK_INT < 23) { return true; diff --git a/www/Mapbox.js b/www/Mapbox.js index 8804509..9a91c51 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -9,7 +9,7 @@ module.exports = { var map = new MapInstance(resp.id); successCallback(map); }, errorCallback, "Mapbox", "create", [options]); - }, + } // hide: function (options, successCallback, errorCallback) { // cordova.exec(successCallback, errorCallback, "Mapbox", "hide", [options]); diff --git a/www/map-instance.js b/www/map-instance.js index 122dfc6..6fa3d38 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -33,11 +33,11 @@ MapInstance.prototype.getCenter = function (successCallback, errorCallback) { this._exec(successCallback, errorCallback, "getCenter"); }; -MapInstance.prototype.getZoomLevel: function (successCallback, errorCallback) { +MapInstance.prototype.getZoomLevel = function (successCallback, errorCallback) { this._exec(successCallback, errorCallback, "getZoomLevel"); }; -MapInstance.prototype.setZoomLevel: function (zoom, successCallback, errorCallback) { +MapInstance.prototype.setZoomLevel = function (zoom, successCallback, errorCallback) { this._exec(successCallback, errorCallback, "setZoomLevel", [zoom]); }; From 36cc05233313aa005592a9374d166285f8129224 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Fri, 18 Mar 2016 16:11:34 -0700 Subject: [PATCH 07/37] Added support for permission requests via command pattern. Implemented showUserLocation. --- src/android/MapInstance.java | 9 ++- src/android/Mapbox.java | 151 +++++++++++++++++++++++++++-------- www/map-instance.js | 4 + 3 files changed, 131 insertions(+), 33 deletions(-) diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index 468f0d5..2e1e77e 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -53,7 +53,6 @@ private MapInstance(final MapView mapView, final JSONObject options, final MapCr @Override public void onMapReady(MapboxMap mMap) { mapboxMap = mMap; - mapboxMap.setMyLocationEnabled(false); applyOptions(options); constructorCallback.onMapReady(MapInstance.this); } @@ -122,6 +121,10 @@ public void addMarkerListener(MapboxMap.OnInfoWindowClickListener listener) { mapboxMap.setOnInfoWindowClickListener(listener); } + public void showUserLocation(boolean enabled) { + mapboxMap.setMyLocationEnabled(enabled); + } + private static String getStyle(final String requested) { if ("light".equalsIgnoreCase(requested)) { return Style.LIGHT; @@ -150,6 +153,10 @@ private void applyOptions(JSONObject options) { uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); + if (!options.isNull("showUserLocation") && !options.getBoolean("showUserLocation")) { + this.showUserLocation(false); + } + if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { uiSettings.setAttributionMargins(-300, 0, 0, 0); } diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 899f0bb..c689f84 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -18,12 +18,13 @@ import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaWebView; -import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.HashMap; + // TODO for screen rotation, see https://www.mapbox.com/mapbox-android-sdk/#screen-rotation // TODO fox Xwalk compat, see nativepagetransitions plugin // TODO look at demo app: https://github.com/mapbox/mapbox-gl-native/blob/master/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -31,14 +32,13 @@ public class Mapbox extends CordovaPlugin { public static final String FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION; public static final String COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION; - public static final int LOCATION_REQ_CODE = 0; public static final int PERMISSION_DENIED_ERROR = 20; private static final String MAPBOX_ACCESSTOKEN_RESOURCE_KEY = "mapbox_accesstoken"; private static final String ACTION_CREATE = "create"; - private static final String ACTION_HIDE = "hide"; + private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; private static final String ACTION_ADD_MARKERS = "addMarkers"; private static final String ACTION_ADD_MARKER_CALLBACK = "addMarkerCallback"; private static final String ACTION_ADD_POLYGON = "addPolygon"; @@ -53,9 +53,6 @@ public class Mapbox extends CordovaPlugin { private static float retinaFactor; private String accessToken; - private CallbackContext callback; - -// private boolean showUserLocation; @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { @@ -65,16 +62,35 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) { this.accessToken = this.getAccessToken(); } - @Override public boolean execute(final String action, final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { - callback = callbackContext; + Command command = Command.create(action, args, callbackContext); + return execute(command); + } + + public boolean execute(Command command) throws JSONException { + final String action = command.getAction(); + final CordovaArgs args = command.getArgs(); + final CallbackContext callbackContext = command.getCallbackContext(); if (ACTION_CREATE.equals(action)) { final JSONObject options = args.getJSONObject(0); + boolean showUserLocation = !options.isNull("showUserLocation") && options.getBoolean("showUserLocation"); + if (showUserLocation && !requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { + return false; + } this.create(options, callbackContext); } + else if (ACTION_SHOW_USER_LOCATION.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + boolean enabled = args.getBoolean(1); + if (requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { + map.showUserLocation(enabled); + } + } + else if (ACTION_GET_CENTER.equals(action)) { final int mapId = args.getInt(0); final MapInstance map = MapInstance.getMap(mapId); @@ -255,15 +271,11 @@ public void run() { } private void create(final JSONObject options, final CallbackContext callback) { - if (!this.permissionGranted(COARSE_LOCATION, FINE_LOCATION)) { - this.requestPermission(COARSE_LOCATION, FINE_LOCATION); - return; - } - if (accessToken == null) { callback.error(MAPBOX_ACCESSTOKEN_RESOURCE_KEY + " not set in strings.xml"); return; } + cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { @@ -276,8 +288,7 @@ public void onMapReady(final MapInstance map) { resp.put("id", map.getId()); callback.success(resp); return; - } - catch (JSONException e) { + } catch (JSONException e) { e.printStackTrace(); callback.error("Failed to create map."); return; @@ -331,34 +342,30 @@ private boolean permissionGranted(String... types) { } protected void showUserLocation() { - if (permissionGranted(COARSE_LOCATION, FINE_LOCATION)) { - //noinspection MissingPermission -// mapView.setMyLocationEnabled(showUserLocation); - } else { - requestPermission(COARSE_LOCATION, FINE_LOCATION); - } + } - private void requestPermission(String... types) { - ActivityCompat.requestPermissions( - this.cordova.getActivity(), - types, - LOCATION_REQ_CODE); + private boolean requestPermission(Command command, String... types) { + if (!permissionGranted(types)) { + int commandId = Command.save(command); + ActivityCompat.requestPermissions(this.cordova.getActivity(), types, commandId); + return false; + } else { + return true; + } } // TODO - public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { + public void onRequestPermissionResult(int commandId, String[] permissions, int[] grantResults) throws JSONException { for (int r : grantResults) { if (r == PackageManager.PERMISSION_DENIED) { - this.callback.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR)); + Command.error(commandId, PERMISSION_DENIED_ERROR); return; } } - switch (requestCode) { - case LOCATION_REQ_CODE: - break; - } + + Command.execute(this, commandId); } // public void onPause(boolean multitasking) { @@ -397,4 +404,84 @@ private String getAccessToken() { return accessToken; } + +} + +class Command { + private static int ids = 0; + + private static HashMap<Integer, Command> commands = new HashMap<Integer, Command>(); + + public static Command create(final String action, final CordovaArgs args, final CallbackContext callbackContext) { + return new Command(action, args, callbackContext); + } + + public static int save(final String action, final CordovaArgs args, final CallbackContext callbackContext) { + return save(create(action, args, callbackContext)); + } + + public static int save(Command command) { + int id = command.getId(); + commands.put(id, command); + return id; + } + + public static void execute(Mapbox plugin, int id) throws JSONException { + Command command = commands.remove(id); + plugin.execute(command); + } + + public static void error(final int id, final int errorCode) { + error(id, errorCode, null); + } + + public static void error(final int id, final int errorCode, final String errorMessage) { + Command command = commands.remove(id); + CallbackContext callback = command.getCallbackContext(); + JSONObject error = new JSONObject(); + String message = "Error (" + errorCode + ")"; + + if (errorMessage != null) { + message += ": " + errorMessage; + } + + try { + error.put("code", id); + error.put("message", message); + callback.error(error); + } catch (JSONException e) { + callback.error(message); + } + } + + private int id; + + private String action; + + private CordovaArgs args; + + private CallbackContext callbackContext; + + private Command(final String action, final CordovaArgs args, final CallbackContext callbackContext) { + this.id = ids++; + this.action = action; + this.args = args; + this.callbackContext = callbackContext; + } + + public int getId() { + return id; + } + + public String getAction() { + return action; + } + + public CordovaArgs getArgs() { + return args; + } + + public CallbackContext getCallbackContext() { + return callbackContext; + } } diff --git a/www/map-instance.js b/www/map-instance.js index 6fa3d38..cd5139e 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -41,4 +41,8 @@ MapInstance.prototype.setZoomLevel = function (zoom, successCallback, errorCallb this._exec(successCallback, errorCallback, "setZoomLevel", [zoom]); }; +MapInstance.prototype.showUserLocation = function (enabled, successCallback, errorCallback) { + this._exec(successCallback, errorCallback, "showUserLocation", [enabled]); +}; + module.exports = MapInstance; From 961c4d98c5761244a1b234ea436cd7c6bf27ffbc Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Mon, 21 Mar 2016 12:51:14 -0700 Subject: [PATCH 08/37] Attempt at matching mapbox-gl-js api. --- src/android/MapInstance.java | 4 +- src/android/Mapbox.java | 12 +--- www/Mapbox.js | 71 +--------------------- www/map-instance.js | 110 +++++++++++++++++++++++++++++++---- 4 files changed, 104 insertions(+), 93 deletions(-) diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index 2e1e77e..72a3342 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -153,8 +153,8 @@ private void applyOptions(JSONObject options) { uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - if (!options.isNull("showUserLocation") && !options.getBoolean("showUserLocation")) { - this.showUserLocation(false); + if (!options.isNull("showUserLocation")) { + this.showUserLocation(options.getBoolean("showUserLocation")); } if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index c689f84..15158c9 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -76,10 +76,9 @@ public boolean execute(Command command) throws JSONException { if (ACTION_CREATE.equals(action)) { final JSONObject options = args.getJSONObject(0); boolean showUserLocation = !options.isNull("showUserLocation") && options.getBoolean("showUserLocation"); - if (showUserLocation && !requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { - return false; + if (!showUserLocation || requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { + this.create(options, callbackContext); } - this.create(options, callbackContext); } else if (ACTION_SHOW_USER_LOCATION.equals(action)) { @@ -341,15 +340,10 @@ private boolean permissionGranted(String... types) { return true; } - protected void showUserLocation() { - - } - - private boolean requestPermission(Command command, String... types) { if (!permissionGranted(types)) { int commandId = Command.save(command); - ActivityCompat.requestPermissions(this.cordova.getActivity(), types, commandId); + cordova.requestPermissions(this, commandId, types); return false; } else { return true; diff --git a/www/Mapbox.js b/www/Mapbox.js index 9a91c51..d6a9d1e 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -1,70 +1 @@ -var exec = require("cordova/exec"), - MapInstance = require("./MapInstance"); - -module.exports = { - create: function (options, successCallback, errorCallback) { - console.log('Mapbox.js create()'); - cordova.exec(function(resp) { - console.log('Mapbox.js create()', resp); - var map = new MapInstance(resp.id); - successCallback(map); - }, errorCallback, "Mapbox", "create", [options]); - } - - // hide: function (options, successCallback, errorCallback) { - // cordova.exec(successCallback, errorCallback, "Mapbox", "hide", [options]); - // }, - - // animateCamera: function (options, successCallback, errorCallback) { - // cordova.exec(successCallback, errorCallback, "Mapbox", "animateCamera", [options]); - // }, - - // addGeoJSON: function (options, successCallback, errorCallback) { - // cordova.exec(successCallback, errorCallback, "Mapbox", "addGeoJSON", [options]); - // }, - - // setTilt: function (options, successCallback, errorCallback) { - // cordova.exec(successCallback, errorCallback, "Mapbox", "setTilt", [options]); - // }, - - // getTilt: function (successCallback, errorCallback) { - // cordova.exec(successCallback, errorCallback, "Mapbox", "getTilt", []); - // }, - -<<<<<<< 881cb4ce1ff2ab3dcdbb4afb9e31b6c4dbeb58be - setCenter: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "setCenter", [options]); - }, - - getCenter: function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "getCenter", []); - }, - - setTilt: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "setTilt", [options]); - }, - - getTilt: function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "getTilt", []); - }, - - getZoomLevel: function (successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "getZoomLevel", []); - }, - - setZoomLevel: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "setZoomLevel", [options]); - }, - - addPolygon: function (options, successCallback, errorCallback) { - cordova.exec(successCallback, errorCallback, "Mapbox", "addPolygon", [options]); - }, - - convertCoordinate: function(options, successCallback, errorCallback){ - cordova.exec(successCallback, errorCallback, "Mapbox", "convertCoordinate", [options]); - }, - - convertPoint: function(options, successCallback, errorCallback){ - cordova.exec(successCallback, errorCallback, "Mapbox", "convertPoint", [options]); - } -}; +module.exports = require("./MapInstance"); diff --git a/www/map-instance.js b/www/map-instance.js index cd5139e..118653c 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -1,48 +1,134 @@ -var exec = require("cordova/exec"); +var exec = require("cordova/exec"), + channel = require("cordova/channel"), + channelIds = 0; -function MapInstance(id) { - this._id = id; +var Events = Mixin({ + initEvents: function (prefix) { + if (!this._channelPrefix) { + this._channelPrefix = prefix + "." + (channelIds++); + } + }, + + _prefix: function (type) { + return this._channelPrefix + "." + type; + }, + + _channel: function (type) { + var t = this._prefix(type); + return this._channels[t]; + }, + + createChannel: function (type) { + var t = this._prefix(type), + c = channel.create(t); + this._channels[t] = c; + }, + + createStickyChannel: function (type) { + var t = this._prefix(type), + c = channel.createSticky(t); + this._channels[type] = c; + }, + + once: function (type, listener) { + var t = this._prefix(type), + onEvent = function (e) { + listener(e); + this.off(t, onEvent); + }; + this.on(t, onEvent); + }, + + on: function (type, listener) { + this._channel(type).subscribe(listener); + }, + + off: function (type, listener) { + this._channel(type).unsubscribe(listener); + }, + + fire: function (type, e) { + this._channel(type).fire(e); + } + }); + +function Mixin(behaviour) { + return function(target) { + return Object.assign(target.behaviour); + }; } +function MapInstance(options) { + var onLoad = _onLoad.bind(this), + onError = this._error.bind(this); + + this._error = onError; + + this.initEvents("Mapbox.MapInstance"); + this.createStickyChannel("load"); + + this._exec(onLoad, this._error, "Mapbox", "create", [options]); + + function _onLoad(resp) { + this._id = resp.id; + this.loaded = true; + + this.fire("load", {map: this}); + } +} + +Events(MapInstance.prototype); + +MapInstance.prototype._error = function (err) { + console.error("Map error (ID: " + this._id + "): ", err); +}; + MapInstance.prototype._exec = function (successCallback, errorCallback, method, args) { args = [this._id].concat(args || []); exec(successCallback, errorCallback, "Mapbox", method, args); }; +MapInstance.prototype._execAfterLoad = function () { + var args = arguments; + this.once('load', function (map) { + this._exec.apply(this, args); + }.bind(this)); +}; + MapInstance.prototype.setCenter = function (options, successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "setCenter", [options]); + this._execAfterLoad(successCallback, errorCallback, "setCenter", [options]); }; MapInstance.prototype.getCenter = function (successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "getCenter"); + this._execAfterLoad(successCallback, errorCallback, "getCenter"); }; MapInstance.prototype.addMarkers = function (options, successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "addMarkers", [options]); + this._execAfterLoad(successCallback, errorCallback, "addMarkers", [options]); }; MapInstance.prototype.addMarkerCallback = function (callback) { - this._exec(callback, null, "addMarkerCallback"); + this._execAfterLoad(callback, null, "addMarkerCallback"); }; MapInstance.prototype.setCenter = function (center, successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "setCenter", [center]); + this._execAfterLoad(successCallback, errorCallback, "setCenter", [center]); }; MapInstance.prototype.getCenter = function (successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "getCenter"); + this._execAfterLoad(successCallback, errorCallback, "getCenter"); }; MapInstance.prototype.getZoomLevel = function (successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "getZoomLevel"); + this._execAfterLoad(successCallback, errorCallback, "getZoomLevel"); }; MapInstance.prototype.setZoomLevel = function (zoom, successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "setZoomLevel", [zoom]); + this._execAfterLoad(successCallback, errorCallback, "setZoomLevel", [zoom]); }; MapInstance.prototype.showUserLocation = function (enabled, successCallback, errorCallback) { - this._exec(successCallback, errorCallback, "showUserLocation", [enabled]); + this._execAfterLoad(successCallback, errorCallback, "showUserLocation", [enabled]); }; module.exports = MapInstance; From cbe2269c3783ba6b21da6ff75bb76499dd5c00c7 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Mon, 21 Mar 2016 12:56:33 -0700 Subject: [PATCH 09/37] adding assign function (based on Object.assign polyfill from MDN). --- www/Mapbox.js | 5 ++++- www/map-instance.js | 52 +++++++++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/www/Mapbox.js b/www/Mapbox.js index d6a9d1e..b68f7db 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -1 +1,4 @@ -module.exports = require("./MapInstance"); +var MapInstance = require("./MapInstance"); +module.exports = { + Map: MapInstance +}; diff --git a/www/map-instance.js b/www/map-instance.js index 118653c..e4ee50d 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -13,30 +13,33 @@ var Events = Mixin({ return this._channelPrefix + "." + type; }, - _channel: function (type) { + _channel: function (type, sticky) { var t = this._prefix(type); + if (!this._channels) { + this._channels = {}; + } + if (sticky !== undefined) { + this._channels[t] = sticky ? + channel.createSticky(t) : + channel.create(t); + } return this._channels[t]; }, createChannel: function (type) { - var t = this._prefix(type), - c = channel.create(t); - this._channels[t] = c; + this._channel(type, false); }, createStickyChannel: function (type) { - var t = this._prefix(type), - c = channel.createSticky(t); - this._channels[type] = c; + this._channel(type, true); }, once: function (type, listener) { - var t = this._prefix(type), - onEvent = function (e) { + var onEvent = function (e) { listener(e); - this.off(t, onEvent); + this.off(type, onEvent); }; - this.on(t, onEvent); + this.on(type, onEvent.bind(this)); }, on: function (type, listener) { @@ -52,9 +55,28 @@ var Events = Mixin({ } }); +function assign(target) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; +} + function Mixin(behaviour) { return function(target) { - return Object.assign(target.behaviour); + return assign(target, behaviour); }; } @@ -67,7 +89,7 @@ function MapInstance(options) { this.initEvents("Mapbox.MapInstance"); this.createStickyChannel("load"); - this._exec(onLoad, this._error, "Mapbox", "create", [options]); + exec(onLoad, this._error, "Mapbox", "create", [options]); function _onLoad(resp) { this._id = resp.id; @@ -80,7 +102,9 @@ function MapInstance(options) { Events(MapInstance.prototype); MapInstance.prototype._error = function (err) { - console.error("Map error (ID: " + this._id + "): ", err); + var error = new Error("Map error (ID: " + this._id + "): " + err); + console.warn("throwing MapError: ", error); + throw error; }; MapInstance.prototype._exec = function (successCallback, errorCallback, method, args) { From 47b73738e7dd60d93bacb4fa0da54e8d5aaca4e3 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Mon, 21 Mar 2016 16:44:10 -0700 Subject: [PATCH 10/37] Implemented jumpTo() from mapbox-gl-js api. --- src/android/MapInstance.java | 57 +++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index 72a3342..63070d9 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -80,13 +80,13 @@ public JSONArray getCenter() throws JSONException { } public void setCenter(JSONArray coords) throws JSONException { - double lat = coords.getDouble(0); - double lng = coords.getDouble(1); - double alt = coords.getDouble(2); + double lng = coords.getDouble(0); + double lat = coords.getDouble(1); + //double alt = coords.getDouble(2); mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition( new CameraPosition.Builder() - .target(new LatLng(lat, lng, alt)) + .target(new LatLng(lat, lng)) .build() )); } @@ -104,6 +104,29 @@ public void setZoom(double zoom) { )); } + public void jumpTo(JSONObject options) throws JSONException { + CameraPosition current = mapboxMap.getCameraPosition(); + CameraPosition.Builder builder = new CameraPosition.Builder(current); + + if (!options.isNull("zoom")) { + builder.zoom(options.getDouble("zoom")); + } + + if (!options.isNull("center")) { + JSONArray center = options.getJSONArray("center"); + double lng = center.getDouble(0); + double lat = center.getDouble(1); + builder.target(new LatLng(lat, lng)); + } + + // TODO: Bearing + + // TODO: Pitch + + CameraPosition position = builder.build(); + mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(position)); + } + public void addMarkers(JSONArray markers) throws JSONException { for (int i = 0; i < markers.length(); i++) { final JSONObject marker = markers.getJSONObject(i); @@ -144,8 +167,16 @@ private static String getStyle(final String requested) { } } - private void applyOptions(JSONObject options) { + public void applyOptions(JSONObject options) { try { + if (options.has("style")) { + this.mapView.setStyleUrl(this.getStyle(options.optString("style"))); + } + + if (!options.isNull("showUserLocation")) { + this.showUserLocation(options.getBoolean("showUserLocation")); + } + UiSettings uiSettings = mapboxMap.getUiSettings(); uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); @@ -153,10 +184,6 @@ private void applyOptions(JSONObject options) { uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - if (!options.isNull("showUserLocation")) { - this.showUserLocation(options.getBoolean("showUserLocation")); - } - if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { uiSettings.setAttributionMargins(-300, 0, 0, 0); } @@ -165,21 +192,11 @@ private void applyOptions(JSONObject options) { uiSettings.setLogoMargins(-300, 0, 0, 0); } - if (!options.isNull("center")) { - this.setCenter(options.getJSONArray("center")); - } - - if (!options.isNull("zoomLevel")) { - this.setZoom(options.getDouble("zoom")); - } - if (options.has("markers")) { this.addMarkers(options.getJSONArray("markers")); } - if (options.has("style")) { - this.mapView.setStyleUrl(this.getStyle(options.optString("style"))); - } + this.jumpTo(options); } catch (JSONException e) { e.printStackTrace(); From cb505f47fea60ad19cc90cf3ef8904c07e28d37f Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Mon, 21 Mar 2016 16:44:31 -0700 Subject: [PATCH 11/37] Fixed thread issue with setUserLocation(). --- src/android/Mapbox.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 15158c9..cc6362c 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -84,9 +84,14 @@ public boolean execute(Command command) throws JSONException { else if (ACTION_SHOW_USER_LOCATION.equals(action)) { final int mapId = args.getInt(0); final MapInstance map = MapInstance.getMap(mapId); - boolean enabled = args.getBoolean(1); + final boolean enabled = args.getBoolean(1); if (requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { - map.showUserLocation(enabled); + cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + map.showUserLocation(enabled); + } + }); } } From df8d69b98dc540c5007e1ac7f165d4fd372a7fc5 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Mon, 21 Mar 2016 16:53:51 -0700 Subject: [PATCH 12/37] Cleaning up file layout. --- plugin.xml | 4 +- src/android/Mapbox.java | 19 +++++++++ www/Mapbox.js | 2 +- www/events-mixin.js | 56 ++++++++++++++++++++++++++ www/map-instance.js | 87 +++-------------------------------------- www/mixin.js | 26 ++++++++++++ 6 files changed, 111 insertions(+), 83 deletions(-) create mode 100644 www/events-mixin.js create mode 100644 www/mixin.js diff --git a/plugin.xml b/plugin.xml index 37cc5ee..136bec2 100755 --- a/plugin.xml +++ b/plugin.xml @@ -24,7 +24,9 @@ <engine name="cordova-plugman" version=">=4.2.0"/><!-- needed for gradleReference support --> </engines> - <js-module src="www/map-instance.js" name="MapInstance" /> + <js-module src="www/mixin.js" name="mixin" /> + <js-module src="www/events-mixin.js" name="events-mixin" /> + <js-module src="www/map-instance.js" name="map-instance" /> <js-module src="www/Mapbox.js" name="Mapbox"> <clobbers target="window.Mapbox" /> diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index cc6362c..a7736c2 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -38,6 +38,7 @@ public class Mapbox extends CordovaPlugin { private static final String MAPBOX_ACCESSTOKEN_RESOURCE_KEY = "mapbox_accesstoken"; private static final String ACTION_CREATE = "create"; + private static final String ACTION_JUMP_TO = "jumpTo"; private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; private static final String ACTION_ADD_MARKERS = "addMarkers"; private static final String ACTION_ADD_MARKER_CALLBACK = "addMarkerCallback"; @@ -95,6 +96,24 @@ public void run() { } } + else if (ACTION_JUMP_TO.equals(action)) { + final int mapId = args.getInt(0); + final MapInstance map = MapInstance.getMap(mapId); + final JSONObject options = args.getJSONObject(1); + + cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + try { + map.jumpTo(options); + callbackContext.success(); + } catch (JSONException e) { + callbackContext.error(e.getMessage()); + } + } + }); + } + else if (ACTION_GET_CENTER.equals(action)) { final int mapId = args.getInt(0); final MapInstance map = MapInstance.getMap(mapId); diff --git a/www/Mapbox.js b/www/Mapbox.js index b68f7db..7d947d0 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -1,4 +1,4 @@ -var MapInstance = require("./MapInstance"); +var MapInstance = require("./map-instance"); module.exports = { Map: MapInstance }; diff --git a/www/events-mixin.js b/www/events-mixin.js new file mode 100644 index 0000000..09f0e32 --- /dev/null +++ b/www/events-mixin.js @@ -0,0 +1,56 @@ +var Mixin = require('./mixin'), + channel = require("cordova/channel"), + channelIds = 0; + +module.exports = Mixin({ + initEvents: function (prefix) { + if (!this._channelPrefix) { + this._channelPrefix = prefix + "." + (channelIds++); + } + }, + + _prefix: function (type) { + return this._channelPrefix + "." + type; + }, + + _channel: function (type, sticky) { + var t = this._prefix(type); + if (!this._channels) { + this._channels = {}; + } + if (sticky !== undefined) { + this._channels[t] = sticky ? + channel.createSticky(t) : + channel.create(t); + } + return this._channels[t]; + }, + + createChannel: function (type) { + this._channel(type, false); + }, + + createStickyChannel: function (type) { + this._channel(type, true); + }, + + once: function (type, listener) { + var onEvent = function (e) { + listener(e); + this.off(type, onEvent); + }; + this.on(type, onEvent.bind(this)); + }, + + on: function (type, listener) { + this._channel(type).subscribe(listener); + }, + + off: function (type, listener) { + this._channel(type).unsubscribe(listener); + }, + + fire: function (type, e) { + this._channel(type).fire(e); + } +}); diff --git a/www/map-instance.js b/www/map-instance.js index e4ee50d..9ee406f 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -1,84 +1,5 @@ var exec = require("cordova/exec"), - channel = require("cordova/channel"), - channelIds = 0; - -var Events = Mixin({ - initEvents: function (prefix) { - if (!this._channelPrefix) { - this._channelPrefix = prefix + "." + (channelIds++); - } - }, - - _prefix: function (type) { - return this._channelPrefix + "." + type; - }, - - _channel: function (type, sticky) { - var t = this._prefix(type); - if (!this._channels) { - this._channels = {}; - } - if (sticky !== undefined) { - this._channels[t] = sticky ? - channel.createSticky(t) : - channel.create(t); - } - return this._channels[t]; - }, - - createChannel: function (type) { - this._channel(type, false); - }, - - createStickyChannel: function (type) { - this._channel(type, true); - }, - - once: function (type, listener) { - var onEvent = function (e) { - listener(e); - this.off(type, onEvent); - }; - this.on(type, onEvent.bind(this)); - }, - - on: function (type, listener) { - this._channel(type).subscribe(listener); - }, - - off: function (type, listener) { - this._channel(type).unsubscribe(listener); - }, - - fire: function (type, e) { - this._channel(type).fire(e); - } - }); - -function assign(target) { - if (target === undefined || target === null) { - throw new TypeError('Cannot convert undefined or null to object'); - } - - var output = Object(target); - for (var index = 1; index < arguments.length; index++) { - var source = arguments[index]; - if (source !== undefined && source !== null) { - for (var nextKey in source) { - if (source.hasOwnProperty(nextKey)) { - output[nextKey] = source[nextKey]; - } - } - } - } - return output; -} - -function Mixin(behaviour) { - return function(target) { - return assign(target, behaviour); - }; -} + EventsMixin = require("./events-mixin"); function MapInstance(options) { var onLoad = _onLoad.bind(this), @@ -99,7 +20,7 @@ function MapInstance(options) { } } -Events(MapInstance.prototype); +EventsMixin(MapInstance.prototype); MapInstance.prototype._error = function (err) { var error = new Error("Map error (ID: " + this._id + "): " + err); @@ -119,6 +40,10 @@ MapInstance.prototype._execAfterLoad = function () { }.bind(this)); }; +MapInstance.prototype.jumpTo = function (options, successCallback, errorCallback) { + this._execAfterLoad(successCallback, errorCallback, "jumpTo", [options]); +}; + MapInstance.prototype.setCenter = function (options, successCallback, errorCallback) { this._execAfterLoad(successCallback, errorCallback, "setCenter", [options]); }; diff --git a/www/mixin.js b/www/mixin.js new file mode 100644 index 0000000..f4178e9 --- /dev/null +++ b/www/mixin.js @@ -0,0 +1,26 @@ +module.exports = Mixin; + +function Mixin(behaviour) { + return function(target) { + return assign(target, behaviour); + }; +} + +function assign(target) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; +} From 864084abff282691ead91c7db20b06b8a8098b98 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Tue, 22 Mar 2016 12:15:14 -0700 Subject: [PATCH 13/37] initial offline pieces --- src/android/MapInstance.java | 33 +++++++++++++++++++++++++++++++++ src/android/Mapbox.java | 8 ++++++++ 2 files changed, 41 insertions(+) diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index 63070d9..01afec6 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -5,10 +5,14 @@ import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.UiSettings; +import com.mapbox.mapboxsdk.offline.OfflineManager; +import com.mapbox.mapboxsdk.offline.OfflineRegion; +import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; import org.json.JSONArray; import org.json.JSONException; @@ -167,6 +171,35 @@ private static String getStyle(final String requested) { } } + public void createOfflineRegion(float pixelRatio, JSONObject options) { + OfflineManager mOfflineManager = OfflineManager.getInstance(this); + mOfflineManager.setAccessToken(ApiAccess.getToken(this)); + + // Definition + String styleURL = mapboxMap.getStyleUrl(); + LatLngBounds bounds = mapboxMap.getProjection().getVisibleRegion().latLngBounds; + double minZoom = mapView.getZoom(); + double maxZoom = mapView.getMaxZoom(); + OfflineRegionDefinition definition = new OfflineRegionDefinition(styleURL, bounds, minZoom, maxZoom, pixelRatio); + + // Your metadata + OfflineRegionMetadata metadata =...; + + // Create region + mOfflineManager.createOfflineRegion(definition, metadata, + new OfflineManager.CreateOfflineRegionCallback() { + @Override + public void onCreate(OfflineRegion offlineRegion) { + Log.d(LOG_TAG, "Offline region created: " + regionName); + } + + @Override + public void onError(String error) { + Log.e(LOG_TAG, "Error: " + error); + } + }); + } + public void applyOptions(JSONObject options) { try { if (options.has("style")) { diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index a7736c2..763680a 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -10,8 +10,10 @@ import android.widget.FrameLayout; import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.offline.OfflineManager; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; @@ -40,6 +42,7 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_CREATE = "create"; private static final String ACTION_JUMP_TO = "jumpTo"; private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; + private static final String ACTION_CREATE_OFFLINE_REGION = "createOfflineRegion"; private static final String ACTION_ADD_MARKERS = "addMarkers"; private static final String ACTION_ADD_MARKER_CALLBACK = "addMarkerCallback"; private static final String ACTION_ADD_POLYGON = "addPolygon"; @@ -186,6 +189,11 @@ public boolean onInfoWindowClick(Marker marker) { } ); } + else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { + final int mapId = args.getInt(0); + final JSONObject options = args.getJSONObject(1); + final MapInstance map = MapInstance.getMap(mapId); + } else if (ACTION_GET_TILT.equals(action)) { // if (mapView != null) { // cordova.getActivity().runOnUiThread(new Runnable() { From 846b174516aaa036b195dba4ae6f058ff9dd38bc Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Tue, 22 Mar 2016 13:10:27 -0700 Subject: [PATCH 14/37] Offline implementation independenant of map instances. --- src/android/MapInstance.java | 37 ++------------------ src/android/Mapbox.java | 68 +++++++++++++++++++++++++++++++++--- src/android/mapbox.gradle | 2 +- 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/android/MapInstance.java b/src/android/MapInstance.java index 01afec6..e37f0c5 100644 --- a/src/android/MapInstance.java +++ b/src/android/MapInstance.java @@ -5,14 +5,10 @@ import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.UiSettings; -import com.mapbox.mapboxsdk.offline.OfflineManager; -import com.mapbox.mapboxsdk.offline.OfflineRegion; -import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; import org.json.JSONArray; import org.json.JSONException; @@ -22,6 +18,8 @@ public class MapInstance { + private final static String LOG_TAG = "MapInstance"; + public interface MapCreatedCallback { void onMapReady(MapInstance map); } @@ -171,36 +169,7 @@ private static String getStyle(final String requested) { } } - public void createOfflineRegion(float pixelRatio, JSONObject options) { - OfflineManager mOfflineManager = OfflineManager.getInstance(this); - mOfflineManager.setAccessToken(ApiAccess.getToken(this)); - - // Definition - String styleURL = mapboxMap.getStyleUrl(); - LatLngBounds bounds = mapboxMap.getProjection().getVisibleRegion().latLngBounds; - double minZoom = mapView.getZoom(); - double maxZoom = mapView.getMaxZoom(); - OfflineRegionDefinition definition = new OfflineRegionDefinition(styleURL, bounds, minZoom, maxZoom, pixelRatio); - - // Your metadata - OfflineRegionMetadata metadata =...; - - // Create region - mOfflineManager.createOfflineRegion(definition, metadata, - new OfflineManager.CreateOfflineRegionCallback() { - @Override - public void onCreate(OfflineRegion offlineRegion) { - Log.d(LOG_TAG, "Offline region created: " + regionName); - } - - @Override - public void onError(String error) { - Log.e(LOG_TAG, "Error: " + error); - } - }); - } - - public void applyOptions(JSONObject options) { + private void applyOptions(JSONObject options) { try { if (options.has("style")) { this.mapView.setStyleUrl(this.getStyle(options.optString("style"))); diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 763680a..ec7271e 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -7,13 +7,18 @@ import android.os.Build; import android.support.v4.app.ActivityCompat; import android.util.DisplayMetrics; +import android.util.Log; import android.widget.FrameLayout; import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.offline.OfflineManager; +import com.mapbox.mapboxsdk.offline.OfflineRegion; +import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; +import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; @@ -32,6 +37,12 @@ // TODO look at demo app: https://github.com/mapbox/mapbox-gl-native/blob/master/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java public class Mapbox extends CordovaPlugin { + private static final String LOG_TAG = "MapboxCordovaPlugin"; + + // JSON encoding/decoding + public static final String JSON_CHARSET = "UTF-8"; + public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; + public static final String FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION; public static final String COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION; @@ -190,9 +201,8 @@ public boolean onInfoWindowClick(Marker marker) { ); } else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { - final int mapId = args.getInt(0); - final JSONObject options = args.getJSONObject(1); - final MapInstance map = MapInstance.getMap(mapId); + final JSONObject options = args.getJSONObject(0); + this.createOfflineRegion(options, callbackContext); } else if (ACTION_GET_TILT.equals(action)) { // if (mapView != null) { @@ -360,6 +370,53 @@ private MapView createMapView(String accessToken, JSONObject options) { return mapView; } + public void createOfflineRegion(JSONObject options, final CallbackContext callbackContext) throws JSONException { + String styleURL = options.getString("style"); + + final String regionName = options.getString("name"); + double minZoom = options.getDouble("minZoom"); + double maxZoom = options.getDouble("maxZoom"); + JSONObject boundsOptions = options.getJSONObject("bounds"); + double north = boundsOptions.getDouble("north"); + double east = boundsOptions.getDouble("east"); + double south = boundsOptions.getDouble("south"); + double west = boundsOptions.getDouble("west"); + + LatLngBounds bounds = new LatLngBounds.Builder() + .include(new LatLng(north, west)) + .include(new LatLng(south, east)) + .build(); + OfflineRegionDefinition definition = new OfflineTilePyramidRegionDefinition(styleURL, bounds, minZoom, maxZoom, this.retinaFactor); + + // Sample way of encoding metadata from a JSONObject + final JSONObject metadata = new JSONObject(); + byte[] encodedMetadata; + try { + metadata.put(JSON_FIELD_REGION_NAME, regionName); + String json = metadata.toString(); + encodedMetadata = json.getBytes(JSON_CHARSET); + } catch (Exception e) { + Log.e(LOG_TAG, "Failed to encode metadata: " + e.getMessage()); + encodedMetadata = null; + } + + OfflineManager offlineManager = OfflineManager.getInstance(this.webView.getContext()); + offlineManager.setAccessToken(this.accessToken); + offlineManager.createOfflineRegion(definition, encodedMetadata, new OfflineManager.CreateOfflineRegionCallback() { + @Override + public void onCreate(OfflineRegion offlineRegion) { + Log.d(LOG_TAG, "Offline region created: " + regionName); + callbackContext.success(metadata); + } + + @Override + public void onError(String error) { + Log.e(LOG_TAG, "Error: " + error); + callbackContext.error(error); + } + }); + } + private boolean permissionGranted(String... types) { if (Build.VERSION.SDK_INT < 23) { return true; @@ -407,8 +464,9 @@ public void onRequestPermissionResult(int commandId, String[] permissions, int[] // } private float getRetinaFactor() { - DisplayMetrics metrics = new DisplayMetrics(); - this.cordova.getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); + Activity activity = this.cordova.getActivity(); + Resources res = activity.getResources(); + DisplayMetrics metrics = res.getDisplayMetrics(); return metrics.density; } diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index 4bbe68b..57aca98 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -6,7 +6,7 @@ repositories { dependencies { compile 'com.android.support:appcompat-v7:23.0.1' - compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-beta.1@aar'){ + compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-beta.2@aar'){ transitive=true } } From 3dd2ad6f4ce6afc341a5c1d6870b631d24608665 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Tue, 22 Mar 2016 16:44:53 -0700 Subject: [PATCH 15/37] Offline regions. --- plugin.xml | 4 +- src/android/{MapInstance.java => Map.java} | 103 ++++++-------- src/android/Mapbox.java | 140 ++++++++++-------- src/android/OfflineRegion.java | 156 +++++++++++++++++++++ www/Mapbox.js | 7 +- www/offline-region.js | 99 +++++++++++++ 6 files changed, 387 insertions(+), 122 deletions(-) rename src/android/{MapInstance.java => Map.java} (57%) create mode 100644 src/android/OfflineRegion.java create mode 100644 www/offline-region.js diff --git a/plugin.xml b/plugin.xml index 136bec2..91ab77a 100755 --- a/plugin.xml +++ b/plugin.xml @@ -27,6 +27,7 @@ <js-module src="www/mixin.js" name="mixin" /> <js-module src="www/events-mixin.js" name="events-mixin" /> <js-module src="www/map-instance.js" name="map-instance" /> + <js-module src="www/offline-region.js" name="offline-region" /> <js-module src="www/Mapbox.js" name="Mapbox"> <clobbers target="window.Mapbox" /> @@ -56,7 +57,8 @@ <framework src="src/android/mapbox.gradle" custom="true" type="gradleReference"/> <source-file src="src/android/Mapbox.java" target-dir="src/com/telerik/plugins/mapbox"/> - <source-file src="src/android/MapInstance.java" target-dir="src/com/telerik/plugins/mapbox"/> + <source-file src="src/android/OfflineRegion.java" target-dir="src/com/telerik/plugins/mapbox"/> + <source-file src="src/android/Map.java" target-dir="src/com/telerik/plugins/mapbox"/> <!-- This leads to trouble in AppBuilder when compiling for Cordova-Android 4 --> <!--source-file src="src/android/res/values/mapboxstrings.xml" target-dir="res/values" /> diff --git a/src/android/MapInstance.java b/src/android/Map.java similarity index 57% rename from src/android/MapInstance.java rename to src/android/Map.java index e37f0c5..a606857 100644 --- a/src/android/MapInstance.java +++ b/src/android/Map.java @@ -3,7 +3,6 @@ import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; -import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -16,27 +15,28 @@ import java.util.HashMap; -public class MapInstance { +public class Map { + private static HashMap<Integer, Map> maps = new HashMap<Integer, Map>(); - private final static String LOG_TAG = "MapInstance"; + private static int ids = 0; public interface MapCreatedCallback { - void onMapReady(MapInstance map); + void onCreate(Map map); + void onError(String error); } - public static MapInstance createMap(MapView mapView, JSONObject options, MapCreatedCallback callback) { - MapInstance map = new MapInstance(mapView, options, callback); + public static void create(MapView mapView, JSONObject options, MapCreatedCallback callback) { + Map map = new Map(mapView, options, callback); maps.put(map.getId(), map); - return map; } - public static MapInstance getMap(int id) { + public static Map getMap(int id) { return maps.get(id); } - private static HashMap<Integer, MapInstance> maps = new HashMap<Integer, MapInstance>(); - - private static int ids = 0; + public static void removeMap(int id) { + maps.remove(id); + } private int id; @@ -46,7 +46,7 @@ public static MapInstance getMap(int id) { private MapCreatedCallback constructorCallback; - private MapInstance(final MapView mapView, final JSONObject options, final MapCreatedCallback callback) { + private Map(final MapView mapView, final JSONObject options, final MapCreatedCallback callback) { this.id = this.ids++; this.constructorCallback = callback; this.mapView = mapView; @@ -55,8 +55,13 @@ private MapInstance(final MapView mapView, final JSONObject options, final MapCr @Override public void onMapReady(MapboxMap mMap) { mapboxMap = mMap; - applyOptions(options); - constructorCallback.onMapReady(MapInstance.this); + try { + applyOptions(options); + constructorCallback.onCreate(Map.this); + } catch (JSONException e) { + Map.removeMap(getId()); + constructorCallback.onError(e.getMessage()); + } } }); } @@ -150,58 +155,34 @@ public void showUserLocation(boolean enabled) { mapboxMap.setMyLocationEnabled(enabled); } - private static String getStyle(final String requested) { - if ("light".equalsIgnoreCase(requested)) { - return Style.LIGHT; - } else if ("dark".equalsIgnoreCase(requested)) { - return Style.DARK; - } else if ("emerald".equalsIgnoreCase(requested)) { - return Style.EMERALD; - } else if ("satellite".equalsIgnoreCase(requested)) { - return Style.SATELLITE; - // TODO not currently supported on Android - //} else if ("hybrid".equalsIgnoreCase(requested)) { - // return Style.HYBRID; - } else if ("streets".equalsIgnoreCase(requested)) { - return Style.MAPBOX_STREETS; - } else { - return requested; + private void applyOptions(JSONObject options) throws JSONException { + if (options.has("style")) { + this.mapView.setStyleUrl(Mapbox.getStyle(options.optString("style"))); } - } - - private void applyOptions(JSONObject options) { - try { - if (options.has("style")) { - this.mapView.setStyleUrl(this.getStyle(options.optString("style"))); - } - - if (!options.isNull("showUserLocation")) { - this.showUserLocation(options.getBoolean("showUserLocation")); - } - UiSettings uiSettings = mapboxMap.getUiSettings(); - uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); - uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); - uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); - uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); - uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - - if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { - uiSettings.setAttributionMargins(-300, 0, 0, 0); - } + if (!options.isNull("showUserLocation")) { + this.showUserLocation(options.getBoolean("showUserLocation")); + } - if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { - uiSettings.setLogoMargins(-300, 0, 0, 0); - } + UiSettings uiSettings = mapboxMap.getUiSettings(); + uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); + uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); + uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); + uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); + uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - if (options.has("markers")) { - this.addMarkers(options.getJSONArray("markers")); - } + if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { + uiSettings.setAttributionMargins(-300, 0, 0, 0); + } - this.jumpTo(options); + if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { + uiSettings.setLogoMargins(-300, 0, 0, 0); } - catch (JSONException e) { - e.printStackTrace(); + + if (options.has("markers")) { + this.addMarkers(options.getJSONArray("markers")); } + + this.jumpTo(options); } -} \ No newline at end of file +} diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index ec7271e..ca15f83 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -11,14 +11,10 @@ import android.widget.FrameLayout; import com.mapbox.mapboxsdk.annotations.Marker; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.offline.OfflineManager; -import com.mapbox.mapboxsdk.offline.OfflineRegion; -import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; -import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; @@ -26,6 +22,7 @@ import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaWebView; +import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -39,10 +36,6 @@ public class Mapbox extends CordovaPlugin { private static final String LOG_TAG = "MapboxCordovaPlugin"; - // JSON encoding/decoding - public static final String JSON_CHARSET = "UTF-8"; - public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; - public static final String FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION; public static final String COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION; @@ -54,6 +47,8 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_JUMP_TO = "jumpTo"; private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; private static final String ACTION_CREATE_OFFLINE_REGION = "createOfflineRegion"; + private static final String ACTION_DOWNLOAD_OFFLINE_REGION = "downloadOfflineRegion"; + private static final String ACTION_PAUSE_OFFLINE_REGION = "pauseOfflineRegion"; private static final String ACTION_ADD_MARKERS = "addMarkers"; private static final String ACTION_ADD_MARKER_CALLBACK = "addMarkerCallback"; private static final String ACTION_ADD_POLYGON = "addPolygon"; @@ -98,7 +93,7 @@ public boolean execute(Command command) throws JSONException { else if (ACTION_SHOW_USER_LOCATION.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); final boolean enabled = args.getBoolean(1); if (requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { cordova.getActivity().runOnUiThread(new Runnable() { @@ -112,7 +107,7 @@ public void run() { else if (ACTION_JUMP_TO.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); final JSONObject options = args.getJSONObject(1); cordova.getActivity().runOnUiThread(new Runnable() { @@ -130,7 +125,7 @@ public void run() { else if (ACTION_GET_CENTER.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); try { callbackContext.success(map.getCenter()); } catch (JSONException e) { @@ -140,7 +135,7 @@ else if (ACTION_GET_CENTER.equals(action)) { else if (ACTION_SET_CENTER.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); final JSONArray center = args.getJSONArray(1); try { map.setCenter(center); @@ -152,13 +147,13 @@ else if (ACTION_SET_CENTER.equals(action)) { else if (ACTION_GET_ZOOMLEVEL.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); callbackContext.success("" + map.getZoom()); } else if (ACTION_SET_ZOOMLEVEL.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); final double zoom = args.getDouble(1); map.setZoom(zoom); callbackContext.success(); @@ -166,7 +161,7 @@ else if (ACTION_SET_ZOOMLEVEL.equals(action)) { else if (ACTION_ADD_MARKERS.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); try { map.addMarkers(args.getJSONArray(1)); callbackContext.success(); @@ -177,7 +172,7 @@ else if (ACTION_ADD_MARKERS.equals(action)) { else if (ACTION_ADD_MARKER_CALLBACK.equals(action)) { final int mapId = args.getInt(0); - final MapInstance map = MapInstance.getMap(mapId); + final Map map = Map.getMap(mapId); map.addMarkerListener( new MapboxMap.OnInfoWindowClickListener() { @Override @@ -200,10 +195,24 @@ public boolean onInfoWindowClick(Marker marker) { } ); } + else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { final JSONObject options = args.getJSONObject(0); - this.createOfflineRegion(options, callbackContext); + final String progressCallbackId = args.getString(1); + final CallbackContext onProgress = new CallbackContext(progressCallbackId, this.webView); + this.createOfflineRegion(options, callbackContext, onProgress); + } + + else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { + final int offlineRegionId = args.getInt(0); + final OfflineRegion region = OfflineRegion.getOfflineRegion(offlineRegionId); + region.download(); } + + else if (ACTION_PAUSE_OFFLINE_REGION.equals(action)) { + + } + else if (ACTION_GET_TILT.equals(action)) { // if (mapView != null) { // cordova.getActivity().runOnUiThread(new Runnable() { @@ -321,20 +330,28 @@ private void create(final JSONObject options, final CallbackContext callback) { @Override public void run() { MapView mapView = createMapView(accessToken, options); - MapInstance.createMap(mapView, options, new MapInstance.MapCreatedCallback() { + Map.create(mapView, options, new Map.MapCreatedCallback() { @Override - public void onMapReady(final MapInstance map) { + public void onCreate(final Map map) { JSONObject resp = new JSONObject(); try { resp.put("id", map.getId()); callback.success(resp); return; } catch (JSONException e) { - e.printStackTrace(); - callback.error("Failed to create map."); + String error = "Failed to create map: " + e.getMessage(); + Log.e(LOG_TAG, error); + callback.error(error); return; } } + + @Override + public void onError(String error) { + String message = "Failed to create map: " + error; + Log.e(LOG_TAG, message); + callback.error(message); + } }); } }); @@ -370,49 +387,37 @@ private MapView createMapView(String accessToken, JSONObject options) { return mapView; } - public void createOfflineRegion(JSONObject options, final CallbackContext callbackContext) throws JSONException { - String styleURL = options.getString("style"); - - final String regionName = options.getString("name"); - double minZoom = options.getDouble("minZoom"); - double maxZoom = options.getDouble("maxZoom"); - JSONObject boundsOptions = options.getJSONObject("bounds"); - double north = boundsOptions.getDouble("north"); - double east = boundsOptions.getDouble("east"); - double south = boundsOptions.getDouble("south"); - double west = boundsOptions.getDouble("west"); - - LatLngBounds bounds = new LatLngBounds.Builder() - .include(new LatLng(north, west)) - .include(new LatLng(south, east)) - .build(); - OfflineRegionDefinition definition = new OfflineTilePyramidRegionDefinition(styleURL, bounds, minZoom, maxZoom, this.retinaFactor); - - // Sample way of encoding metadata from a JSONObject - final JSONObject metadata = new JSONObject(); - byte[] encodedMetadata; - try { - metadata.put(JSON_FIELD_REGION_NAME, regionName); - String json = metadata.toString(); - encodedMetadata = json.getBytes(JSON_CHARSET); - } catch (Exception e) { - Log.e(LOG_TAG, "Failed to encode metadata: " + e.getMessage()); - encodedMetadata = null; - } - + public void createOfflineRegion(JSONObject options, final CallbackContext callback, final CallbackContext onProgress) throws JSONException { OfflineManager offlineManager = OfflineManager.getInstance(this.webView.getContext()); offlineManager.setAccessToken(this.accessToken); - offlineManager.createOfflineRegion(definition, encodedMetadata, new OfflineManager.CreateOfflineRegionCallback() { + OfflineRegion.create(offlineManager, this.retinaFactor, options, new OfflineRegion.OfflineRegionCreatedCallback() { @Override - public void onCreate(OfflineRegion offlineRegion) { - Log.d(LOG_TAG, "Offline region created: " + regionName); - callbackContext.success(metadata); + public void onCreate(OfflineRegion region) { + JSONObject resp = new JSONObject(); + try { + resp.put("id", region.getId()); + callback.success(resp); + return; + } catch (JSONException e) { + String error = "Failed to create offline region: " + e.getMessage(); + Log.e(LOG_TAG, error); + callback.error(error); + return; + } + } + + @Override + public void onProgress(JSONObject progress) { + PluginResult result = new PluginResult(PluginResult.Status.OK, progress); + result.setKeepCallback(true); + onProgress.sendPluginResult(result); } @Override public void onError(String error) { - Log.e(LOG_TAG, "Error: " + error); - callbackContext.error(error); + String message = "Failed to create offline region: " + error; + Log.e(LOG_TAG, message); + callback.error(message); } }); } @@ -489,6 +494,25 @@ private String getAccessToken() { return accessToken; } + public static String getStyle(final String requested) { + if ("light".equalsIgnoreCase(requested)) { + return Style.LIGHT; + } else if ("dark".equalsIgnoreCase(requested)) { + return Style.DARK; + } else if ("emerald".equalsIgnoreCase(requested)) { + return Style.EMERALD; + } else if ("satellite".equalsIgnoreCase(requested)) { + return Style.SATELLITE; + // TODO not currently supported on Android + //} else if ("hybrid".equalsIgnoreCase(requested)) { + // return Style.HYBRID; + } else if ("streets".equalsIgnoreCase(requested)) { + return Style.MAPBOX_STREETS; + } else { + return requested; + } + } + } class Command { diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java new file mode 100644 index 0000000..2a1ad6e --- /dev/null +++ b/src/android/OfflineRegion.java @@ -0,0 +1,156 @@ +package com.telerik.plugins.mapbox; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.offline.OfflineManager; +import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; +import com.mapbox.mapboxsdk.offline.OfflineRegionError; +import com.mapbox.mapboxsdk.offline.OfflineRegionStatus; +import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; + +public class OfflineRegion { + // JSON encoding/decoding + public static final String JSON_CHARSET = "UTF-8"; + public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; + + private static HashMap<Integer, OfflineRegion> regions = new HashMap<Integer, OfflineRegion>(); + + private static HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion> mapboxRegions = new HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion>(); + + private static int ids = 0; + + public interface OfflineRegionCreatedCallback { + void onCreate(OfflineRegion region); + void onProgress(JSONObject progress); + void onError(String error); + } + + public static void create(OfflineManager offlineManager, float retinaFactor, JSONObject options, OfflineRegionCreatedCallback callback) { + OfflineRegion region = new OfflineRegion(offlineManager, retinaFactor, options, callback); + regions.put(region.getId(), region); + } + + public static OfflineRegion getOfflineRegion(int id) { + return regions.get(id); + } + + public static void removeOfflineRegion(int id) { + regions.remove(id); + } + + private int id; + + private long mapboxOfflineRegionId; + + private OfflineRegionCreatedCallback constructorCallback; + + private String regionName; + + private OfflineRegion(final OfflineManager offlineManager, final float retinaFactor, final JSONObject options, final OfflineRegionCreatedCallback callback) { + this.id = this.ids++; + this.constructorCallback = callback; + + try { + this.regionName = options.getString("name"); + + OfflineRegionDefinition definition = this.createOfflineRegionDefinition(retinaFactor, options); + byte[] encodedMetadata = this.getMetadata().toString().getBytes(JSON_CHARSET); + + offlineManager.createOfflineRegion(definition, encodedMetadata, new OfflineManager.CreateOfflineRegionCallback() { + @Override + public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { + offlineRegion.setObserver(new OfflineRegionObserver(constructorCallback)); + mapboxOfflineRegionId = offlineRegion.getID(); + mapboxRegions.put(mapboxOfflineRegionId, offlineRegion); + constructorCallback.onCreate(OfflineRegion.this); + } + + @Override + public void onError(String error) { + constructorCallback.onError(error); + OfflineRegion.removeOfflineRegion(getId()); + } + }); + } catch (JSONException e) { + constructorCallback.onError(e.getMessage()); + } catch (UnsupportedEncodingException e) { + constructorCallback.onError(e.getMessage()); + } finally { + OfflineRegion.removeOfflineRegion(getId()); + } + } + + public int getId() { + return this.id; + } + + public JSONObject getMetadata() throws JSONException { + JSONObject metadata = new JSONObject(); + metadata.put(JSON_FIELD_REGION_NAME, this.regionName); + return metadata; + } + + public void download() { + mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_ACTIVE); + } + + public void pause() { + mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_INACTIVE); + } + + private OfflineRegionDefinition createOfflineRegionDefinition(float retinaFactor, JSONObject options) throws JSONException { + String styleURL = Mapbox.getStyle(options.getString("style")); + double minZoom = options.getDouble("minZoom"); + double maxZoom = options.getDouble("maxZoom"); + JSONObject boundsOptions = options.getJSONObject("bounds"); + double north = boundsOptions.getDouble("north"); + double east = boundsOptions.getDouble("east"); + double south = boundsOptions.getDouble("south"); + double west = boundsOptions.getDouble("west"); + + LatLngBounds bounds = new LatLngBounds.Builder() + .include(new LatLng(north, west)) + .include(new LatLng(south, east)) + .build(); + + return new OfflineTilePyramidRegionDefinition(styleURL, bounds, minZoom, maxZoom, retinaFactor); + } + + private class OfflineRegionObserver implements com.mapbox.mapboxsdk.offline.OfflineRegion.OfflineRegionObserver { + OfflineRegionCreatedCallback constructorCallback; + + OfflineRegionObserver(OfflineRegionCreatedCallback callback) { + this.constructorCallback = callback; + } + + @Override + public void onStatusChanged(OfflineRegionStatus status) { + try { + JSONObject progress = new JSONObject(); + progress.put("completedCount", status.getCompletedResourceCount()); + progress.put("completedSize", status.getCompletedResourceSize()); + progress.put("requiredCount", status.getRequiredResourceCount()); + constructorCallback.onProgress(progress); + } catch (JSONException e) { + constructorCallback.onError(e.getMessage()); + } + } + + @Override + public void onError(OfflineRegionError error) { + constructorCallback.onError(error.getMessage()); + } + + @Override + public void mapboxTileCountLimitExceeded(long limit) { + constructorCallback.onError("Tile limit exceeded (limit: " + limit + ")"); + } + } + +} diff --git a/www/Mapbox.js b/www/Mapbox.js index 7d947d0..3c72836 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -1,4 +1,7 @@ -var MapInstance = require("./map-instance"); +var MapInstance = require("./map-instance"), + OfflineRegion = require("./offline-region"); + module.exports = { - Map: MapInstance + Map: MapInstance, + OfflineRegion: OfflineRegion }; diff --git a/www/offline-region.js b/www/offline-region.js new file mode 100644 index 0000000..1442cc3 --- /dev/null +++ b/www/offline-region.js @@ -0,0 +1,99 @@ +var cordova = require("cordova"), + exec = require("cordova/exec"), + EventsMixin = require("./events-mixin"); + +function OfflineRegion(options) { + var onLoad = _onLoad.bind(this), + onProgress = _onProgress.bind(this), + onError = this._error.bind(this), + onProgressId = this._registerCallback('onProgress', onProgress); + + this._error = onError; + this._downloaded = false; + this._downloading = false; + + this.initEvents("Mapbox.MapInstance"); + this.createStickyChannel("load"); + this.createStickyChannel("complete"); + this.createStickyChannel("error"); + this.createChannel("progress"); + + if (options.progress) { + var progress = options.progress; + delete options.progress; + this.on("progress", progress); + } + + exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId]); + + function _onLoad(resp) { + this._id = resp.id; + this.loaded = true; + + this.fire("load", {map: this}); + } + + function _onProgress(progress) { + this.fire("progress", progress); + } +} + +EventsMixin(OfflineRegion.prototype); + +OfflineRegion.prototype._error = function (err) { + var error = new Error("OfflineRegion error (ID: " + this._id + "): " + err); + console.warn("throwing OfflineRegionError: ", error); + throw error; +}; + +OfflineRegion.prototype._exec = function (successCallback, errorCallback, method, args) { + args = [this._id].concat(args || []); + exec(successCallback, errorCallback, "Mapbox", method, args); +}; + +OfflineRegion.prototype._execAfterLoad = function () { + var args = arguments; + this.once('load', function (map) { + this._exec.apply(this, args); + }.bind(this)); +}; + +OfflineRegion.prototype._registerCallback = function (name, success, fail) { + var callbackId = "MapboxOfflineRegion" + name + cordova.callbackId++; + + console.log("_registerCallback(): " + callbackId); + + success = success || function () { console.log(callbackId + "() success!", arguments); }; + fail = fail || function () { console.log(callbackId + "() fail :(", arguments); }; + + cordova.callbacks[callbackId] = {success: success, fail: fail}; + return callbackId; +}; + +OfflineRegion.prototype.download = function () { + this._downloading = true; + + this._execAfterLoad(onSuccess, onError, "downloadOfflineRegion"); + + function onSuccess(resp) { + this._downloading = false; + this._downloaded = true; + this.fire("complete", resp); + } + + function onError(error) { + this._downloading = false; + this._downloaded = false; + try { + this._error(error); + } catch (e) { + this.fire("error", e); + } + } +}; + +OfflineRegion.prototype.pause = function () { + this._execAfterLoad(successCallback, errorCallback, "pauseOfflineRegion"); +}; + +module.exports = OfflineRegion; From 9b003e8333455c6c7b4fac87bf6ba717e38b90f0 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Tue, 22 Mar 2016 18:09:00 -0700 Subject: [PATCH 16/37] Added some todo notes. --- src/android/Mapbox.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index ca15f83..a4b6002 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -207,10 +207,14 @@ else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { final int offlineRegionId = args.getInt(0); final OfflineRegion region = OfflineRegion.getOfflineRegion(offlineRegionId); region.download(); + // TODO: Need to fire callbackContext.success() upon completion and callbackContext.error() upon error. } else if (ACTION_PAUSE_OFFLINE_REGION.equals(action)) { - + final int offlineRegionId = args.getInt(0); + final OfflineRegion region = OfflineRegion.getOfflineRegion(offlineRegionId); + region.pause(); + // TODO: Need to fire callbackContext.success() upon completion and callbackContext.error() upon error. } else if (ACTION_GET_TILT.equals(action)) { From 77ec66391cb35fed651a3527863deb20eede6dce Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Wed, 23 Mar 2016 11:05:50 -0700 Subject: [PATCH 17/37] Fixing up complete and error callbacks --- src/android/Mapbox.java | 14 +++++--- src/android/OfflineRegion.java | 14 ++++++-- www/offline-region.js | 64 +++++++++++++++++++--------------- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index a4b6002..04fa223 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -198,9 +198,9 @@ public boolean onInfoWindowClick(Marker marker) { else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { final JSONObject options = args.getJSONObject(0); - final String progressCallbackId = args.getString(1); - final CallbackContext onProgress = new CallbackContext(progressCallbackId, this.webView); - this.createOfflineRegion(options, callbackContext, onProgress); + final CallbackContext onProgress = new CallbackContext(args.getString(1), this.webView); + final CallbackContext onComplete = new CallbackContext(args.getString(2), this.webView); + this.createOfflineRegion(options, callbackContext, onProgress, onComplete); } else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { @@ -391,7 +391,7 @@ private MapView createMapView(String accessToken, JSONObject options) { return mapView; } - public void createOfflineRegion(JSONObject options, final CallbackContext callback, final CallbackContext onProgress) throws JSONException { + public void createOfflineRegion(JSONObject options, final CallbackContext callback, final CallbackContext onProgress, final CallbackContext onComplete) throws JSONException { OfflineManager offlineManager = OfflineManager.getInstance(this.webView.getContext()); offlineManager.setAccessToken(this.accessToken); OfflineRegion.create(offlineManager, this.retinaFactor, options, new OfflineRegion.OfflineRegionCreatedCallback() { @@ -417,11 +417,17 @@ public void onProgress(JSONObject progress) { onProgress.sendPluginResult(result); } + @Override + public void onComplete(JSONObject progress) { + onComplete.success(progress); + } + @Override public void onError(String error) { String message = "Failed to create offline region: " + error; Log.e(LOG_TAG, message); callback.error(message); + onComplete.error(message); } }); } diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java index 2a1ad6e..4d3061a 100644 --- a/src/android/OfflineRegion.java +++ b/src/android/OfflineRegion.java @@ -27,6 +27,7 @@ public class OfflineRegion { public interface OfflineRegionCreatedCallback { void onCreate(OfflineRegion region); + void onComplete(JSONObject progress); void onProgress(JSONObject progress); void onError(String error); } @@ -131,14 +132,23 @@ private class OfflineRegionObserver implements com.mapbox.mapboxsdk.offline.Offl @Override public void onStatusChanged(OfflineRegionStatus status) { + long completedCount = status.getCompletedResourceCount(); + long requiredCount = status.getRequiredResourceCount(); + JSONObject progress = new JSONObject(); + try { - JSONObject progress = new JSONObject(); progress.put("completedCount", status.getCompletedResourceCount()); progress.put("completedSize", status.getCompletedResourceSize()); progress.put("requiredCount", status.getRequiredResourceCount()); - constructorCallback.onProgress(progress); } catch (JSONException e) { constructorCallback.onError(e.getMessage()); + return; + } + + constructorCallback.onProgress(progress); + + if (completedCount == requiredCount) { + constructorCallback.onComplete(progress); } } diff --git a/www/offline-region.js b/www/offline-region.js index 1442cc3..042fc81 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -5,26 +5,22 @@ var cordova = require("cordova"), function OfflineRegion(options) { var onLoad = _onLoad.bind(this), onProgress = _onProgress.bind(this), - onError = this._error.bind(this), - onProgressId = this._registerCallback('onProgress', onProgress); + onComplete = _onComplete.bind(this), + onError = _onError.bind(this), + onProgressId = this._registerCallback('onProgress', onProgress), + onCompleteId = this._registerCallback('onComplete', onComplete, onError); - this._error = onError; + this._error = this._error.bind(this); this._downloaded = false; this._downloading = false; this.initEvents("Mapbox.MapInstance"); this.createStickyChannel("load"); + this.createChannel("progress"); this.createStickyChannel("complete"); this.createStickyChannel("error"); - this.createChannel("progress"); - if (options.progress) { - var progress = options.progress; - delete options.progress; - this.on("progress", progress); - } - - exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId]); + exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId, onCompleteId]); function _onLoad(resp) { this._id = resp.id; @@ -36,6 +32,22 @@ function OfflineRegion(options) { function _onProgress(progress) { this.fire("progress", progress); } + + function _onComplete(resp) { + this._downloading = false; + this._downloaded = true; + this.fire("complete", resp); + } + + function _onError(error) { + this._downloading = false; + this._downloaded = false; + try { + this._error(error); + } catch (e) { + this.fire("error", e); + } + } } EventsMixin(OfflineRegion.prototype); @@ -72,28 +84,24 @@ OfflineRegion.prototype._registerCallback = function (name, success, fail) { OfflineRegion.prototype.download = function () { this._downloading = true; - this._execAfterLoad(onSuccess, onError, "downloadOfflineRegion"); - - function onSuccess(resp) { - this._downloading = false; - this._downloaded = true; - this.fire("complete", resp); - } - - function onError(error) { - this._downloading = false; - this._downloaded = false; - try { - this._error(error); - } catch (e) { - this.fire("error", e); - } - } }; OfflineRegion.prototype.pause = function () { + this._downloading = false; this._execAfterLoad(successCallback, errorCallback, "pauseOfflineRegion"); }; +Object.defineProperty(OfflineRegion, "downloading", { + get: function () { + return this._downloading; + } +}); + +Object.defineProperty(OfflineRegion, "downloaded", { + get: function () { + return this._downloaded; + } +}); + module.exports = OfflineRegion; From 2c557327804c89fe97e1544d055c09c0b46a958d Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 24 Mar 2016 12:11:10 -0700 Subject: [PATCH 18/37] fixing complete and error callbacks. --- src/android/Mapbox.java | 12 ++++++++---- src/android/OfflineRegion.java | 24 ++++++++++++++++++------ www/offline-region.js | 22 ++++++++++++++++------ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 04fa223..97594f7 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -207,14 +207,14 @@ else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { final int offlineRegionId = args.getInt(0); final OfflineRegion region = OfflineRegion.getOfflineRegion(offlineRegionId); region.download(); - // TODO: Need to fire callbackContext.success() upon completion and callbackContext.error() upon error. + callbackContext.success(); } else if (ACTION_PAUSE_OFFLINE_REGION.equals(action)) { final int offlineRegionId = args.getInt(0); final OfflineRegion region = OfflineRegion.getOfflineRegion(offlineRegionId); region.pause(); - // TODO: Need to fire callbackContext.success() upon completion and callbackContext.error() upon error. + callbackContext.success(); } else if (ACTION_GET_TILT.equals(action)) { @@ -419,14 +419,18 @@ public void onProgress(JSONObject progress) { @Override public void onComplete(JSONObject progress) { - onComplete.success(progress); + Log.d(LOG_TAG, "complete"); + PluginResult result = new PluginResult(PluginResult.Status.OK, progress); + result.setKeepCallback(true); + onComplete.sendPluginResult(result); } @Override public void onError(String error) { String message = "Failed to create offline region: " + error; Log.e(LOG_TAG, message); - callback.error(message); + PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); + result.setKeepCallback(true); onComplete.error(message); } }); diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java index 4d3061a..a2b71f1 100644 --- a/src/android/OfflineRegion.java +++ b/src/android/OfflineRegion.java @@ -1,5 +1,7 @@ package com.telerik.plugins.mapbox; +import android.util.Log; + import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.offline.OfflineManager; @@ -15,6 +17,8 @@ import java.util.HashMap; public class OfflineRegion { + public static final String LOG_TAG = "OfflineRegion"; + // JSON encoding/decoding public static final String JSON_CHARSET = "UTF-8"; public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; @@ -75,6 +79,7 @@ public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { @Override public void onError(String error) { constructorCallback.onError(error); + Log.e(LOG_TAG, error); OfflineRegion.removeOfflineRegion(getId()); } }); @@ -98,10 +103,12 @@ public JSONObject getMetadata() throws JSONException { } public void download() { + Log.d(LOG_TAG, "download()"); mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_ACTIVE); } public void pause() { + Log.d(LOG_TAG, "pause()"); mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_INACTIVE); } @@ -134,27 +141,32 @@ private class OfflineRegionObserver implements com.mapbox.mapboxsdk.offline.Offl public void onStatusChanged(OfflineRegionStatus status) { long completedCount = status.getCompletedResourceCount(); long requiredCount = status.getRequiredResourceCount(); + double percentage = requiredCount >= 0 ? (100.0 * completedCount / requiredCount) : 0.0; JSONObject progress = new JSONObject(); try { - progress.put("completedCount", status.getCompletedResourceCount()); + progress.put("completedCount", completedCount); progress.put("completedSize", status.getCompletedResourceSize()); - progress.put("requiredCount", status.getRequiredResourceCount()); + progress.put("requiredCount", requiredCount); + progress.put("percentage", percentage); } catch (JSONException e) { constructorCallback.onError(e.getMessage()); return; } - constructorCallback.onProgress(progress); - - if (completedCount == requiredCount) { + if (status.isComplete()) { constructorCallback.onComplete(progress); + } else { + constructorCallback.onProgress(progress); } } @Override public void onError(OfflineRegionError error) { - constructorCallback.onError(error.getMessage()); + String message = "OfflineRegionError: [" + error.getReason() + "] " + error.getMessage(); + constructorCallback.onError(message); + Log.e(LOG_TAG, message); + pause(); } @Override diff --git a/www/offline-region.js b/www/offline-region.js index 042fc81..86f52e3 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -36,12 +36,12 @@ function OfflineRegion(options) { function _onComplete(resp) { this._downloading = false; this._downloaded = true; + console.log("_onComplete()", resp); this.fire("complete", resp); } function _onError(error) { - this._downloading = false; - this._downloaded = false; + console.log("_onError()", error); try { this._error(error); } catch (e) { @@ -54,6 +54,8 @@ EventsMixin(OfflineRegion.prototype); OfflineRegion.prototype._error = function (err) { var error = new Error("OfflineRegion error (ID: " + this._id + "): " + err); + this._downloading = false; + this._downloaded = false; console.warn("throwing OfflineRegionError: ", error); throw error; }; @@ -83,22 +85,30 @@ OfflineRegion.prototype._registerCallback = function (name, success, fail) { }; OfflineRegion.prototype.download = function () { + console.log("download", this); this._downloading = true; - this._execAfterLoad(onSuccess, onError, "downloadOfflineRegion"); + this._execAfterLoad(onSuccess, this._error, "downloadOfflineRegion"); + function onSuccess() { + console.log("Download started!"); + } }; OfflineRegion.prototype.pause = function () { + console.log("pause", this); this._downloading = false; - this._execAfterLoad(successCallback, errorCallback, "pauseOfflineRegion"); + this._execAfterLoad(onSuccess, this._error, "pauseOfflineRegion"); + function onSuccess() { + console.log("Download paused!"); + } }; -Object.defineProperty(OfflineRegion, "downloading", { +Object.defineProperty(OfflineRegion.prototype, "downloading", { get: function () { return this._downloading; } }); -Object.defineProperty(OfflineRegion, "downloaded", { +Object.defineProperty(OfflineRegion.prototype, "downloaded", { get: function () { return this._downloaded; } From 9452eb0c03a571bde2334a1a2e49e1917894b50f Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 24 Mar 2016 13:20:24 -0700 Subject: [PATCH 19/37] fixing multiple complete callback issue and cordova freezing issue --- src/android/Mapbox.java | 1 - src/android/OfflineRegion.java | 8 -------- www/offline-region.js | 7 ++++++- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 97594f7..ac3e29c 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -421,7 +421,6 @@ public void onProgress(JSONObject progress) { public void onComplete(JSONObject progress) { Log.d(LOG_TAG, "complete"); PluginResult result = new PluginResult(PluginResult.Status.OK, progress); - result.setKeepCallback(true); onComplete.sendPluginResult(result); } diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java index a2b71f1..c33797f 100644 --- a/src/android/OfflineRegion.java +++ b/src/android/OfflineRegion.java @@ -1,7 +1,5 @@ package com.telerik.plugins.mapbox; -import android.util.Log; - import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.offline.OfflineManager; @@ -17,8 +15,6 @@ import java.util.HashMap; public class OfflineRegion { - public static final String LOG_TAG = "OfflineRegion"; - // JSON encoding/decoding public static final String JSON_CHARSET = "UTF-8"; public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; @@ -79,7 +75,6 @@ public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { @Override public void onError(String error) { constructorCallback.onError(error); - Log.e(LOG_TAG, error); OfflineRegion.removeOfflineRegion(getId()); } }); @@ -103,12 +98,10 @@ public JSONObject getMetadata() throws JSONException { } public void download() { - Log.d(LOG_TAG, "download()"); mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_ACTIVE); } public void pause() { - Log.d(LOG_TAG, "pause()"); mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_INACTIVE); } @@ -165,7 +158,6 @@ public void onStatusChanged(OfflineRegionStatus status) { public void onError(OfflineRegionError error) { String message = "OfflineRegionError: [" + error.getReason() + "] " + error.getMessage(); constructorCallback.onError(message); - Log.e(LOG_TAG, message); pause(); } diff --git a/www/offline-region.js b/www/offline-region.js index 86f52e3..9f0b716 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -20,7 +20,12 @@ function OfflineRegion(options) { this.createStickyChannel("complete"); this.createStickyChannel("error"); - exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId, onCompleteId]); + // TODO: For some reason calling exec within Cordova's 'deviceready' + // callback causes cordova to freeze and stop loading. Delaying it by + // one 'tick' seems to avoid the issue. + window.setTimeout(function () { + exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId, onCompleteId]); + }, 0); function _onLoad(resp) { this._id = resp.id; From af2e379ef370b262786373611c34521d060c622d Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 24 Mar 2016 13:23:15 -0700 Subject: [PATCH 20/37] removing debug statments --- www/offline-region.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/www/offline-region.js b/www/offline-region.js index 9f0b716..ba31fd6 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -80,8 +80,6 @@ OfflineRegion.prototype._execAfterLoad = function () { OfflineRegion.prototype._registerCallback = function (name, success, fail) { var callbackId = "MapboxOfflineRegion" + name + cordova.callbackId++; - console.log("_registerCallback(): " + callbackId); - success = success || function () { console.log(callbackId + "() success!", arguments); }; fail = fail || function () { console.log(callbackId + "() fail :(", arguments); }; @@ -90,7 +88,6 @@ OfflineRegion.prototype._registerCallback = function (name, success, fail) { }; OfflineRegion.prototype.download = function () { - console.log("download", this); this._downloading = true; this._execAfterLoad(onSuccess, this._error, "downloadOfflineRegion"); function onSuccess() { @@ -99,7 +96,6 @@ OfflineRegion.prototype.download = function () { }; OfflineRegion.prototype.pause = function () { - console.log("pause", this); this._downloading = false; this._execAfterLoad(onSuccess, this._error, "pauseOfflineRegion"); function onSuccess() { From 527f68c1726381f3ac76083b3c10a3442b43c7ed Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 24 Mar 2016 14:17:12 -0700 Subject: [PATCH 21/37] Updated debug statements. --- www/offline-region.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/www/offline-region.js b/www/offline-region.js index ba31fd6..7a9b598 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -41,12 +41,10 @@ function OfflineRegion(options) { function _onComplete(resp) { this._downloading = false; this._downloaded = true; - console.log("_onComplete()", resp); this.fire("complete", resp); } function _onError(error) { - console.log("_onError()", error); try { this._error(error); } catch (e) { @@ -91,7 +89,7 @@ OfflineRegion.prototype.download = function () { this._downloading = true; this._execAfterLoad(onSuccess, this._error, "downloadOfflineRegion"); function onSuccess() { - console.log("Download started!"); + console.log("Mapbox OfflineRegion download started."); } }; @@ -99,7 +97,7 @@ OfflineRegion.prototype.pause = function () { this._downloading = false; this._execAfterLoad(onSuccess, this._error, "pauseOfflineRegion"); function onSuccess() { - console.log("Download paused!"); + console.log("Mapbox OfflineRegion download paused."); } }; From b42a016027dbaaf9675f66a179efebcd71430884 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 24 Mar 2016 14:37:32 -0700 Subject: [PATCH 22/37] Implemented Android lifecycle methods. --- src/android/Map.java | 5 ++++ src/android/Mapbox.java | 53 +++++++++++++++++++++++++++++------------ www/map-instance.js | 2 +- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/android/Map.java b/src/android/Map.java index a606857..3b3e7de 100644 --- a/src/android/Map.java +++ b/src/android/Map.java @@ -13,6 +13,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.util.Collection; import java.util.HashMap; public class Map { @@ -30,6 +31,10 @@ public static void create(MapView mapView, JSONObject options, MapCreatedCallbac maps.put(map.getId(), map); } + public static Collection<Map> maps() { + return maps.values(); + } + public static Map getMap(int id) { return maps.get(id); } diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index ac3e29c..d1c30cd 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -43,7 +43,7 @@ public class Mapbox extends CordovaPlugin { private static final String MAPBOX_ACCESSTOKEN_RESOURCE_KEY = "mapbox_accesstoken"; - private static final String ACTION_CREATE = "create"; + private static final String ACTION_CREATE_MAP = "createMap"; private static final String ACTION_JUMP_TO = "jumpTo"; private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; private static final String ACTION_CREATE_OFFLINE_REGION = "createOfflineRegion"; @@ -83,11 +83,11 @@ public boolean execute(Command command) throws JSONException { final CordovaArgs args = command.getArgs(); final CallbackContext callbackContext = command.getCallbackContext(); - if (ACTION_CREATE.equals(action)) { + if (ACTION_CREATE_MAP.equals(action)) { final JSONObject options = args.getJSONObject(0); boolean showUserLocation = !options.isNull("showUserLocation") && options.getBoolean("showUserLocation"); if (!showUserLocation || requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { - this.create(options, callbackContext); + this.createMap(options, callbackContext); } } @@ -324,7 +324,7 @@ public void run() { return true; } - private void create(final JSONObject options, final CallbackContext callback) { + private void createMap(final JSONObject options, final CallbackContext callback) { if (accessToken == null) { callback.error(MAPBOX_ACCESSTOKEN_RESOURCE_KEY + " not set in strings.xml"); return; @@ -469,17 +469,40 @@ public void onRequestPermissionResult(int commandId, String[] permissions, int[] Command.execute(this, commandId); } -// public void onPause(boolean multitasking) { -// mapView.onPause(); -// } -// -// public void onResume(boolean multitasking) { -// mapView.onResume(); -// } -// -// public void onDestroy() { -// mapView.onDestroy(); -// } + @Override + public void onStart() { + for (Map map : Map.maps()) { + map.getMapView().onStart(); + } + } + + @Override + public void onResume(boolean multitasking) { + for (Map map : Map.maps()) { + map.getMapView().onResume(); + } + } + + @Override + public void onPause(boolean multitasking) { + for (Map map : Map.maps()) { + map.getMapView().onPause(); + } + } + + @Override + public void onStop() { + for (Map map : Map.maps()) { + map.getMapView().onStop(); + } + } + + @Override + public void onDestroy() { + for (Map map : Map.maps()) { + map.getMapView().onDestroy(); + } + } private float getRetinaFactor() { Activity activity = this.cordova.getActivity(); diff --git a/www/map-instance.js b/www/map-instance.js index 9ee406f..75c2074 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -10,7 +10,7 @@ function MapInstance(options) { this.initEvents("Mapbox.MapInstance"); this.createStickyChannel("load"); - exec(onLoad, this._error, "Mapbox", "create", [options]); + exec(onLoad, this._error, "Mapbox", "createMap", [options]); function _onLoad(resp) { this._id = resp.id; From a95a49067f4cb4620913fbbc954c4af91b7a1e8c Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Thu, 24 Mar 2016 17:56:13 -0700 Subject: [PATCH 23/37] Implmented loadOfflineRegions() --- plugin.xml | 1 + src/android/Mapbox.java | 92 ++++++++++++-------- src/android/MapboxManager.java | 154 +++++++++++++++++++++++++++++++++ src/android/OfflineRegion.java | 115 +++++------------------- src/android/mapbox.gradle | 3 +- www/Mapbox.js | 20 ++++- www/offline-region.js | 7 +- 7 files changed, 253 insertions(+), 139 deletions(-) create mode 100644 src/android/MapboxManager.java diff --git a/plugin.xml b/plugin.xml index 91ab77a..3d0516e 100755 --- a/plugin.xml +++ b/plugin.xml @@ -57,6 +57,7 @@ <framework src="src/android/mapbox.gradle" custom="true" type="gradleReference"/> <source-file src="src/android/Mapbox.java" target-dir="src/com/telerik/plugins/mapbox"/> + <source-file src="src/android/MapboxManager.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/OfflineRegion.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/Map.java" target-dir="src/com/telerik/plugins/mapbox"/> diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index d1c30cd..990a29f 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -46,6 +46,7 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_CREATE_MAP = "createMap"; private static final String ACTION_JUMP_TO = "jumpTo"; private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; + private static final String ACTION_LIST_OFFLINE_REGIONS = "listOfflineRegions"; private static final String ACTION_CREATE_OFFLINE_REGION = "createOfflineRegion"; private static final String ACTION_DOWNLOAD_OFFLINE_REGION = "downloadOfflineRegion"; private static final String ACTION_PAUSE_OFFLINE_REGION = "pauseOfflineRegion"; @@ -64,12 +65,15 @@ public class Mapbox extends CordovaPlugin { private static float retinaFactor; private String accessToken; + private MapboxManager mapboxManager; + @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); this.retinaFactor = this.getRetinaFactor(); this.accessToken = this.getAccessToken(); + this.mapboxManager = new MapboxManager(accessToken, retinaFactor, webView); } @Override @@ -196,6 +200,10 @@ public boolean onInfoWindowClick(Marker marker) { ); } + else if (ACTION_LIST_OFFLINE_REGIONS.equals(action)) { + this.listOfflineRegions(callbackContext); + } + else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { final JSONObject options = args.getJSONObject(0); final CallbackContext onProgress = new CallbackContext(args.getString(1), this.webView); @@ -205,14 +213,14 @@ else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { final int offlineRegionId = args.getInt(0); - final OfflineRegion region = OfflineRegion.getOfflineRegion(offlineRegionId); + final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); region.download(); callbackContext.success(); } else if (ACTION_PAUSE_OFFLINE_REGION.equals(action)) { final int offlineRegionId = args.getInt(0); - final OfflineRegion region = OfflineRegion.getOfflineRegion(offlineRegionId); + final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); region.pause(); callbackContext.success(); } @@ -391,46 +399,58 @@ private MapView createMapView(String accessToken, JSONObject options) { return mapView; } - public void createOfflineRegion(JSONObject options, final CallbackContext callback, final CallbackContext onProgress, final CallbackContext onComplete) throws JSONException { - OfflineManager offlineManager = OfflineManager.getInstance(this.webView.getContext()); - offlineManager.setAccessToken(this.accessToken); - OfflineRegion.create(offlineManager, this.retinaFactor, options, new OfflineRegion.OfflineRegionCreatedCallback() { + public void listOfflineRegions(final CallbackContext callback) { + cordova.getActivity().runOnUiThread(new Runnable() { @Override - public void onCreate(OfflineRegion region) { - JSONObject resp = new JSONObject(); - try { - resp.put("id", region.getId()); - callback.success(resp); - return; - } catch (JSONException e) { - String error = "Failed to create offline region: " + e.getMessage(); - Log.e(LOG_TAG, error); - callback.error(error); - return; - } - } + public void run() { + OfflineManager offlineManager = OfflineManager.getInstance(webView.getContext()); + offlineManager.setAccessToken(accessToken); + mapboxManager.loadOfflineRegions(new MapboxManager.LoadOfflineRegionsCallback() { + @Override + public void onList(JSONArray offlineRegions) { + callback.success(offlineRegions); + } - @Override - public void onProgress(JSONObject progress) { - PluginResult result = new PluginResult(PluginResult.Status.OK, progress); - result.setKeepCallback(true); - onProgress.sendPluginResult(result); + @Override + public void onError(String error) { + String message = "Error loading offline regions: " + error; + callback.error(message); + } + }); } + }); + } + public void createOfflineRegion(final JSONObject options, final CallbackContext callback, final CallbackContext onProgress, final CallbackContext onComplete) throws JSONException { + cordova.getActivity().runOnUiThread(new Runnable() { @Override - public void onComplete(JSONObject progress) { - Log.d(LOG_TAG, "complete"); - PluginResult result = new PluginResult(PluginResult.Status.OK, progress); - onComplete.sendPluginResult(result); - } + public void run() { + OfflineManager offlineManager = OfflineManager.getInstance(webView.getContext()); + offlineManager.setAccessToken(accessToken); + mapboxManager.createOfflineRegion(options, callback, new MapboxManager.OfflineRegionStatusCallback() { + @Override + public void onProgress(JSONObject progress) { + PluginResult result = new PluginResult(PluginResult.Status.OK, progress); + result.setKeepCallback(true); + onProgress.sendPluginResult(result); + } - @Override - public void onError(String error) { - String message = "Failed to create offline region: " + error; - Log.e(LOG_TAG, message); - PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); - result.setKeepCallback(true); - onComplete.error(message); + @Override + public void onComplete(JSONObject progress) { + Log.d(LOG_TAG, "complete"); + PluginResult result = new PluginResult(PluginResult.Status.OK, progress); + onComplete.sendPluginResult(result); + } + + @Override + public void onError(String error) { + String message = "Failed to create offline region: " + error; + Log.e(LOG_TAG, message); + PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); + result.setKeepCallback(true); + onComplete.error(message); + } + }); } }); } diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java new file mode 100644 index 0000000..8e99a58 --- /dev/null +++ b/src/android/MapboxManager.java @@ -0,0 +1,154 @@ +package com.telerik.plugins.mapbox; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.offline.OfflineManager; +import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; +import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaWebView; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; + +class MapboxManager { + + // JSON encoding/decoding + public static final String JSON_CHARSET = "UTF-8"; + public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; + + private String mapboxAccessToken; + + private Float density; + + private CordovaWebView cordovaWebView; + + private OfflineManager offlineManager; + + private HashMap<Integer, OfflineRegion> regions = new HashMap<Integer, OfflineRegion>(); + + private HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion> mapboxRegions = new HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion>(); + + private int ids = 0; + + public interface OfflineRegionStatusCallback { + void onComplete(JSONObject progress); + void onProgress(JSONObject progress); + void onError(String error); + } + + public interface LoadOfflineRegionsCallback { + void onList(JSONArray regions); + void onError(String error); + } + + public MapboxManager(String accessToken, Float screenDensity, CordovaWebView webView) { + this.mapboxAccessToken = accessToken; + this.density = screenDensity; + this.cordovaWebView = webView; + this.offlineManager = OfflineManager.getInstance(webView.getContext()); + } + + public void loadOfflineRegions(final LoadOfflineRegionsCallback callback) { + this.offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { + @Override + public void onList(com.mapbox.mapboxsdk.offline.OfflineRegion[] offlineRegions) { + try { + JSONArray regions = new JSONArray(); + JSONObject response; + for (com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion : offlineRegions) { + OfflineRegion region = createOfflineRegion(offlineRegion); + response = new JSONObject(); + response.put("id", region.getId()); + regions.put(response); + } + callback.onList(regions); + } catch (JSONException e) { + String error = "Error loading OfflineRegions: " + e.getMessage(); + callback.onError(error); + } + } + + @Override + public void onError(String error) { + + } + }); + } + + public void createOfflineRegion(final JSONObject options, final CallbackContext callback, final OfflineRegionStatusCallback offlineRegionStatusCallback) { + try { + final String regionName = options.getString("name"); + + JSONObject metadata = new JSONObject(); + metadata.put(JSON_FIELD_REGION_NAME, regionName); + byte[] encodedMetadata = metadata.toString().getBytes(JSON_CHARSET); + OfflineRegionDefinition definition = this.createOfflineRegionDefinition(density, options); + + offlineManager.createOfflineRegion(definition, encodedMetadata, new OfflineManager.CreateOfflineRegionCallback() { + @Override + public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { + try { + OfflineRegion region = createOfflineRegion(offlineRegion); + region.setObserver(offlineRegionStatusCallback); + JSONObject resp = new JSONObject(); + resp.put("id", region.getId()); + callback.success(resp); + } catch (JSONException e) { + this.onError(e.getMessage()); + } + } + + @Override + public void onError(String error) { + String message = "Failed to create offline region: " + error; + callback.error(message); + } + }); + } catch (JSONException e) { + callback.error(e.getMessage()); + } catch (UnsupportedEncodingException e) { + callback.error(e.getMessage()); + } + } + + private OfflineRegion createOfflineRegion(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) throws JSONException { + int id = this.ids++; + OfflineRegion region = new OfflineRegion(id, offlineRegion); + byte[] encodedMetadata = offlineRegion.getMetadata(); + JSONObject metadata = new JSONObject(encodedMetadata.toString()); + region.setRegionName(metadata.getString(JSON_FIELD_REGION_NAME)); + regions.put(id, region); + return region; + } + + public OfflineRegion getOfflineRegion(int id) { + return regions.get(id); + } + + public void removeOfflineRegion(int id) { + regions.remove(id); + } + + private OfflineRegionDefinition createOfflineRegionDefinition(float retinaFactor, JSONObject options) throws JSONException { + String styleURL = Mapbox.getStyle(options.getString("style")); + double minZoom = options.getDouble("minZoom"); + double maxZoom = options.getDouble("maxZoom"); + JSONObject boundsOptions = options.getJSONObject("bounds"); + double north = boundsOptions.getDouble("north"); + double east = boundsOptions.getDouble("east"); + double south = boundsOptions.getDouble("south"); + double west = boundsOptions.getDouble("west"); + + LatLngBounds bounds = new LatLngBounds.Builder() + .include(new LatLng(north, west)) + .include(new LatLng(south, east)) + .build(); + + return new OfflineTilePyramidRegionDefinition(styleURL, bounds, minZoom, maxZoom, retinaFactor); + } +} diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java index c33797f..41f5a23 100644 --- a/src/android/OfflineRegion.java +++ b/src/android/OfflineRegion.java @@ -6,128 +6,56 @@ import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; import com.mapbox.mapboxsdk.offline.OfflineRegionError; import com.mapbox.mapboxsdk.offline.OfflineRegionStatus; -import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition; import org.json.JSONException; import org.json.JSONObject; -import java.io.UnsupportedEncodingException; -import java.util.HashMap; - public class OfflineRegion { - // JSON encoding/decoding - public static final String JSON_CHARSET = "UTF-8"; - public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; - - private static HashMap<Integer, OfflineRegion> regions = new HashMap<Integer, OfflineRegion>(); - - private static HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion> mapboxRegions = new HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion>(); - - private static int ids = 0; - - public interface OfflineRegionCreatedCallback { + protected interface OfflineRegionCreateCallback { void onCreate(OfflineRegion region); - void onComplete(JSONObject progress); - void onProgress(JSONObject progress); void onError(String error); } - public static void create(OfflineManager offlineManager, float retinaFactor, JSONObject options, OfflineRegionCreatedCallback callback) { - OfflineRegion region = new OfflineRegion(offlineManager, retinaFactor, options, callback); - regions.put(region.getId(), region); - } - - public static OfflineRegion getOfflineRegion(int id) { - return regions.get(id); - } - - public static void removeOfflineRegion(int id) { - regions.remove(id); - } - private int id; private long mapboxOfflineRegionId; - private OfflineRegionCreatedCallback constructorCallback; + private OfflineRegionCreateCallback createCallback; private String regionName; - private OfflineRegion(final OfflineManager offlineManager, final float retinaFactor, final JSONObject options, final OfflineRegionCreatedCallback callback) { - this.id = this.ids++; - this.constructorCallback = callback; - - try { - this.regionName = options.getString("name"); - - OfflineRegionDefinition definition = this.createOfflineRegionDefinition(retinaFactor, options); - byte[] encodedMetadata = this.getMetadata().toString().getBytes(JSON_CHARSET); - - offlineManager.createOfflineRegion(definition, encodedMetadata, new OfflineManager.CreateOfflineRegionCallback() { - @Override - public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { - offlineRegion.setObserver(new OfflineRegionObserver(constructorCallback)); - mapboxOfflineRegionId = offlineRegion.getID(); - mapboxRegions.put(mapboxOfflineRegionId, offlineRegion); - constructorCallback.onCreate(OfflineRegion.this); - } - - @Override - public void onError(String error) { - constructorCallback.onError(error); - OfflineRegion.removeOfflineRegion(getId()); - } - }); - } catch (JSONException e) { - constructorCallback.onError(e.getMessage()); - } catch (UnsupportedEncodingException e) { - constructorCallback.onError(e.getMessage()); - } finally { - OfflineRegion.removeOfflineRegion(getId()); - } + private com.mapbox.mapboxsdk.offline.OfflineRegion region; + + protected OfflineRegion(int id, com.mapbox.mapboxsdk.offline.OfflineRegion region) { + this.id = id; + this.region = region; } public int getId() { return this.id; } - public JSONObject getMetadata() throws JSONException { - JSONObject metadata = new JSONObject(); - metadata.put(JSON_FIELD_REGION_NAME, this.regionName); - return metadata; + public void setRegionName(String regionName) { + this.regionName = regionName; } public void download() { - mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_ACTIVE); + this.region.setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_ACTIVE); } public void pause() { - mapboxRegions.get(this.mapboxOfflineRegionId).setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_INACTIVE); + this.region.setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_INACTIVE); } - private OfflineRegionDefinition createOfflineRegionDefinition(float retinaFactor, JSONObject options) throws JSONException { - String styleURL = Mapbox.getStyle(options.getString("style")); - double minZoom = options.getDouble("minZoom"); - double maxZoom = options.getDouble("maxZoom"); - JSONObject boundsOptions = options.getJSONObject("bounds"); - double north = boundsOptions.getDouble("north"); - double east = boundsOptions.getDouble("east"); - double south = boundsOptions.getDouble("south"); - double west = boundsOptions.getDouble("west"); - - LatLngBounds bounds = new LatLngBounds.Builder() - .include(new LatLng(north, west)) - .include(new LatLng(south, east)) - .build(); - - return new OfflineTilePyramidRegionDefinition(styleURL, bounds, minZoom, maxZoom, retinaFactor); + public void setObserver(MapboxManager.OfflineRegionStatusCallback statusCallback) { + this.region.setObserver(new OfflineRegionObserver(statusCallback)); } private class OfflineRegionObserver implements com.mapbox.mapboxsdk.offline.OfflineRegion.OfflineRegionObserver { - OfflineRegionCreatedCallback constructorCallback; + MapboxManager.OfflineRegionStatusCallback statusCallback; - OfflineRegionObserver(OfflineRegionCreatedCallback callback) { - this.constructorCallback = callback; + OfflineRegionObserver(MapboxManager.OfflineRegionStatusCallback callback) { + this.statusCallback = callback; } @Override @@ -143,28 +71,27 @@ public void onStatusChanged(OfflineRegionStatus status) { progress.put("requiredCount", requiredCount); progress.put("percentage", percentage); } catch (JSONException e) { - constructorCallback.onError(e.getMessage()); + statusCallback.onError(e.getMessage()); return; } if (status.isComplete()) { - constructorCallback.onComplete(progress); + statusCallback.onComplete(progress); } else { - constructorCallback.onProgress(progress); + statusCallback.onProgress(progress); } } @Override public void onError(OfflineRegionError error) { String message = "OfflineRegionError: [" + error.getReason() + "] " + error.getMessage(); - constructorCallback.onError(message); + statusCallback.onError(message); pause(); } @Override public void mapboxTileCountLimitExceeded(long limit) { - constructorCallback.onError("Tile limit exceeded (limit: " + limit + ")"); + statusCallback.onError("Tile limit exceeded (limit: " + limit + ")"); } } - } diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index 57aca98..a7910cd 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -2,11 +2,12 @@ ext.cdvMinSdkVersion = 15 repositories { mavenCentral() + maven { url "http://oss.sonatype.org/content/repositories/snapshots/" } } dependencies { compile 'com.android.support:appcompat-v7:23.0.1' - compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-beta.2@aar'){ + compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-SNAPSHOT@aar'){ transitive=true } } diff --git a/www/Mapbox.js b/www/Mapbox.js index 3c72836..72b376c 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -1,5 +1,21 @@ -var MapInstance = require("./map-instance"), - OfflineRegion = require("./offline-region"); +var exec = require("cordova/exec"), + MapInstance = require("./map-instance"), + OfflineRegion = require("./offline-region"), + offlineRegions = []; + + +function listOfflineRegions(successCallback, errorCallback) { + exec( + function (regions) { + console.log("Offline regions: ", regions); + }, + function (error) { + console.error("Error getting offline regions: ", error); + }, + "Mapbox", + "listOfflineRegions" + ); +} module.exports = { Map: MapInstance, diff --git a/www/offline-region.js b/www/offline-region.js index 7a9b598..4a92259 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -20,12 +20,7 @@ function OfflineRegion(options) { this.createStickyChannel("complete"); this.createStickyChannel("error"); - // TODO: For some reason calling exec within Cordova's 'deviceready' - // callback causes cordova to freeze and stop loading. Delaying it by - // one 'tick' seems to avoid the issue. - window.setTimeout(function () { - exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId, onCompleteId]); - }, 0); + exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId, onCompleteId]); function _onLoad(resp) { this._id = resp.id; From 96e29b9df1af22999f27505bf2b512f49b72857a Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Fri, 25 Mar 2016 03:05:01 -0700 Subject: [PATCH 24/37] implemented region status --- src/android/Mapbox.java | 120 ++++++++++++++++++--------------- src/android/MapboxManager.java | 69 +++++++++---------- src/android/OfflineRegion.java | 98 +++++++++++++++------------ src/android/mapbox.gradle | 3 +- www/Mapbox.js | 20 +----- www/offline-region.js | 93 +++++++++++++++++++------ 6 files changed, 233 insertions(+), 170 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 990a29f..99cbb8d 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -50,6 +50,7 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_CREATE_OFFLINE_REGION = "createOfflineRegion"; private static final String ACTION_DOWNLOAD_OFFLINE_REGION = "downloadOfflineRegion"; private static final String ACTION_PAUSE_OFFLINE_REGION = "pauseOfflineRegion"; + private static final String ACTION_OFFLINE_REGION_STATUS = "offlineRegionStatus"; private static final String ACTION_ADD_MARKERS = "addMarkers"; private static final String ACTION_ADD_MARKER_CALLBACK = "addMarkerCallback"; private static final String ACTION_ADD_POLYGON = "addPolygon"; @@ -225,6 +226,23 @@ else if (ACTION_PAUSE_OFFLINE_REGION.equals(action)) { callbackContext.success(); } + else if (ACTION_OFFLINE_REGION_STATUS.equals(action)) { + final int offlineRegionId = args.getInt(0); + final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); + + region.getStatus(new MapboxManager.OfflineRegionStatusCallback() { + @Override + public void onStatus(JSONObject status) { + callbackContext.success(status); + } + + @Override + public void onError(String error) { + callbackContext.error(error); + } + }); + } + else if (ACTION_GET_TILT.equals(action)) { // if (mapView != null) { // cordova.getActivity().runOnUiThread(new Runnable() { @@ -333,69 +351,65 @@ public void run() { } private void createMap(final JSONObject options, final CallbackContext callback) { - if (accessToken == null) { - callback.error(MAPBOX_ACCESSTOKEN_RESOURCE_KEY + " not set in strings.xml"); - return; - } - - cordova.getActivity().runOnUiThread(new Runnable() { + cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - MapView mapView = createMapView(accessToken, options); - Map.create(mapView, options, new Map.MapCreatedCallback() { - @Override - public void onCreate(final Map map) { - JSONObject resp = new JSONObject(); - try { - resp.put("id", map.getId()); - callback.success(resp); - return; - } catch (JSONException e) { - String error = "Failed to create map: " + e.getMessage(); - Log.e(LOG_TAG, error); - callback.error(error); - return; + try { + MapView mapView = createMapView(accessToken, options); + Map.create(mapView, options, new Map.MapCreatedCallback() { + @Override + public void onCreate(final Map map) { + JSONObject resp = new JSONObject(); + try { + resp.put("id", map.getId()); + callback.success(resp); + return; + } catch (JSONException e) { + String error = "Failed to create map: " + e.getMessage(); + Log.e(LOG_TAG, error); + callback.error(error); + return; + } } - } - @Override - public void onError(String error) { - String message = "Failed to create map: " + error; - Log.e(LOG_TAG, message); - callback.error(message); - } - }); + @Override + public void onError(String error) { + String message = "Failed to create map: " + error; + Log.e(LOG_TAG, message); + callback.error(message); + } + }); + } catch (JSONException e) { + callback.error(e.getMessage()); + } } }); } - private MapView createMapView(String accessToken, JSONObject options) { + private MapView createMapView(String accessToken, JSONObject options) throws JSONException { MapView mapView = new MapView(this.webView.getContext()); mapView.setAccessToken(accessToken); - try { - final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); - final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); - final int right = (int) (retinaFactor * (margins == null || margins.isNull("right") ? 0 : margins.getInt("right"))); - final int top = (int) (retinaFactor * (margins == null || margins.isNull("top") ? 0 : margins.getInt("top"))); - final int bottom = (int) (retinaFactor * (margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"))); - - // need to do this to register a receiver which onPause later needs - mapView.onResume(); - mapView.onCreate(null); - - // position the mapView overlay - int webViewWidth = webView.getView().getWidth(); - int webViewHeight = webView.getView().getHeight(); - final FrameLayout layout = (FrameLayout) webView.getView().getParent(); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(webViewWidth - left - right, webViewHeight - top - bottom); - params.setMargins(left, top, right, bottom); - mapView.setLayoutParams(params); - - layout.addView(mapView); - } catch (JSONException e) { - e.printStackTrace(); - } + final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); + final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); + final int right = (int) (retinaFactor * (margins == null || margins.isNull("right") ? 0 : margins.getInt("right"))); + final int top = (int) (retinaFactor * (margins == null || margins.isNull("top") ? 0 : margins.getInt("top"))); + final int bottom = (int) (retinaFactor * (margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"))); + + // need to do this to register a receiver which onPause later needs + mapView.onResume(); + mapView.onCreate(null); + + // position the mapView overlay + int webViewWidth = webView.getView().getWidth(); + int webViewHeight = webView.getView().getHeight(); + final FrameLayout layout = (FrameLayout) webView.getView().getParent(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(webViewWidth - left - right, webViewHeight - top - bottom); + params.setMargins(left, top, right, bottom); + mapView.setLayoutParams(params); + + layout.addView(mapView); + return mapView; } @@ -427,7 +441,7 @@ public void createOfflineRegion(final JSONObject options, final CallbackContext public void run() { OfflineManager offlineManager = OfflineManager.getInstance(webView.getContext()); offlineManager.setAccessToken(accessToken); - mapboxManager.createOfflineRegion(options, callback, new MapboxManager.OfflineRegionStatusCallback() { + mapboxManager.createOfflineRegion(options, callback, new MapboxManager.OfflineRegionProgressCallback() { @Override public void onProgress(JSONObject progress) { PluginResult result = new PluginResult(PluginResult.Status.OK, progress); diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java index 8e99a58..0190ee4 100644 --- a/src/android/MapboxManager.java +++ b/src/android/MapboxManager.java @@ -19,23 +19,21 @@ class MapboxManager { // JSON encoding/decoding public static final String JSON_CHARSET = "UTF-8"; - public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME"; - - private String mapboxAccessToken; + public static final String JSON_FIELD_ID = "id"; + public static final String JSON_FIELD_REGION_NAME = "name"; private Float density; - private CordovaWebView cordovaWebView; - private OfflineManager offlineManager; - private HashMap<Integer, OfflineRegion> regions = new HashMap<Integer, OfflineRegion>(); - - private HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion> mapboxRegions = new HashMap<Long, com.mapbox.mapboxsdk.offline.OfflineRegion>(); - - private int ids = 0; + private HashMap<Long, OfflineRegion> regions = new HashMap<Long, OfflineRegion>(); public interface OfflineRegionStatusCallback { + void onStatus(JSONObject status); + void onError(String error); + } + + public interface OfflineRegionProgressCallback { void onComplete(JSONObject progress); void onProgress(JSONObject progress); void onError(String error); @@ -47,10 +45,9 @@ public interface LoadOfflineRegionsCallback { } public MapboxManager(String accessToken, Float screenDensity, CordovaWebView webView) { - this.mapboxAccessToken = accessToken; this.density = screenDensity; - this.cordovaWebView = webView; this.offlineManager = OfflineManager.getInstance(webView.getContext()); + this.offlineManager.setAccessToken(accessToken); } public void loadOfflineRegions(final LoadOfflineRegionsCallback callback) { @@ -58,29 +55,35 @@ public void loadOfflineRegions(final LoadOfflineRegionsCallback callback) { @Override public void onList(com.mapbox.mapboxsdk.offline.OfflineRegion[] offlineRegions) { try { - JSONArray regions = new JSONArray(); + JSONArray responses = new JSONArray(); JSONObject response; + OfflineRegion region; for (com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion : offlineRegions) { - OfflineRegion region = createOfflineRegion(offlineRegion); - response = new JSONObject(); - response.put("id", region.getId()); - regions.put(response); + if (regions.containsKey(offlineRegion.getID())) { + region = regions.get(offlineRegion.getID()); + } else { + region = createOfflineRegion(offlineRegion); + } + response = region.getMetadata(); + response.put(JSON_FIELD_ID, region.getId()); + responses.put(response); } - callback.onList(regions); + callback.onList(responses); } catch (JSONException e) { - String error = "Error loading OfflineRegions: " + e.getMessage(); - callback.onError(error); + this.onError(e.getMessage()); + } catch (UnsupportedEncodingException e) { + this.onError(e.getMessage()); } } @Override public void onError(String error) { - + callback.onError(error); } }); } - public void createOfflineRegion(final JSONObject options, final CallbackContext callback, final OfflineRegionStatusCallback offlineRegionStatusCallback) { + public void createOfflineRegion(final JSONObject options, final CallbackContext callback, final OfflineRegionProgressCallback offlineRegionStatusCallback) { try { final String regionName = options.getString("name"); @@ -95,11 +98,13 @@ public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { try { OfflineRegion region = createOfflineRegion(offlineRegion); region.setObserver(offlineRegionStatusCallback); - JSONObject resp = new JSONObject(); - resp.put("id", region.getId()); - callback.success(resp); + JSONObject response = region.getMetadata(); + response.put(JSON_FIELD_ID, region.getId()); + callback.success(response); } catch (JSONException e) { this.onError(e.getMessage()); + } catch (UnsupportedEncodingException e) { + this.onError(e.getMessage()); } } @@ -116,21 +121,17 @@ public void onError(String error) { } } - private OfflineRegion createOfflineRegion(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) throws JSONException { - int id = this.ids++; - OfflineRegion region = new OfflineRegion(id, offlineRegion); - byte[] encodedMetadata = offlineRegion.getMetadata(); - JSONObject metadata = new JSONObject(encodedMetadata.toString()); - region.setRegionName(metadata.getString(JSON_FIELD_REGION_NAME)); - regions.put(id, region); + private OfflineRegion createOfflineRegion(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) throws JSONException, UnsupportedEncodingException { + OfflineRegion region = new OfflineRegion(offlineRegion); + regions.put(offlineRegion.getID(), region); return region; } - public OfflineRegion getOfflineRegion(int id) { + public OfflineRegion getOfflineRegion(long id) { return regions.get(id); } - public void removeOfflineRegion(int id) { + public void removeOfflineRegion(long id) { regions.remove(id); } diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java index 41f5a23..464a36b 100644 --- a/src/android/OfflineRegion.java +++ b/src/android/OfflineRegion.java @@ -1,42 +1,51 @@ package com.telerik.plugins.mapbox; -import com.mapbox.mapboxsdk.geometry.LatLng; -import com.mapbox.mapboxsdk.geometry.LatLngBounds; -import com.mapbox.mapboxsdk.offline.OfflineManager; -import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; import com.mapbox.mapboxsdk.offline.OfflineRegionError; import com.mapbox.mapboxsdk.offline.OfflineRegionStatus; import org.json.JSONException; import org.json.JSONObject; -public class OfflineRegion { - protected interface OfflineRegionCreateCallback { - void onCreate(OfflineRegion region); - void onError(String error); - } +import java.io.UnsupportedEncodingException; - private int id; - - private long mapboxOfflineRegionId; +public class OfflineRegion { - private OfflineRegionCreateCallback createCallback; + public static final String JSON_CHARSET = "UTF-8"; - private String regionName; + private JSONObject metadata; private com.mapbox.mapboxsdk.offline.OfflineRegion region; - protected OfflineRegion(int id, com.mapbox.mapboxsdk.offline.OfflineRegion region) { - this.id = id; + protected OfflineRegion(com.mapbox.mapboxsdk.offline.OfflineRegion region) throws JSONException, UnsupportedEncodingException { this.region = region; + byte[] encodedMetadata = region.getMetadata(); + this.metadata = new JSONObject(new String(encodedMetadata, JSON_CHARSET)); } - public int getId() { - return this.id; + public Long getId() { + return this.region.getID(); } - public void setRegionName(String regionName) { - this.regionName = regionName; + public JSONObject getMetadata() { + return this.metadata; + } + + public void getStatus(final MapboxManager.OfflineRegionStatusCallback statusCallback) { + this.region.getStatus(new com.mapbox.mapboxsdk.offline.OfflineRegion.OfflineRegionStatusCallback() { + @Override + public void onStatus(OfflineRegionStatus status) { + try { + statusCallback.onStatus(statusToJSON(status)); + } catch (JSONException e) { + this.onError(e.getMessage()); + } + } + + @Override + public void onError(String error) { + statusCallback.onError(error); + } + }); } public void download() { @@ -47,51 +56,54 @@ public void pause() { this.region.setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_INACTIVE); } - public void setObserver(MapboxManager.OfflineRegionStatusCallback statusCallback) { + public void setObserver(MapboxManager.OfflineRegionProgressCallback statusCallback) { this.region.setObserver(new OfflineRegionObserver(statusCallback)); } + private JSONObject statusToJSON(OfflineRegionStatus status) throws JSONException { + long completedCount = status.getCompletedResourceCount(); + long requiredCount = status.getRequiredResourceCount(); + double percentage = requiredCount >= 0 ? (100.0 * completedCount / requiredCount) : 0.0; + JSONObject jsonStatus = new JSONObject() + .put("completedCount", completedCount) + .put("completedSize", status.getCompletedResourceSize()) + .put("requiredCount", requiredCount) + .put("percentage", percentage); + + return jsonStatus; + } + private class OfflineRegionObserver implements com.mapbox.mapboxsdk.offline.OfflineRegion.OfflineRegionObserver { - MapboxManager.OfflineRegionStatusCallback statusCallback; + private MapboxManager.OfflineRegionProgressCallback progressCallback; - OfflineRegionObserver(MapboxManager.OfflineRegionStatusCallback callback) { - this.statusCallback = callback; + OfflineRegionObserver(MapboxManager.OfflineRegionProgressCallback callback) { + this.progressCallback = callback; } @Override public void onStatusChanged(OfflineRegionStatus status) { - long completedCount = status.getCompletedResourceCount(); - long requiredCount = status.getRequiredResourceCount(); - double percentage = requiredCount >= 0 ? (100.0 * completedCount / requiredCount) : 0.0; - JSONObject progress = new JSONObject(); - try { - progress.put("completedCount", completedCount); - progress.put("completedSize", status.getCompletedResourceSize()); - progress.put("requiredCount", requiredCount); - progress.put("percentage", percentage); + JSONObject progress = statusToJSON(status); + if (!status.isComplete()) { + progressCallback.onProgress(progress); + } else { + progressCallback.onComplete(progress); + } } catch (JSONException e) { - statusCallback.onError(e.getMessage()); - return; - } - - if (status.isComplete()) { - statusCallback.onComplete(progress); - } else { - statusCallback.onProgress(progress); + progressCallback.onError(e.getMessage()); } } @Override public void onError(OfflineRegionError error) { String message = "OfflineRegionError: [" + error.getReason() + "] " + error.getMessage(); - statusCallback.onError(message); + progressCallback.onError(message); pause(); } @Override public void mapboxTileCountLimitExceeded(long limit) { - statusCallback.onError("Tile limit exceeded (limit: " + limit + ")"); + progressCallback.onError("Tile limit exceeded (limit: " + limit + ")"); } } } diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index a7910cd..57aca98 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -2,12 +2,11 @@ ext.cdvMinSdkVersion = 15 repositories { mavenCentral() - maven { url "http://oss.sonatype.org/content/repositories/snapshots/" } } dependencies { compile 'com.android.support:appcompat-v7:23.0.1' - compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-SNAPSHOT@aar'){ + compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-beta.2@aar'){ transitive=true } } diff --git a/www/Mapbox.js b/www/Mapbox.js index 72b376c..fecf072 100644 --- a/www/Mapbox.js +++ b/www/Mapbox.js @@ -1,23 +1,9 @@ var exec = require("cordova/exec"), MapInstance = require("./map-instance"), - OfflineRegion = require("./offline-region"), - offlineRegions = []; - - -function listOfflineRegions(successCallback, errorCallback) { - exec( - function (regions) { - console.log("Offline regions: ", regions); - }, - function (error) { - console.error("Error getting offline regions: ", error); - }, - "Mapbox", - "listOfflineRegions" - ); -} + offline = require("./offline-region"); module.exports = { Map: MapInstance, - OfflineRegion: OfflineRegion + createOfflineRegion: offline.createOfflineRegion, + listOfflineRegions: offline.listOfflineRegions }; diff --git a/www/offline-region.js b/www/offline-region.js index 4a92259..b6601d1 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -2,16 +2,20 @@ var cordova = require("cordova"), exec = require("cordova/exec"), EventsMixin = require("./events-mixin"); -function OfflineRegion(options) { - var onLoad = _onLoad.bind(this), - onProgress = _onProgress.bind(this), +var OFFLINE_REGIONS = {}; + +function OfflineRegion() { + var onProgress = _onProgress.bind(this), onComplete = _onComplete.bind(this), - onError = _onError.bind(this), - onProgressId = this._registerCallback('onProgress', onProgress), - onCompleteId = this._registerCallback('onComplete', onComplete, onError); + onError = _onError.bind(this); + + this._onProgressId = this._registerCallback('onProgress', onProgress); + this._onCompleteId = this._registerCallback('onComplete', onComplete, onError); this._error = this._error.bind(this); - this._downloaded = false; + this._create = this._create.bind(this); + this._instance = this._instance.bind(this); + this._downloading = false; this.initEvents("Mapbox.MapInstance"); @@ -20,22 +24,12 @@ function OfflineRegion(options) { this.createStickyChannel("complete"); this.createStickyChannel("error"); - exec(onLoad, this._error, "Mapbox", "createOfflineRegion", [options, onProgressId, onCompleteId]); - - function _onLoad(resp) { - this._id = resp.id; - this.loaded = true; - - this.fire("load", {map: this}); - } - function _onProgress(progress) { this.fire("progress", progress); } function _onComplete(resp) { this._downloading = false; - this._downloaded = true; this.fire("complete", resp); } @@ -50,10 +44,22 @@ function OfflineRegion(options) { EventsMixin(OfflineRegion.prototype); +OfflineRegion.prototype._create = function (options) { + var args = [options, this._onProgressId, this._onCompleteId]; + exec(this._instance, this._error, "Mapbox", "createOfflineRegion", args); +}; + +OfflineRegion.prototype._instance = function (response) { + this._id = response.id; + this._name = response.name; + this.loaded = true; + this.fire("load", {offlineRegion: this}); + OFFLINE_REGIONS[this._id] = this; +}; + OfflineRegion.prototype._error = function (err) { var error = new Error("OfflineRegion error (ID: " + this._id + "): " + err); this._downloading = false; - this._downloaded = false; console.warn("throwing OfflineRegionError: ", error); throw error; }; @@ -96,16 +102,61 @@ OfflineRegion.prototype.pause = function () { } }; +OfflineRegion.prototype.getStatus = function (callback) { + this._execAfterLoad(onSuccess, onError, "offlineRegionStatus"); + function onSuccess(status) { + callback(null, status); + } + function onError(error) { + callback(error); + } +}; + Object.defineProperty(OfflineRegion.prototype, "downloading", { get: function () { return this._downloading; } }); -Object.defineProperty(OfflineRegion.prototype, "downloaded", { +Object.defineProperty(OfflineRegion.prototype, "name", { get: function () { - return this._downloaded; + return this._name; } }); -module.exports = OfflineRegion; +module.exports = { + createOfflineRegion: function (options) { + var region = new OfflineRegion(); + region._create(options); + return region; + }, + + listOfflineRegions: function (callback) { + exec( + function (responses) { + console.log("Offline regions: ", responses); + var regions = responses.map(function (response) { + var region = OFFLINE_REGIONS[response.id]; + if (!region) { + region = new OfflineRegion(); + region._instance(response); + } + return region; + }), + byName = regions.reduce(function (regionsByName, region) { + regionsByName[region.name] = region; + return regionsByName; + }, {}); + callback(null, byName); + }, + function (errorMessage) { + var error = "Error getting offline regions: " + errorMessage; + console.error(error); + callback(error); + }, + "Mapbox", + "listOfflineRegions", + [] + ); + } +}; From ff8b3f4f34162f87454292a0dc20608b82b9c779 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Fri, 25 Mar 2016 03:15:20 -0700 Subject: [PATCH 25/37] fixed issue where no style was set when offline. --- src/android/Map.java | 4 ---- src/android/Mapbox.java | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/android/Map.java b/src/android/Map.java index 3b3e7de..9ed7d8c 100644 --- a/src/android/Map.java +++ b/src/android/Map.java @@ -161,10 +161,6 @@ public void showUserLocation(boolean enabled) { } private void applyOptions(JSONObject options) throws JSONException { - if (options.has("style")) { - this.mapView.setStyleUrl(Mapbox.getStyle(options.optString("style"))); - } - if (!options.isNull("showUserLocation")) { this.showUserLocation(options.getBoolean("showUserLocation")); } diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 99cbb8d..4076f5c 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -389,6 +389,7 @@ public void onError(String error) { private MapView createMapView(String accessToken, JSONObject options) throws JSONException { MapView mapView = new MapView(this.webView.getContext()); mapView.setAccessToken(accessToken); + mapView.setStyleUrl(Mapbox.getStyle(options.optString("style"))); final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); From 331c5337eccd069b9cc8130d48e3851513c348ce Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Sat, 26 Mar 2016 15:28:23 -0700 Subject: [PATCH 26/37] updating to 4.0.0-rc.1 --- src/android/mapbox.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index 57aca98..4580af0 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -6,7 +6,7 @@ repositories { dependencies { compile 'com.android.support:appcompat-v7:23.0.1' - compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-beta.2@aar'){ + compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-rc.1@aar'){ transitive=true } } From 67e8736badd7ae4cb13b47359226817f5ff628fc Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Sat, 26 Mar 2016 15:38:26 -0700 Subject: [PATCH 27/37] Added example of proposed javascript api --- demo/mapboxgl-android-4.x.x-example.js | 87 ++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 demo/mapboxgl-android-4.x.x-example.js diff --git a/demo/mapboxgl-android-4.x.x-example.js b/demo/mapboxgl-android-4.x.x-example.js new file mode 100644 index 0000000..6d0afb4 --- /dev/null +++ b/demo/mapboxgl-android-4.x.x-example.js @@ -0,0 +1,87 @@ +var cabo = { + name: "Cabo San Lucas", + style: "emerald", + minZoom: 0, + maxZoom: 16, + bounds: { + north: 22.891, + east: -109.919, + south: 22.879, + west: -109.905 + } +}; + +Mapbox.listOfflineRegions(function (err, regions) { + if (err) return onError(err); + console.log('listOfflineRegions()', regions); + + var region = regions[cabo.name]; + + // First load will download region. + if (!region) { + region = Mapbox.createOfflineRegion(cabo); + + region.on("error", function (e) { + console.error("OfflineRegion onError", e); + }); + + region.on("progress", function (progress) { + console.log("OfflineRegion download onProgress", progress); + }); + + region.on("complete", function (progress) { + console.log("OfflineRegion download onComplete", progress); + }); + + region.download(); + } + // Subsequent loads will display offline region download status. + else { + region.getStatus(function (err, status) { + if (err) return onError(err); + console.log("OfflineRegion getStatus()", status); + }); + } +}); + +var map = new Mapbox.Map({ + style: 'emerald', + zoom: 15, + center: [-109.912, 22.885], + showUserLocation: true, + margins: { + left: 0, + right: 0, + top: 0, + bottom: 0 + }, + markers: [ + {"title": "Marker 1", "lng": -109.912, "lat": 22.885} + ], + // NOTE: the options below are broken... + hideAttribution: true, // default false + hideLogo: true, // default false + hideCompass: false, // default false + disableRotation: false, // default false + disableScroll: false, // default false + disableZoom: false, // default false + disablePitch: false // default false + }); + +map.on('load', function (e) { + map.addMarkers( + [ + {"title": "Marker 2", "lng": -109.910, "lat": 22.886}, + {"title": "Marker 3", "lng": -109.913, "lat": 22.883} + ], + function () { console.log("Markers added!"); }, + function (e) { console.error("Error adding markers:", e); } + ); + + map.addMarkerCallback(printMarker); + + function printMarker(selectedMarker) { + alert("Marker selected: " + JSON.stringify(selectedMarker)); + map.addMarkerCallback(printMarker); + } +}); From 26c519279d51b48beddde93c584b48e7f7d15d8f Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Sat, 26 Mar 2016 17:29:27 -0700 Subject: [PATCH 28/37] Moving static Map instance management into domain of mapboxManager. --- src/android/Map.java | 108 ++++++++------------------ src/android/Mapbox.java | 137 +++++++-------------------------- src/android/MapboxManager.java | 131 ++++++++++++++++++++++++++++++- 3 files changed, 186 insertions(+), 190 deletions(-) diff --git a/src/android/Map.java b/src/android/Map.java index 9ed7d8c..4b13579 100644 --- a/src/android/Map.java +++ b/src/android/Map.java @@ -6,77 +6,58 @@ import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; -import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.UiSettings; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.Collection; -import java.util.HashMap; - public class Map { - private static HashMap<Integer, Map> maps = new HashMap<Integer, Map>(); - - private static int ids = 0; + private long id; - public interface MapCreatedCallback { - void onCreate(Map map); - void onError(String error); - } + private MapView mapView; - public static void create(MapView mapView, JSONObject options, MapCreatedCallback callback) { - Map map = new Map(mapView, options, callback); - maps.put(map.getId(), map); - } + private MapboxMap mapboxMap; - public static Collection<Map> maps() { - return maps.values(); + public Map(long id, final MapView mapView, final JSONObject options) throws JSONException { + this.id = id; + this.mapView = mapView; } - public static Map getMap(int id) { - return maps.get(id); + public long getId() { + return this.id; } - public static void removeMap(int id) { - maps.remove(id); + public MapView getMapView() { + return this.mapView; } - private int id; - - private MapView mapView; - - private MapboxMap mapboxMap; + public void setMapboxMap(MapboxMap mMap, JSONObject options) throws JSONException { + this.mapboxMap = mMap; + UiSettings uiSettings = mMap.getUiSettings(); + uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); + uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); + uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); + uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); + uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - private MapCreatedCallback constructorCallback; + if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { + uiSettings.setAttributionMargins(-300, 0, 0, 0); + } - private Map(final MapView mapView, final JSONObject options, final MapCreatedCallback callback) { - this.id = this.ids++; - this.constructorCallback = callback; - this.mapView = mapView; + if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { + uiSettings.setLogoMargins(-300, 0, 0, 0); + } - mapView.getMapAsync(new OnMapReadyCallback() { - @Override - public void onMapReady(MapboxMap mMap) { - mapboxMap = mMap; - try { - applyOptions(options); - constructorCallback.onCreate(Map.this); - } catch (JSONException e) { - Map.removeMap(getId()); - constructorCallback.onError(e.getMessage()); - } - } - }); - } + if (!options.isNull("showUserLocation")) { + this.showUserLocation(options.getBoolean("showUserLocation")); + } - public int getId() { - return this.id; - } + if (options.has("markers")) { + this.addMarkers(options.getJSONArray("markers")); + } - public MapView getMapView() { - return this.mapView; + this.jumpTo(options); } public MapboxMap getMapboxMap() { @@ -159,31 +140,4 @@ public void addMarkerListener(MapboxMap.OnInfoWindowClickListener listener) { public void showUserLocation(boolean enabled) { mapboxMap.setMyLocationEnabled(enabled); } - - private void applyOptions(JSONObject options) throws JSONException { - if (!options.isNull("showUserLocation")) { - this.showUserLocation(options.getBoolean("showUserLocation")); - } - - UiSettings uiSettings = mapboxMap.getUiSettings(); - uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); - uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); - uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); - uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); - uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - - if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { - uiSettings.setAttributionMargins(-300, 0, 0, 0); - } - - if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { - uiSettings.setLogoMargins(-300, 0, 0, 0); - } - - if (options.has("markers")) { - this.addMarkers(options.getJSONArray("markers")); - } - - this.jumpTo(options); - } } diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 4076f5c..8b2b76d 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -63,18 +63,12 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_SET_TILT = "setTilt"; private static final String ACTION_ANIMATE_CAMERA = "animateCamera"; - private static float retinaFactor; - private String accessToken; - private MapboxManager mapboxManager; @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); - - this.retinaFactor = this.getRetinaFactor(); - this.accessToken = this.getAccessToken(); - this.mapboxManager = new MapboxManager(accessToken, retinaFactor, webView); + this.mapboxManager = new MapboxManager(this.getAccessToken(), this.getRetinaFactor(), webView); } @Override @@ -97,8 +91,8 @@ public boolean execute(Command command) throws JSONException { } else if (ACTION_SHOW_USER_LOCATION.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); final boolean enabled = args.getBoolean(1); if (requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { cordova.getActivity().runOnUiThread(new Runnable() { @@ -111,8 +105,8 @@ public void run() { } else if (ACTION_JUMP_TO.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); final JSONObject options = args.getJSONObject(1); cordova.getActivity().runOnUiThread(new Runnable() { @@ -129,8 +123,8 @@ public void run() { } else if (ACTION_GET_CENTER.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); try { callbackContext.success(map.getCenter()); } catch (JSONException e) { @@ -139,8 +133,8 @@ else if (ACTION_GET_CENTER.equals(action)) { } else if (ACTION_SET_CENTER.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); final JSONArray center = args.getJSONArray(1); try { map.setCenter(center); @@ -151,22 +145,22 @@ else if (ACTION_SET_CENTER.equals(action)) { } else if (ACTION_GET_ZOOMLEVEL.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); callbackContext.success("" + map.getZoom()); } else if (ACTION_SET_ZOOMLEVEL.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); final double zoom = args.getDouble(1); map.setZoom(zoom); callbackContext.success(); } else if (ACTION_ADD_MARKERS.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); try { map.addMarkers(args.getJSONArray(1)); callbackContext.success(); @@ -176,8 +170,8 @@ else if (ACTION_ADD_MARKERS.equals(action)) { } else if (ACTION_ADD_MARKER_CALLBACK.equals(action)) { - final int mapId = args.getInt(0); - final Map map = Map.getMap(mapId); + final long mapId = args.getLong(0); + final Map map = mapboxManager.getMap(mapId); map.addMarkerListener( new MapboxMap.OnInfoWindowClickListener() { @Override @@ -213,21 +207,21 @@ else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { } else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { - final int offlineRegionId = args.getInt(0); + final long offlineRegionId = args.getLong(0); final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); region.download(); callbackContext.success(); } else if (ACTION_PAUSE_OFFLINE_REGION.equals(action)) { - final int offlineRegionId = args.getInt(0); + final long offlineRegionId = args.getLong(0); final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); region.pause(); callbackContext.success(); } else if (ACTION_OFFLINE_REGION_STATUS.equals(action)) { - final int offlineRegionId = args.getInt(0); + final long offlineRegionId = args.getLong(0); final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); region.getStatus(new MapboxManager.OfflineRegionStatusCallback() { @@ -354,72 +348,15 @@ private void createMap(final JSONObject options, final CallbackContext callback) cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - try { - MapView mapView = createMapView(accessToken, options); - Map.create(mapView, options, new Map.MapCreatedCallback() { - @Override - public void onCreate(final Map map) { - JSONObject resp = new JSONObject(); - try { - resp.put("id", map.getId()); - callback.success(resp); - return; - } catch (JSONException e) { - String error = "Failed to create map: " + e.getMessage(); - Log.e(LOG_TAG, error); - callback.error(error); - return; - } - } - - @Override - public void onError(String error) { - String message = "Failed to create map: " + error; - Log.e(LOG_TAG, message); - callback.error(message); - } - }); - } catch (JSONException e) { - callback.error(e.getMessage()); - } + mapboxManager.createMap(options, callback); } }); } - private MapView createMapView(String accessToken, JSONObject options) throws JSONException { - MapView mapView = new MapView(this.webView.getContext()); - mapView.setAccessToken(accessToken); - mapView.setStyleUrl(Mapbox.getStyle(options.optString("style"))); - - final JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); - final int left = (int) (retinaFactor * (margins == null || margins.isNull("left") ? 0 : margins.getInt("left"))); - final int right = (int) (retinaFactor * (margins == null || margins.isNull("right") ? 0 : margins.getInt("right"))); - final int top = (int) (retinaFactor * (margins == null || margins.isNull("top") ? 0 : margins.getInt("top"))); - final int bottom = (int) (retinaFactor * (margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"))); - - // need to do this to register a receiver which onPause later needs - mapView.onResume(); - mapView.onCreate(null); - - // position the mapView overlay - int webViewWidth = webView.getView().getWidth(); - int webViewHeight = webView.getView().getHeight(); - final FrameLayout layout = (FrameLayout) webView.getView().getParent(); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(webViewWidth - left - right, webViewHeight - top - bottom); - params.setMargins(left, top, right, bottom); - mapView.setLayoutParams(params); - - layout.addView(mapView); - - return mapView; - } - public void listOfflineRegions(final CallbackContext callback) { cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - OfflineManager offlineManager = OfflineManager.getInstance(webView.getContext()); - offlineManager.setAccessToken(accessToken); mapboxManager.loadOfflineRegions(new MapboxManager.LoadOfflineRegionsCallback() { @Override public void onList(JSONArray offlineRegions) { @@ -440,8 +377,6 @@ public void createOfflineRegion(final JSONObject options, final CallbackContext cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - OfflineManager offlineManager = OfflineManager.getInstance(webView.getContext()); - offlineManager.setAccessToken(accessToken); mapboxManager.createOfflineRegion(options, callback, new MapboxManager.OfflineRegionProgressCallback() { @Override public void onProgress(JSONObject progress) { @@ -506,35 +441,35 @@ public void onRequestPermissionResult(int commandId, String[] permissions, int[] @Override public void onStart() { - for (Map map : Map.maps()) { + for (Map map : mapboxManager.maps()) { map.getMapView().onStart(); } } @Override public void onResume(boolean multitasking) { - for (Map map : Map.maps()) { + for (Map map : mapboxManager.maps()) { map.getMapView().onResume(); } } @Override public void onPause(boolean multitasking) { - for (Map map : Map.maps()) { + for (Map map : mapboxManager.maps()) { map.getMapView().onPause(); } } @Override public void onStop() { - for (Map map : Map.maps()) { + for (Map map : mapboxManager.maps()) { map.getMapView().onStop(); } } @Override public void onDestroy() { - for (Map map : Map.maps()) { + for (Map map : mapboxManager.maps()) { map.getMapView().onDestroy(); } } @@ -564,26 +499,6 @@ private String getAccessToken() { return accessToken; } - - public static String getStyle(final String requested) { - if ("light".equalsIgnoreCase(requested)) { - return Style.LIGHT; - } else if ("dark".equalsIgnoreCase(requested)) { - return Style.DARK; - } else if ("emerald".equalsIgnoreCase(requested)) { - return Style.EMERALD; - } else if ("satellite".equalsIgnoreCase(requested)) { - return Style.SATELLITE; - // TODO not currently supported on Android - //} else if ("hybrid".equalsIgnoreCase(requested)) { - // return Style.HYBRID; - } else if ("streets".equalsIgnoreCase(requested)) { - return Style.MAPBOX_STREETS; - } else { - return requested; - } - } - } class Command { diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java index 0190ee4..835da44 100644 --- a/src/android/MapboxManager.java +++ b/src/android/MapboxManager.java @@ -1,7 +1,13 @@ package com.telerik.plugins.mapbox; +import android.widget.FrameLayout; + +import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition; @@ -13,19 +19,46 @@ import org.json.JSONObject; import java.io.UnsupportedEncodingException; +import java.util.Collection; import java.util.HashMap; class MapboxManager { - // JSON encoding/decoding public static final String JSON_CHARSET = "UTF-8"; public static final String JSON_FIELD_ID = "id"; public static final String JSON_FIELD_REGION_NAME = "name"; + public static String getStyle(final String requested) { + if ("light".equalsIgnoreCase(requested)) { + return Style.LIGHT; + } else if ("dark".equalsIgnoreCase(requested)) { + return Style.DARK; + } else if ("emerald".equalsIgnoreCase(requested)) { + return Style.EMERALD; + } else if ("satellite".equalsIgnoreCase(requested)) { + return Style.SATELLITE; + // TODO not currently supported on Android + //} else if ("hybrid".equalsIgnoreCase(requested)) { + // return Style.HYBRID; + } else if ("streets".equalsIgnoreCase(requested)) { + return Style.MAPBOX_STREETS; + } else { + return requested; + } + } + + private int ids = 0; + + private String accessToken; + private Float density; + private CordovaWebView webView; + private OfflineManager offlineManager; + private static HashMap<Long, Map> maps = new HashMap<Long, Map>(); + private HashMap<Long, OfflineRegion> regions = new HashMap<Long, OfflineRegion>(); public interface OfflineRegionStatusCallback { @@ -45,11 +78,88 @@ public interface LoadOfflineRegionsCallback { } public MapboxManager(String accessToken, Float screenDensity, CordovaWebView webView) { + this.accessToken = accessToken; this.density = screenDensity; + this.webView = webView; this.offlineManager = OfflineManager.getInstance(webView.getContext()); this.offlineManager.setAccessToken(accessToken); } + public void createMap(final JSONObject options, final CallbackContext callback) { + try { + final long id = ids++; + final MapView mapView = createMapView(this.accessToken, options); + final Map map = new Map(id, mapView, options); + + mapView.setStyleUrl(MapboxManager.getStyle(options.getString("style"))); + JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); + + positionMapView(mapView, margins); + mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(MapboxMap mMap) { + try { + map.setMapboxMap(mMap, options); + maps.put(id, map); + + JSONObject resp = new JSONObject(); + resp.put("id", id); + callback.success(resp); + } catch (JSONException e) { + removeMap(id); + callback.error("Failed to create map: " + e.getMessage()); + } + } + }); + } catch (JSONException e) { + callback.error("Failed to create map: " + e.getMessage()); + } + } + + + private MapView createMapView(String accessToken, JSONObject options) throws JSONException { + MapView mapView = new MapView(this.webView.getContext()); + mapView.setAccessToken(accessToken); + + // need to do this to register a receiver which onPause later needs + mapView.onResume(); + mapView.onCreate(null); + + return mapView; + } + + private void positionMapView(MapView mapView, JSONObject margins) throws JSONException { + PositionInfo positionInfo = new PositionInfo(margins); + int top = (int) (density * positionInfo.top); + int right = (int) (density * positionInfo.right); + int bottom = (int) (density * positionInfo.bottom); + int left = (int) (density * positionInfo.left); + int webViewWidth = webView.getView().getWidth(); + int webViewHeight = webView.getView().getHeight(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + webViewWidth - left - right, + webViewHeight - top - bottom + ); + + params.setMargins(left, top, right, bottom); + mapView.setLayoutParams(params); + + final FrameLayout layout = (FrameLayout) webView.getView().getParent(); + layout.addView(mapView); + } + + public Collection<Map> maps() { + return maps.values(); + } + + public Map getMap(long id) { + return maps.get(id); + } + + public void removeMap(long id) { + maps.remove(id); + } + public void loadOfflineRegions(final LoadOfflineRegionsCallback callback) { this.offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() { @Override @@ -98,6 +208,7 @@ public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { try { OfflineRegion region = createOfflineRegion(offlineRegion); region.setObserver(offlineRegionStatusCallback); + JSONObject response = region.getMetadata(); response.put(JSON_FIELD_ID, region.getId()); callback.success(response); @@ -105,6 +216,8 @@ public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { this.onError(e.getMessage()); } catch (UnsupportedEncodingException e) { this.onError(e.getMessage()); + } finally { + removeOfflineRegion(offlineRegion.getID()); } } @@ -136,7 +249,7 @@ public void removeOfflineRegion(long id) { } private OfflineRegionDefinition createOfflineRegionDefinition(float retinaFactor, JSONObject options) throws JSONException { - String styleURL = Mapbox.getStyle(options.getString("style")); + String styleURL = MapboxManager.getStyle(options.getString("style")); double minZoom = options.getDouble("minZoom"); double maxZoom = options.getDouble("maxZoom"); JSONObject boundsOptions = options.getJSONObject("bounds"); @@ -152,4 +265,18 @@ private OfflineRegionDefinition createOfflineRegionDefinition(float retinaFactor return new OfflineTilePyramidRegionDefinition(styleURL, bounds, minZoom, maxZoom, retinaFactor); } + + private class PositionInfo { + int top = 0; + int right = 0; + int bottom = 0; + int left = 0; + + public PositionInfo(JSONObject margins) throws JSONException { + this.top = margins == null || margins.isNull("top") ? 0 : margins.getInt("top"); + this.right = margins == null || margins.isNull("right") ? 0 : margins.getInt("right"); + this.bottom = margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"); + this.left = margins == null || margins.isNull("left") ? 0 : margins.getInt("left"); + } + } } From 3b4607fda5d84713698d5c5b1f247b7e11fcd1cb Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Sat, 26 Mar 2016 18:23:47 -0700 Subject: [PATCH 29/37] fixing chicken and egg issue with map and mapview creation. --- src/android/Map.java | 88 ++++++++++++++++------------------ src/android/Mapbox.java | 6 +-- src/android/MapboxManager.java | 59 +++++++++++------------ 3 files changed, 70 insertions(+), 83 deletions(-) diff --git a/src/android/Map.java b/src/android/Map.java index 4b13579..a50dd1f 100644 --- a/src/android/Map.java +++ b/src/android/Map.java @@ -1,25 +1,63 @@ package com.telerik.plugins.mapbox; +import android.support.annotation.Nullable; + import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; -import com.mapbox.mapboxsdk.maps.UiSettings; +import com.mapbox.mapboxsdk.maps.MapboxMapOptions; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class Map { + public static MapboxMapOptions createMapboxMapOptions(JSONObject options) throws JSONException { + MapboxMapOptions opts = new MapboxMapOptions(); + opts.styleUrl(MapboxManager.getStyle(options.getString("style"))); + opts.attributionEnabled(options.isNull("hideAttribution") || !options.getBoolean("hideAttribution")); + opts.logoEnabled(options.isNull("hideLogo") || options.getBoolean("hideLogo")); + opts.locationEnabled(!options.isNull("showUserLocation") && options.getBoolean("showUserLocation")); + opts.camera(Map.getCameraPostion(options, null)); + opts.compassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); + opts.rotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); + opts.scrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); + opts.zoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); + opts.tiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); + return opts; + } + + public static CameraPosition getCameraPostion(JSONObject options, @Nullable CameraPosition start) throws JSONException { + CameraPosition.Builder builder = new CameraPosition.Builder(start); + + if (!options.isNull("zoom")) { + builder.zoom(options.getDouble("zoom")); + } + + if (!options.isNull("center")) { + JSONArray center = options.getJSONArray("center"); + double lng = center.getDouble(0); + double lat = center.getDouble(1); + builder.target(new LatLng(lat, lng)); + } + + // TODO: Bearing + + // TODO: Pitch + + return builder.build(); + } + private long id; private MapView mapView; private MapboxMap mapboxMap; - public Map(long id, final MapView mapView, final JSONObject options) throws JSONException { + public Map(long id, final MapView mapView) { this.id = id; this.mapView = mapView; } @@ -32,32 +70,8 @@ public MapView getMapView() { return this.mapView; } - public void setMapboxMap(MapboxMap mMap, JSONObject options) throws JSONException { + public void setMapboxMap(MapboxMap mMap) { this.mapboxMap = mMap; - UiSettings uiSettings = mMap.getUiSettings(); - uiSettings.setCompassEnabled(options.isNull("hideCompass") || !options.getBoolean("hideCompass")); - uiSettings.setRotateGesturesEnabled(options.isNull("disableRotation") || !options.getBoolean("disableRotation")); - uiSettings.setScrollGesturesEnabled(options.isNull("disableScroll") || !options.getBoolean("disableScroll")); - uiSettings.setZoomGesturesEnabled(options.isNull("disableZoom") || !options.getBoolean("disableZoom")); - uiSettings.setTiltGesturesEnabled(options.isNull("disableTilt") || !options.getBoolean("disableTilt")); - - if (!options.isNull("hideAttribution") && options.getBoolean("hideAttribution")) { - uiSettings.setAttributionMargins(-300, 0, 0, 0); - } - - if (!options.isNull("hideLogo") && options.getBoolean("hideLogo")) { - uiSettings.setLogoMargins(-300, 0, 0, 0); - } - - if (!options.isNull("showUserLocation")) { - this.showUserLocation(options.getBoolean("showUserLocation")); - } - - if (options.has("markers")) { - this.addMarkers(options.getJSONArray("markers")); - } - - this.jumpTo(options); } public MapboxMap getMapboxMap() { @@ -98,25 +112,7 @@ public void setZoom(double zoom) { } public void jumpTo(JSONObject options) throws JSONException { - CameraPosition current = mapboxMap.getCameraPosition(); - CameraPosition.Builder builder = new CameraPosition.Builder(current); - - if (!options.isNull("zoom")) { - builder.zoom(options.getDouble("zoom")); - } - - if (!options.isNull("center")) { - JSONArray center = options.getJSONArray("center"); - double lng = center.getDouble(0); - double lat = center.getDouble(1); - builder.target(new LatLng(lat, lng)); - } - - // TODO: Bearing - - // TODO: Pitch - - CameraPosition position = builder.build(); + CameraPosition position = Map.getCameraPostion(options, mapboxMap.getCameraPosition()); mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(position)); } diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 8b2b76d..1e14644 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -8,13 +8,9 @@ import android.support.v4.app.ActivityCompat; import android.util.DisplayMetrics; import android.util.Log; -import android.widget.FrameLayout; import com.mapbox.mapboxsdk.annotations.Marker; -import com.mapbox.mapboxsdk.constants.Style; -import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; -import com.mapbox.mapboxsdk.offline.OfflineManager; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; @@ -345,7 +341,7 @@ public void run() { } private void createMap(final JSONObject options, final CallbackContext callback) { - cordova.getActivity().runOnUiThread(new Runnable() { + cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { mapboxManager.createMap(options, callback); diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java index 835da44..8bae306 100644 --- a/src/android/MapboxManager.java +++ b/src/android/MapboxManager.java @@ -7,6 +7,7 @@ import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.MapboxMapOptions; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.offline.OfflineRegionDefinition; @@ -87,26 +88,28 @@ public MapboxManager(String accessToken, Float screenDensity, CordovaWebView web public void createMap(final JSONObject options, final CallbackContext callback) { try { - final long id = ids++; - final MapView mapView = createMapView(this.accessToken, options); - final Map map = new Map(id, mapView, options); + PositionInfo position = new PositionInfo(options.isNull("margins") ? null : options.getJSONObject("margins"), density); + MapView mapView = createMapView(Map.createMapboxMapOptions(options), position); - mapView.setStyleUrl(MapboxManager.getStyle(options.getString("style"))); - JSONObject margins = options.isNull("margins") ? null : options.getJSONObject("margins"); - - positionMapView(mapView, margins); + long id = ids++; + final Map map = new Map(id, mapView); mapView.getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(MapboxMap mMap) { try { - map.setMapboxMap(mMap, options); - maps.put(id, map); + map.setMapboxMap(mMap); + + if (options.has("markers")) { + map.addMarkers(options.getJSONArray("markers")); + } + + maps.put(map.getId(), map); JSONObject resp = new JSONObject(); - resp.put("id", id); + resp.put("id", map.getId()); callback.success(resp); } catch (JSONException e) { - removeMap(id); + removeMap(map.getId()); callback.error("Failed to create map: " + e.getMessage()); } } @@ -116,36 +119,28 @@ public void onMapReady(MapboxMap mMap) { } } - - private MapView createMapView(String accessToken, JSONObject options) throws JSONException { - MapView mapView = new MapView(this.webView.getContext()); - mapView.setAccessToken(accessToken); + private MapView createMapView(MapboxMapOptions options, PositionInfo position) throws JSONException { + options.accessToken(accessToken); + MapView mapView = new MapView(this.webView.getContext(), options); // need to do this to register a receiver which onPause later needs mapView.onResume(); mapView.onCreate(null); - return mapView; - } - - private void positionMapView(MapView mapView, JSONObject margins) throws JSONException { - PositionInfo positionInfo = new PositionInfo(margins); - int top = (int) (density * positionInfo.top); - int right = (int) (density * positionInfo.right); - int bottom = (int) (density * positionInfo.bottom); - int left = (int) (density * positionInfo.left); int webViewWidth = webView.getView().getWidth(); int webViewHeight = webView.getView().getHeight(); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - webViewWidth - left - right, - webViewHeight - top - bottom + webViewWidth - position.left - position.right, + webViewHeight - position.top - position.bottom ); - params.setMargins(left, top, right, bottom); + params.setMargins(position.left, position.top, position.right, position.bottom); mapView.setLayoutParams(params); final FrameLayout layout = (FrameLayout) webView.getView().getParent(); layout.addView(mapView); + + return mapView; } public Collection<Map> maps() { @@ -272,11 +267,11 @@ private class PositionInfo { int bottom = 0; int left = 0; - public PositionInfo(JSONObject margins) throws JSONException { - this.top = margins == null || margins.isNull("top") ? 0 : margins.getInt("top"); - this.right = margins == null || margins.isNull("right") ? 0 : margins.getInt("right"); - this.bottom = margins == null || margins.isNull("bottom") ? 0 : margins.getInt("bottom"); - this.left = margins == null || margins.isNull("left") ? 0 : margins.getInt("left"); + public PositionInfo(JSONObject margins, float density) throws JSONException { + this.top = margins == null || margins.isNull("top") ? 0 : (int) (density * margins.getInt("top")); + this.right = margins == null || margins.isNull("right") ? 0 : (int) (density * margins.getInt("right")); + this.bottom = margins == null || margins.isNull("bottom") ? 0 : (int) (density * margins.getInt("bottom")); + this.left = margins == null || margins.isNull("left") ? 0 : (int) (density * margins.getInt("left")); } } } From d9031e168512fd5d8547b65b33769258c3592062 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Sat, 26 Mar 2016 19:50:27 -0700 Subject: [PATCH 30/37] Added initial geojson support with js api that matches mapbox-gl-js. --- plugin.xml | 2 + src/android/FeatureManager.java | 321 ++++++++++++++++++++++++++++++++ src/android/Map.java | 63 +++++++ src/android/Mapbox.java | 36 +++- src/android/MapboxManager.java | 1 + src/android/mapbox.gradle | 1 + www/map-instance.js | 114 ++++++++++-- 7 files changed, 518 insertions(+), 20 deletions(-) create mode 100644 src/android/FeatureManager.java diff --git a/plugin.xml b/plugin.xml index 3d0516e..035c608 100755 --- a/plugin.xml +++ b/plugin.xml @@ -56,10 +56,12 @@ </config-file> <framework src="src/android/mapbox.gradle" custom="true" type="gradleReference"/> + <framework src="com.android.support:appcompat-v7:22.+" /> <source-file src="src/android/Mapbox.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/MapboxManager.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/OfflineRegion.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/Map.java" target-dir="src/com/telerik/plugins/mapbox"/> + <source-file src="src/android/FeatureManager.java" target-dir="src/com/telerik/plugins/mapbox"/> <!-- This leads to trouble in AppBuilder when compiling for Cordova-Android 4 --> <!--source-file src="src/android/res/values/mapboxstrings.xml" target-dir="res/values" /> diff --git a/src/android/FeatureManager.java b/src/android/FeatureManager.java new file mode 100644 index 0000000..e1b9575 --- /dev/null +++ b/src/android/FeatureManager.java @@ -0,0 +1,321 @@ +package com.telerik.plugins.mapbox; + +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.DisplayMetrics; +import android.util.Log; + +import com.cocoahero.android.geojson.Feature; +import com.cocoahero.android.geojson.FeatureCollection; +import com.cocoahero.android.geojson.GeoJSON; +import com.cocoahero.android.geojson.GeoJSONObject; +import com.cocoahero.android.geojson.Geometry; +import com.cocoahero.android.geojson.GeometryCollection; +import com.cocoahero.android.geojson.LineString; +import com.cocoahero.android.geojson.MultiLineString; +import com.cocoahero.android.geojson.MultiPoint; +import com.cocoahero.android.geojson.MultiPolygon; +import com.cocoahero.android.geojson.Point; +import com.cocoahero.android.geojson.Polygon; +import com.cocoahero.android.geojson.Position; +import com.cocoahero.android.geojson.Ring; +import com.mapbox.mapboxsdk.annotations.Icon; +import com.mapbox.mapboxsdk.annotations.IconFactory; +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.annotations.MarkerOptions; +import com.mapbox.mapboxsdk.annotations.PolygonOptions; +import com.mapbox.mapboxsdk.annotations.PolylineOptions; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapboxMap; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +interface DataSource { + List<Feature> getFills(); + List<Feature> getLines(); + List<Feature> getSymbols(); +} + +class FeatureManager { + private String TAG = "FeatureManager"; + + protected Context ctx; + + protected IconFactory iconFactory; + + protected MapboxMap mapboxMap; + + protected HashMap<String, DataSource> sources = new HashMap<String, DataSource>(); + + protected HashMap<Long, Feature> markerIndex = new HashMap<Long, Feature>(); + + public FeatureManager(Context ctx, MapboxMap mapboxMap) { + this.ctx = ctx; + this.mapboxMap = mapboxMap; + this.iconFactory = IconFactory.getInstance(ctx); + } + + public boolean hasSource(String name) { + return this.sources.containsKey(name); + } + + public boolean hasMarkerFeature(Long id) { + return this.markerIndex.containsKey(id); + } + + public Feature getMarkerFeature(Long id) { + if (this.hasMarkerFeature(id)) { + return this.markerIndex.get(id); + } else { + return null; + } + } + + public Feature getMarkerFeature(Marker marker) { + return this.getMarkerFeature(marker.getId()); + } + + public void addGeoJSONSource(String name, String json) throws JSONException { + this.sources.put(name, new GeoJSONSource(name).addGeoJSON(json)); + } + + public void addGeoJSONSource(String name, JSONObject json) throws JSONException { + this.sources.put(name, new GeoJSONSource(name).addGeoJSON(json)); + } + + public void addFillLayer(String id, String source, JSONObject layer) { + for (Feature feature : this.sources.get(source).getFills()) { + ArrayList<LatLng> latLngs = new ArrayList<LatLng>(); + for (Ring ring : ((Polygon) feature.getGeometry()).getRings()) { + for (Position position : ring.getPositions()) { + latLngs.add(new LatLng(position.getLatitude(), position.getLongitude())); + } + } + LatLng[] points = latLngs.toArray(new LatLng[latLngs.size()]); + + PolygonOptions polygon = new PolygonOptions() + // TODO: Need to use values in layer to set options. + .add(points); + + this.mapboxMap.addPolygon(polygon); + } + } + + public void addLineLayer(String id, String source, JSONObject layer) { + for (Feature feature : this.sources.get(source).getLines()) { + ArrayList<LatLng> latLngs = new ArrayList<LatLng>(); + for (Position position : ((LineString) feature.getGeometry()).getPositions()) { + latLngs.add(new LatLng(position.getLatitude(), position.getLongitude())); + } + LatLng[] points = latLngs.toArray(new LatLng[latLngs.size()]); + + PolylineOptions line = new PolylineOptions() + // TODO: Need to use values in layer to set options. + .add(points); + + this.mapboxMap.addPolyline(line); + } + } + + public void addMarkerLayer(String id, String source, JSONObject layer) { + List<Feature> features = this.sources.get(source).getSymbols(); + + for (Feature feature : features) { + MarkerOptions options = this.createMarker(feature, layer); + Marker marker = this.mapboxMap.addMarker(options); + this.markerIndex.put(marker.getId(), feature); + } + } + + protected MarkerOptions createMarker(Feature feature, JSONObject style) { + final JSONObject properties = feature.getProperties(); + final Position p = ((Point) feature.getGeometry()).getPosition(); + final MarkerOptions marker = new MarkerOptions() + .position(new LatLng(p.getLatitude(), p.getLongitude())); + + try { + final String textField = style.getJSONObject("layout").getString("text-field"); + marker.title(textField.replace("{title}", properties.getString("title"))); + } catch (JSONException e) { + Log.w(TAG, "Error parsing Style JSON properties: " + e.getMessage()); + } + + try { + marker.snippet(properties.getString("description")); + } catch (JSONException e) { + Log.w(TAG, "Error parsing Style JSON properties: " + e.getMessage()); + } + + try { + final String iconImage = style.getJSONObject("layout").getString("icon-image"); + final String markerSymbol = properties.getString("marker-symbol"); + final URI uri = new URI(iconImage.replace("{marker-symbol}", markerSymbol)); + final Icon icon = this.loadIcon(uri); + if (icon != null) { + marker.icon(icon); + } + } catch (JSONException e) { + Log.w(TAG, "Error parsing Style JSON properties: " + e.getMessage()); + } catch (URISyntaxException e) { + Log.w(TAG, "Invalid icon-image URI: " + e.getMessage()); + } catch (IOException e) { + Log.w(TAG, "Error loading file: " + e.getMessage()); + } + + return marker; + } + + protected Icon loadIcon(URI uri) throws IOException { + Icon icon; + + if (uri.getScheme().equals("asset")) { + // Stripping leading '/'. + String path = uri.getPath().substring(1); + icon = iconFactory.fromBitmap(this.loadScaledBitmap(path)); + } + else { + icon = iconFactory.fromPath(uri.getPath()); + } + + return icon; + } + + protected Bitmap loadScaledBitmap(String path) throws IOException { + AssetManager am = ctx.getAssets(); + InputStream image = am.open(path); + Bitmap bmp = BitmapFactory.decodeStream(image); + DisplayMetrics dm = ctx.getResources().getDisplayMetrics(); + bmp.setDensity(dm.densityDpi); + return bmp; + } + + private class GeoJSONSource implements DataSource { + private String TAG = "GeoJSONSource"; + + protected String name; + + protected ArrayList<Feature> polygons = new ArrayList<Feature>(); + protected ArrayList<Feature> polylines = new ArrayList<Feature>(); + protected ArrayList<Feature> markers = new ArrayList<Feature>(); + + public GeoJSONSource(String name) { + this.name = name; + } + + @Override + public List<Feature> getFills() { + return polygons; + } + + @Override + public List<Feature> getLines() { + return polylines; + } + + @Override + public List<Feature> getSymbols() { + return markers; + } + + public GeoJSONSource addGeoJSON(String json) throws JSONException { + return this.addGeoJSON(GeoJSON.parse(json)); + } + + public GeoJSONSource addGeoJSON(JSONObject json) throws JSONException { + return this.addGeoJSON(GeoJSON.parse(json)); + } + + public GeoJSONSource addGeoJSON(GeoJSONObject geojson) { + if (geojson instanceof FeatureCollection) { + FeatureCollection fc = (FeatureCollection) geojson; + for (Feature f : fc.getFeatures()) { + this.addGeoJSON(f); + } + } else if (geojson instanceof Feature) { + Feature feature = (Feature) geojson; + this.addFeature(feature); + } else { + Log.e(TAG, "GeoJSON must be FeatureCollection or Feature."); + } + + return this; + } + + /** + * TODO: Handling of Complex geometries (GeometryCollection & Multi*) needs improvement when Mapbox SDK supports them better. + * TODO: Recursive processing of GeoJSON could probably be optimized. + * + * @param feature + */ + public void addFeature(Feature feature) { + Geometry geom = feature.getGeometry(); + + if (geom instanceof GeometryCollection) { + GeometryCollection gc = (GeometryCollection) geom; + for (Geometry g : gc.getGeometries()) { + this.addFeature(feature, g); + } + } + else if (geom instanceof MultiPolygon) { + MultiPolygon multiPoly = (MultiPolygon) geom; + for (Polygon poly : multiPoly.getPolygons()) { + this.addFeature(feature, poly); + } + } + else if (geom instanceof MultiLineString) { + MultiLineString multiLine = (MultiLineString) geom; + for (LineString ls : multiLine.getLineStrings()) { + this.addFeature(feature, ls); + } + } + else if (geom instanceof MultiPoint) { + MultiPoint multiPoint = (MultiPoint) geom; + for (Position p : multiPoint.getPositions()) { + this.addFeature(feature, new Point(p)); + } + } + else { + this.addFeature(feature, geom); + } + } + + protected void addFeature(Feature feature, Geometry geom) { + if (geom instanceof MultiPolygon || geom instanceof MultiLineString || geom instanceof MultiPoint) { + feature.setGeometry(geom); + Feature f = new Feature(geom); + f.setProperties(feature.getProperties()); + this.addFeature(f); + } + else if (geom instanceof Polygon) { + this.polygons.add(this.createFeature(feature.getProperties(), geom)); + } + else if (geom instanceof LineString) { + this.polylines.add(this.createFeature(feature.getProperties(), geom)); + } + else if (geom instanceof Point) { + this.markers.add(this.createFeature(feature.getProperties(), geom)); + } else { + // Unsupported geometry type. + Log.e(TAG, String.format("Unsupported GeoJSON geometry type: %s.", geom.getType())); + } + } + + protected Feature createFeature(JSONObject properties, Geometry geom) { + Feature f = new Feature(); + f.setGeometry(geom); + f.setProperties(properties); + return f; + } + } +} \ No newline at end of file diff --git a/src/android/Map.java b/src/android/Map.java index a50dd1f..a1d8434 100644 --- a/src/android/Map.java +++ b/src/android/Map.java @@ -57,6 +57,8 @@ public static CameraPosition getCameraPostion(JSONObject options, @Nullable Came private MapboxMap mapboxMap; + private FeatureManager features; + public Map(long id, final MapView mapView) { this.id = id; this.mapView = mapView; @@ -74,6 +76,10 @@ public void setMapboxMap(MapboxMap mMap) { this.mapboxMap = mMap; } + public void setFeatureManager(FeatureManager featureManager) { + this.features = featureManager; + } + public MapboxMap getMapboxMap() { return this.mapboxMap; } @@ -136,4 +142,61 @@ public void addMarkerListener(MapboxMap.OnInfoWindowClickListener listener) { public void showUserLocation(boolean enabled) { mapboxMap.setMyLocationEnabled(enabled); } + + public void addSource(String name, JSONObject source) throws UnsupportedTypeException, JSONException { + final String sourceType = source.getString("type"); + + if (sourceType.equals("geojson") && source.has("data")) { + final JSONObject data = source.getJSONObject("data"); + features.addGeoJSONSource(name, data); + } else { + throw new UnsupportedTypeException("source:" + sourceType); + } + } + + public void addLayer(JSONObject layer) throws UnknownSourceException, UnsupportedTypeException, JSONException { + final String layerType = layer.getString("type"); + final String source = layer.getString("source"); + final String id = layer.getString("id"); + + if (features.hasSource(source)) { + if (layerType.equals("fill")) { + features.addFillLayer(id, source, layer); + } else if (layerType.equals("line")) { + features.addLineLayer(id, source, layer); + } else if (layerType.equals("symbol")) { + features.addMarkerLayer(id, source, layer); + } else { + throw new UnsupportedTypeException("layer:" + layerType); + } + } else { + throw new UnknownSourceException(source); + } + } } + +class UnsupportedTypeException extends Exception { + String type; + + public UnsupportedTypeException(String type) { + this.type = type; + } + + @Override + public String getMessage() { + return "Unsupported type: " + this.type; + } +} + +class UnknownSourceException extends Exception { + String source; + + public UnknownSourceException(String source) { + this.source = source; + } + + @Override + public String getMessage() { + return "Unknown source: " + this.source; + } +} \ No newline at end of file diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 1e14644..75460c8 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -51,6 +51,8 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_ADD_MARKER_CALLBACK = "addMarkerCallback"; private static final String ACTION_ADD_POLYGON = "addPolygon"; private static final String ACTION_ADD_GEOJSON = "addGeoJSON"; + private static final String ACTION_ADD_SOURCE = "addSource"; + private static final String ACTION_ADD_LAYER = "addLayer"; private static final String ACTION_GET_ZOOMLEVEL = "getZoomLevel"; private static final String ACTION_SET_ZOOMLEVEL = "setZoomLevel"; private static final String ACTION_GET_CENTER = "getCenter"; @@ -69,7 +71,7 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) { @Override public boolean execute(final String action, final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { - Command command = Command.create(action, args, callbackContext); + Command command = Command.create(action, args, callbackContext); return execute(command); } @@ -95,6 +97,7 @@ else if (ACTION_SHOW_USER_LOCATION.equals(action)) { @Override public void run() { map.showUserLocation(enabled); + callbackContext.success(); } }); } @@ -191,6 +194,36 @@ public boolean onInfoWindowClick(Marker marker) { ); } + else if (ACTION_ADD_SOURCE.equals(action)) { + final long mapId = args.getLong(0); + final String name = args.getString(1); + final JSONObject source = args.getJSONObject(2); + final Map map = mapboxManager.getMap(mapId); + try { + map.addSource(name, source); + callbackContext.success(); + } catch (Exception e) { + callbackContext.error("Unable to add data source to map: " + e.getMessage()); + } + } + + else if (ACTION_ADD_LAYER.equals(action)) { + final long mapId = args.getLong(0); + final JSONObject layer = args.getJSONObject(1); + final Map map = mapboxManager.getMap(mapId); + cordova.getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + try { + map.addLayer(layer); + callbackContext.success(); + } catch (Exception e) { + callbackContext.error("Unable to add layer to map: " + e.getMessage()); + } + } + }); + } + else if (ACTION_LIST_OFFLINE_REGIONS.equals(action)) { this.listOfflineRegions(callbackContext); } @@ -442,7 +475,6 @@ public void onStart() { } } - @Override public void onResume(boolean multitasking) { for (Map map : mapboxManager.maps()) { map.getMapView().onResume(); diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java index 8bae306..0d4d187 100644 --- a/src/android/MapboxManager.java +++ b/src/android/MapboxManager.java @@ -98,6 +98,7 @@ public void createMap(final JSONObject options, final CallbackContext callback) public void onMapReady(MapboxMap mMap) { try { map.setMapboxMap(mMap); + map.setFeatureManager(new FeatureManager(webView.getContext(), mMap)); if (options.has("markers")) { map.addMarkers(options.getJSONArray("markers")); diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index 4580af0..f67c263 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -5,6 +5,7 @@ repositories { } dependencies { + compile 'com.cocoahero.android:geojson:1.0.1@jar' compile 'com.android.support:appcompat-v7:23.0.1' compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-rc.1@aar'){ transitive=true diff --git a/www/map-instance.js b/www/map-instance.js index 75c2074..95daa93 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -40,44 +40,122 @@ MapInstance.prototype._execAfterLoad = function () { }.bind(this)); }; -MapInstance.prototype.jumpTo = function (options, successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "jumpTo", [options]); +MapInstance.prototype.jumpTo = function (options, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "jumpTo", + [options] + ); }; -MapInstance.prototype.setCenter = function (options, successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "setCenter", [options]); +MapInstance.prototype.setCenter = function (options, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "setCenter", + [options] + ); }; -MapInstance.prototype.getCenter = function (successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "getCenter"); +MapInstance.prototype.getCenter = function (callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "getCenter" + ); }; -MapInstance.prototype.addMarkers = function (options, successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "addMarkers", [options]); +MapInstance.prototype.addMarkers = function (options, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "addMarkers", + [options] + ); }; MapInstance.prototype.addMarkerCallback = function (callback) { this._execAfterLoad(callback, null, "addMarkerCallback"); }; -MapInstance.prototype.setCenter = function (center, successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "setCenter", [center]); +MapInstance.prototype.setCenter = function (center, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "setCenter", + [center] + ); }; -MapInstance.prototype.getCenter = function (successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "getCenter"); +MapInstance.prototype.getCenter = function (callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "getCenter" + ); }; -MapInstance.prototype.getZoomLevel = function (successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "getZoomLevel"); +MapInstance.prototype.getZoomLevel = function (callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "getZoomLevel" + ); }; -MapInstance.prototype.setZoomLevel = function (zoom, successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "setZoomLevel", [zoom]); +MapInstance.prototype.setZoomLevel = function (zoom, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "setZoomLevel", + [zoom] + ); }; -MapInstance.prototype.showUserLocation = function (enabled, successCallback, errorCallback) { - this._execAfterLoad(successCallback, errorCallback, "showUserLocation", [enabled]); +MapInstance.prototype.showUserLocation = function (enabled, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "showUserLocation", + [enabled] + ); }; +MapInstance.prototype.addSource = function (name, source, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "addSource", + [name, source] + ); +}; + +MapInstance.prototype.addLayer = function (layer, callback) { + var result = wrapCallback(callback); + this._execAfterLoad( + result.success, + result.error, + "addLayer", + [layer] + ); +}; + +function wrapCallback(callback) { + return { + success: function (response) { callback(null, response); }, + error: function (err) { callback(err); } + }; +} + module.exports = MapInstance; From b8561db9d74a693257cd59900a7d59d6fd82b9a4 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Sun, 27 Mar 2016 01:08:05 -0700 Subject: [PATCH 31/37] Added promise support to API. --- plugin.xml | 1 + src/android/Mapbox.java | 19 +++-- www/events-mixin.js | 96 +++++++++++------------ www/map-instance.js | 137 ++++++-------------------------- www/mapbox-plugin-api-mixin.js | 57 ++++++++++++++ www/offline-region.js | 138 +++++++++++++-------------------- 6 files changed, 195 insertions(+), 253 deletions(-) create mode 100644 www/mapbox-plugin-api-mixin.js diff --git a/plugin.xml b/plugin.xml index 035c608..c132f98 100755 --- a/plugin.xml +++ b/plugin.xml @@ -25,6 +25,7 @@ </engines> <js-module src="www/mixin.js" name="mixin" /> + <js-module src="www/mapbox-plugin-api-mixin.js" name="mapbox-plugin-api-mixin" /> <js-module src="www/events-mixin.js" name="events-mixin" /> <js-module src="www/map-instance.js" name="map-instance" /> <js-module src="www/offline-region.js" name="offline-region" /> diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 75460c8..212f4c6 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -170,28 +170,31 @@ else if (ACTION_ADD_MARKERS.equals(action)) { else if (ACTION_ADD_MARKER_CALLBACK.equals(action)) { final long mapId = args.getLong(0); + final CallbackContext markerCallback = new CallbackContext(args.getString(1), this.webView); final Map map = mapboxManager.getMap(mapId); map.addMarkerListener( new MapboxMap.OnInfoWindowClickListener() { @Override public boolean onInfoWindowClick(Marker marker) { try { - callbackContext.success( - new JSONObject() - .put("title", marker.getTitle()) - .put("subtitle", marker.getSnippet()) - .put("lat", marker.getPosition().getLatitude()) - .put("lng", marker.getPosition().getLongitude()) - ); + JSONObject markerInfo = new JSONObject() + .put("title", marker.getTitle()) + .put("subtitle", marker.getSnippet()) + .put("lat", marker.getPosition().getLatitude()) + .put("lng", marker.getPosition().getLongitude()); + PluginResult result = new PluginResult(PluginResult.Status.OK, markerInfo); + result.setKeepCallback(true); + markerCallback.sendPluginResult(result); return true; } catch (JSONException e) { String message = "Error in callback of " + ACTION_ADD_MARKER_CALLBACK + ": " + e.getMessage(); - callbackContext.error(message); + markerCallback.error(message); return false; } } } ); + callbackContext.success(); } else if (ACTION_ADD_SOURCE.equals(action)) { diff --git a/www/events-mixin.js b/www/events-mixin.js index 09f0e32..585a9c2 100644 --- a/www/events-mixin.js +++ b/www/events-mixin.js @@ -2,55 +2,53 @@ var Mixin = require('./mixin'), channel = require("cordova/channel"), channelIds = 0; -module.exports = Mixin({ - initEvents: function (prefix) { - if (!this._channelPrefix) { - this._channelPrefix = prefix + "." + (channelIds++); - } - }, +module.exports = function (prefix, target) { + var _channelPrefix = prefix + "." + (channelIds++); - _prefix: function (type) { - return this._channelPrefix + "." + type; - }, + function _prefix(type) { + return _channelPrefix + "." + type; + } - _channel: function (type, sticky) { - var t = this._prefix(type); - if (!this._channels) { - this._channels = {}; + return Mixin({ + _channel: function (type, sticky) { + var t = _prefix(type); + if (!this._channels) { + this._channels = {}; + } + if (sticky !== undefined) { + this._channels[t] = sticky ? + channel.createSticky(t) : + channel.create(t); + } + return this._channels[t]; + }, + + createChannel: function (type) { + this._channel(type, false); + }, + + createStickyChannel: function (type) { + this._channel(type, true); + }, + + once: function (type, listener) { + var onEvent = function (e) { + listener(e); + this.off(type, onEvent); + }; + this.on(type, onEvent.bind(this)); + }, + + on: function (type, listener) { + this._channel(type).subscribe(listener); + }, + + off: function (type, listener) { + this._channel(type).unsubscribe(listener); + }, + + fire: function (type, e) { + this._channel(type).fire(e); } - if (sticky !== undefined) { - this._channels[t] = sticky ? - channel.createSticky(t) : - channel.create(t); - } - return this._channels[t]; - }, - - createChannel: function (type) { - this._channel(type, false); - }, - - createStickyChannel: function (type) { - this._channel(type, true); - }, - - once: function (type, listener) { - var onEvent = function (e) { - listener(e); - this.off(type, onEvent); - }; - this.on(type, onEvent.bind(this)); - }, - - on: function (type, listener) { - this._channel(type).subscribe(listener); - }, - - off: function (type, listener) { - this._channel(type).unsubscribe(listener); - }, - - fire: function (type, e) { - this._channel(type).fire(e); - } -}); + })(target); +}; diff --git a/www/map-instance.js b/www/map-instance.js index 95daa93..f2aab1a 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -1,161 +1,72 @@ var exec = require("cordova/exec"), + MapboxPluginAPI = require("./mapbox-plugin-api-mixin"), EventsMixin = require("./events-mixin"); function MapInstance(options) { - var onLoad = _onLoad.bind(this), + var onLoad = this._onLoad.bind(this), onError = this._error.bind(this); - this._error = onError; - - this.initEvents("Mapbox.MapInstance"); this.createStickyChannel("load"); - exec(onLoad, this._error, "Mapbox", "createMap", [options]); - - function _onLoad(resp) { - this._id = resp.id; - this.loaded = true; - - this.fire("load", {map: this}); - } + exec(onLoad, onError, "Mapbox", "createMap", [options]); } -EventsMixin(MapInstance.prototype); - -MapInstance.prototype._error = function (err) { - var error = new Error("Map error (ID: " + this._id + "): " + err); - console.warn("throwing MapError: ", error); - throw error; -}; - -MapInstance.prototype._exec = function (successCallback, errorCallback, method, args) { - args = [this._id].concat(args || []); - exec(successCallback, errorCallback, "Mapbox", method, args); -}; - -MapInstance.prototype._execAfterLoad = function () { - var args = arguments; - this.once('load', function (map) { - this._exec.apply(this, args); - }.bind(this)); -}; +MapboxPluginAPI('MapInstance', MapInstance.prototype); +EventsMixin('MapInstance', MapInstance.prototype); MapInstance.prototype.jumpTo = function (options, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "jumpTo", - [options] - ); + return this._execAfterLoad(callback, "jumpTo", [options]); }; MapInstance.prototype.setCenter = function (options, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "setCenter", - [options] - ); + return this._execAfterLoad(callback, "setCenter", [options]); }; MapInstance.prototype.getCenter = function (callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "getCenter" - ); + return this._execAfterLoad(callback, "getCenter"); }; MapInstance.prototype.addMarkers = function (options, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "addMarkers", - [options] - ); + return this._execAfterLoad(callback, "addMarkers", [options]); }; -MapInstance.prototype.addMarkerCallback = function (callback) { - this._execAfterLoad(callback, null, "addMarkerCallback"); +MapInstance.prototype.addMarkerCallback = function (markerCallback, callback) { + return this._execAfterLoad(callback, "addMarkerCallback", [markerCallback]); }; MapInstance.prototype.setCenter = function (center, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "setCenter", - [center] - ); + return this._execAfterLoad(callback, "setCenter", [center]); }; MapInstance.prototype.getCenter = function (callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "getCenter" - ); + return this._execAfterLoad(callback, "getCenter"); }; MapInstance.prototype.getZoomLevel = function (callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "getZoomLevel" - ); + return this._execAfterLoad(callback, "getZoomLevel"); }; MapInstance.prototype.setZoomLevel = function (zoom, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "setZoomLevel", - [zoom] - ); + return this._execAfterLoad(callback, "setZoomLevel", [zoom]); }; MapInstance.prototype.showUserLocation = function (enabled, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "showUserLocation", - [enabled] - ); + return this._execAfterLoad(callback, "showUserLocation", [enabled]); }; MapInstance.prototype.addSource = function (name, source, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "addSource", - [name, source] - ); + return this._execAfterLoad(callback, "addSource", [name, source]); }; MapInstance.prototype.addLayer = function (layer, callback) { - var result = wrapCallback(callback); - this._execAfterLoad( - result.success, - result.error, - "addLayer", - [layer] - ); + return this._execAfterLoad(callback, "addLayer", [layer]); }; -function wrapCallback(callback) { - return { - success: function (response) { callback(null, response); }, - error: function (err) { callback(err); } - }; -} +MapInstance.prototype._onLoad = function (resp) { + this._id = resp.id; + this.loaded = true; + + this.fire("load", {map: this}); +}; module.exports = MapInstance; diff --git a/www/mapbox-plugin-api-mixin.js b/www/mapbox-plugin-api-mixin.js new file mode 100644 index 0000000..46b38fd --- /dev/null +++ b/www/mapbox-plugin-api-mixin.js @@ -0,0 +1,57 @@ +var cordova = require("cordova"), + exec = require("cordova/exec"), + Mixin = require('./mixin'); + +module.exports = function (type, target) { + var _mapboxType = type; + + return Mixin({ + _registerCallback: function (name, success, fail) { + var callbackId = ["Mapbox", type, name, cordova.callbackId++].join('.'); + + success = success || function () { console.log(callbackId + "() success!", arguments); }; + fail = fail || function () { console.log(callbackId + "() fail :(", arguments); }; + + cordova.callbacks[callbackId] = {success: success, fail: fail}; + return callbackId; + }, + + _error: function (err) { + var error = new Error("MapboxPlugin error (" + _mapboxType + ":" + this._id + "): " + err); + console.warn("throwing MapboxPluginError: ", error); + throw error; + }, + + _execAfterLoad: function () { + var args = Array.prototype.slice.call(arguments), + once = this.once.bind(this), + onLoad = function () { + return this._exec.apply(this, args); + }.bind(this); + + return new Promise(function (resolve, reject) { + once('load', function (obj) { + onLoad().then(resolve, reject); + }); + }); + }, + + _exec: function (callback, method, args) { + args = [this._id].concat(args || []); + callback = callback || function (err, response) {}; + return new Promise(function (resolve, reject) { + exec( + function onSuccess(response) { + callback(null, response); + resolve(response); + }, + function onError(error) { + callback(error); + reject(error); + }, + "Mapbox", method, args + ); + }); + } + })(target); +}; diff --git a/www/offline-region.js b/www/offline-region.js index b6601d1..c6a8046 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -1,5 +1,5 @@ -var cordova = require("cordova"), - exec = require("cordova/exec"), +var exec = require("cordova/exec"), + MapboxPluginAPI = require("./mapbox-plugin-api-mixin"), EventsMixin = require("./events-mixin"); var OFFLINE_REGIONS = {}; @@ -18,7 +18,6 @@ function OfflineRegion() { this._downloading = false; - this.initEvents("Mapbox.MapInstance"); this.createStickyChannel("load"); this.createChannel("progress"); this.createStickyChannel("complete"); @@ -42,7 +41,20 @@ function OfflineRegion() { } } -EventsMixin(OfflineRegion.prototype); +MapboxPluginAPI('OfflineRegion', OfflineRegion.prototype); +EventsMixin('OfflineRegion', OfflineRegion.prototype); + +Object.defineProperty(OfflineRegion.prototype, "downloading", { + get: function () { + return this._downloading; + } +}); + +Object.defineProperty(OfflineRegion.prototype, "name", { + get: function () { + return this._name; + } +}); OfflineRegion.prototype._create = function (options) { var args = [options, this._onProgressId, this._onCompleteId]; @@ -57,73 +69,28 @@ OfflineRegion.prototype._instance = function (response) { OFFLINE_REGIONS[this._id] = this; }; -OfflineRegion.prototype._error = function (err) { - var error = new Error("OfflineRegion error (ID: " + this._id + "): " + err); - this._downloading = false; - console.warn("throwing OfflineRegionError: ", error); - throw error; -}; - -OfflineRegion.prototype._exec = function (successCallback, errorCallback, method, args) { - args = [this._id].concat(args || []); - exec(successCallback, errorCallback, "Mapbox", method, args); -}; - -OfflineRegion.prototype._execAfterLoad = function () { - var args = arguments; - this.once('load', function (map) { - this._exec.apply(this, args); - }.bind(this)); -}; - -OfflineRegion.prototype._registerCallback = function (name, success, fail) { - var callbackId = "MapboxOfflineRegion" + name + cordova.callbackId++; - - success = success || function () { console.log(callbackId + "() success!", arguments); }; - fail = fail || function () { console.log(callbackId + "() fail :(", arguments); }; - - cordova.callbacks[callbackId] = {success: success, fail: fail}; - return callbackId; -}; - -OfflineRegion.prototype.download = function () { +OfflineRegion.prototype.download = function (callback) { this._downloading = true; - this._execAfterLoad(onSuccess, this._error, "downloadOfflineRegion"); - function onSuccess() { + return this._execAfterLoad(onSuccess, "downloadOfflineRegion"); + function onSuccess(err) { + if (err) return (callback || this._err)(err); console.log("Mapbox OfflineRegion download started."); } }; -OfflineRegion.prototype.pause = function () { +OfflineRegion.prototype.pause = function (callback) { this._downloading = false; - this._execAfterLoad(onSuccess, this._error, "pauseOfflineRegion"); + return this._execAfterLoad(onSuccess, "pauseOfflineRegion"); function onSuccess() { + if (err) return (callback || this._err)(err); console.log("Mapbox OfflineRegion download paused."); } }; OfflineRegion.prototype.getStatus = function (callback) { - this._execAfterLoad(onSuccess, onError, "offlineRegionStatus"); - function onSuccess(status) { - callback(null, status); - } - function onError(error) { - callback(error); - } + return this._execAfterLoad(callback, "offlineRegionStatus"); }; -Object.defineProperty(OfflineRegion.prototype, "downloading", { - get: function () { - return this._downloading; - } -}); - -Object.defineProperty(OfflineRegion.prototype, "name", { - get: function () { - return this._name; - } -}); - module.exports = { createOfflineRegion: function (options) { var region = new OfflineRegion(); @@ -132,31 +99,36 @@ module.exports = { }, listOfflineRegions: function (callback) { - exec( - function (responses) { - console.log("Offline regions: ", responses); - var regions = responses.map(function (response) { - var region = OFFLINE_REGIONS[response.id]; - if (!region) { - region = new OfflineRegion(); - region._instance(response); - } - return region; - }), - byName = regions.reduce(function (regionsByName, region) { - regionsByName[region.name] = region; - return regionsByName; - }, {}); - callback(null, byName); - }, - function (errorMessage) { - var error = "Error getting offline regions: " + errorMessage; - console.error(error); - callback(error); - }, - "Mapbox", - "listOfflineRegions", - [] - ); + callback = callback || function (err, response) {}; + return new Promise(function (resolve, reject) { + exec( + function (responses) { + console.log("Offline regions: ", responses); + var regions = responses.map(function (response) { + var region = OFFLINE_REGIONS[response.id]; + if (!region) { + region = new OfflineRegion(); + region._instance(response); + } + return region; + }), + byName = regions.reduce(function (regionsByName, region) { + regionsByName[region.name] = region; + return regionsByName; + }, {}); + callback(null, byName); + resolve(byName); + }, + function (errorMessage) { + var error = "Error getting offline regions: " + errorMessage; + console.error(error); + callback(error); + reject(error); + }, + "Mapbox", + "listOfflineRegions", + [] + ); + }); } }; From 563e7150e02514c060e4c967b253dfd211e6bd56 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Mon, 28 Mar 2016 00:58:06 -0700 Subject: [PATCH 32/37] Fixes bunch of issues with offline layer loading. --- src/android/Mapbox.java | 62 ++++++++++++++---------- src/android/MapboxManager.java | 8 ++-- src/android/OfflineRegion.java | 2 + src/android/mapbox.gradle | 3 +- www/offline-region.js | 87 +++++++++++++++++++--------------- 5 files changed, 94 insertions(+), 68 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 212f4c6..ae410be 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -44,6 +44,7 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; private static final String ACTION_LIST_OFFLINE_REGIONS = "listOfflineRegions"; private static final String ACTION_CREATE_OFFLINE_REGION = "createOfflineRegion"; + private static final String ACTION_BIND_OFFLINE_REGION_CALLBACKS = "bindOfflineRegionCallbacks"; private static final String ACTION_DOWNLOAD_OFFLINE_REGION = "downloadOfflineRegion"; private static final String ACTION_PAUSE_OFFLINE_REGION = "pauseOfflineRegion"; private static final String ACTION_OFFLINE_REGION_STATUS = "offlineRegionStatus"; @@ -233,9 +234,41 @@ else if (ACTION_LIST_OFFLINE_REGIONS.equals(action)) { else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { final JSONObject options = args.getJSONObject(0); + this.createOfflineRegion(options, callbackContext); + } + + else if (ACTION_BIND_OFFLINE_REGION_CALLBACKS.equals(action)) { + final long offlineRegionId = args.getLong(0); final CallbackContext onProgress = new CallbackContext(args.getString(1), this.webView); final CallbackContext onComplete = new CallbackContext(args.getString(2), this.webView); - this.createOfflineRegion(options, callbackContext, onProgress, onComplete); + final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); + + region.setObserver(new MapboxManager.OfflineRegionProgressCallback() { + @Override + public void onProgress(JSONObject progress) { + PluginResult result = new PluginResult(PluginResult.Status.OK, progress); + result.setKeepCallback(true); + onProgress.sendPluginResult(result); + } + + @Override + public void onComplete(JSONObject progress) { + Log.d(LOG_TAG, "OfflineRegion (" + region.getId() + ") download complete."); + PluginResult result = new PluginResult(PluginResult.Status.OK, progress); + onComplete.sendPluginResult(result); + } + + @Override + public void onError(String error) { + String message = "Failed to create OfflineRegion (" + region.getId() + "): " + error; + Log.e(LOG_TAG, message); + PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); + result.setKeepCallback(true); + onComplete.error(message); + } + }); + + callbackContext.success(); } else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { @@ -405,34 +438,11 @@ public void onError(String error) { }); } - public void createOfflineRegion(final JSONObject options, final CallbackContext callback, final CallbackContext onProgress, final CallbackContext onComplete) throws JSONException { + public void createOfflineRegion(final JSONObject options, final CallbackContext callback) throws JSONException { cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - mapboxManager.createOfflineRegion(options, callback, new MapboxManager.OfflineRegionProgressCallback() { - @Override - public void onProgress(JSONObject progress) { - PluginResult result = new PluginResult(PluginResult.Status.OK, progress); - result.setKeepCallback(true); - onProgress.sendPluginResult(result); - } - - @Override - public void onComplete(JSONObject progress) { - Log.d(LOG_TAG, "complete"); - PluginResult result = new PluginResult(PluginResult.Status.OK, progress); - onComplete.sendPluginResult(result); - } - - @Override - public void onError(String error) { - String message = "Failed to create offline region: " + error; - Log.e(LOG_TAG, message); - PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); - result.setKeepCallback(true); - onComplete.error(message); - } - }); + mapboxManager.createOfflineRegion(options, callback); } }); } diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java index 0d4d187..098cfd3 100644 --- a/src/android/MapboxManager.java +++ b/src/android/MapboxManager.java @@ -1,5 +1,6 @@ package com.telerik.plugins.mapbox; +import android.util.Log; import android.widget.FrameLayout; import com.mapbox.mapboxsdk.constants.Style; @@ -15,6 +16,7 @@ import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaWebView; +import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -189,7 +191,7 @@ public void onError(String error) { }); } - public void createOfflineRegion(final JSONObject options, final CallbackContext callback, final OfflineRegionProgressCallback offlineRegionStatusCallback) { + public void createOfflineRegion(final JSONObject options, final CallbackContext callback) { try { final String regionName = options.getString("name"); @@ -203,16 +205,14 @@ public void createOfflineRegion(final JSONObject options, final CallbackContext public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { try { OfflineRegion region = createOfflineRegion(offlineRegion); - region.setObserver(offlineRegionStatusCallback); - JSONObject response = region.getMetadata(); response.put(JSON_FIELD_ID, region.getId()); callback.success(response); } catch (JSONException e) { this.onError(e.getMessage()); + removeOfflineRegion(offlineRegion.getID()); } catch (UnsupportedEncodingException e) { this.onError(e.getMessage()); - } finally { removeOfflineRegion(offlineRegion.getID()); } } diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java index 464a36b..dcb5994 100644 --- a/src/android/OfflineRegion.java +++ b/src/android/OfflineRegion.java @@ -65,6 +65,8 @@ private JSONObject statusToJSON(OfflineRegionStatus status) throws JSONException long requiredCount = status.getRequiredResourceCount(); double percentage = requiredCount >= 0 ? (100.0 * completedCount / requiredCount) : 0.0; JSONObject jsonStatus = new JSONObject() + .put("completed", status.isComplete()) + .put("downloading", status.getDownloadState() == com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_ACTIVE) .put("completedCount", completedCount) .put("completedSize", status.getCompletedResourceSize()) .put("requiredCount", requiredCount) diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index f67c263..1ce0310 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -2,12 +2,13 @@ ext.cdvMinSdkVersion = 15 repositories { mavenCentral() + maven { url "http://oss.sonatype.org/content/repositories/snapshots/" } } dependencies { compile 'com.cocoahero.android:geojson:1.0.1@jar' compile 'com.android.support:appcompat-v7:23.0.1' - compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-rc.1@aar'){ + compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-SNAPSHOT@aar'){ transitive=true } } diff --git a/www/offline-region.js b/www/offline-region.js index c6a8046..6ccfa0d 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -57,23 +57,31 @@ Object.defineProperty(OfflineRegion.prototype, "name", { }); OfflineRegion.prototype._create = function (options) { - var args = [options, this._onProgressId, this._onCompleteId]; + var args = [options]; exec(this._instance, this._error, "Mapbox", "createOfflineRegion", args); }; OfflineRegion.prototype._instance = function (response) { this._id = response.id; this._name = response.name; - this.loaded = true; - this.fire("load", {offlineRegion: this}); - OFFLINE_REGIONS[this._id] = this; + return this._exec( + onSuccess.bind(this), + 'bindOfflineRegionCallbacks', + [this._onProgressId, this._onCompleteId] + ); + function onSuccess(err) { + if (err) return this._error(err); + OFFLINE_REGIONS[this._id] = this; + this.loaded = true; + this.fire("load", {offlineRegion: this}); + } }; OfflineRegion.prototype.download = function (callback) { this._downloading = true; return this._execAfterLoad(onSuccess, "downloadOfflineRegion"); function onSuccess(err) { - if (err) return (callback || this._err)(err); + if (err) return (callback || this._error)(err); console.log("Mapbox OfflineRegion download started."); } }; @@ -81,8 +89,8 @@ OfflineRegion.prototype.download = function (callback) { OfflineRegion.prototype.pause = function (callback) { this._downloading = false; return this._execAfterLoad(onSuccess, "pauseOfflineRegion"); - function onSuccess() { - if (err) return (callback || this._err)(err); + function onSuccess(err) { + if (err) return (callback || this._error)(err); console.log("Mapbox OfflineRegion download paused."); } }; @@ -91,6 +99,21 @@ OfflineRegion.prototype.getStatus = function (callback) { return this._execAfterLoad(callback, "offlineRegionStatus"); }; +function listOfflineRegions() { + return new Promise(function (resolve, reject) { + exec(resolve, reject, "Mapbox", "listOfflineRegions", []); + }); +} + +function createRegionFromResponse(response) { + var region = OFFLINE_REGIONS[response.id]; + if (!region) { + region = new OfflineRegion(); + region._instance(response); + } + return region; +} + module.exports = { createOfflineRegion: function (options) { var region = new OfflineRegion(); @@ -100,35 +123,25 @@ module.exports = { listOfflineRegions: function (callback) { callback = callback || function (err, response) {}; - return new Promise(function (resolve, reject) { - exec( - function (responses) { - console.log("Offline regions: ", responses); - var regions = responses.map(function (response) { - var region = OFFLINE_REGIONS[response.id]; - if (!region) { - region = new OfflineRegion(); - region._instance(response); - } - return region; - }), - byName = regions.reduce(function (regionsByName, region) { - regionsByName[region.name] = region; - return regionsByName; - }, {}); - callback(null, byName); - resolve(byName); - }, - function (errorMessage) { - var error = "Error getting offline regions: " + errorMessage; - console.error(error); - callback(error); - reject(error); - }, - "Mapbox", - "listOfflineRegions", - [] - ); - }); + + return listOfflineRegions() + .then(function (responses) { + return Promise.all(responses.map(createRegionFromResponse)); + }) + .then(function (regions) { + return regions.reduce(function (regionsByName, region) { + regionsByName[region.name] = region; + return regionsByName; + }, {}); + }) + .then(function (regionsByName) { + callback(null, regionsByName); + return regionsByName; + }) + .catch(function (errorMessage) { + var error = "Error getting offline regions: " + errorMessage; + console.error(error); + callback(error); + }); } }; From 857674a5374b0f6028f300fb4e764d783b3878e0 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Tue, 29 Mar 2016 00:28:20 -0700 Subject: [PATCH 33/37] switched downloading log statements to debug --- www/offline-region.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/offline-region.js b/www/offline-region.js index 6ccfa0d..3c89fdc 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -82,7 +82,7 @@ OfflineRegion.prototype.download = function (callback) { return this._execAfterLoad(onSuccess, "downloadOfflineRegion"); function onSuccess(err) { if (err) return (callback || this._error)(err); - console.log("Mapbox OfflineRegion download started."); + console.debug("Mapbox OfflineRegion download started."); } }; @@ -91,7 +91,7 @@ OfflineRegion.prototype.pause = function (callback) { return this._execAfterLoad(onSuccess, "pauseOfflineRegion"); function onSuccess(err) { if (err) return (callback || this._error)(err); - console.log("Mapbox OfflineRegion download paused."); + console.debug("Mapbox OfflineRegion download paused."); } }; From d0f5b03cab642cad8b18042a17ce93d09fdf62bc Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Tue, 29 Mar 2016 15:59:52 -0700 Subject: [PATCH 34/37] Removed calls to MapView's onStart() and onStop() as they have been removed upstream. --- src/android/Mapbox.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index ae410be..56e32b0 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -481,13 +481,6 @@ public void onRequestPermissionResult(int commandId, String[] permissions, int[] Command.execute(this, commandId); } - @Override - public void onStart() { - for (Map map : mapboxManager.maps()) { - map.getMapView().onStart(); - } - } - public void onResume(boolean multitasking) { for (Map map : mapboxManager.maps()) { map.getMapView().onResume(); @@ -501,13 +494,6 @@ public void onPause(boolean multitasking) { } } - @Override - public void onStop() { - for (Map map : mapboxManager.maps()) { - map.getMapView().onStop(); - } - } - @Override public void onDestroy() { for (Map map : mapboxManager.maps()) { From db2154ae3245d8e4a55d97766f0b2824ad79dc7e Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Wed, 30 Mar 2016 11:08:29 -0700 Subject: [PATCH 35/37] Cleaned up OfflineRegion implementation. Removed extra call to Android to bind offline progress callbacks upon creation. --- src/android/Mapbox.java | 71 ++++++++--------------- src/android/MapboxManager.java | 80 ++++++++++++-------------- src/android/OfflineRegion.java | 85 +++++++++++++++------------ www/offline-region.js | 102 +++++++++++++++------------------ 4 files changed, 154 insertions(+), 184 deletions(-) diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 56e32b0..ce726c6 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -7,7 +7,6 @@ import android.os.Build; import android.support.v4.app.ActivityCompat; import android.util.DisplayMetrics; -import android.util.Log; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -29,9 +28,6 @@ // TODO fox Xwalk compat, see nativepagetransitions plugin // TODO look at demo app: https://github.com/mapbox/mapbox-gl-native/blob/master/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java public class Mapbox extends CordovaPlugin { - - private static final String LOG_TAG = "MapboxCordovaPlugin"; - public static final String FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION; public static final String COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION; @@ -44,7 +40,6 @@ public class Mapbox extends CordovaPlugin { private static final String ACTION_SHOW_USER_LOCATION = "showUserLocation"; private static final String ACTION_LIST_OFFLINE_REGIONS = "listOfflineRegions"; private static final String ACTION_CREATE_OFFLINE_REGION = "createOfflineRegion"; - private static final String ACTION_BIND_OFFLINE_REGION_CALLBACKS = "bindOfflineRegionCallbacks"; private static final String ACTION_DOWNLOAD_OFFLINE_REGION = "downloadOfflineRegion"; private static final String ACTION_PAUSE_OFFLINE_REGION = "pauseOfflineRegion"; private static final String ACTION_OFFLINE_REGION_STATUS = "offlineRegionStatus"; @@ -234,41 +229,10 @@ else if (ACTION_LIST_OFFLINE_REGIONS.equals(action)) { else if (ACTION_CREATE_OFFLINE_REGION.equals(action)) { final JSONObject options = args.getJSONObject(0); - this.createOfflineRegion(options, callbackContext); - } - - else if (ACTION_BIND_OFFLINE_REGION_CALLBACKS.equals(action)) { - final long offlineRegionId = args.getLong(0); final CallbackContext onProgress = new CallbackContext(args.getString(1), this.webView); final CallbackContext onComplete = new CallbackContext(args.getString(2), this.webView); - final OfflineRegion region = this.mapboxManager.getOfflineRegion(offlineRegionId); - - region.setObserver(new MapboxManager.OfflineRegionProgressCallback() { - @Override - public void onProgress(JSONObject progress) { - PluginResult result = new PluginResult(PluginResult.Status.OK, progress); - result.setKeepCallback(true); - onProgress.sendPluginResult(result); - } - - @Override - public void onComplete(JSONObject progress) { - Log.d(LOG_TAG, "OfflineRegion (" + region.getId() + ") download complete."); - PluginResult result = new PluginResult(PluginResult.Status.OK, progress); - onComplete.sendPluginResult(result); - } - - @Override - public void onError(String error) { - String message = "Failed to create OfflineRegion (" + region.getId() + "): " + error; - Log.e(LOG_TAG, message); - PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); - result.setKeepCallback(true); - onComplete.error(message); - } - }); - callbackContext.success(); + this.createOfflineRegion(options, onProgress, onComplete, callbackContext); } else if (ACTION_DOWNLOAD_OFFLINE_REGION.equals(action)) { @@ -423,26 +387,37 @@ public void listOfflineRegions(final CallbackContext callback) { @Override public void run() { mapboxManager.loadOfflineRegions(new MapboxManager.LoadOfflineRegionsCallback() { - @Override - public void onList(JSONArray offlineRegions) { - callback.success(offlineRegions); - } + @Override + public void onList(JSONArray offlineRegions) { + callback.success(offlineRegions); + } - @Override - public void onError(String error) { - String message = "Error loading offline regions: " + error; - callback.error(message); - } + @Override + public void onError(String error) { + String message = "Error loading offline regions: " + error; + callback.error(message); + } }); } }); } - public void createOfflineRegion(final JSONObject options, final CallbackContext callback) throws JSONException { + public void createOfflineRegion(final JSONObject options, final CallbackContext onProgress, final CallbackContext onComplete, final CallbackContext callback) throws JSONException { cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - mapboxManager.createOfflineRegion(options, callback); + mapboxManager.getOrCreateOfflineRegion(options, new MapboxManager.OfflineRegionLoadCallback() { + @Override + public void onLoad(OfflineRegion region) { + region.bindStatusCallbacks(onProgress, onComplete); + callback.success(region.getMetadata()); + } + + @Override + public void onError(String error) { + callback.error(error); + } + }); } }); } diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java index 098cfd3..c36cf56 100644 --- a/src/android/MapboxManager.java +++ b/src/android/MapboxManager.java @@ -1,6 +1,5 @@ package com.telerik.plugins.mapbox; -import android.util.Log; import android.widget.FrameLayout; import com.mapbox.mapboxsdk.constants.Style; @@ -16,7 +15,7 @@ import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaWebView; -import org.apache.cordova.PluginResult; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -26,11 +25,6 @@ import java.util.HashMap; class MapboxManager { - // JSON encoding/decoding - public static final String JSON_CHARSET = "UTF-8"; - public static final String JSON_FIELD_ID = "id"; - public static final String JSON_FIELD_REGION_NAME = "name"; - public static String getStyle(final String requested) { if ("light".equalsIgnoreCase(requested)) { return Style.LIGHT; @@ -69,9 +63,8 @@ public interface OfflineRegionStatusCallback { void onError(String error); } - public interface OfflineRegionProgressCallback { - void onComplete(JSONObject progress); - void onProgress(JSONObject progress); + public interface OfflineRegionLoadCallback { + void onLoad(OfflineRegion region); void onError(String error); } @@ -173,7 +166,6 @@ public void onList(com.mapbox.mapboxsdk.offline.OfflineRegion[] offlineRegions) region = createOfflineRegion(offlineRegion); } response = region.getMetadata(); - response.put(JSON_FIELD_ID, region.getId()); responses.put(response); } callback.onList(responses); @@ -191,42 +183,44 @@ public void onError(String error) { }); } - public void createOfflineRegion(final JSONObject options, final CallbackContext callback) { + public void getOrCreateOfflineRegion(final JSONObject options, final OfflineRegionLoadCallback callback) { try { - final String regionName = options.getString("name"); - - JSONObject metadata = new JSONObject(); - metadata.put(JSON_FIELD_REGION_NAME, regionName); - byte[] encodedMetadata = metadata.toString().getBytes(JSON_CHARSET); - OfflineRegionDefinition definition = this.createOfflineRegionDefinition(density, options); - - offlineManager.createOfflineRegion(definition, encodedMetadata, new OfflineManager.CreateOfflineRegionCallback() { - @Override - public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { - try { - OfflineRegion region = createOfflineRegion(offlineRegion); - JSONObject response = region.getMetadata(); - response.put(JSON_FIELD_ID, region.getId()); - callback.success(response); - } catch (JSONException e) { - this.onError(e.getMessage()); - removeOfflineRegion(offlineRegion.getID()); - } catch (UnsupportedEncodingException e) { - this.onError(e.getMessage()); - removeOfflineRegion(offlineRegion.getID()); + if (options.has("id")) { + OfflineRegion region = getOfflineRegion(options.getLong("id")); + callback.onLoad(region); + } else { + JSONObject properties = options.getJSONObject(OfflineRegion.JSON_FIELD_PROPERTIES); + byte[] encodedMetadata = new JSONObject() + .put(OfflineRegion.JSON_FIELD_PROPERTIES, properties) + .toString() + .getBytes(OfflineRegion.JSON_CHARSET); + OfflineRegionDefinition definition = this.createOfflineRegionDefinition(density, options); + + offlineManager.createOfflineRegion(definition, encodedMetadata, new OfflineManager.CreateOfflineRegionCallback() { + @Override + public void onCreate(com.mapbox.mapboxsdk.offline.OfflineRegion offlineRegion) { + try { + OfflineRegion region = createOfflineRegion(offlineRegion); + callback.onLoad(region); + } catch (JSONException e) { + this.onError(e.getMessage()); + removeOfflineRegion(offlineRegion.getID()); + } catch (UnsupportedEncodingException e) { + this.onError(e.getMessage()); + removeOfflineRegion(offlineRegion.getID()); + } } - } - @Override - public void onError(String error) { - String message = "Failed to create offline region: " + error; - callback.error(message); - } - }); - } catch (JSONException e) { - callback.error(e.getMessage()); + @Override + public void onError(String error) { + callback.onError(error); + } + }); + } + } catch(JSONException e) { + callback.onError(e.getMessage()); } catch (UnsupportedEncodingException e) { - callback.error(e.getMessage()); + callback.onError(e.getMessage()); } } diff --git a/src/android/OfflineRegion.java b/src/android/OfflineRegion.java index dcb5994..a486d4d 100644 --- a/src/android/OfflineRegion.java +++ b/src/android/OfflineRegion.java @@ -1,8 +1,12 @@ package com.telerik.plugins.mapbox; +import android.util.Log; + import com.mapbox.mapboxsdk.offline.OfflineRegionError; import com.mapbox.mapboxsdk.offline.OfflineRegionStatus; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.PluginResult; import org.json.JSONException; import org.json.JSONObject; @@ -10,7 +14,11 @@ public class OfflineRegion { + public static final String LOG_TAG = "OfflineRegion"; + public static final String JSON_CHARSET = "UTF-8"; + public static final String JSON_FIELD_ID = "id"; + public static final String JSON_FIELD_PROPERTIES = "properties"; private JSONObject metadata; @@ -19,7 +27,9 @@ public class OfflineRegion { protected OfflineRegion(com.mapbox.mapboxsdk.offline.OfflineRegion region) throws JSONException, UnsupportedEncodingException { this.region = region; byte[] encodedMetadata = region.getMetadata(); - this.metadata = new JSONObject(new String(encodedMetadata, JSON_CHARSET)); + JSONObject metadata = new JSONObject(new String(encodedMetadata, JSON_CHARSET)) + .put(JSON_FIELD_ID, this.getId()); + this.metadata = metadata; } public Long getId() { @@ -56,8 +66,43 @@ public void pause() { this.region.setDownloadState(com.mapbox.mapboxsdk.offline.OfflineRegion.STATE_INACTIVE); } - public void setObserver(MapboxManager.OfflineRegionProgressCallback statusCallback) { - this.region.setObserver(new OfflineRegionObserver(statusCallback)); + public void bindStatusCallbacks(final CallbackContext onProgress, final CallbackContext onComplete) { + this.region.setObserver(new com.mapbox.mapboxsdk.offline.OfflineRegion.OfflineRegionObserver() { + @Override + public void onStatusChanged(OfflineRegionStatus status) { + try { + JSONObject progress = statusToJSON(status); + PluginResult result = new PluginResult(PluginResult.Status.OK, progress); + if (status.isComplete()) { + onComplete.sendPluginResult(result); + } else { + result.setKeepCallback(true); + onProgress.sendPluginResult(result); + } + } catch (JSONException e) { + this.onError(e.getMessage()); + } + } + + @Override + public void onError(OfflineRegionError error) { + String message = "OfflineRegionError: [" + error.getReason() + "] " + error.getMessage(); + this.onError(message); + } + + @Override + public void mapboxTileCountLimitExceeded(long limit) { + this.onError("Tile limit exceeded (limit: " + limit + ")"); + } + + private void onError(String error) { + Log.e(LOG_TAG, error); + PluginResult result = new PluginResult(PluginResult.Status.ERROR, error); + result.setKeepCallback(true); + onComplete.error(error); + pause(); + } + }); } private JSONObject statusToJSON(OfflineRegionStatus status) throws JSONException { @@ -74,38 +119,4 @@ private JSONObject statusToJSON(OfflineRegionStatus status) throws JSONException return jsonStatus; } - - private class OfflineRegionObserver implements com.mapbox.mapboxsdk.offline.OfflineRegion.OfflineRegionObserver { - private MapboxManager.OfflineRegionProgressCallback progressCallback; - - OfflineRegionObserver(MapboxManager.OfflineRegionProgressCallback callback) { - this.progressCallback = callback; - } - - @Override - public void onStatusChanged(OfflineRegionStatus status) { - try { - JSONObject progress = statusToJSON(status); - if (!status.isComplete()) { - progressCallback.onProgress(progress); - } else { - progressCallback.onComplete(progress); - } - } catch (JSONException e) { - progressCallback.onError(e.getMessage()); - } - } - - @Override - public void onError(OfflineRegionError error) { - String message = "OfflineRegionError: [" + error.getReason() + "] " + error.getMessage(); - progressCallback.onError(message); - pause(); - } - - @Override - public void mapboxTileCountLimitExceeded(long limit) { - progressCallback.onError("Tile limit exceeded (limit: " + limit + ")"); - } - } } diff --git a/www/offline-region.js b/www/offline-region.js index 3c89fdc..10871f3 100644 --- a/www/offline-region.js +++ b/www/offline-region.js @@ -4,17 +4,44 @@ var exec = require("cordova/exec"), var OFFLINE_REGIONS = {}; -function OfflineRegion() { +function OfflineRegion(options) { + this._init(); + this._properties = options.properties || {}; + + var args = [options, this._onProgressId, this._onCompleteId]; + exec(this._onCreate, this._error, "Mapbox", "createOfflineRegion", args); +} + +MapboxPluginAPI('OfflineRegion', OfflineRegion.prototype); +EventsMixin('OfflineRegion', OfflineRegion.prototype); + +Object.defineProperty(OfflineRegion.prototype, "downloading", { + get: function () { + return this._downloading; + } +}); + +Object.defineProperty(OfflineRegion.prototype, "loaded", { + get: function () { + return this._loaded; + } +}); + +Object.defineProperty(OfflineRegion.prototype, "properties", { + get: function () { + return this._properties; + } +}); + +OfflineRegion.prototype._init = function () { var onProgress = _onProgress.bind(this), onComplete = _onComplete.bind(this), onError = _onError.bind(this); this._onProgressId = this._registerCallback('onProgress', onProgress); this._onCompleteId = this._registerCallback('onComplete', onComplete, onError); - - this._error = this._error.bind(this); - this._create = this._create.bind(this); - this._instance = this._instance.bind(this); + this._error = onError; + this._onCreate = this._onCreate.bind(this); this._downloading = false; @@ -34,47 +61,20 @@ function OfflineRegion() { function _onError(error) { try { - this._error(error); + this.prototype._error.call(this, error); } catch (e) { this.fire("error", e); } } -} - -MapboxPluginAPI('OfflineRegion', OfflineRegion.prototype); -EventsMixin('OfflineRegion', OfflineRegion.prototype); - -Object.defineProperty(OfflineRegion.prototype, "downloading", { - get: function () { - return this._downloading; - } -}); - -Object.defineProperty(OfflineRegion.prototype, "name", { - get: function () { - return this._name; - } -}); - -OfflineRegion.prototype._create = function (options) { - var args = [options]; - exec(this._instance, this._error, "Mapbox", "createOfflineRegion", args); }; -OfflineRegion.prototype._instance = function (response) { +OfflineRegion.prototype._onCreate = function onCreate(response) { this._id = response.id; - this._name = response.name; - return this._exec( - onSuccess.bind(this), - 'bindOfflineRegionCallbacks', - [this._onProgressId, this._onCompleteId] - ); - function onSuccess(err) { - if (err) return this._error(err); - OFFLINE_REGIONS[this._id] = this; - this.loaded = true; - this.fire("load", {offlineRegion: this}); - } + this._properties = response.properties; + this._loaded = true; + this.fire("load", this); + + OFFLINE_REGIONS[this._id] = this; }; OfflineRegion.prototype.download = function (callback) { @@ -106,19 +106,15 @@ function listOfflineRegions() { } function createRegionFromResponse(response) { - var region = OFFLINE_REGIONS[response.id]; - if (!region) { - region = new OfflineRegion(); - region._instance(response); - } - return region; + return new Promise(function (resolve, reject) { + var region = OFFLINE_REGIONS[response.id] || new OfflineRegion(response); + region.once('load', resolve); + }); } module.exports = { createOfflineRegion: function (options) { - var region = new OfflineRegion(); - region._create(options); - return region; + return new OfflineRegion(options); }, listOfflineRegions: function (callback) { @@ -129,14 +125,8 @@ module.exports = { return Promise.all(responses.map(createRegionFromResponse)); }) .then(function (regions) { - return regions.reduce(function (regionsByName, region) { - regionsByName[region.name] = region; - return regionsByName; - }, {}); - }) - .then(function (regionsByName) { - callback(null, regionsByName); - return regionsByName; + callback(null, regions); + return regions; }) .catch(function (errorMessage) { var error = "Error getting offline regions: " + errorMessage; From efd96e60145f7097288c3d7977a6c2bda2386913 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Wed, 30 Mar 2016 14:51:46 -0700 Subject: [PATCH 36/37] Initial attempt at supporting ALL map events. --- src/android/Map.java | 98 +++++++++++++++++++++++++++++++++- src/android/Mapbox.java | 47 +++++++++++----- src/android/MapboxManager.java | 4 +- www/events-mixin.js | 3 ++ www/map-instance.js | 21 ++++++-- 5 files changed, 155 insertions(+), 18 deletions(-) diff --git a/src/android/Map.java b/src/android/Map.java index a1d8434..76ef884 100644 --- a/src/android/Map.java +++ b/src/android/Map.java @@ -1,7 +1,9 @@ package com.telerik.plugins.mapbox; +import android.location.Location; import android.support.annotation.Nullable; +import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; @@ -15,6 +17,11 @@ import org.json.JSONObject; public class Map { + + public interface MapEventListener { + void onEvent(String name, JSONObject event); + } + public static MapboxMapOptions createMapboxMapOptions(JSONObject options) throws JSONException { MapboxMapOptions opts = new MapboxMapOptions(); opts.styleUrl(MapboxManager.getStyle(options.getString("style"))); @@ -57,11 +64,14 @@ public static CameraPosition getCameraPostion(JSONObject options, @Nullable Came private MapboxMap mapboxMap; + private MapEventListener eventListener; + private FeatureManager features; - public Map(long id, final MapView mapView) { + public Map(long id, MapEventListener eventListener, final MapView mapView) { this.id = id; this.mapView = mapView; + this.eventListener = eventListener; } public long getId() { @@ -74,6 +84,92 @@ public MapView getMapView() { public void setMapboxMap(MapboxMap mMap) { this.mapboxMap = mMap; + + this.mapboxMap.setOnCameraChangeListener(new MapboxMap.OnCameraChangeListener() { + @Override + public void onCameraChange(CameraPosition position) { + eventListener.onEvent("camerachange", new JSONObject()); + } + }); + + this.mapboxMap.setOnFlingListener(new MapboxMap.OnFlingListener() { + @Override + public void onFling() { + eventListener.onEvent("fling", new JSONObject()); + } + }); + + this.mapboxMap.setOnInfoWindowClickListener(new MapboxMap.OnInfoWindowClickListener() { + @Override + public boolean onInfoWindowClick(Marker marker) { + eventListener.onEvent("infowindowclick", new JSONObject()); + return true; + } + }); + + this.mapboxMap.setOnInfoWindowCloseListener(new MapboxMap.OnInfoWindowCloseListener() { + @Override + public void onInfoWindowClose(Marker marker) { + eventListener.onEvent("infowindowclose", new JSONObject()); + } + }); + + this.mapboxMap.setOnInfoWindowLongClickListener(new MapboxMap.OnInfoWindowLongClickListener() { + @Override + public void onInfoWindowLongClick(Marker marker) { + eventListener.onEvent("infowindowlongclick", new JSONObject()); + } + }); + + this.mapboxMap.setOnMapClickListener(new MapboxMap.OnMapClickListener() { + @Override + public void onMapClick(LatLng point) { + eventListener.onEvent("mapclick", new JSONObject()); + } + }); + + this.mapboxMap.setOnMapLongClickListener(new MapboxMap.OnMapLongClickListener() { + @Override + public void onMapLongClick(LatLng point) { + eventListener.onEvent("maplongclick", new JSONObject()); + } + }); + + this.mapboxMap.setOnMarkerClickListener(new MapboxMap.OnMarkerClickListener() { + @Override + public boolean onMarkerClick(Marker marker) { + eventListener.onEvent("markerclick", new JSONObject()); + return true; + } + }); + + this.mapboxMap.setOnMyBearingTrackingModeChangeListener(new MapboxMap.OnMyBearingTrackingModeChangeListener() { + @Override + public void onMyBearingTrackingModeChange(int myBearingTrackingMode) { + eventListener.onEvent("bearingtrackingmodechange", new JSONObject()); + } + }); + + this.mapboxMap.setOnMyLocationChangeListener(new MapboxMap.OnMyLocationChangeListener() { + @Override + public void onMyLocationChange(@Nullable Location location) { + eventListener.onEvent("locationchange", new JSONObject()); + } + }); + + this.mapboxMap.setOnMyLocationTrackingModeChangeListener(new MapboxMap.OnMyLocationTrackingModeChangeListener() { + @Override + public void onMyLocationTrackingModeChange(int myLocationTrackingMode) { + eventListener.onEvent("locationtrackingmodechange", new JSONObject()); + } + }); + + this.mapboxMap.setOnScrollListener(new MapboxMap.OnScrollListener() { + @Override + public void onScroll() { + eventListener.onEvent("onscroll", new JSONObject()); + } + }); } public void setFeatureManager(FeatureManager featureManager) { diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index ce726c6..9a6a219 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -80,7 +80,8 @@ public boolean execute(Command command) throws JSONException { final JSONObject options = args.getJSONObject(0); boolean showUserLocation = !options.isNull("showUserLocation") && options.getBoolean("showUserLocation"); if (!showUserLocation || requestPermission(command, COARSE_LOCATION, FINE_LOCATION)) { - this.createMap(options, callbackContext); + final CallbackContext eventCallback = new CallbackContext(args.getString(1), this.webView); + this.createMap(options, eventCallback, callbackContext); } } @@ -373,30 +374,52 @@ public void run() { return true; } - private void createMap(final JSONObject options, final CallbackContext callback) { + private void createMap(final JSONObject options, final CallbackContext eventCallback, final CallbackContext callback) { cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { - mapboxManager.createMap(options, callback); + Map.MapEventListener eventListener = createEventListener(eventCallback); + mapboxManager.createMap(options, eventListener, callback); } }); } + private Map.MapEventListener createEventListener(final CallbackContext callback) { + return new Map.MapEventListener() { + @Override + public void onEvent(String name, JSONObject data) { + try { + JSONObject event = new JSONObject() + .put("name", name) + .put("data", data); + PluginResult result = new PluginResult(PluginResult.Status.OK, event); + result.setKeepCallback(true); + callback.sendPluginResult(result); + } catch (JSONException e) { + String message = "Error during map event: " + e.getMessage(); + PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); + result.setKeepCallback(true); + callback.sendPluginResult(result); + } + } + }; + } + public void listOfflineRegions(final CallbackContext callback) { cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { mapboxManager.loadOfflineRegions(new MapboxManager.LoadOfflineRegionsCallback() { - @Override - public void onList(JSONArray offlineRegions) { - callback.success(offlineRegions); - } + @Override + public void onList(JSONArray offlineRegions) { + callback.success(offlineRegions); + } - @Override - public void onError(String error) { - String message = "Error loading offline regions: " + error; - callback.error(message); - } + @Override + public void onError(String error) { + String message = "Error loading offline regions: " + error; + callback.error(message); + } }); } }); diff --git a/src/android/MapboxManager.java b/src/android/MapboxManager.java index c36cf56..61170af 100644 --- a/src/android/MapboxManager.java +++ b/src/android/MapboxManager.java @@ -81,13 +81,13 @@ public MapboxManager(String accessToken, Float screenDensity, CordovaWebView web this.offlineManager.setAccessToken(accessToken); } - public void createMap(final JSONObject options, final CallbackContext callback) { + public void createMap(final JSONObject options, Map.MapEventListener eventListener, final CallbackContext callback) { try { PositionInfo position = new PositionInfo(options.isNull("margins") ? null : options.getJSONObject("margins"), density); MapView mapView = createMapView(Map.createMapboxMapOptions(options), position); long id = ids++; - final Map map = new Map(id, mapView); + final Map map = new Map(id, eventListener, mapView); mapView.getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(MapboxMap mMap) { diff --git a/www/events-mixin.js b/www/events-mixin.js index 585a9c2..2ee5045 100644 --- a/www/events-mixin.js +++ b/www/events-mixin.js @@ -48,6 +48,9 @@ module.exports = function (prefix, target) { }, fire: function (type, e) { + if (!this._channel(type)) { + this.createChannel(type); + } this._channel(type).fire(e); } })(target); diff --git a/www/map-instance.js b/www/map-instance.js index f2aab1a..ade0e07 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -4,11 +4,26 @@ var exec = require("cordova/exec"), function MapInstance(options) { var onLoad = this._onLoad.bind(this), - onError = this._error.bind(this); + onEvent = _onEvent.bind(this), + onError = _onError.bind(this); this.createStickyChannel("load"); - - exec(onLoad, onError, "Mapbox", "createMap", [options]); + this._onEventId = this._registerCallback('onEvent', onEvent, onError); + + exec(onLoad, onError, "Mapbox", "createMap", [options, this._onEventId]); + + function _onEvent(event) { + console.debug("Event recieved: " + event.name, event.data); + this.fire(event.name, event.data); + } + + function _onError(error) { + try { + this.prototype._error.call(this, error); + } catch (e) { + this.fire("error", e); + } + } } MapboxPluginAPI('MapInstance', MapInstance.prototype); From d4125fe78e368bbba4b1825aec4b13bdf2125131 Mon Sep 17 00:00:00 2001 From: Tom Nightingale <tom@tnightingale.com> Date: Wed, 30 Mar 2016 18:17:27 -0700 Subject: [PATCH 37/37] Added initial attempt at map events. --- plugin.xml | 3 + src/android/FeatureManager.java | 77 +++++++++++------ src/android/GeoJSONMarker.java | 25 ++++++ src/android/GeoJSONMarkerOptions.java | 75 +++++++++++++++++ src/android/Map.java | 116 ++++++++++++++++++++------ src/android/Mapbox.java | 17 ++-- src/android/mapbox.gradle | 3 +- www/map-events.js | 33 ++++++++ www/map-instance.js | 27 +++++- 9 files changed, 313 insertions(+), 63 deletions(-) create mode 100644 src/android/GeoJSONMarker.java create mode 100644 src/android/GeoJSONMarkerOptions.java create mode 100644 www/map-events.js diff --git a/plugin.xml b/plugin.xml index c132f98..1f6111e 100755 --- a/plugin.xml +++ b/plugin.xml @@ -27,6 +27,7 @@ <js-module src="www/mixin.js" name="mixin" /> <js-module src="www/mapbox-plugin-api-mixin.js" name="mapbox-plugin-api-mixin" /> <js-module src="www/events-mixin.js" name="events-mixin" /> + <js-module src="www/map-events.js" name="map-events" /> <js-module src="www/map-instance.js" name="map-instance" /> <js-module src="www/offline-region.js" name="offline-region" /> @@ -63,6 +64,8 @@ <source-file src="src/android/OfflineRegion.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/Map.java" target-dir="src/com/telerik/plugins/mapbox"/> <source-file src="src/android/FeatureManager.java" target-dir="src/com/telerik/plugins/mapbox"/> + <source-file src="src/android/GeoJSONMarker.java" target-dir="src/com/telerik/plugins/mapbox"/> + <source-file src="src/android/GeoJSONMarkerOptions.java" target-dir="src/com/telerik/plugins/mapbox"/> <!-- This leads to trouble in AppBuilder when compiling for Cordova-Android 4 --> <!--source-file src="src/android/res/values/mapboxstrings.xml" target-dir="res/values" /> diff --git a/src/android/FeatureManager.java b/src/android/FeatureManager.java index e1b9575..b333de8 100644 --- a/src/android/FeatureManager.java +++ b/src/android/FeatureManager.java @@ -4,6 +4,9 @@ import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; import android.util.DisplayMetrics; import android.util.Log; @@ -21,6 +24,7 @@ import com.cocoahero.android.geojson.Polygon; import com.cocoahero.android.geojson.Position; import com.cocoahero.android.geojson.Ring; +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.Marker; @@ -50,6 +54,8 @@ interface DataSource { class FeatureManager { private String TAG = "FeatureManager"; + protected long ids = 0; + protected Context ctx; protected IconFactory iconFactory; @@ -58,7 +64,7 @@ class FeatureManager { protected HashMap<String, DataSource> sources = new HashMap<String, DataSource>(); - protected HashMap<Long, Feature> markerIndex = new HashMap<Long, Feature>(); + protected HashMap<Long, GeoJSONMarker> markerIndex = new HashMap<Long, GeoJSONMarker>(); public FeatureManager(Context ctx, MapboxMap mapboxMap) { this.ctx = ctx; @@ -70,22 +76,18 @@ public boolean hasSource(String name) { return this.sources.containsKey(name); } - public boolean hasMarkerFeature(Long id) { + public boolean hasMarker(Long id) { return this.markerIndex.containsKey(id); } - public Feature getMarkerFeature(Long id) { - if (this.hasMarkerFeature(id)) { + public GeoJSONMarker getMarker(Long id) { + if (this.hasMarker(id)) { return this.markerIndex.get(id); } else { return null; } } - public Feature getMarkerFeature(Marker marker) { - return this.getMarkerFeature(marker.getId()); - } - public void addGeoJSONSource(String name, String json) throws JSONException { this.sources.put(name, new GeoJSONSource(name).addGeoJSON(json)); } @@ -132,39 +134,60 @@ public void addMarkerLayer(String id, String source, JSONObject layer) { List<Feature> features = this.sources.get(source).getSymbols(); for (Feature feature : features) { - MarkerOptions options = this.createMarker(feature, layer); - Marker marker = this.mapboxMap.addMarker(options); - this.markerIndex.put(marker.getId(), feature); + GeoJSONMarkerOptions options = this.createMarker(feature, layer); + GeoJSONMarker marker = (GeoJSONMarker) this.mapboxMap.addMarker(options); + this.markerIndex.put(marker.getFeatureId(), marker); } } - protected MarkerOptions createMarker(Feature feature, JSONObject style) { - final JSONObject properties = feature.getProperties(); - final Position p = ((Point) feature.getGeometry()).getPosition(); - final MarkerOptions marker = new MarkerOptions() - .position(new LatLng(p.getLatitude(), p.getLongitude())); + public GeoJSONMarkerOptions createMarker(LatLng latLng, String title, String snippet, @Nullable Icon icon, @Nullable JSONObject properties) { + GeoJSONMarkerOptions marker = new GeoJSONMarkerOptions() + .title(title) + .snippet(snippet) + .position(latLng) + .featureId(this.ids++); + + if (icon != null) { + marker.icon(icon); + } + + if (properties != null) { + marker.properties(properties); + } + + return marker; + } + + public GeoJSONMarkerOptions createMarker(LatLng latLng, String title, String snippet) { + return createMarker(latLng, title, snippet, null, null); + } + + protected GeoJSONMarkerOptions createMarker(Feature feature, JSONObject style) { + JSONObject properties = feature.getProperties(); + Position p = ((Point) feature.getGeometry()).getPosition(); + LatLng latLng = new LatLng(p.getLatitude(), p.getLongitude()); + String title = ""; + String snippet = ""; + Icon icon = null; try { - final String textField = style.getJSONObject("layout").getString("text-field"); - marker.title(textField.replace("{title}", properties.getString("title"))); + String textField = style.getJSONObject("layout").getString("text-field"); + title = textField.replace("{title}", properties.getString("title")); } catch (JSONException e) { Log.w(TAG, "Error parsing Style JSON properties: " + e.getMessage()); } try { - marker.snippet(properties.getString("description")); + snippet = properties.getString("description"); } catch (JSONException e) { Log.w(TAG, "Error parsing Style JSON properties: " + e.getMessage()); } try { - final String iconImage = style.getJSONObject("layout").getString("icon-image"); - final String markerSymbol = properties.getString("marker-symbol"); - final URI uri = new URI(iconImage.replace("{marker-symbol}", markerSymbol)); - final Icon icon = this.loadIcon(uri); - if (icon != null) { - marker.icon(icon); - } + String iconImage = style.getJSONObject("layout").getString("icon-image"); + String markerSymbol = properties.getString("marker-symbol"); + URI uri = new URI(iconImage.replace("{marker-symbol}", markerSymbol)); + icon = this.loadIcon(uri); } catch (JSONException e) { Log.w(TAG, "Error parsing Style JSON properties: " + e.getMessage()); } catch (URISyntaxException e) { @@ -173,7 +196,7 @@ protected MarkerOptions createMarker(Feature feature, JSONObject style) { Log.w(TAG, "Error loading file: " + e.getMessage()); } - return marker; + return this.createMarker(latLng, title, snippet, icon, feature.getProperties()); } protected Icon loadIcon(URI uri) throws IOException { diff --git a/src/android/GeoJSONMarker.java b/src/android/GeoJSONMarker.java new file mode 100644 index 0000000..36c5344 --- /dev/null +++ b/src/android/GeoJSONMarker.java @@ -0,0 +1,25 @@ +package com.telerik.plugins.mapbox; + +import com.mapbox.mapboxsdk.annotations.Marker; + +import org.json.JSONObject; + +public class GeoJSONMarker extends Marker { + private JSONObject properties; + + private long featureId; + + public GeoJSONMarker(GeoJSONMarkerOptions options, JSONObject properties, long featureId) { + super(options); + this.properties = properties; + this.featureId = featureId; + } + + public JSONObject getProperties() { + return this.properties != null ? this.properties : new JSONObject(); + } + + public long getFeatureId() { + return this.featureId; + } +} \ No newline at end of file diff --git a/src/android/GeoJSONMarkerOptions.java b/src/android/GeoJSONMarkerOptions.java new file mode 100644 index 0000000..04b6f50 --- /dev/null +++ b/src/android/GeoJSONMarkerOptions.java @@ -0,0 +1,75 @@ +package com.telerik.plugins.mapbox; + +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; + +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.Icon; +import com.mapbox.mapboxsdk.annotations.IconFactory; +import com.mapbox.mapboxsdk.geometry.LatLng; + +import org.json.JSONObject; + +public class GeoJSONMarkerOptions extends BaseMarkerOptions<GeoJSONMarker, GeoJSONMarkerOptions> { + private long featureId; + private JSONObject properties; + + public GeoJSONMarkerOptions properties(JSONObject properties) { + this.properties = properties; + return this.getThis(); + } + + public GeoJSONMarkerOptions featureId(long id) { + this.featureId = id; + return this.getThis(); + } + + public GeoJSONMarkerOptions() { + + } + + private GeoJSONMarkerOptions(Parcel in) { + position((LatLng) in.readParcelable(LatLng.class.getClassLoader())); + snippet(in.readString()); + String iconId = in.readString(); + Bitmap iconBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + Icon icon = IconFactory.recreate(iconId, iconBitmap); + icon(icon); + title(in.readString()); + } + + @Override + public GeoJSONMarkerOptions getThis() { + return this; + } + + @Override + public GeoJSONMarker getMarker() { + return new GeoJSONMarker(this, this.properties, this.featureId); + } + + public static final Parcelable.Creator<GeoJSONMarkerOptions> CREATOR = new Parcelable.Creator<GeoJSONMarkerOptions>() { + public GeoJSONMarkerOptions createFromParcel(Parcel in) { + return new GeoJSONMarkerOptions(in); + } + + public GeoJSONMarkerOptions[] newArray(int size) { + return new GeoJSONMarkerOptions[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(position, flags); + out.writeString(snippet); + out.writeString(icon.getId()); + out.writeParcelable(icon.getBitmap(), flags); + out.writeString(title); + } +} \ No newline at end of file diff --git a/src/android/Map.java b/src/android/Map.java index 76ef884..33c2ebc 100644 --- a/src/android/Map.java +++ b/src/android/Map.java @@ -4,7 +4,6 @@ import android.support.annotation.Nullable; import com.mapbox.mapboxsdk.annotations.Marker; -import com.mapbox.mapboxsdk.annotations.MarkerOptions; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; @@ -18,8 +17,26 @@ public class Map { + public static final String JSON_FIELD_ID = "id"; + public static final String JSON_FIELD_PROPERTIES = "properties"; + public static final String JSON_FIELD_LNGLAT = "lngLat"; + + public static final int EVENT_CAMERACHANGE = 0; + public static final int EVENT_FLING = 1; + public static final int EVENT_INFOWINDOWCLICK = 2; + public static final int EVENT_INFOWINDOWCLOSE = 3; + public static final int EVENT_INFOWINDOWLONGCLICK = 4; + public static final int EVENT_MAPCLICK = 5; + public static final int EVENT_MAPLONGCLICK = 6; + public static final int EVENT_MARKERCLICK = 7; + public static final int EVENT_BEARINGTRACKINGMODECHANGE = 8; + public static final int EVENT_LOCATIONCHANGE = 9; + public static final int EVENT_LOCATIONTRACKINGMODECHANGE = 10; + public static final int EVENT_ONSCROLL = 11; + public interface MapEventListener { - void onEvent(String name, JSONObject event); + void onEvent(int code, JSONObject event); + void onError(String message); } public static MapboxMapOptions createMapboxMapOptions(JSONObject options) throws JSONException { @@ -82,92 +99,146 @@ public MapView getMapView() { return this.mapView; } + public JSONArray latLngToJSON(LatLng latLng) throws JSONException { + return new JSONArray() + .put(latLng.getLongitude()) + .put(latLng.getLatitude()); + } + + public JSONObject getMarkerJSON(Marker marker) throws JSONException { + LatLng point = marker.getPosition(); + JSONObject data = new JSONObject() + .put(JSON_FIELD_LNGLAT, latLngToJSON(point)); + + if (marker instanceof GeoJSONMarker) { + long featureId = ((GeoJSONMarker) marker).getFeatureId(); + data.put(JSON_FIELD_ID, featureId); + data.put(JSON_FIELD_PROPERTIES, features.getMarker(featureId).getProperties()); + } + + return data; + } + public void setMapboxMap(MapboxMap mMap) { this.mapboxMap = mMap; this.mapboxMap.setOnCameraChangeListener(new MapboxMap.OnCameraChangeListener() { @Override public void onCameraChange(CameraPosition position) { - eventListener.onEvent("camerachange", new JSONObject()); + eventListener.onEvent(EVENT_CAMERACHANGE, new JSONObject()); } }); this.mapboxMap.setOnFlingListener(new MapboxMap.OnFlingListener() { @Override public void onFling() { - eventListener.onEvent("fling", new JSONObject()); + eventListener.onEvent(EVENT_FLING, new JSONObject()); } }); this.mapboxMap.setOnInfoWindowClickListener(new MapboxMap.OnInfoWindowClickListener() { @Override public boolean onInfoWindowClick(Marker marker) { - eventListener.onEvent("infowindowclick", new JSONObject()); - return true; + try { + JSONObject data = getMarkerJSON(marker); + eventListener.onEvent(EVENT_INFOWINDOWCLICK, data); + return true; + } catch (JSONException e) { + eventListener.onError(e.getMessage()); + return false; + } } }); this.mapboxMap.setOnInfoWindowCloseListener(new MapboxMap.OnInfoWindowCloseListener() { @Override public void onInfoWindowClose(Marker marker) { - eventListener.onEvent("infowindowclose", new JSONObject()); + try { + JSONObject data = getMarkerJSON(marker); + eventListener.onEvent(EVENT_INFOWINDOWCLOSE, data); + } catch (JSONException e) { + eventListener.onError(e.getMessage()); + } } }); this.mapboxMap.setOnInfoWindowLongClickListener(new MapboxMap.OnInfoWindowLongClickListener() { @Override public void onInfoWindowLongClick(Marker marker) { - eventListener.onEvent("infowindowlongclick", new JSONObject()); + try { + JSONObject data = getMarkerJSON(marker); + eventListener.onEvent(EVENT_INFOWINDOWLONGCLICK, data); + } catch (JSONException e) { + eventListener.onError(e.getMessage()); + } } }); this.mapboxMap.setOnMapClickListener(new MapboxMap.OnMapClickListener() { @Override public void onMapClick(LatLng point) { - eventListener.onEvent("mapclick", new JSONObject()); + try { + JSONObject data = new JSONObject() + .put("lngLat", latLngToJSON(point)); + eventListener.onEvent(EVENT_MAPCLICK, data); + } catch (JSONException e) { + eventListener.onError(e.getMessage()); + } } }); this.mapboxMap.setOnMapLongClickListener(new MapboxMap.OnMapLongClickListener() { @Override public void onMapLongClick(LatLng point) { - eventListener.onEvent("maplongclick", new JSONObject()); + try { + JSONObject data = new JSONObject() + .put("lngLat", latLngToJSON(point)); + eventListener.onEvent(EVENT_MAPLONGCLICK, data); + } catch (JSONException e) { + eventListener.onError(e.getMessage()); + } } }); this.mapboxMap.setOnMarkerClickListener(new MapboxMap.OnMarkerClickListener() { @Override public boolean onMarkerClick(Marker marker) { - eventListener.onEvent("markerclick", new JSONObject()); - return true; + try { + JSONObject data = getMarkerJSON(marker); + eventListener.onEvent(EVENT_MARKERCLICK, data); + return true; + } catch (JSONException e) { + eventListener.onError(e.getMessage()); + return false; + } } }); this.mapboxMap.setOnMyBearingTrackingModeChangeListener(new MapboxMap.OnMyBearingTrackingModeChangeListener() { @Override public void onMyBearingTrackingModeChange(int myBearingTrackingMode) { - eventListener.onEvent("bearingtrackingmodechange", new JSONObject()); + eventListener.onEvent(EVENT_BEARINGTRACKINGMODECHANGE, new JSONObject()); } }); this.mapboxMap.setOnMyLocationChangeListener(new MapboxMap.OnMyLocationChangeListener() { @Override public void onMyLocationChange(@Nullable Location location) { - eventListener.onEvent("locationchange", new JSONObject()); + eventListener.onEvent(EVENT_LOCATIONCHANGE, new JSONObject()); } }); this.mapboxMap.setOnMyLocationTrackingModeChangeListener(new MapboxMap.OnMyLocationTrackingModeChangeListener() { @Override public void onMyLocationTrackingModeChange(int myLocationTrackingMode) { - eventListener.onEvent("locationtrackingmodechange", new JSONObject()); + eventListener.onEvent(EVENT_LOCATIONTRACKINGMODECHANGE, new JSONObject()); } }); this.mapboxMap.setOnScrollListener(new MapboxMap.OnScrollListener() { @Override public void onScroll() { - eventListener.onEvent("onscroll", new JSONObject()); + eventListener.onEvent(EVENT_ONSCROLL, new JSONObject()); } }); } @@ -220,14 +291,11 @@ public void jumpTo(JSONObject options) throws JSONException { public void addMarkers(JSONArray markers) throws JSONException { for (int i = 0; i < markers.length(); i++) { - final JSONObject marker = markers.getJSONObject(i); - final MarkerOptions mo = new MarkerOptions(); - - mo.title(marker.isNull("title") ? null : marker.getString("title")); - mo.snippet(marker.isNull("subtitle") ? null : marker.getString("subtitle")); - mo.position(new LatLng(marker.getDouble("lat"), marker.getDouble("lng"))); - - mapboxMap.addMarker(mo); + JSONObject marker = markers.getJSONObject(i); + LatLng latLng = new LatLng(marker.getDouble("lat"), marker.getDouble("lng")); + String title = marker.isNull("title") ? null : marker.getString("title"); + String snippet = marker.isNull("subtitle") ? null : marker.getString("subtitle"); + mapboxMap.addMarker(features.createMarker(latLng, title, snippet)); } } diff --git a/src/android/Mapbox.java b/src/android/Mapbox.java index 9a6a219..cc4d449 100644 --- a/src/android/Mapbox.java +++ b/src/android/Mapbox.java @@ -387,21 +387,26 @@ public void run() { private Map.MapEventListener createEventListener(final CallbackContext callback) { return new Map.MapEventListener() { @Override - public void onEvent(String name, JSONObject data) { + public void onEvent(int code, JSONObject data) { try { JSONObject event = new JSONObject() - .put("name", name) + .put("code", code) .put("data", data); PluginResult result = new PluginResult(PluginResult.Status.OK, event); result.setKeepCallback(true); callback.sendPluginResult(result); } catch (JSONException e) { - String message = "Error during map event: " + e.getMessage(); - PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); - result.setKeepCallback(true); - callback.sendPluginResult(result); + this.onError(e.getMessage()); } } + + @Override + public void onError(String error) { + String message = "Error during map event: " + error; + PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); + result.setKeepCallback(true); + callback.sendPluginResult(result); + } }; } diff --git a/src/android/mapbox.gradle b/src/android/mapbox.gradle index 1ce0310..d2c694b 100644 --- a/src/android/mapbox.gradle +++ b/src/android/mapbox.gradle @@ -2,13 +2,12 @@ ext.cdvMinSdkVersion = 15 repositories { mavenCentral() - maven { url "http://oss.sonatype.org/content/repositories/snapshots/" } } dependencies { compile 'com.cocoahero.android:geojson:1.0.1@jar' compile 'com.android.support:appcompat-v7:23.0.1' - compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0-SNAPSHOT@aar'){ + compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0@aar'){ transitive=true } } diff --git a/www/map-events.js b/www/map-events.js new file mode 100644 index 0000000..92e0076 --- /dev/null +++ b/www/map-events.js @@ -0,0 +1,33 @@ +var codes = { + CAMERACHANGE: 0, + FLING: 1, + INFOWINDOWCLICK: 2, + INFOWINDOWCLOSE: 3, + INFOWINDOWLONGCLICK: 4, + MAPCLICK: 5, + MAPLONGCLICK: 6, + MARKERCLICK: 7, + BEARINGTRACKINGMODECHANGE: 8, + LOCATIONCHANGE: 9, + LOCATIONTRACKINGMODECHANGE: 10, + ONSCROLL: 11, + }, + events = {}; + +events[codes.CAMERACHANGE] = {name: "camerachange"}; +events[codes.FLING] = {name: "fling"}; +events[codes.INFOWINDOWCLICK] = {name: "infowindowclick"}; +events[codes.INFOWINDOWCLOSE] = {name: "infowindowclose"}; +events[codes.INFOWINDOWLONGCLICK] = {name: "infowindowlongclick"}; +events[codes.MAPCLICK] = {name: "mapclick"}; +events[codes.MAPLONGCLICK] = {name: "maplongclick"}; +events[codes.MARKERCLICK] = {name: "markerclick"}; +events[codes.BEARINGTRACKINGMODECHANGE] = {name: "bearingtrackingmodechange"}; +events[codes.LOCATIONCHANGE] = {name: "locationchange"}; +events[codes.LOCATIONTRACKINGMODECHANGE] = {name: "locationtrackingmodechange"}; +events[codes.ONSCROLL] = {name: "onscroll"}; + +module.exports = { + events: events, + codes: codes +}; diff --git a/www/map-instance.js b/www/map-instance.js index ade0e07..dc6139f 100644 --- a/www/map-instance.js +++ b/www/map-instance.js @@ -1,6 +1,7 @@ var exec = require("cordova/exec"), MapboxPluginAPI = require("./mapbox-plugin-api-mixin"), - EventsMixin = require("./events-mixin"); + EventsMixin = require("./events-mixin"), + E = require("./map-events"); function MapInstance(options) { var onLoad = this._onLoad.bind(this), @@ -12,9 +13,20 @@ function MapInstance(options) { exec(onLoad, onError, "Mapbox", "createMap", [options, this._onEventId]); - function _onEvent(event) { - console.debug("Event recieved: " + event.name, event.data); - this.fire(event.name, event.data); + function _onEvent(e) { + var event = E.events[e.code]; + switch (e.code) { + case E.codes.INFOWINDOWCLICK: + case E.codes.INFOWINDOWCLOSE: + case E.codes.INFOWINDOWLONGCLICK: + case E.codes.MARKERCLICK: + var marker = new Marker(e.data.id); + console.debug("Event recieved: " + event.name, marker); + return this.fire(event.name, marker); + default: + console.debug("Event recieved: " + event.name, e.data); + return this.fire(event.name, e.data); + } } function _onError(error) { @@ -84,4 +96,11 @@ MapInstance.prototype._onLoad = function (resp) { this.fire("load", {map: this}); }; +function Marker(id) { + this._id = id; +} + +MapboxPluginAPI('Marker', Marker.prototype); +EventsMixin('Marker', Marker.prototype); + module.exports = MapInstance;