Loading GmsApi @ bfe649fc Compare 7197b832 to bfe649fc Original line number Diff line number Diff line Subproject commit 7197b8320b4e6d55d15b76d4faf05adaba36bf72 Subproject commit bfe649fc36a40e2498f1ecf123edfb9278fe0a1d play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +292 −96 Original line number Diff line number Diff line Loading @@ -16,18 +16,20 @@ package org.microg.gms.maps.mapbox import android.annotation.SuppressLint import android.content.Context import android.location.Location import android.os.Bundle import android.os.Parcel import android.os.RemoteException import android.support.annotation.IdRes import android.support.annotation.Keep import android.support.v4.util.LongSparseArray import android.util.DisplayMetrics import android.util.Log import android.view.Gravity import android.view.View import android.widget.FrameLayout import android.widget.RelativeLayout import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.internal.* Loading @@ -38,6 +40,7 @@ import com.mapbox.mapboxsdk.LibraryLoader import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.R import com.mapbox.mapboxsdk.camera.CameraUpdate import com.mapbox.mapboxsdk.constants.MapboxConstants import com.mapbox.mapboxsdk.maps.MapView import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.Style Loading @@ -53,9 +56,9 @@ import org.microg.gms.maps.mapbox.utils.MultiArchLoader import org.microg.gms.maps.mapbox.utils.toGms import org.microg.gms.maps.mapbox.utils.toMapbox fun <T : Any> LongSparseArray<T>.values() = (0..size()).map { valueAt(it) }.mapNotNull { it } private fun <T : Any> LongSparseArray<T>.values() = (0..size()).map { valueAt(it) }.mapNotNull { it } class GoogleMapImpl(private val context: Context, private val options: GoogleMapOptions) : IGoogleMapDelegate.Stub() { class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) : IGoogleMapDelegate.Stub() { val view: FrameLayout var map: MapboxMap? = null Loading @@ -64,10 +67,10 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap get() = context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT private var mapView: MapView? private var created = false private var initialized = false private val initializedCallbackList = mutableListOf<IOnMapReadyCallback>() private val mapLock = Object() val markers = mutableMapOf<Long, MarkerImpl>() private var cameraChangeListener: IOnCameraChangeListener? = null private var cameraMoveListener: IOnCameraMoveListener? = null Loading @@ -79,47 +82,130 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap private var markerClickListener: IOnMarkerClickListener? = null private var markerDragListener: IOnMarkerDragListener? = null var circleManager: CircleManager? = null var lineManager: LineManager? = null var fillManager: FillManager? = null var circleManager: CircleManager? = null val pendingCircles = mutableSetOf<CircleImpl>() var circleId = 0L var symbolManager: SymbolManager? = null var storedMapType: Int = MAP_TYPE_NORMAL val pendingMarkers = mutableSetOf<MarkerImpl>() val markers = mutableMapOf<Long, MarkerImpl>() var markerId = 0L var storedMapType: Int = options.mapType val waitingCameraUpdates = mutableListOf<CameraUpdate>() init { val mapContext = MapContext(context) LibraryLoader.setLibraryLoader(MultiArchLoader(mapContext, context)) Mapbox.getInstance(mapContext, BuildConfig.MAPBOX_KEY) val fakeWatermark = View(mapContext) fakeWatermark.layoutParams = object : RelativeLayout.LayoutParams(0, 0) { @SuppressLint("RtlHardcoded") override fun addRule(verb: Int, subject: Int) { super.addRule(verb, subject) val rules = this.rules var gravity = 0 if (rules[RelativeLayout.ALIGN_PARENT_BOTTOM] == RelativeLayout.TRUE) gravity = gravity or Gravity.BOTTOM if (rules[RelativeLayout.ALIGN_PARENT_TOP] == RelativeLayout.TRUE) gravity = gravity or Gravity.TOP if (rules[RelativeLayout.ALIGN_PARENT_LEFT] == RelativeLayout.TRUE) gravity = gravity or Gravity.LEFT if (rules[RelativeLayout.ALIGN_PARENT_RIGHT] == RelativeLayout.TRUE) gravity = gravity or Gravity.RIGHT if (rules[RelativeLayout.ALIGN_PARENT_START] == RelativeLayout.TRUE) gravity = gravity or Gravity.START if (rules[RelativeLayout.ALIGN_PARENT_END] == RelativeLayout.TRUE) gravity = gravity or Gravity.END map?.uiSettings?.logoGravity = gravity } } this.view = object : FrameLayout(mapContext) { @Keep fun <T : View> findViewTraversal(@IdRes id: Int): T? { return null } @Keep fun <T : View> findViewWithTagTraversal(tag: Any): T? { if ("GoogleWatermark" == tag) { return try { @Suppress("UNCHECKED_CAST") fakeWatermark as T } catch (e: ClassCastException) { null } } return null } } this.mapView = MapView(mapContext) this.view.addView(this.mapView) } override fun getCameraPosition(): CameraPosition? = map?.cameraPosition?.toGms() override fun getMaxZoomLevel(): Float = map?.maxZoomLevel?.toFloat() ?: 20f override fun getMinZoomLevel(): Float = map?.minZoomLevel?.toFloat() ?: 1f override fun moveCamera(cameraUpdate: IObjectWrapper?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.moveCamera(it) } ?: Unit override fun getMaxZoomLevel(): Float = (map?.maxZoomLevel?.toFloat() ?: 20f) + 1f override fun getMinZoomLevel(): Float = (map?.minZoomLevel?.toFloat() ?: 0f) + 1f override fun moveCamera(cameraUpdate: IObjectWrapper?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.moveCamera(update) } else { waitingCameraUpdates.add(update) } } override fun animateCamera(cameraUpdate: IObjectWrapper?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it) } ?: Unit override fun animateCamera(cameraUpdate: IObjectWrapper?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.animateCamera(update) } else { waitingCameraUpdates.add(update) } } override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it, callback?.toMapbox()) } ?: Unit override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.animateCamera(update, callback?.toMapbox()) } else { waitingCameraUpdates.add(update) callback?.onFinish() } } override fun animateCameraWithDurationAndCallback(cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it, duration, callback?.toMapbox()) } ?: Unit override fun animateCameraWithDurationAndCallback(cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.animateCamera(update, duration, callback?.toMapbox()) } else { waitingCameraUpdates.add(update) callback?.onFinish() } } override fun stopAnimation() = map?.cancelTransitions() ?: Unit override fun setMinZoomPreference(minZoom: Float) { map?.setMinZoomPreference(minZoom.toDouble() - 1) } override fun setMaxZoomPreference(maxZoom: Float) { map?.setMaxZoomPreference(maxZoom.toDouble() - 1) } override fun resetMinMaxZoomPreference() { map?.setMinZoomPreference(MapboxConstants.MINIMUM_ZOOM.toDouble()) map?.setMaxZoomPreference(MapboxConstants.MAXIMUM_ZOOM.toDouble()) } override fun setLatLngBoundsForCameraTarget(bounds: LatLngBounds?) { map?.setLatLngBoundsForCameraTarget(bounds?.toMapbox()) } override fun addPolyline(options: PolylineOptions): IPolylineDelegate? { val lineOptions = LineOptions() .withLatLngs(options.points.map { it.toMapbox() }) Loading @@ -131,26 +217,23 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap override fun addPolygon(options: PolygonOptions): IPolygonDelegate? { Log.d(TAG, "unimplemented Method: addPolygon") return null val fillOptions = FillOptions() .withLatLngs(listOf(options.points.map { it.toMapbox() })) .withFillColor(ColorUtils.colorToRgbaString(options.fillColor)) .withFillOpacity(if (options.isVisible) 1f else 0f) return fillManager?.let { PolygonImpl(this, it.create(fillOptions)) } } override fun addMarker(options: MarkerOptions): IMarkerDelegate? { var intBits = java.lang.Float.floatToIntBits(options.zIndex) if (intBits < 0) intBits = intBits xor 0x7fffffff val symbolOptions = SymbolOptions() .withIconOpacity(if (options.isVisible) options.alpha else 0f) .withIconRotate(options.rotation) .withZIndex(intBits) .withDraggable(options.isDraggable) options.position?.let { symbolOptions.withLatLng(it.toMapbox()) } options.icon?.remoteObject.unwrap<BitmapDescriptorImpl>()?.applyTo(symbolOptions, floatArrayOf(options.anchorU, options.anchorV), dpiFactor) val symbol = symbolManager?.create(symbolOptions) ?: return null val marker = MarkerImpl(this, symbol, floatArrayOf(options.anchorU, options.anchorV), options.icon?.remoteObject.unwrap<BitmapDescriptorImpl>(), options.alpha, options.title, options.snippet) markers.put(symbol.id, marker) val marker = MarkerImpl(this, "m${markerId++}", options) synchronized(this) { val symbolManager = symbolManager if (symbolManager == null) { pendingMarkers.add(marker) } else { marker.update(symbolManager) } } return marker } Loading @@ -165,16 +248,16 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap } override fun addCircle(options: CircleOptions): ICircleDelegate? { val circleOptions = com.mapbox.mapboxsdk.plugins.annotation.CircleOptions() .withLatLng(options.center.toMapbox()) .withCircleColor(ColorUtils.colorToRgbaString(options.fillColor)) .withCircleRadius(options.radius.toFloat()) .withCircleStrokeColor(ColorUtils.colorToRgbaString(options.strokeColor)) .withCircleStrokeWidth(options.strokeWidth / dpiFactor) .withCircleOpacity(if (options.isVisible) 1f else 0f) .withCircleStrokeOpacity(if (options.isVisible) 1f else 0f) return circleManager?.let { CircleImpl(this, it.create(circleOptions)) } val circle = CircleImpl(this, "c${circleId++}", options) synchronized(this) { val circleManager = circleManager if (circleManager == null) { pendingCircles.add(circle) } else { circle.update(circleManager) } } return circle } override fun clear() { Loading Loading @@ -208,20 +291,27 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap val fills = fillManager?.annotations?.values() val symbols = symbolManager?.annotations?.values() val update: (Style) -> Unit = { circles?.let { circleManager?.update(it) } lines?.let { lineManager?.update(it) } fills?.let { fillManager?.update(it) } symbols?.let { symbolManager?.update(it) } circles?.let { runCatching { circleManager?.update(it) } } lines?.let { runCatching { lineManager?.update(it) } } fills?.let { runCatching { fillManager?.update(it) } } symbols?.let { runCatching { symbolManager?.update(it) } } } // TODO: Serve map styles locally when (storedMapType) { MAP_TYPE_NORMAL -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjui4020201oo1fmca7yuwbor"), update) MAP_TYPE_SATELLITE -> map?.setStyle(Style.SATELLITE, update) MAP_TYPE_SATELLITE -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update) MAP_TYPE_TERRAIN -> map?.setStyle(Style.OUTDOORS, update) MAP_TYPE_HYBRID -> map?.setStyle(Style.SATELLITE_STREETS, update) else -> map?.setStyle(Style.LIGHT, update) MAP_TYPE_HYBRID -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update) //MAP_TYPE_NONE, MAP_TYPE_NORMAL, else -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjui4020201oo1fmca7yuwbor"), update) } map?.let { BitmapDescriptorFactoryImpl.registerMap(it) } } override fun setWatermarkEnabled(watermark: Boolean) { map?.uiSettings?.isLogoEnabled = watermark } override fun isTrafficEnabled(): Boolean { Loading Loading @@ -261,7 +351,10 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap override fun setLocationSource(locationSource: ILocationSourceDelegate) { Log.d(TAG, "unimplemented Method: setLocationSource") } override fun setContentDescription(desc: String?) { mapView?.contentDescription = desc } override fun getUiSettings(): IUiSettingsDelegate? = map?.uiSettings?.let { UiSettingsImpl(it) } Loading Loading @@ -360,80 +453,159 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap cameraIdleListener = listener } fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) { if (!created) { mapView?.onCreate(savedInstanceState?.toMapbox()) mapView?.getMapAsync(this::initMap) created = true } } private fun hasSymbolAt(latlng: com.mapbox.mapboxsdk.geometry.LatLng): Boolean { val point = map?.projection?.toScreenLocation(latlng) ?: return false val features = map?.queryRenderedFeatures(point, SymbolManager.ID_GEOJSON_LAYER) ?: return false return !features.isEmpty() return features.isNotEmpty() } private fun initMap(map: MapboxMap) { if (this.map != null) return this.map = map map.addOnCameraIdleListener { try { cameraChangeListener?.onCameraChange(map.cameraPosition.toGms()) } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraIdleListener { try { cameraIdleListener?.onCameraIdle() } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraMoveListener { try { cameraMoveListener?.onCameraMove() } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraMoveStartedListener { try { cameraMoveStartedListener?.onCameraMoveStarted(it) } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraMoveCancelListener { try { cameraMoveCanceledListener?.onCameraMoveCanceled() } catch (e: Exception) { Log.w(TAG, e) } } map.addOnMapClickListener { latlng -> try { mapClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapClick(latlng.toGms()); } } catch (e: Exception) { Log.w(TAG, e) } false } map.addOnMapLongClickListener { latlng -> try { mapLongClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapLongClick(latlng.toGms()); } } catch (e: Exception) { Log.w(TAG, e) } false } applyMapType() options.minZoomPreference?.let { if (it != 0f) map.setMinZoomPreference(it.toDouble()) } options.maxZoomPreference?.let { if (it != 0f) map.setMaxZoomPreference(it.toDouble()) } options.latLngBoundsForCameraTarget?.let { map.setLatLngBoundsForCameraTarget(it.toMapbox()) } options.compassEnabled?.let { map.uiSettings.isCompassEnabled = it } options.rotateGesturesEnabled?.let { map.uiSettings.isRotateGesturesEnabled = it } options.scrollGesturesEnabled?.let { map.uiSettings.isScrollGesturesEnabled = it } options.tiltGesturesEnabled?.let { map.uiSettings.isTiltGesturesEnabled = it } options.camera?.let { map.cameraPosition = it.toMapbox() } waitingCameraUpdates.forEach { map.moveCamera(it) } synchronized(mapLock) { Log.d(TAG, "Invoking ${initializedCallbackList.size} callbacks delayed, as map is initialized") for (callback in initializedCallbackList) { try { callback.onMapReady(this) } catch (e: Exception) { Log.w(TAG, e) } } initialized = true } map.getStyle { mapView?.let { view -> BitmapDescriptorFactoryImpl.registerMap(map) circleManager = CircleManager(view, map, it) lineManager = LineManager(view, map, it) lineManager?.lineCap = LINE_CAP_ROUND fillManager = FillManager(view, map, it) symbolManager = SymbolManager(view, map, it) lineManager?.lineCap = LINE_CAP_ROUND synchronized(this) { val symbolManager = SymbolManager(view, map, it) pendingMarkers.forEach { it.update(symbolManager) } pendingMarkers.clear() this.symbolManager = symbolManager } symbolManager?.iconAllowOverlap = true symbolManager?.addClickListener { markers[it.id]?.let { markerClickListener?.onMarkerClick(it) } } symbolManager?.addClickListener { try { markers[it.id]?.let { markerClickListener?.onMarkerClick(it) } } catch (e: Exception) { Log.w(TAG, e) } } symbolManager?.addDragListener(object : OnSymbolDragListener { override fun onAnnotationDragStarted(annotation: Symbol?) { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDragStart(it) } } catch (e: Exception) { Log.w(TAG, e) } } override fun onAnnotationDrag(annotation: Symbol?) { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDrag(it) } } catch (e: Exception) { Log.w(TAG, e) } } override fun onAnnotationDragFinished(annotation: Symbol?) { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDragEnd(it) } } catch (e: Exception) { Log.w(TAG, e) } } }) map.addOnCameraIdleListener { cameraChangeListener?.onCameraChange(map.cameraPosition.toGms()) } map.addOnCameraIdleListener { cameraIdleListener?.onCameraIdle() } map.addOnCameraMoveListener { cameraMoveListener?.onCameraMove() } map.addOnCameraMoveStartedListener { cameraMoveStartedListener?.onCameraMoveStarted(it) } map.addOnCameraMoveCancelListener { cameraMoveCanceledListener?.onCameraMoveCanceled() } map.addOnMapClickListener { val latlng = it mapClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapClick(latlng.toGms()); } false } map.addOnMapLongClickListener { val latlng = it mapLongClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapLongClick(latlng.toGms()); } false } synchronized(mapLock) { for (callback in initializedCallbackList) { try { callback.onMapReady(this) } catch (e: RemoteException) { Log.w(TAG, e) } } initialized = true } } } override fun useViewLifecycleWhenInFragment(): Boolean { Log.d(TAG, "unimplemented Method: useViewLifecycleWhenInFragment") return false } fun onResume() = mapView?.onResume() fun onPause() = mapView?.onPause() fun onDestroy() { override fun onResume() = mapView?.onResume() ?: Unit override fun onPause() = mapView?.onPause() ?: Unit override fun onDestroy() { circleManager?.onDestroy() circleManager = null lineManager?.onDestroy() Loading @@ -442,6 +614,8 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap fillManager = null symbolManager?.onDestroy() symbolManager = null pendingMarkers.clear() markers.clear() BitmapDescriptorFactoryImpl.unregisterMap(map) view.removeView(mapView) // TODO can crash? Loading @@ -449,8 +623,24 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap mapView = null } fun onLowMemory() = mapView?.onLowMemory() fun onSaveInstanceState(outState: Bundle) { override fun onStart() { Log.d(TAG, "unimplemented Method: onStart") } override fun onStop() { Log.d(TAG, "unimplemented Method: onStop") } override fun onEnterAmbient(bundle: Bundle?) { Log.d(TAG, "unimplemented Method: onEnterAmbient") } override fun onExitAmbient() { Log.d(TAG, "unimplemented Method: onExitAmbient") } override fun onLowMemory() = mapView?.onLowMemory() ?: Unit override fun onSaveInstanceState(outState: Bundle) { val newBundle = Bundle() mapView?.onSaveInstanceState(newBundle) outState.putAll(newBundle.toGms()) Loading @@ -459,8 +649,14 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap fun getMapAsync(callback: IOnMapReadyCallback) { synchronized(mapLock) { if (initialized) { Log.d(TAG, "Invoking callback instantly, as map is initialized") try { callback.onMapReady(this) } catch (e: Exception) { Log.w(TAG, e) } } else { Log.d(TAG, "Delay callback invocation, as map is not yet initialized") initializedCallbackList.add(callback) } } Loading Loading
GmsApi @ bfe649fc Compare 7197b832 to bfe649fc Original line number Diff line number Diff line Subproject commit 7197b8320b4e6d55d15b76d4faf05adaba36bf72 Subproject commit bfe649fc36a40e2498f1ecf123edfb9278fe0a1d
play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +292 −96 Original line number Diff line number Diff line Loading @@ -16,18 +16,20 @@ package org.microg.gms.maps.mapbox import android.annotation.SuppressLint import android.content.Context import android.location.Location import android.os.Bundle import android.os.Parcel import android.os.RemoteException import android.support.annotation.IdRes import android.support.annotation.Keep import android.support.v4.util.LongSparseArray import android.util.DisplayMetrics import android.util.Log import android.view.Gravity import android.view.View import android.widget.FrameLayout import android.widget.RelativeLayout import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.internal.* Loading @@ -38,6 +40,7 @@ import com.mapbox.mapboxsdk.LibraryLoader import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.R import com.mapbox.mapboxsdk.camera.CameraUpdate import com.mapbox.mapboxsdk.constants.MapboxConstants import com.mapbox.mapboxsdk.maps.MapView import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.Style Loading @@ -53,9 +56,9 @@ import org.microg.gms.maps.mapbox.utils.MultiArchLoader import org.microg.gms.maps.mapbox.utils.toGms import org.microg.gms.maps.mapbox.utils.toMapbox fun <T : Any> LongSparseArray<T>.values() = (0..size()).map { valueAt(it) }.mapNotNull { it } private fun <T : Any> LongSparseArray<T>.values() = (0..size()).map { valueAt(it) }.mapNotNull { it } class GoogleMapImpl(private val context: Context, private val options: GoogleMapOptions) : IGoogleMapDelegate.Stub() { class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) : IGoogleMapDelegate.Stub() { val view: FrameLayout var map: MapboxMap? = null Loading @@ -64,10 +67,10 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap get() = context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT private var mapView: MapView? private var created = false private var initialized = false private val initializedCallbackList = mutableListOf<IOnMapReadyCallback>() private val mapLock = Object() val markers = mutableMapOf<Long, MarkerImpl>() private var cameraChangeListener: IOnCameraChangeListener? = null private var cameraMoveListener: IOnCameraMoveListener? = null Loading @@ -79,47 +82,130 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap private var markerClickListener: IOnMarkerClickListener? = null private var markerDragListener: IOnMarkerDragListener? = null var circleManager: CircleManager? = null var lineManager: LineManager? = null var fillManager: FillManager? = null var circleManager: CircleManager? = null val pendingCircles = mutableSetOf<CircleImpl>() var circleId = 0L var symbolManager: SymbolManager? = null var storedMapType: Int = MAP_TYPE_NORMAL val pendingMarkers = mutableSetOf<MarkerImpl>() val markers = mutableMapOf<Long, MarkerImpl>() var markerId = 0L var storedMapType: Int = options.mapType val waitingCameraUpdates = mutableListOf<CameraUpdate>() init { val mapContext = MapContext(context) LibraryLoader.setLibraryLoader(MultiArchLoader(mapContext, context)) Mapbox.getInstance(mapContext, BuildConfig.MAPBOX_KEY) val fakeWatermark = View(mapContext) fakeWatermark.layoutParams = object : RelativeLayout.LayoutParams(0, 0) { @SuppressLint("RtlHardcoded") override fun addRule(verb: Int, subject: Int) { super.addRule(verb, subject) val rules = this.rules var gravity = 0 if (rules[RelativeLayout.ALIGN_PARENT_BOTTOM] == RelativeLayout.TRUE) gravity = gravity or Gravity.BOTTOM if (rules[RelativeLayout.ALIGN_PARENT_TOP] == RelativeLayout.TRUE) gravity = gravity or Gravity.TOP if (rules[RelativeLayout.ALIGN_PARENT_LEFT] == RelativeLayout.TRUE) gravity = gravity or Gravity.LEFT if (rules[RelativeLayout.ALIGN_PARENT_RIGHT] == RelativeLayout.TRUE) gravity = gravity or Gravity.RIGHT if (rules[RelativeLayout.ALIGN_PARENT_START] == RelativeLayout.TRUE) gravity = gravity or Gravity.START if (rules[RelativeLayout.ALIGN_PARENT_END] == RelativeLayout.TRUE) gravity = gravity or Gravity.END map?.uiSettings?.logoGravity = gravity } } this.view = object : FrameLayout(mapContext) { @Keep fun <T : View> findViewTraversal(@IdRes id: Int): T? { return null } @Keep fun <T : View> findViewWithTagTraversal(tag: Any): T? { if ("GoogleWatermark" == tag) { return try { @Suppress("UNCHECKED_CAST") fakeWatermark as T } catch (e: ClassCastException) { null } } return null } } this.mapView = MapView(mapContext) this.view.addView(this.mapView) } override fun getCameraPosition(): CameraPosition? = map?.cameraPosition?.toGms() override fun getMaxZoomLevel(): Float = map?.maxZoomLevel?.toFloat() ?: 20f override fun getMinZoomLevel(): Float = map?.minZoomLevel?.toFloat() ?: 1f override fun moveCamera(cameraUpdate: IObjectWrapper?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.moveCamera(it) } ?: Unit override fun getMaxZoomLevel(): Float = (map?.maxZoomLevel?.toFloat() ?: 20f) + 1f override fun getMinZoomLevel(): Float = (map?.minZoomLevel?.toFloat() ?: 0f) + 1f override fun moveCamera(cameraUpdate: IObjectWrapper?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.moveCamera(update) } else { waitingCameraUpdates.add(update) } } override fun animateCamera(cameraUpdate: IObjectWrapper?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it) } ?: Unit override fun animateCamera(cameraUpdate: IObjectWrapper?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.animateCamera(update) } else { waitingCameraUpdates.add(update) } } override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it, callback?.toMapbox()) } ?: Unit override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.animateCamera(update, callback?.toMapbox()) } else { waitingCameraUpdates.add(update) callback?.onFinish() } } override fun animateCameraWithDurationAndCallback(cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback?) = cameraUpdate.unwrap<CameraUpdate>()?.let { map?.animateCamera(it, duration, callback?.toMapbox()) } ?: Unit override fun animateCameraWithDurationAndCallback(cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback?) { val update = cameraUpdate.unwrap<CameraUpdate>() ?: return val map = this.map if (map != null) { map.animateCamera(update, duration, callback?.toMapbox()) } else { waitingCameraUpdates.add(update) callback?.onFinish() } } override fun stopAnimation() = map?.cancelTransitions() ?: Unit override fun setMinZoomPreference(minZoom: Float) { map?.setMinZoomPreference(minZoom.toDouble() - 1) } override fun setMaxZoomPreference(maxZoom: Float) { map?.setMaxZoomPreference(maxZoom.toDouble() - 1) } override fun resetMinMaxZoomPreference() { map?.setMinZoomPreference(MapboxConstants.MINIMUM_ZOOM.toDouble()) map?.setMaxZoomPreference(MapboxConstants.MAXIMUM_ZOOM.toDouble()) } override fun setLatLngBoundsForCameraTarget(bounds: LatLngBounds?) { map?.setLatLngBoundsForCameraTarget(bounds?.toMapbox()) } override fun addPolyline(options: PolylineOptions): IPolylineDelegate? { val lineOptions = LineOptions() .withLatLngs(options.points.map { it.toMapbox() }) Loading @@ -131,26 +217,23 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap override fun addPolygon(options: PolygonOptions): IPolygonDelegate? { Log.d(TAG, "unimplemented Method: addPolygon") return null val fillOptions = FillOptions() .withLatLngs(listOf(options.points.map { it.toMapbox() })) .withFillColor(ColorUtils.colorToRgbaString(options.fillColor)) .withFillOpacity(if (options.isVisible) 1f else 0f) return fillManager?.let { PolygonImpl(this, it.create(fillOptions)) } } override fun addMarker(options: MarkerOptions): IMarkerDelegate? { var intBits = java.lang.Float.floatToIntBits(options.zIndex) if (intBits < 0) intBits = intBits xor 0x7fffffff val symbolOptions = SymbolOptions() .withIconOpacity(if (options.isVisible) options.alpha else 0f) .withIconRotate(options.rotation) .withZIndex(intBits) .withDraggable(options.isDraggable) options.position?.let { symbolOptions.withLatLng(it.toMapbox()) } options.icon?.remoteObject.unwrap<BitmapDescriptorImpl>()?.applyTo(symbolOptions, floatArrayOf(options.anchorU, options.anchorV), dpiFactor) val symbol = symbolManager?.create(symbolOptions) ?: return null val marker = MarkerImpl(this, symbol, floatArrayOf(options.anchorU, options.anchorV), options.icon?.remoteObject.unwrap<BitmapDescriptorImpl>(), options.alpha, options.title, options.snippet) markers.put(symbol.id, marker) val marker = MarkerImpl(this, "m${markerId++}", options) synchronized(this) { val symbolManager = symbolManager if (symbolManager == null) { pendingMarkers.add(marker) } else { marker.update(symbolManager) } } return marker } Loading @@ -165,16 +248,16 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap } override fun addCircle(options: CircleOptions): ICircleDelegate? { val circleOptions = com.mapbox.mapboxsdk.plugins.annotation.CircleOptions() .withLatLng(options.center.toMapbox()) .withCircleColor(ColorUtils.colorToRgbaString(options.fillColor)) .withCircleRadius(options.radius.toFloat()) .withCircleStrokeColor(ColorUtils.colorToRgbaString(options.strokeColor)) .withCircleStrokeWidth(options.strokeWidth / dpiFactor) .withCircleOpacity(if (options.isVisible) 1f else 0f) .withCircleStrokeOpacity(if (options.isVisible) 1f else 0f) return circleManager?.let { CircleImpl(this, it.create(circleOptions)) } val circle = CircleImpl(this, "c${circleId++}", options) synchronized(this) { val circleManager = circleManager if (circleManager == null) { pendingCircles.add(circle) } else { circle.update(circleManager) } } return circle } override fun clear() { Loading Loading @@ -208,20 +291,27 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap val fills = fillManager?.annotations?.values() val symbols = symbolManager?.annotations?.values() val update: (Style) -> Unit = { circles?.let { circleManager?.update(it) } lines?.let { lineManager?.update(it) } fills?.let { fillManager?.update(it) } symbols?.let { symbolManager?.update(it) } circles?.let { runCatching { circleManager?.update(it) } } lines?.let { runCatching { lineManager?.update(it) } } fills?.let { runCatching { fillManager?.update(it) } } symbols?.let { runCatching { symbolManager?.update(it) } } } // TODO: Serve map styles locally when (storedMapType) { MAP_TYPE_NORMAL -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjui4020201oo1fmca7yuwbor"), update) MAP_TYPE_SATELLITE -> map?.setStyle(Style.SATELLITE, update) MAP_TYPE_SATELLITE -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update) MAP_TYPE_TERRAIN -> map?.setStyle(Style.OUTDOORS, update) MAP_TYPE_HYBRID -> map?.setStyle(Style.SATELLITE_STREETS, update) else -> map?.setStyle(Style.LIGHT, update) MAP_TYPE_HYBRID -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update) //MAP_TYPE_NONE, MAP_TYPE_NORMAL, else -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjui4020201oo1fmca7yuwbor"), update) } map?.let { BitmapDescriptorFactoryImpl.registerMap(it) } } override fun setWatermarkEnabled(watermark: Boolean) { map?.uiSettings?.isLogoEnabled = watermark } override fun isTrafficEnabled(): Boolean { Loading Loading @@ -261,7 +351,10 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap override fun setLocationSource(locationSource: ILocationSourceDelegate) { Log.d(TAG, "unimplemented Method: setLocationSource") } override fun setContentDescription(desc: String?) { mapView?.contentDescription = desc } override fun getUiSettings(): IUiSettingsDelegate? = map?.uiSettings?.let { UiSettingsImpl(it) } Loading Loading @@ -360,80 +453,159 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap cameraIdleListener = listener } fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) { if (!created) { mapView?.onCreate(savedInstanceState?.toMapbox()) mapView?.getMapAsync(this::initMap) created = true } } private fun hasSymbolAt(latlng: com.mapbox.mapboxsdk.geometry.LatLng): Boolean { val point = map?.projection?.toScreenLocation(latlng) ?: return false val features = map?.queryRenderedFeatures(point, SymbolManager.ID_GEOJSON_LAYER) ?: return false return !features.isEmpty() return features.isNotEmpty() } private fun initMap(map: MapboxMap) { if (this.map != null) return this.map = map map.addOnCameraIdleListener { try { cameraChangeListener?.onCameraChange(map.cameraPosition.toGms()) } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraIdleListener { try { cameraIdleListener?.onCameraIdle() } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraMoveListener { try { cameraMoveListener?.onCameraMove() } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraMoveStartedListener { try { cameraMoveStartedListener?.onCameraMoveStarted(it) } catch (e: Exception) { Log.w(TAG, e) } } map.addOnCameraMoveCancelListener { try { cameraMoveCanceledListener?.onCameraMoveCanceled() } catch (e: Exception) { Log.w(TAG, e) } } map.addOnMapClickListener { latlng -> try { mapClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapClick(latlng.toGms()); } } catch (e: Exception) { Log.w(TAG, e) } false } map.addOnMapLongClickListener { latlng -> try { mapLongClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapLongClick(latlng.toGms()); } } catch (e: Exception) { Log.w(TAG, e) } false } applyMapType() options.minZoomPreference?.let { if (it != 0f) map.setMinZoomPreference(it.toDouble()) } options.maxZoomPreference?.let { if (it != 0f) map.setMaxZoomPreference(it.toDouble()) } options.latLngBoundsForCameraTarget?.let { map.setLatLngBoundsForCameraTarget(it.toMapbox()) } options.compassEnabled?.let { map.uiSettings.isCompassEnabled = it } options.rotateGesturesEnabled?.let { map.uiSettings.isRotateGesturesEnabled = it } options.scrollGesturesEnabled?.let { map.uiSettings.isScrollGesturesEnabled = it } options.tiltGesturesEnabled?.let { map.uiSettings.isTiltGesturesEnabled = it } options.camera?.let { map.cameraPosition = it.toMapbox() } waitingCameraUpdates.forEach { map.moveCamera(it) } synchronized(mapLock) { Log.d(TAG, "Invoking ${initializedCallbackList.size} callbacks delayed, as map is initialized") for (callback in initializedCallbackList) { try { callback.onMapReady(this) } catch (e: Exception) { Log.w(TAG, e) } } initialized = true } map.getStyle { mapView?.let { view -> BitmapDescriptorFactoryImpl.registerMap(map) circleManager = CircleManager(view, map, it) lineManager = LineManager(view, map, it) lineManager?.lineCap = LINE_CAP_ROUND fillManager = FillManager(view, map, it) symbolManager = SymbolManager(view, map, it) lineManager?.lineCap = LINE_CAP_ROUND synchronized(this) { val symbolManager = SymbolManager(view, map, it) pendingMarkers.forEach { it.update(symbolManager) } pendingMarkers.clear() this.symbolManager = symbolManager } symbolManager?.iconAllowOverlap = true symbolManager?.addClickListener { markers[it.id]?.let { markerClickListener?.onMarkerClick(it) } } symbolManager?.addClickListener { try { markers[it.id]?.let { markerClickListener?.onMarkerClick(it) } } catch (e: Exception) { Log.w(TAG, e) } } symbolManager?.addDragListener(object : OnSymbolDragListener { override fun onAnnotationDragStarted(annotation: Symbol?) { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDragStart(it) } } catch (e: Exception) { Log.w(TAG, e) } } override fun onAnnotationDrag(annotation: Symbol?) { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDrag(it) } } catch (e: Exception) { Log.w(TAG, e) } } override fun onAnnotationDragFinished(annotation: Symbol?) { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDragEnd(it) } } catch (e: Exception) { Log.w(TAG, e) } } }) map.addOnCameraIdleListener { cameraChangeListener?.onCameraChange(map.cameraPosition.toGms()) } map.addOnCameraIdleListener { cameraIdleListener?.onCameraIdle() } map.addOnCameraMoveListener { cameraMoveListener?.onCameraMove() } map.addOnCameraMoveStartedListener { cameraMoveStartedListener?.onCameraMoveStarted(it) } map.addOnCameraMoveCancelListener { cameraMoveCanceledListener?.onCameraMoveCanceled() } map.addOnMapClickListener { val latlng = it mapClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapClick(latlng.toGms()); } false } map.addOnMapLongClickListener { val latlng = it mapLongClickListener?.let { if (!hasSymbolAt(latlng)) it.onMapLongClick(latlng.toGms()); } false } synchronized(mapLock) { for (callback in initializedCallbackList) { try { callback.onMapReady(this) } catch (e: RemoteException) { Log.w(TAG, e) } } initialized = true } } } override fun useViewLifecycleWhenInFragment(): Boolean { Log.d(TAG, "unimplemented Method: useViewLifecycleWhenInFragment") return false } fun onResume() = mapView?.onResume() fun onPause() = mapView?.onPause() fun onDestroy() { override fun onResume() = mapView?.onResume() ?: Unit override fun onPause() = mapView?.onPause() ?: Unit override fun onDestroy() { circleManager?.onDestroy() circleManager = null lineManager?.onDestroy() Loading @@ -442,6 +614,8 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap fillManager = null symbolManager?.onDestroy() symbolManager = null pendingMarkers.clear() markers.clear() BitmapDescriptorFactoryImpl.unregisterMap(map) view.removeView(mapView) // TODO can crash? Loading @@ -449,8 +623,24 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap mapView = null } fun onLowMemory() = mapView?.onLowMemory() fun onSaveInstanceState(outState: Bundle) { override fun onStart() { Log.d(TAG, "unimplemented Method: onStart") } override fun onStop() { Log.d(TAG, "unimplemented Method: onStop") } override fun onEnterAmbient(bundle: Bundle?) { Log.d(TAG, "unimplemented Method: onEnterAmbient") } override fun onExitAmbient() { Log.d(TAG, "unimplemented Method: onExitAmbient") } override fun onLowMemory() = mapView?.onLowMemory() ?: Unit override fun onSaveInstanceState(outState: Bundle) { val newBundle = Bundle() mapView?.onSaveInstanceState(newBundle) outState.putAll(newBundle.toGms()) Loading @@ -459,8 +649,14 @@ class GoogleMapImpl(private val context: Context, private val options: GoogleMap fun getMapAsync(callback: IOnMapReadyCallback) { synchronized(mapLock) { if (initialized) { Log.d(TAG, "Invoking callback instantly, as map is initialized") try { callback.onMapReady(this) } catch (e: Exception) { Log.w(TAG, e) } } else { Log.d(TAG, "Delay callback invocation, as map is not yet initialized") initializedCallbackList.add(callback) } } Loading