Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions android/src/main/java/com/luggmaps/LuggMarkerView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.facebook.react.views.view.ReactViewGroup
import com.google.android.gms.maps.model.AdvancedMarker
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.luggmaps.events.MarkerDragEvent
import com.luggmaps.events.MarkerPressEvent
import com.luggmaps.extensions.dispatchEvent

Expand Down Expand Up @@ -58,6 +59,11 @@ class LuggMarkerView(context: Context) : ReactViewGroup(context) {
var rasterize: Boolean = true
private set

var draggable: Boolean = false
private set

var isDragging: Boolean = false

var didLayout: Boolean = false
private set

Expand Down Expand Up @@ -240,10 +246,26 @@ class LuggMarkerView(context: Context) : ReactViewGroup(context) {
this.rasterize = rasterize
}

fun setDraggable(draggable: Boolean) {
this.draggable = draggable
}

fun emitPressEvent(x: Float, y: Float) {
dispatchEvent(MarkerPressEvent(this, latitude, longitude, x, y))
}

fun emitDragStartEvent(x: Float, y: Float) {
dispatchEvent(MarkerDragEvent(this, MarkerDragEvent.DRAG_START, latitude, longitude, x, y))
}

fun emitDragChangeEvent(x: Float, y: Float) {
dispatchEvent(MarkerDragEvent(this, MarkerDragEvent.DRAG_CHANGE, latitude, longitude, x, y))
}

fun emitDragEndEvent(x: Float, y: Float) {
dispatchEvent(MarkerDragEvent(this, MarkerDragEvent.DRAG_END, latitude, longitude, x, y))
}

fun setName(name: String?) {
this.name = name
}
Expand Down
12 changes: 11 additions & 1 deletion android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ class LuggMarkerViewManager :
override fun createViewInstance(context: ThemedReactContext): LuggMarkerView = LuggMarkerView(context)

override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> =
mapOf("topMarkerPress" to mapOf("registrationName" to "onMarkerPress"))
mapOf(
"topMarkerPress" to mapOf("registrationName" to "onMarkerPress"),
"topMarkerDragStart" to mapOf("registrationName" to "onMarkerDragStart"),
"topMarkerDragChange" to mapOf("registrationName" to "onMarkerDragChange"),
"topMarkerDragEnd" to mapOf("registrationName" to "onMarkerDragEnd"),
)

override fun onDropViewInstance(view: LuggMarkerView) {
super.onDropViewInstance(view)
Expand Down Expand Up @@ -87,6 +92,11 @@ class LuggMarkerViewManager :
view.setRasterize(value)
}

@ReactProp(name = "draggable", defaultBoolean = false)
override fun setDraggable(view: LuggMarkerView, value: Boolean) {
view.setDraggable(value)
}

companion object {
const val NAME = "LuggMarkerView"
}
Expand Down
37 changes: 35 additions & 2 deletions android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class GoogleMapProvider(private val context: Context) :
GoogleMap.OnMapClickListener,
GoogleMap.OnMapLongClickListener,
GoogleMap.OnPolygonClickListener,
GoogleMap.OnMarkerClickListener {
GoogleMap.OnMarkerClickListener,
GoogleMap.OnMarkerDragListener {

override var delegate: MapProviderDelegate? = null
override val isMapReady: Boolean get() = _isMapReady
Expand Down Expand Up @@ -118,6 +119,7 @@ class GoogleMapProvider(private val context: Context) :
googleMap?.setOnMapLongClickListener(null)
googleMap?.setOnPolygonClickListener(null)
googleMap?.setOnMarkerClickListener(null)
googleMap?.setOnMarkerDragListener(null)
googleMap?.clear()
googleMap = null
_isMapReady = false
Expand All @@ -140,6 +142,7 @@ class GoogleMapProvider(private val context: Context) :
map.setOnMapLongClickListener(this)
map.setOnPolygonClickListener(this)
map.setOnMarkerClickListener(this)
map.setOnMarkerDragListener(this)

wrapperView?.touchEventHandler = { event ->
if (event.action == android.view.MotionEvent.ACTION_DOWN) {
Expand Down Expand Up @@ -215,6 +218,32 @@ class GoogleMapProvider(private val context: Context) :
return false
}

override fun onMarkerDragStart(marker: Marker) {
markerToViewMap[marker]?.let { view ->
view.isDragging = true
view.setCoordinate(marker.position.latitude, marker.position.longitude)
val point = googleMap?.projection?.toScreenLocation(marker.position)
view.emitDragStartEvent(point?.x?.toFloat() ?: 0f, point?.y?.toFloat() ?: 0f)
}
}

override fun onMarkerDrag(marker: Marker) {
markerToViewMap[marker]?.let { view ->
view.setCoordinate(marker.position.latitude, marker.position.longitude)
val point = googleMap?.projection?.toScreenLocation(marker.position)
view.emitDragChangeEvent(point?.x?.toFloat() ?: 0f, point?.y?.toFloat() ?: 0f)
}
}

override fun onMarkerDragEnd(marker: Marker) {
markerToViewMap[marker]?.let { view ->
view.isDragging = false
view.setCoordinate(marker.position.latitude, marker.position.longitude)
val point = googleMap?.projection?.toScreenLocation(marker.position)
view.emitDragEndEvent(point?.x?.toFloat() ?: 0f, point?.y?.toFloat() ?: 0f)
}
}

// endregion

// region Props
Expand Down Expand Up @@ -371,12 +400,15 @@ class GoogleMapProvider(private val context: Context) :
}

markerView.marker?.apply {
position = LatLng(markerView.latitude, markerView.longitude)
if (!markerView.isDragging) {
position = LatLng(markerView.latitude, markerView.longitude)
}
title = markerView.title
snippet = markerView.description
setAnchor(markerView.anchorX, markerView.anchorY)
zIndex = markerView.zIndex
rotation = markerView.rotate
isDraggable = markerView.draggable
}

if (markerView.hasCustomView && markerView.scaleChanged) {
Expand Down Expand Up @@ -404,6 +436,7 @@ class GoogleMapProvider(private val context: Context) :
marker.setAnchor(markerView.anchorX, markerView.anchorY)
marker.zIndex = markerView.zIndex
marker.rotation = markerView.rotate
marker.isDraggable = markerView.draggable

markerView.marker = marker
markerToViewMap[marker] = markerView
Expand Down
41 changes: 41 additions & 0 deletions android/src/main/java/com/luggmaps/events/MarkerDragEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.luggmaps.events

import android.view.View
import com.facebook.react.bridge.Arguments
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.Event

class MarkerDragEvent(
view: View,
private val eventType: String,
private val latitude: Double,
private val longitude: Double,
private val x: Float,
private val y: Float
) : Event<MarkerDragEvent>(UIManagerHelper.getSurfaceId(view), view.id) {
override fun getEventName() = eventType

override fun getEventData() =
Arguments.createMap().apply {
putMap(
"coordinate",
Arguments.createMap().apply {
putDouble("latitude", latitude)
putDouble("longitude", longitude)
}
)
putMap(
"point",
Arguments.createMap().apply {
putDouble("x", x.toDouble())
putDouble("y", y.toDouble())
}
)
}

companion object {
const val DRAG_START = "topMarkerDragStart"
const val DRAG_CHANGE = "topMarkerDragChange"
const val DRAG_END = "topMarkerDragEnd"
}
}
18 changes: 18 additions & 0 deletions docs/MARKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,27 @@ import { MapView, Marker } from '@lugg/maps';
| `rotate` | `number` | `0` | Rotation angle in degrees clockwise from north |
| `scale` | `number` | `1` | Scale factor for the marker |
| `rasterize` | `boolean` | `true` | Rasterize custom marker view to bitmap (iOS/Android only) |
| `draggable` | `boolean` | `false` | Whether the marker can be dragged by the user |
| `onPress` | `(event: MarkerPressEvent) => void` | - | Called when the marker is pressed. Event includes `coordinate` and `point` |
| `onDragStart` | `(event: MarkerDragEvent) => void` | - | Called when marker drag starts. Event includes `coordinate` and `point` |
| `onDragChange` | `(event: MarkerDragEvent) => void` | - | Called continuously as the marker is dragged. Event includes `coordinate` and `point` |
| `onDragEnd` | `(event: MarkerDragEvent) => void` | - | Called when marker drag ends. Event includes `coordinate` and `point` |
| `children` | `ReactNode` | - | Custom marker view |

## Draggable Markers

Set `draggable` to enable marker dragging. Use the drag event callbacks to track position changes.

```tsx
<Marker
coordinate={markerCoordinate}
draggable
onDragStart={(e) => console.log('Drag started', e.nativeEvent.coordinate)}
onDragChange={(e) => console.log('Dragging', e.nativeEvent.coordinate)}
onDragEnd={(e) => setMarkerCoordinate(e.nativeEvent.coordinate)}
/>
```

## Custom Markers

Use the `children` prop to render a custom marker view. The `anchor` prop controls the point that is placed at the coordinate.
Expand Down
7 changes: 7 additions & 0 deletions example/shared/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ function HomeContent() {
onCameraMove={(e) => formatCameraEvent(e, false)}
onCameraIdle={(e) => formatCameraEvent(e, true)}
onMarkerPress={(e, m) => formatPressEvent(e, `Marker(${m.name})`)}
onMarkerDragStart={(e, m) =>
formatPressEvent(e, `Drag start(${m.name})`)
}
onMarkerDragChange={(e, m) =>
formatPressEvent(e, `Dragging(${m.name})`)
}
onMarkerDragEnd={(e, m) => formatPressEvent(e, `Drag end(${m.name})`)}
onPolygonPress={() => {
lockStatus();
setStatusText('Polygon pressed');
Expand Down
51 changes: 49 additions & 2 deletions example/shared/src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type MapViewProps,
type MapCameraEvent,
type MarkerPressEvent,
type MarkerDragEvent,
} from '@lugg/maps';
import Animated, {
useAnimatedStyle,
Expand All @@ -25,6 +26,9 @@ interface MapProps extends MapViewProps {
animatedPosition?: SharedValue<number>;
onPolygonPress?: () => void;
onMarkerPress?: (event: MarkerPressEvent, marker: MarkerData) => void;
onMarkerDragStart?: (event: MarkerDragEvent, marker: MarkerData) => void;
onMarkerDragChange?: (event: MarkerDragEvent, marker: MarkerData) => void;
onMarkerDragEnd?: (event: MarkerDragEvent, marker: MarkerData) => void;
}

const INITIAL_ZOOM = 14;
Expand All @@ -44,7 +48,10 @@ const CIRCLE_COORDS = Array.from({ length: 36 }, (_, i) => {

const renderMarker = (
marker: MarkerData,
onPress?: (event: MarkerPressEvent, marker: MarkerData) => void
onPress?: (event: MarkerPressEvent, marker: MarkerData) => void,
onDragStart?: (event: MarkerDragEvent, marker: MarkerData) => void,
onDragChange?: (event: MarkerDragEvent, marker: MarkerData) => void,
onDragEnd?: (event: MarkerDragEvent, marker: MarkerData) => void
) => {
const {
id,
Expand All @@ -62,6 +69,15 @@ const renderMarker = (
const handlePress = onPress
? (e: MarkerPressEvent) => onPress(e, marker)
: undefined;
const handleDragStart = onDragStart
? (e: MarkerDragEvent) => onDragStart(e, marker)
: undefined;
const handleDragChange = onDragChange
? (e: MarkerDragEvent) => onDragChange(e, marker)
: undefined;
const handleDragEnd = onDragEnd
? (e: MarkerDragEvent) => onDragEnd(e, marker)
: undefined;

switch (type) {
case 'icon':
Expand All @@ -70,7 +86,11 @@ const renderMarker = (
key={id}
name={name}
coordinate={coordinate}
draggable
onPress={handlePress}
onDragStart={handleDragStart}
onDragChange={handleDragChange}
onDragEnd={handleDragEnd}
/>
);
case 'text':
Expand All @@ -81,7 +101,11 @@ const renderMarker = (
coordinate={coordinate}
text={text ?? 'X'}
color={color}
draggable
onPress={handlePress}
onDragStart={handleDragStart}
onDragChange={handleDragChange}
onDragEnd={handleDragEnd}
/>
);
case 'image':
Expand All @@ -91,7 +115,11 @@ const renderMarker = (
name={name}
coordinate={coordinate}
source={{ uri: imageUrl }}
draggable
onPress={handlePress}
onDragStart={handleDragStart}
onDragChange={handleDragChange}
onDragEnd={handleDragEnd}
/>
);
case 'custom':
Expand All @@ -101,7 +129,11 @@ const renderMarker = (
name={name}
coordinate={coordinate}
anchor={anchor}
draggable
onPress={handlePress}
onDragStart={handleDragStart}
onDragChange={handleDragChange}
onDragEnd={handleDragEnd}
>
<View
style={[styles.customMarker, { backgroundColor: color ?? 'gray' }]}
Expand All @@ -116,7 +148,11 @@ const renderMarker = (
coordinate={coordinate}
title={title}
description={description}
draggable
onPress={handlePress}
onDragStart={handleDragStart}
onDragChange={handleDragChange}
onDragEnd={handleDragEnd}
/>
);
}
Expand All @@ -134,6 +170,9 @@ export const Map = forwardRef<MapView, MapProps>(
onLongPress,
onPolygonPress,
onMarkerPress,
onMarkerDragStart,
onMarkerDragChange,
onMarkerDragEnd,
...props
},
ref
Expand Down Expand Up @@ -182,7 +221,15 @@ export const Map = forwardRef<MapView, MapProps>(
onCameraIdle={handleCameraIdle}
{...props}
>
{markers.map((m) => renderMarker(m, onMarkerPress))}
{markers.map((m) =>
renderMarker(
m,
onMarkerPress,
onMarkerDragStart,
onMarkerDragChange,
onMarkerDragEnd
)
)}
<Route coordinates={smoothedRoute} />
<CrewMarker route={smoothedRoute} zoom={zoom} />
<Polygon
Expand Down
5 changes: 5 additions & 0 deletions ios/LuggMarkerView.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly) CLLocationDegrees rotate;
@property(nonatomic, readonly) CGFloat scale;
@property(nonatomic, readonly) BOOL rasterize;
@property(nonatomic, readonly) BOOL draggable;
@property(nonatomic, readonly) BOOL hasCustomView;
@property(nonatomic, readonly) BOOL didLayout;
@property(nonatomic, readonly) UIView *iconView;
Expand All @@ -33,6 +34,10 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable UIImage *)createScaledIconImage;
- (void)resetIconViewTransform;
- (void)emitPressEventWithPoint:(CGPoint)point;
- (void)emitDragStartEventWithPoint:(CGPoint)point;
- (void)emitDragChangeEventWithPoint:(CGPoint)point;
- (void)emitDragEndEventWithPoint:(CGPoint)point;
- (void)updateCoordinate:(CLLocationCoordinate2D)coordinate;

@end

Expand Down
Loading