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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
2. YOU MUST NOT commit changes yourself until I explicitly tell you to.
3. YOU MUST NOT create summary documents unless you are told to.
4. YOU MUST NOT add code comments that are obvious.
5. Always update relevant docs in `docs/` when making changes to components or APIs.

## Project Overview

Expand Down
6 changes: 6 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,8 @@ 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.MarkerPressEvent
import com.luggmaps.extensions.dispatchEvent

interface LuggMarkerViewDelegate {
fun markerViewDidUpdate(markerView: LuggMarkerView)
Expand Down Expand Up @@ -238,6 +240,10 @@ class LuggMarkerView(context: Context) : ReactViewGroup(context) {
this.rasterize = rasterize
}

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

fun setName(name: String?) {
this.name = name
}
Expand Down
3 changes: 3 additions & 0 deletions android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class LuggMarkerViewManager :
override fun getName(): String = NAME
override fun createViewInstance(context: ThemedReactContext): LuggMarkerView = LuggMarkerView(context)

override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> =
mapOf("topMarkerPress" to mapOf("registrationName" to "onMarkerPress"))

override fun onDropViewInstance(view: LuggMarkerView) {
super.onDropViewInstance(view)
view.onDropViewInstance()
Expand Down
18 changes: 17 additions & 1 deletion android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.model.AdvancedMarker
import com.google.android.gms.maps.model.AdvancedMarkerOptions
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MapColorScheme
import com.google.android.gms.maps.model.Polygon
import com.google.android.gms.maps.model.PolygonOptions
Expand All @@ -35,7 +36,8 @@ class GoogleMapProvider(private val context: Context) :
GoogleMap.OnCameraIdleListener,
GoogleMap.OnMapClickListener,
GoogleMap.OnMapLongClickListener,
GoogleMap.OnPolygonClickListener {
GoogleMap.OnPolygonClickListener,
GoogleMap.OnMarkerClickListener {

override var delegate: MapProviderDelegate? = null
override val isMapReady: Boolean get() = _isMapReady
Expand All @@ -52,6 +54,7 @@ class GoogleMapProvider(private val context: Context) :
private val pendingPolygonViews = mutableSetOf<LuggPolygonView>()
private val polylineAnimators = mutableMapOf<LuggPolylineView, PolylineAnimator>()
private val polygonToViewMap = mutableMapOf<Polygon, LuggPolygonView>()
private val markerToViewMap = mutableMapOf<Marker, LuggMarkerView>()
private var tapLocation: LatLng? = null

// Initial camera settings
Expand Down Expand Up @@ -105,6 +108,7 @@ class GoogleMapProvider(private val context: Context) :
polylineAnimators.values.forEach { it.destroy() }
polylineAnimators.clear()
polygonToViewMap.clear()
markerToViewMap.clear()
wrapperView?.touchEventHandler = null
wrapperView = null
googleMap?.setOnCameraMoveStartedListener(null)
Expand All @@ -113,6 +117,7 @@ class GoogleMapProvider(private val context: Context) :
googleMap?.setOnMapClickListener(null)
googleMap?.setOnMapLongClickListener(null)
googleMap?.setOnPolygonClickListener(null)
googleMap?.setOnMarkerClickListener(null)
googleMap?.clear()
googleMap = null
_isMapReady = false
Expand All @@ -134,6 +139,7 @@ class GoogleMapProvider(private val context: Context) :
map.setOnMapClickListener(this)
map.setOnMapLongClickListener(this)
map.setOnPolygonClickListener(this)
map.setOnMarkerClickListener(this)

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

override fun onMarkerClick(marker: Marker): Boolean {
markerToViewMap[marker]?.let { view ->
val point = googleMap?.projection?.toScreenLocation(marker.position)
view.emitPressEvent(point?.x?.toFloat() ?: 0f, point?.y?.toFloat() ?: 0f)
}
return false
}

// endregion

// region Props
Expand Down Expand Up @@ -339,6 +353,7 @@ class GoogleMapProvider(private val context: Context) :
}

override fun removeMarkerView(markerView: LuggMarkerView) {
markerView.marker?.let { markerToViewMap.remove(it) }
markerView.marker?.remove()
markerView.marker = null
}
Expand Down Expand Up @@ -391,6 +406,7 @@ class GoogleMapProvider(private val context: Context) :
marker.rotation = markerView.rotate

markerView.marker = marker
markerToViewMap[marker] = markerView
markerView.applyIconToMarker()
}

Expand Down
34 changes: 34 additions & 0 deletions android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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 MarkerPressEvent(
view: View,
private val latitude: Double,
private val longitude: Double,
private val x: Float,
private val y: Float
) : Event<MarkerPressEvent>(UIManagerHelper.getSurfaceId(view), view.id) {
override fun getEventName() = "topMarkerPress"

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())
}
)
}
}
18 changes: 16 additions & 2 deletions docs/MAPVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ import { MapView } from '@lugg/maps';
| `userLocationEnabled` | `boolean` | `false` | Show current user location on the map |
| `userLocationButtonEnabled` | `boolean` | `false` | Show native my-location button (Android only) |
| `theme` | `'light' \| 'dark' \| 'system'` | `'system'` | Map color theme |
| `onCameraMove` | `(event) => void` | - | Called when camera moves |
| `onCameraIdle` | `(event) => void` | - | Called when camera stops moving |
| `onPress` | `(event: MapPressEvent) => void` | - | Called when the map is pressed |
| `onLongPress` | `(event: MapPressEvent) => void` | - | Called when the map is long pressed |
| `onCameraMove` | `(event: MapCameraEvent) => void` | - | Called when camera moves |
| `onCameraIdle` | `(event: MapCameraEvent) => void` | - | Called when camera stops moving |
| `onReady` | `() => void` | - | Called when map is loaded and ready |

