diff --git a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt index 5a0486d..29e51e0 100644 --- a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt +++ b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt @@ -47,6 +47,7 @@ class GoogleMapsViewImpl( GoogleMap.OnPolylineClickListener, GoogleMap.OnPolygonClickListener, GoogleMap.OnCircleClickListener, + GoogleMap.OnMarkerDragListener, LifecycleEventListener { private var initialized = false private var mapReady = false @@ -136,6 +137,7 @@ class GoogleMapsViewImpl( googleMap?.setOnPolygonClickListener(this@GoogleMapsViewImpl) googleMap?.setOnCircleClickListener(this@GoogleMapsViewImpl) googleMap?.setOnMapClickListener(this@GoogleMapsViewImpl) + googleMap?.setOnMarkerDragListener(this@GoogleMapsViewImpl) } initLocationCallbacks() applyPending() @@ -481,10 +483,13 @@ class GoogleMapsViewImpl( var onLocationUpdate: ((RNLocation) -> Unit)? = null var onLocationError: ((RNLocationErrorCode) -> Unit)? = null var onMapPress: ((RNLatLng) -> Unit)? = null - var onMarkerPress: ((String) -> Unit)? = null - var onPolylinePress: ((String) -> Unit)? = null - var onPolygonPress: ((String) -> Unit)? = null - var onCirclePress: ((String) -> Unit)? = null + var onMarkerPress: ((String?) -> Unit)? = null + var onPolylinePress: ((String?) -> Unit)? = null + var onPolygonPress: ((String?) -> Unit)? = null + var onCirclePress: ((String?) -> Unit)? = null + var onMarkerDragStart: ((String?, RNLatLng) -> Unit)? = null + var onMarkerDrag: ((String?, RNLatLng) -> Unit)? = null + var onMarkerDragEnd: ((String?, RNLatLng) -> Unit)? = null var onCameraChangeStart: ((RNRegion, RNCamera, Boolean) -> Unit)? = null var onCameraChange: ((RNRegion, RNCamera, Boolean) -> Unit)? = null var onCameraChangeComplete: ((RNRegion, RNCamera, Boolean) -> Unit)? = null @@ -900,6 +905,7 @@ class GoogleMapsViewImpl( setOnPolygonClickListener(null) setOnCircleClickListener(null) setOnMapClickListener(null) + setOnMarkerDragListener(null) } googleMap = null mapView?.apply { @@ -954,20 +960,21 @@ class GoogleMapsViewImpl( } override fun onMarkerClick(marker: Marker): Boolean { - onMarkerPress?.invoke(marker.tag?.toString() ?: "unknown") + marker.showInfoWindow() + onMarkerPress?.invoke(marker.tag?.toString()) return true } override fun onPolylineClick(polyline: Polyline) { - onPolylinePress?.invoke(polyline.tag?.toString() ?: "unknown") + onPolylinePress?.invoke(polyline.tag?.toString()) } override fun onPolygonClick(polygon: Polygon) { - onPolygonPress?.invoke(polygon.tag?.toString() ?: "unknown") + onPolygonPress?.invoke(polygon.tag?.toString()) } override fun onCircleClick(circle: Circle) { - onCirclePress?.invoke(circle.tag?.toString() ?: "unknown") + onCirclePress?.invoke(circle.tag?.toString()) } override fun onMapClick(coordinates: LatLng) { @@ -975,6 +982,27 @@ class GoogleMapsViewImpl( RNLatLng(coordinates.latitude, coordinates.longitude), ) } + + override fun onMarkerDragStart(marker: Marker) { + onMarkerDragStart?.invoke( + marker.tag?.toString(), + RNLatLng(marker.position.latitude, marker.position.longitude), + ) + } + + override fun onMarkerDrag(marker: Marker) { + onMarkerDrag?.invoke( + marker.tag?.toString(), + RNLatLng(marker.position.latitude, marker.position.longitude), + ) + } + + override fun onMarkerDragEnd(marker: Marker) { + onMarkerDragEnd?.invoke( + marker.tag?.toString(), + RNLatLng(marker.position.latitude, marker.position.longitude), + ) + } } private inline fun onUi(crossinline block: () -> Unit) { diff --git a/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt b/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt index abd0d86..1e2fe56 100644 --- a/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt +++ b/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt @@ -41,8 +41,13 @@ class MapMarkerBuilder( ): MarkerOptions = MarkerOptions().apply { position(LatLng(m.coordinate.latitude, m.coordinate.longitude)) - anchor((m.anchor?.x ?: 0.5).toFloat(), (m.anchor?.y ?: 0.5).toFloat()) icon(icon) + m.title?.let { title(it) } + m.snippet?.let { snippet(it) } + m.opacity?.let { alpha(it.toFloat()) } + m.flat?.let { flat(it) } + m.draggable?.let { draggable(it) } + m.anchor?.let { anchor((m.anchor.x).toFloat(), (m.anchor.y).toFloat()) } m.zIndex?.let { zIndex(it.toFloat()) } } @@ -56,17 +61,22 @@ class MapMarkerBuilder( next.coordinate.latitude, next.coordinate.longitude, ) - marker.zIndex = next.zIndex?.toFloat() ?: 0f if (!prev.markerStyleEquals(next)) { buildIconAsync(marker.id, next) { icon -> marker.setIcon(icon) } } + marker.title = next.title + marker.snippet = next.snippet + marker.alpha = next.opacity?.toFloat() ?: 0f + marker.isFlat = next.flat ?: false + marker.isDraggable = next.draggable ?: false marker.setAnchor( (next.anchor?.x ?: 0.5).toFloat(), - (next.anchor?.y ?: 0.5).toFloat(), + (next.anchor?.y ?: 1.0).toFloat(), ) + marker.zIndex = next.zIndex?.toFloat() ?: 0f } fun buildIconAsync( diff --git a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt index ede216d..3173f46 100644 --- a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt +++ b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt @@ -280,26 +280,41 @@ class RNGoogleMapsPlusView( view.onMapPress = cb } - override var onMarkerPress: ((String) -> Unit)? = null + override var onMarkerPress: ((String?) -> Unit)? = null set(cb) { view.onMarkerPress = cb } - override var onPolylinePress: ((String) -> Unit)? = null + override var onPolylinePress: ((String?) -> Unit)? = null set(cb) { view.onPolylinePress = cb } - override var onPolygonPress: ((String) -> Unit)? = null + override var onPolygonPress: ((String?) -> Unit)? = null set(cb) { view.onPolygonPress = cb } - override var onCirclePress: ((String) -> Unit)? = null + override var onCirclePress: ((String?) -> Unit)? = null set(cb) { view.onCirclePress = cb } + override var onMarkerDragStart: ((String?, RNLatLng) -> Unit)? = null + set(cb) { + view.onMarkerDragStart = cb + } + + override var onMarkerDrag: ((String?, RNLatLng) -> Unit)? = null + set(cb) { + view.onMarkerDrag = cb + } + + override var onMarkerDragEnd: ((String?, RNLatLng) -> Unit)? = null + set(cb) { + view.onMarkerDragEnd = cb + } + override var onCameraChangeStart: ((RNRegion, RNCamera, Boolean) -> Unit)? = null set(cb) { view.onCameraChangeStart = cb diff --git a/example/src/components/MapWrapper.tsx b/example/src/components/MapWrapper.tsx index d6075fd..984ccc4 100644 --- a/example/src/components/MapWrapper.tsx +++ b/example/src/components/MapWrapper.tsx @@ -15,6 +15,7 @@ import { } from 'react-native-google-maps-plus'; import type { ViewProps } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { callback } from 'react-native-nitro-modules'; type Props = ViewProps & RNGoogleMapsPlusViewProps & { @@ -94,86 +95,82 @@ export default function MapWrapper(props: Props) { mapZoomConfig={props.mapZoomConfig ?? mapZoomConfig} mapPadding={props.mapPadding ?? mapPadding} locationConfig={props.locationConfig ?? locationConfig} - onMapReady={ - props.onMapReady - ? { - f: (ready: boolean) => console.log('Map is ready! ' + ready), - } - : undefined - } - onMapPress={ - props.onMapPress - ? { - f: (c: RNLatLng) => console.log('Map press:', c), - } - : undefined - } - onMarkerPress={ - props.onMarkerPress - ? { - f: (id: string) => console.log('Marker press:', id), - } - : undefined - } - onPolylinePress={ - props.onPolylinePress - ? { - f: (id: string) => console.log('Polyline press:', id), - } - : undefined - } - onPolygonPress={ - props.onPolygonPress - ? { - f: (id: string) => console.log('Polygon press:', id), - } - : undefined - } - onCirclePress={ - props.onCirclePress - ? { - f: (id: string) => console.log('Circle press:', id), - } - : undefined - } - onCameraChangeStart={ - props.onCameraChangeStart - ? { - f: (r: RNRegion, cam: RNCamera, g: boolean) => - console.log('Cam start', r, cam, g), - } - : undefined - } - onCameraChange={ - props.onCameraChange - ? { - f: (r: RNRegion, cam: RNCamera, g: boolean) => - console.log('Cam', r, cam, g), - } - : undefined - } - onCameraChangeComplete={ - props.onCameraChangeComplete - ? { - f: (r: RNRegion, cam: RNCamera, g: boolean) => - console.log('Cam complete', r, cam, g), - } - : undefined - } - onLocationUpdate={ - props.onLocationUpdate - ? { - f: (l: RNLocation) => console.log('Location', l), - } - : undefined - } - onLocationError={ - props.onLocationError - ? { - f: (e: any) => console.log('Location error', e), - } - : undefined - } + onMapReady={callback( + props.onMapReady ?? { + f: (ready: boolean) => console.log('Map is ready! ' + ready), + } + )} + onMapPress={callback( + props.onMapPress ?? { + f: (c: RNLatLng) => console.log('Map press:', c), + } + )} + onMarkerPress={callback( + props.onMarkerPress ?? { + f: (id: string | undefined) => console.log('Marker press:', id), + } + )} + onPolylinePress={callback( + props.onPolylinePress ?? { + f: (id: string | undefined) => console.log('Polyline press:', id), + } + )} + onPolygonPress={callback( + props.onPolygonPress ?? { + f: (id: string | undefined) => console.log('Polygon press:', id), + } + )} + onCirclePress={callback( + props.onCirclePress ?? { + f: (id: string | undefined) => console.log('Circle press:', id), + } + )} + onMarkerDragStart={callback( + props.onMarkerDragStart ?? { + f: (id: string | undefined, latLng: RNLatLng) => + console.log('Marker drag start', id, latLng), + } + )} + onMarkerDrag={callback( + props.onMarkerDrag ?? { + f: (id: string | undefined, latLng: RNLatLng) => + console.log('Marker drag', id, latLng), + } + )} + onMarkerDragEnd={callback( + props.onMarkerDragEnd ?? { + f: (id: string | undefined, latLng: RNLatLng) => + console.log('Marker drag end', id, latLng), + } + )} + onCameraChangeStart={callback( + props.onCameraChangeStart ?? { + f: (r: RNRegion, cam: RNCamera, g: boolean) => + console.log('Cam start', r, cam, g), + } + )} + onCameraChange={callback( + props.onCameraChange ?? { + f: (r: RNRegion, cam: RNCamera, g: boolean) => + console.log('Cam', r, cam, g), + } + )} + onCameraChangeComplete={callback( + props.onCameraChangeComplete ?? { + f: (r: RNRegion, cam: RNCamera, g: boolean) => + console.log('Cam complete', r, cam, g), + } + )} + onLocationUpdate={callback( + props.onLocationUpdate ?? { + f: (l: RNLocation) => console.log('Location', l), + } + )} + onLocationError={callback( + props.onLocationError ?? { + f: (e: any) => console.log('Location error', e), + } + )} /> {children} diff --git a/example/src/utils/mapGenerators.ts b/example/src/utils/mapGenerators.ts index f83e01b..af0720d 100644 --- a/example/src/utils/mapGenerators.ts +++ b/example/src/utils/mapGenerators.ts @@ -106,17 +106,22 @@ export const makeHeatmap = (id: number): RNHeatmap => ({ opacity: 1, }); -export const makeMarker = (id: number): RNMarker => ({ - id: id.toString(), - zIndex: id, - coordinate: randomCoordinates(37.7749, -122.4194, 0.2), - anchor: { x: 0.5, y: 1.0 }, - iconSvg: - id % 2 === 0 +export function makeMarker(id: number): RNMarker { + const customIcon = id % 2 === 0; + return { + id: id.toString(), + zIndex: id, + coordinate: randomCoordinates(37.7749, -122.4194, 0.2), + anchor: customIcon ? { x: 0.5, y: 1.0 } : undefined, + title: `Marker title id: ${id}`, + snippet: `Marker snippet id: ${id}`, + draggable: customIcon, + iconSvg: customIcon ? { width: (64 / 100) * 50, height: (88 / 100) * 50, svgString: makeSvgIcon(64, 88), } : undefined, -}); + }; +} diff --git a/ios/GoogleMapViewImpl.swift b/ios/GoogleMapViewImpl.swift index a800ebe..3080a84 100644 --- a/ios/GoogleMapViewImpl.swift +++ b/ios/GoogleMapViewImpl.swift @@ -297,10 +297,13 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { var onLocationUpdate: ((RNLocation) -> Void)? var onLocationError: ((_ error: RNLocationErrorCode) -> Void)? var onMapPress: ((RNLatLng) -> Void)? - var onMarkerPress: ((String) -> Void)? - var onPolylinePress: ((String) -> Void)? - var onPolygonPress: ((String) -> Void)? - var onCirclePress: ((String) -> Void)? + var onMarkerPress: ((String?) -> Void)? + var onPolylinePress: ((String?) -> Void)? + var onPolygonPress: ((String?) -> Void)? + var onCirclePress: ((String?) -> Void)? + var onMarkerDragStart: ((String?, RNLatLng) -> Void)? + var onMarkerDrag: ((String?, RNLatLng) -> Void)? + var onMarkerDragEnd: ((String?, RNLatLng) -> Void)? var onCameraChangeStart: ((RNRegion, RNCamera, Bool) -> Void)? var onCameraChange: ((RNRegion, RNCamera, Bool) -> Void)? var onCameraChangeComplete: ((RNRegion, RNCamera, Bool) -> Void)? @@ -722,27 +725,45 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { } func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool { - let id = (marker.userData as? String) ?? "unknown" - onMarkerPress?(id) + mapView.selectedMarker = marker + onMarkerPress?(marker.userData as? String, ) return true } func mapView(_ mapView: GMSMapView, didTap overlay: GMSOverlay) { switch overlay { case let circle as GMSCircle: - let id = (circle.userData as? String) ?? "unknown" - onCirclePress?(id) + onCirclePress?(circle.userData as? String, ) case let polygon as GMSPolygon: - let id = (polygon.userData as? String) ?? "unknown" - onPolygonPress?(id) + onPolygonPress?(polygon.userData as? String, ) case let polyline as GMSPolyline: - let id = (polyline.userData as? String) ?? "unknown" - onPolylinePress?(id) + onPolylinePress?(polyline.userData as? String, ) default: break } } + + func mapView(_ mapView: GMSMapView, didBeginDragging marker: GMSMarker) { + onMarkerDragStart?( + marker.userData as? String, + RNLatLng(marker.position.latitude, marker.position.longitude) + ) + } + + func mapView(_ mapView: GMSMapView, didDrag marker: GMSMarker) { + onMarkerDrag?( + marker.userData as? String, + RNLatLng(marker.position.latitude, marker.position.longitude) + ) + } + + func mapView(_ mapView: GMSMapView, didEndDragging marker: GMSMarker) { + onMarkerDragEnd?( + marker.userData as? String, + RNLatLng(marker.position.latitude, marker.position.longitude) + ) + } } diff --git a/ios/MapMarkerBuilder.swift b/ios/MapMarkerBuilder.swift index 98e2fe6..d0e5803 100644 --- a/ios/MapMarkerBuilder.swift +++ b/ios/MapMarkerBuilder.swift @@ -21,11 +21,17 @@ final class MapMarkerBuilder { marker.userData = m.id marker.tracksViewChanges = true marker.icon = icon - marker.groundAnchor = CGPoint( - x: m.anchor?.x ?? 0.5, - y: m.anchor?.y ?? 0.5 - ) - + m.title.map { marker.title = $0 } + m.snippet.map { marker.snippet = $0 } + m.opacity.map { marker.iconView?.alpha = CGFloat($0) } + m.flat.map { marker.isFlat = $0 } + m.draggable.map { marker.isDraggable = $0 } + m.anchor.map { + marker.groundAnchor = CGPoint( + x: $0.x, + y: $0.y + ) + } m.zIndex.map { marker.zIndex = Int32($0) } DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak marker] in @@ -42,13 +48,16 @@ final class MapMarkerBuilder { longitude: next.coordinate.longitude ) + m.title = next.title + m.snippet = next.snippet + m.iconView?.alpha = CGFloat(next.opacity ?? 0) + m.isFlat = next.flat ?? false + m.isDraggable = next.draggable ?? false m.zIndex = Int32(next.zIndex ?? 0) - m.groundAnchor = CGPoint( x: next.anchor?.x ?? 0.5, - y: next.anchor?.y ?? 0.5 + y: next.anchor?.y ?? 1 ) - if !prev.markerStyleEquals(next) { buildIconAsync(next.id, next) { img in m.tracksViewChanges = true diff --git a/ios/RNGoogleMapsPlusView.swift b/ios/RNGoogleMapsPlusView.swift index 49c498f..a39c764 100644 --- a/ios/RNGoogleMapsPlusView.swift +++ b/ios/RNGoogleMapsPlusView.swift @@ -298,18 +298,27 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { var onMapPress: ((RNLatLng) -> Void)? { didSet { impl.onMapPress = onMapPress } } - var onMarkerPress: ((String) -> Void)? { + var onMarkerPress: ((String?) -> Void)? { didSet { impl.onMarkerPress = onMarkerPress } } - var onPolylinePress: ((String) -> Void)? { + var onPolylinePress: ((String?) -> Void)? { didSet { impl.onPolylinePress = onPolylinePress } } - var onPolygonPress: ((String) -> Void)? { + var onPolygonPress: ((String?) -> Void)? { didSet { impl.onPolygonPress = onPolygonPress } } - var onCirclePress: ((String) -> Void)? { + var onCirclePress: ((String?) -> Void)? { didSet { impl.onCirclePress = onCirclePress } } + var onMarkerDragStart: ((String?, RNLatLng) -> Void)? { + didSet { impl.onMarkerDragStart = onMarkerDragStart } + } + var onMarkerDrag: ((String?, RNLatLng) -> Void)? { + didSet { impl.onMarkerDrag = onMarkerDrag } + } + var onMarkerDragEnd: ((String?, RNLatLng) -> Void)? { + didSet { impl.onMarkerDragEnd = onMarkerDragEnd } + } var onCameraChangeStart: ((RNRegion, RNCamera, Bool) -> Void)? { didSet { impl.onCameraChangeStart = onCameraChangeStart } } diff --git a/src/RNGoogleMapsPlusView.nitro.ts b/src/RNGoogleMapsPlusView.nitro.ts index 07f104f..f3b7dda 100644 --- a/src/RNGoogleMapsPlusView.nitro.ts +++ b/src/RNGoogleMapsPlusView.nitro.ts @@ -50,10 +50,13 @@ export interface RNGoogleMapsPlusViewProps extends HybridViewProps { onLocationUpdate?: (location: RNLocation) => void; onLocationError?: (error: RNLocationErrorCode) => void; onMapPress?: (coordinate: RNLatLng) => void; - onMarkerPress?: (id: string) => void; - onPolylinePress?: (id: string) => void; - onPolygonPress?: (id: string) => void; - onCirclePress?: (id: string) => void; + onMarkerPress?: (id?: string | undefined) => void; + onPolylinePress?: (id?: string | undefined) => void; + onPolygonPress?: (id?: string | undefined) => void; + onCirclePress?: (id?: string | undefined) => void; + onMarkerDragStart?: (id: string | undefined, location: RNLatLng) => void; + onMarkerDrag?: (id: string | undefined, location: RNLatLng) => void; + onMarkerDragEnd?: (id: string | undefined, location: RNLatLng) => void; onCameraChangeStart?: ( region: RNRegion, camera: RNCamera, diff --git a/src/types.ts b/src/types.ts index fedb7b8..7fdf3ec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -136,6 +136,12 @@ export type RNMarker = { zIndex?: number; coordinate: RNLatLng; anchor?: RNPosition; + showInfoWindow?: boolean; + title?: string; + snippet?: string; + opacity?: number; + flat?: boolean; + draggable?: boolean; iconSvg?: RNMarkerSvg; };