diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotation.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotation.kt index 30091e86e..50f42edd7 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotation.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotation.kt @@ -183,12 +183,12 @@ class RNMBXPointAnnotation(private val mContext: Context, private val mManager: } } + val isDraggable: Boolean + get() = mDraggable + fun setDraggable(draggable: Boolean) { + // Mapbox isDraggable stays false (set in makeMarker) mDraggable = draggable - annotation?.let { annotation -> - annotation.isDraggable = draggable - pointAnnotations?.update(annotation) - } } fun setReactSelected(selected: Boolean) { @@ -239,7 +239,8 @@ class RNMBXPointAnnotation(private val mContext: Context, private val mManager: val options = mCoordinate?.let { PointAnnotationOptions() .withPoint(it) - .withDraggable(mDraggable) + // SDK drag is disabled, long-press drag is handled by RNMBXPointAnnotationCoordinator + .withDraggable(false) .withIconSize(1.0) .withSymbolSortKey(10.0) } diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotationCoordinator.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotationCoordinator.kt index b5da67910..a6c3c5731 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotationCoordinator.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/annotation/RNMBXPointAnnotationCoordinator.kt @@ -1,28 +1,30 @@ package com.rnmapbox.rnmbx.components.annotation import com.mapbox.maps.MapView -import com.mapbox.maps.plugin.annotation.Annotation +import com.mapbox.maps.ScreenCoordinate import com.mapbox.maps.plugin.annotation.AnnotationConfig import com.mapbox.maps.plugin.annotation.annotations import com.mapbox.maps.plugin.annotation.generated.OnPointAnnotationClickListener -import com.mapbox.maps.plugin.annotation.generated.OnPointAnnotationDragListener import com.mapbox.maps.plugin.annotation.generated.PointAnnotation import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager import com.mapbox.maps.plugin.annotation.generated.PointAnnotationOptions import com.mapbox.maps.plugin.annotation.generated.createPointAnnotationManager -import com.rnmapbox.rnmbx.components.annotation.RNMBXPointAnnotation import com.rnmapbox.rnmbx.utils.Logger class RNMBXPointAnnotationCoordinator(val mapView: MapView, layerId: String? = "RNMBX-mapview-annotations") { val manager: PointAnnotationManager; var annotationClicked = false - var annotationDragged = false var selected: RNMBXPointAnnotation? = null + private var draggedAnnotation: RNMBXPointAnnotation? = null + val annotations: MutableMap = hashMapOf() val callouts: MutableMap = hashMapOf() + val isDragging: Boolean + get() = draggedAnnotation != null + init { manager = if (layerId != null) { mapView.annotations.createPointAnnotationManager(AnnotationConfig(layerId = layerId)) @@ -33,45 +35,6 @@ class RNMBXPointAnnotationCoordinator(val mapView: MapView, layerId: String? = " onAnnotationClick(pointAnnotation) false }) - manager.addDragListener(object : OnPointAnnotationDragListener { - override fun onAnnotationDragStarted(_annotation: Annotation<*>) { - annotationDragged = true; - var reactAnnotation: RNMBXPointAnnotation? = null - for (key in annotations.keys) { - val annotation = annotations[key] - val curMarkerID = annotation?.mapboxID - if (_annotation.id == curMarkerID) { - reactAnnotation = annotation - } - } - reactAnnotation?.let { it.onDragStart() } - } - - override fun onAnnotationDrag(_annotation: Annotation<*>) { - var reactAnnotation: RNMBXPointAnnotation? = null - for (key in annotations.keys) { - val annotation = annotations[key] - val curMarkerID = annotation?.mapboxID - if (_annotation.id == curMarkerID) { - reactAnnotation = annotation - } - } - reactAnnotation?.let { it.onDrag() } - } - - override fun onAnnotationDragFinished(_annotation: Annotation<*>) { - annotationDragged = false; - var reactAnnotation: RNMBXPointAnnotation? = null - for (key in annotations.keys) { - val annotation = annotations[key] - val curMarkerID = annotation?.mapboxID - if (_annotation.id == curMarkerID) { - reactAnnotation = annotation - } - } - reactAnnotation?.let { it.onDragEnd() } - } - }) } fun getAndClearAnnotationClicked(): Boolean { @@ -82,12 +45,38 @@ class RNMBXPointAnnotationCoordinator(val mapView: MapView, layerId: String? = " return false } - fun getAndClearAnnotationDragged(): Boolean { - if (annotationDragged) { - annotationDragged = false + /** + * Starts a custom long-press drag if a draggable RN PointAnnotation is under [screenCoordinate]. + * Mapbox SDK drag is disabled; this mirrors iOS UILongPress-driven dragging. + */ + fun handleLongPress(screenCoordinate: ScreenCoordinate): Boolean { + if (draggedAnnotation != null) { return true } - return false + val pointAnnotation = manager.queryMapForFeatures(screenCoordinate) ?: return false + val reactAnnotation = lookupForClick(pointAnnotation) ?: return false + if (!reactAnnotation.isDraggable) { + return false + } + draggedAnnotation = reactAnnotation + reactAnnotation.onDragStart() + return true + } + + fun handleDragMove(screenCoordinate: ScreenCoordinate): Boolean { + val reactAnnotation = draggedAnnotation ?: return false + val mapboxMap = mapView.mapboxMap + val point = mapboxMap.coordinateForPixel(screenCoordinate) + reactAnnotation.setCoordinate(point) + reactAnnotation.onDrag() + return true + } + + fun handleDragEnd(): Boolean { + val reactAnnotation = draggedAnnotation ?: return false + draggedAnnotation = null + reactAnnotation.onDragEnd() + return true } fun lookupForClick(point: PointAnnotation): RNMBXPointAnnotation? { @@ -146,6 +135,9 @@ class RNMBXPointAnnotationCoordinator(val mapView: MapView, layerId: String? = " if (annotation == selected) { selected = null } + if (annotation == draggedAnnotation) { + draggedAnnotation = null + } annotations.remove(annotation.iD) } @@ -166,6 +158,7 @@ class RNMBXPointAnnotationCoordinator(val mapView: MapView, layerId: String? = " } fun destroy() { + draggedAnnotation = null mapView.annotations.removeAnnotationManager(manager) } diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapView.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapView.kt index ce02ddb4e..604f7b9fa 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapView.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/mapview/RNMBXMapView.kt @@ -5,6 +5,7 @@ import android.graphics.BitmapFactory import android.os.Handler import android.os.Looper import android.view.Gravity +import android.view.MotionEvent import android.view.View import android.view.View.OnLayoutChangeListener import android.view.ViewGroup @@ -310,14 +311,27 @@ open class RNMBXMapView(private val mContext: Context, var mManager: RNMBXMapVie gesturesPlugin.addOnMoveListener(object : OnMoveListener { override fun onMoveBegin(moveGestureDetector: MoveGestureDetector) { + if (pointAnnotationCoordinators.any { it.isDragging }) { + return + } mapGestureBegin(MapGestureType.Move, moveGestureDetector) } override fun onMove(moveGestureDetector: MoveGestureDetector): Boolean { + val focalPoint = ScreenCoordinate( + moveGestureDetector.focalPoint.x.toDouble(), + moveGestureDetector.focalPoint.y.toDouble() + ) + if (pointAnnotationCoordinators.any { it.handleDragMove(focalPoint) }) { + return true + } return mapGesture(MapGestureType.Move, moveGestureDetector) } override fun onMoveEnd(moveGestureDetector: MoveGestureDetector) { + if (pointAnnotationCoordinators.any { it.handleDragEnd() }) { + return + } mapGestureEnd(MapGestureType.Move, moveGestureDetector) } }) @@ -776,17 +790,14 @@ open class RNMBXMapView(private val mContext: Context, var mManager: RNMBXMapVie } override fun onMapLongClick(point: Point): Boolean { - val _this = this - if (pointAnnotationCoordinators.any { it.getAndClearAnnotationDragged() }) { + val screenPointPx = mMap?.pixelForCoordinate(point) ?: return false + // Long-press on a draggable PointAnnotation starts custom drag (iOS parity) + if (pointAnnotationCoordinators.any { it.handleLongPress(screenPointPx) }) { return true } - val screenPointPx = mMap?.pixelForCoordinate(point) - if (screenPointPx != null) { - val screenPointDp = toDp(screenPointPx) - val event = MapClickEvent(_this, LatLng(point), screenPointDp, EventTypes.MAP_LONG_CLICK) - mManager.handleEvent(event) - } - + val screenPointDp = toDp(screenPointPx) + val event = MapClickEvent(this, LatLng(point), screenPointDp, EventTypes.MAP_LONG_CLICK) + mManager.handleEvent(event) return false } @@ -1277,6 +1288,7 @@ open class RNMBXMapView(private val mContext: Context, var mManager: RNMBXMapVie mapView.setLayoutParams(matchParent) addView(mapView) } + installMapTouchListener() this.addOnLayoutChangeListener(this) val map = mapView.getMapboxMap() @@ -1620,27 +1632,35 @@ open class RNMBXMapView(private val mContext: Context, var mManager: RNMBXMapVie // endregion } -// region requestDisallowInterceptTouchEvent -fun RNMBXMapView.updateRequestDisallowInterceptTouchEvent(oldValue: Boolean, value: Boolean) { - if (oldValue == value) { - return - } - if (value) { - withMapView { - it.setOnTouchListener { view, event -> +// region map touch (PointAnnotation drag + requestDisallowInterceptTouchEvent) +fun RNMBXMapView.installMapTouchListener() { + withMapView { map -> + map.setOnTouchListener { _, event -> + when (event.actionMasked) { + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + // End long-press drag even when the finger lifts without a move gesture + pointAnnotationCoordinators.any { it.handleDragEnd() } + } + } + if (requestDisallowInterceptTouchEvent) { + // Preserve pre-drag behavior: disallow parent intercept, forward, consume this.requestDisallowInterceptTouchEvent(true) - mapView.onTouchEvent(event) + map.onTouchEvent(event) true + } else { + // Observe only, do not consume or manually forward + false } } - } else { - withMapView { - it.setOnTouchListener { view, event -> - mapView.onTouchEvent(event) - } - } } } + +fun RNMBXMapView.updateRequestDisallowInterceptTouchEvent(oldValue: Boolean, value: Boolean) { + if (oldValue == value) { + return + } + installMapTouchListener() +} // endregion diff --git a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/layers/RNMBXFillLayerManager.kt b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/layers/RNMBXFillLayerManager.kt index e4ec83826..9f65c9f84 100644 --- a/android/src/main/java/com/rnmapbox/rnmbx/components/styles/layers/RNMBXFillLayerManager.kt +++ b/android/src/main/java/com/rnmapbox/rnmbx/components/styles/layers/RNMBXFillLayerManager.kt @@ -7,6 +7,7 @@ import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.viewmanagers.RNMBXFillLayerManagerInterface import com.facebook.react.uimanager.ViewManagerDelegate import com.facebook.react.viewmanagers.RNMBXFillLayerManagerDelegate +import com.rnmapbox.rnmbx.utils.extensions.asStringOrNull class RNMBXFillLayerManager : ViewGroupManager(), RNMBXFillLayerManagerInterface { @@ -40,12 +41,12 @@ class RNMBXFillLayerManager : ViewGroupManager(), @ReactProp(name = "aboveLayerID") override fun setAboveLayerID(layer: RNMBXFillLayer, aboveLayerID: Dynamic) { - layer.setAboveLayerID(aboveLayerID.asString()) + layer.setAboveLayerID(aboveLayerID.asStringOrNull()) } @ReactProp(name = "belowLayerID") override fun setBelowLayerID(layer: RNMBXFillLayer, belowLayerID: Dynamic) { - layer.setBelowLayerID(belowLayerID.asString()) + layer.setBelowLayerID(belowLayerID.asStringOrNull()) } @ReactProp(name = "layerIndex") diff --git a/docs/PointAnnotation.md b/docs/PointAnnotation.md index a915e5df6..9e9ded531 100644 --- a/docs/PointAnnotation.md +++ b/docs/PointAnnotation.md @@ -63,6 +63,7 @@ Manually selects/deselects annotation boolean ``` Enable or disable dragging. Defaults to false. +When enabled, a long press is required to begin dragging (iOS and Android). _defaults to:_ `false` diff --git a/docs/docs.json b/docs/docs.json index e27e8cc67..c4bca134a 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -6454,7 +6454,7 @@ "required": false, "type": "boolean", "default": "false", - "description": "Enable or disable dragging. Defaults to false." + "description": "Enable or disable dragging. Defaults to false.\nWhen enabled, a long press is required to begin dragging (iOS and Android)." }, { "name": "coordinate", diff --git a/src/components/PointAnnotation.tsx b/src/components/PointAnnotation.tsx index 3c889e042..353b64506 100644 --- a/src/components/PointAnnotation.tsx +++ b/src/components/PointAnnotation.tsx @@ -59,6 +59,7 @@ type Props = BaseProps & { /** * Enable or disable dragging. Defaults to false. + * When enabled, a long press is required to begin dragging (iOS and Android). */ draggable?: boolean;