## Ref Methods

Expand Down Expand Up @@ -108,6 +111,17 @@ interface SetEdgeInsetsOptions {

## Events

### onPress / onLongPress

Called when the map is pressed or long pressed. Event includes the geographic coordinate and screen point.

```ts
interface PressEventPayload {
coordinate: Coordinate;
point: Point;
}
```

### onCameraMove

Called continuously while the camera is moving.
Expand Down
1 change: 1 addition & 0 deletions docs/MARKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ 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) |
| `onPress` | `(event: MarkerPressEvent) => void` | - | Called when the marker is pressed. Event includes `coordinate` and `point` |
| `children` | `ReactNode` | - | Custom marker view |

## Custom Markers
Expand Down
18 changes: 16 additions & 2 deletions example/shared/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ function HomeContent() {
const [markers, setMarkers] = useState(INITIAL_MARKERS);
const [statusText, setStatusText] = useState('Loading...');
const lastCoordinate = useRef({ latitude: 37.78, longitude: -122.43 });
const statusLockRef = useRef(false);

const lockStatus = useCallback(() => {
statusLockRef.current = true;
setTimeout(() => {
statusLockRef.current = false;
}, 1000);
}, []);

const getSheetBottom = useCallback(
(event: DetentChangeEvent) => screenHeight - event.nativeEvent.position,
Expand Down Expand Up @@ -97,20 +105,22 @@ function HomeContent() {

const formatPressEvent = useCallback(
(event: MapPressEvent, label: string) => {
lockStatus();
const { coordinate, point } = event.nativeEvent;
const lat = coordinate.latitude.toFixed(5);
const lng = coordinate.longitude.toFixed(5);
const px = point.x.toFixed(0);
const py = point.y.toFixed(0);
setStatusText(`${label}: ${lat}, ${lng} (${px}, ${py})`);
},
[]
[lockStatus]
);

const formatCameraEvent = useCallback(
(event: MapCameraEvent, idle: boolean) => {
const { coordinate, zoom, gesture } = event.nativeEvent;
lastCoordinate.current = coordinate;
if (statusLockRef.current) return;
const pos = `${coordinate.latitude.toFixed(
5
)}, ${coordinate.longitude.toFixed(5)} (z${zoom.toFixed(1)})`;
Expand Down Expand Up @@ -183,7 +193,11 @@ function HomeContent() {
onLongPress={(e) => formatPressEvent(e, 'Long press')}
onCameraMove={(e) => formatCameraEvent(e, false)}
onCameraIdle={(e) => formatCameraEvent(e, true)}
onPolygonPress={() => setStatusText('Polygon pressed')}
onMarkerPress={(e, m) => formatPressEvent(e, `Marker(${m.name})`)}
onPolygonPress={() => {
lockStatus();
setStatusText('Polygon pressed');
}}
/>
)}

Expand Down
34 changes: 30 additions & 4 deletions example/shared/src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Polygon,
type MapViewProps,
type MapCameraEvent,
type MarkerPressEvent,
} from '@lugg/maps';
import Animated, {
useAnimatedStyle,
Expand All @@ -23,6 +24,7 @@ interface MapProps extends MapViewProps {
markers: MarkerData[];
animatedPosition?: SharedValue<number>;
onPolygonPress?: () => void;
onMarkerPress?: (event: MarkerPressEvent, marker: MarkerData) => void;
}

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

const renderMarker = (marker: MarkerData) => {
const renderMarker = (
marker: MarkerData,
onPress?: (event: MarkerPressEvent, marker: MarkerData) => void
) => {
const {
id,
name,
Expand All @@ -54,9 +59,20 @@ const renderMarker = (marker: MarkerData) => {
imageUrl,
} = marker;

const handlePress = onPress
? (e: MarkerPressEvent) => onPress(e, marker)
: undefined;

switch (type) {
case 'icon':
return <MarkerIcon key={id} name={name} coordinate={coordinate} />;
return (
<MarkerIcon
key={id}
name={name}
coordinate={coordinate}
onPress={handlePress}
/>
);
case 'text':
return (
<MarkerText
Expand All @@ -65,6 +81,7 @@ const renderMarker = (marker: MarkerData) => {
coordinate={coordinate}
text={text ?? 'X'}
color={color}
onPress={handlePress}
/>
);
case 'image':
Expand All @@ -74,11 +91,18 @@ const renderMarker = (marker: MarkerData) => {
name={name}
coordinate={coordinate}
source={{ uri: imageUrl }}
onPress={handlePress}
/>
);
case 'custom':
return (
<Marker key={id} name={name} coordinate={coordinate} anchor={anchor}>
<Marker
key={id}
name={name}
coordinate={coordinate}
anchor={anchor}
onPress={handlePress}
>
<View
style={[styles.customMarker, { backgroundColor: color ?? 'gray' }]}
/>
Expand All @@ -92,6 +116,7 @@ const renderMarker = (marker: MarkerData) => {
coordinate={coordinate}
title={title}
description={description}
onPress={handlePress}
/>
);
}
Expand All @@ -108,6 +133,7 @@ export const Map = forwardRef<MapView, MapProps>(
onPress,
onLongPress,
onPolygonPress,
onMarkerPress,
...props
},
ref
Expand Down Expand Up @@ -156,7 +182,7 @@ export const Map = forwardRef<MapView, MapProps>(
onCameraIdle={handleCameraIdle}
{...props}
>
{markers.map(renderMarker)}
{markers.map((m) => renderMarker(m, onMarkerPress))}
<Route coordinates={smoothedRoute} />
<CrewMarker route={smoothedRoute} zoom={zoom} />
<Polygon
Expand Down
1 change: 1 addition & 0 deletions ios/LuggMarkerView.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable UIImage *)createIconImage;
- (nullable UIImage *)createScaledIconImage;
- (void)resetIconViewTransform;
- (void)emitPressEventWithPoint:(CGPoint)point;

@end

Expand Down
12 changes: 12 additions & 0 deletions ios/LuggMarkerView.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "LuggMarkerView.h"
#import "events/MarkerPressEvent.h"

#import <react/renderer/components/RNMapsSpec/ComponentDescriptors.h>
#import <react/renderer/components/RNMapsSpec/EventEmitters.h>
Expand All @@ -8,6 +9,7 @@
#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;
using namespace luggmaps::events;

@interface LuggMarkerView () <RCTLuggMarkerViewViewProtocol>
@end
Expand Down Expand Up @@ -208,6 +210,16 @@ - (UIImage *)createScaledIconImage {
}];
}

- (void)emitPressEventWithPoint:(CGPoint)point {
MarkerPressEvent event{
.latitude = _coordinate.latitude,
.longitude = _coordinate.longitude,
.x = point.x,
.y = point.y,
};
event.emit<LuggMarkerViewEventEmitter>(_eventEmitter);
}

- (void)resetIconViewTransform {
_iconView.transform = CGAffineTransformIdentity;
_iconView.layer.anchorPoint = CGPointMake(0.5, 0.5);
Expand Down
Loading