Skip to content
Open
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
35 changes: 32 additions & 3 deletions android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.google.android.gms.maps.model.Circle
import com.google.android.gms.maps.model.IndoorBuilding
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.gms.maps.model.MapCapabilities
import com.google.android.gms.maps.model.MapColorScheme
import com.google.android.gms.maps.model.MapStyleOptions
import com.google.android.gms.maps.model.Marker
Expand Down Expand Up @@ -71,10 +72,17 @@ class GoogleMapsViewImpl(
private var mapViewInitialized = false
private var mapViewLoaded = false
private var destroyed = false
private var advancedMarkersAvailable: Boolean? = null
private var googleMap: GoogleMap? = null
private var mapView: MapView? = null

private val markerManager = MapMarkerManager(MapMarkerBuilder(reactContext, mapErrorHandler))
private val mapCapabilitiesChangedListener =
GoogleMap.OnMapCapabilitiesChangedListener(::updateMapCapabilities)

private val markerManager =
MapMarkerManager(
MapMarkerBuilder(reactContext, mapErrorHandler),
mapErrorHandler,
)
private val polylineManager = MapPolylineManager(MapPolylineBuilder())
private val polygonManager = MapPolygonManager(MapPolygonBuilder())
private val circleManager = MapCircleManager(MapCircleBuilder())
Expand Down Expand Up @@ -113,7 +121,9 @@ class GoogleMapsViewImpl(
googleMap = map
googleMap?.setLocationSource(locationHandler)
googleMap?.setOnMapLoadedCallback(this@GoogleMapsViewImpl)
markerManager.attachMap(map)
markerManager.attachMap(map, googleMapsOptions.mapId != null)
map.addOnMapCapabilitiesChangedListener(mapCapabilitiesChangedListener)
updateMapCapabilities(map.mapCapabilities)
polylineManager.attachMap(map)
polygonManager.attachMap(map)
circleManager.attachMap(map)
Expand Down Expand Up @@ -344,6 +354,13 @@ class GoogleMapsViewImpl(
}

var onMapReady: ((Boolean) -> Unit)? = null
var onMapCapabilitiesChange: ((RNMapCapabilities) -> Unit)? = null
set(value) {
field = value
advancedMarkersAvailable?.let { available ->
value?.invoke(RNMapCapabilities(supportsAdvancedMarkers = available))
}
}
var onMapLoaded: ((RNRegion, RNCamera) -> Unit)? = null
var onLocationUpdate: ((RNLocation) -> Unit)? = null
var onLocationError: ((RNLocationErrorCode) -> Unit)? = null
Expand Down Expand Up @@ -514,6 +531,7 @@ class GoogleMapsViewImpl(
urlTileOverlayManager.destroy()
kmlLayerManager.destroy()
googleMap?.apply {
removeOnMapCapabilitiesChangedListener(mapCapabilitiesChangedListener)
setOnMapLoadedCallback(null)
setOnCameraMoveStartedListener(null)
setOnCameraMoveListener(null)
Expand Down Expand Up @@ -546,6 +564,17 @@ class GoogleMapsViewImpl(
reactContext.unregisterComponentCallbacks(this)
}

private fun updateMapCapabilities(capabilities: MapCapabilities) =
onUi {
val available = capabilities.isAdvancedMarkersAvailable
markerManager.updateAdvancedMarkersAvailable(available)
if (advancedMarkersAvailable == available) return@onUi
advancedMarkersAvailable = available
onMapCapabilitiesChange?.invoke(
RNMapCapabilities(supportsAdvancedMarkers = available),
)
}

override fun requestLayout() {
super.requestLayout()
// setPadding issue
Expand Down
12 changes: 11 additions & 1 deletion android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ import com.caverock.androidsvg.SVGExternalFileResolver
import com.caverock.androidsvg.SVGParseException
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.facebook.react.uimanager.ThemedReactContext
import com.google.android.gms.maps.model.AdvancedMarkerOptions
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.rngooglemapsplus.extensions.advancedMarkerCollisionBehavior
import com.rngooglemapsplus.extensions.anchorEquals
import com.rngooglemapsplus.extensions.coordinatesEquals
import com.rngooglemapsplus.extensions.infoWindowAnchorEquals
import com.rngooglemapsplus.extensions.markerInfoWindowStyleEquals
import com.rngooglemapsplus.extensions.toGoogleCollisionBehavior
import com.rngooglemapsplus.extensions.toLatLng
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -158,8 +161,9 @@ class MapMarkerBuilder(
fun build(
m: RNMarker,
icon: BitmapDescriptor?,
useAdvancedMarker: Boolean,
): MarkerOptions =
MarkerOptions().apply {
(if (useAdvancedMarker) AdvancedMarkerOptions() else MarkerOptions()).apply {
position(m.coordinate.toLatLng())
icon(icon)
m.title?.let { title(it) }
Expand All @@ -171,6 +175,12 @@ class MapMarkerBuilder(
m.infoWindowAnchor?.let { infoWindowAnchor(it.x.toFloat(), it.y.toFloat()) }
m.anchor?.let { anchor(it.x.toFloat(), it.y.toFloat()) }
m.zIndex?.let { zIndex(it.toFloat()) }
if (this is AdvancedMarkerOptions) {
collisionBehavior(
m.advancedMarkerCollisionBehavior()?.toGoogleCollisionBehavior()
?: AdvancedMarkerOptions.CollisionBehavior.REQUIRED,
)
}
}

fun update(
Expand Down
89 changes: 75 additions & 14 deletions android/src/main/java/com/rngooglemapsplus/MapMarkerManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import android.widget.ImageView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.Marker
import com.rngooglemapsplus.extensions.advancedEquals
import com.rngooglemapsplus.extensions.advancedMarkerConfigurationError
import com.rngooglemapsplus.extensions.anchorEquals
import com.rngooglemapsplus.extensions.infoWindowAnchorEquals
import com.rngooglemapsplus.extensions.infoWindowContentEquals
import com.rngooglemapsplus.extensions.infoWindowIsEmpty
import com.rngooglemapsplus.extensions.markerEquals
import com.rngooglemapsplus.extensions.styleHash
import com.rngooglemapsplus.extensions.usesAdvancedMarker
import kotlinx.coroutines.Job

private class MarkerState(
Expand All @@ -23,30 +26,38 @@ private class MarkerState(
var appliedStyleHash: Int? = null
var renderingStyleHash: Int? = null
var renderJob: Job? = null
var configurationErrorReported: Boolean = false
}

class MapMarkerManager(
private val builder: MapMarkerBuilder,
private val mapErrorHandler: MapErrorHandler,
) {
private var map: GoogleMap? = null
private var hasMapId = false
private var advancedMarkersAvailable = false
private val states = mutableMapOf<String, MarkerState>()
private var iconGeneration = 0L
private var destroyed = false

fun attachMap(map: GoogleMap) =
onUi {
if (destroyed) return@onUi
this.map = map
states.values
.filter { it.marker == null && it.renderJob == null }
.forEach { state ->
if (state.iconReady) {
addToMap(state)
} else {
requestIcon(state)
}
fun attachMap(
map: GoogleMap,
hasMapId: Boolean,
) = onUi {
if (destroyed) return@onUi
this.map = map
this.hasMapId = hasMapId
advancedMarkersAvailable = map.mapCapabilities.isAdvancedMarkersAvailable
states.values
.filter { it.marker == null && it.renderJob == null }
.forEach { state ->
if (state.iconReady) {
addToMap(state)
} else {
requestIcon(state)
}
}
}
}

fun add(marker: RNMarker) =
onUi {
Expand All @@ -65,6 +76,11 @@ class MapMarkerManager(
if (prev.markerEquals(next)) return@onUi
state.current = next

if (!prev.advancedEquals(next)) {
recreate(state)
return@onUi
}

val nextStyleHash = if (next.iconSvg != null) next.styleHash() else null
val renderingSameStyle = state.renderJob != null && state.renderingStyleHash == nextStyleHash
val iconUpToDate = state.renderJob == null && state.iconReady && state.appliedStyleHash == nextStyleHash
Expand Down Expand Up @@ -173,8 +189,33 @@ class MapMarkerManager(
}

private fun addToMap(state: MarkerState) {
state.current.advancedMarkerConfigurationError()?.let { message ->
if (!state.configurationErrorReported) {
state.configurationErrorReported = true
mapErrorHandler.report(
RNMapErrorCode.INVALID_ARGUMENT,
"markerId=${state.current.id} $message",
)
}
state.appliedIcon = null
return
}

if (state.current.usesAdvancedMarker() && !hasMapId) {
if (!state.configurationErrorReported) {
state.configurationErrorReported = true
mapErrorHandler.report(
RNMapErrorCode.INVALID_ARGUMENT,
"markerId=${state.current.id} Advanced Markers require initialProps.mapId",
)
}
state.appliedIcon = null
return
}

val useAdvancedMarker = state.current.usesAdvancedMarker() && advancedMarkersAvailable
state.marker =
map?.addMarker(builder.build(state.current, state.appliedIcon))?.apply {
map?.addMarker(builder.build(state.current, state.appliedIcon, useAdvancedMarker))?.apply {
tag = MarkerTag(id = state.current.id, iconSvg = state.current.infoWindowIconSvg)
}
state.appliedIcon = null
Expand All @@ -184,5 +225,25 @@ class MapMarkerManager(
private fun removeFromMap(state: MarkerState) {
state.renderJob?.cancel()
state.marker?.remove()
state.marker = null
}

private fun recreate(state: MarkerState) {
removeFromMap(state)
state.iconReady = false
state.appliedIcon = null
state.appliedStyleHash = null
state.anchorsDeferred = false
state.configurationErrorReported = false
requestIcon(state)
}

fun updateAdvancedMarkersAvailable(available: Boolean) =
onUi {
if (advancedMarkersAvailable == available) return@onUi
advancedMarkersAvailable = available
states.values
.filter { it.current.usesAdvancedMarker() }
.forEach(::recreate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,11 @@ class RNGoogleMapsPlusView(
view.onMapReady = cb
}

override var onMapCapabilitiesChange: ((RNMapCapabilities) -> Unit)? = null
set(cb) {
view.onMapCapabilitiesChange = cb
}

override var onMapLoaded: ((RNRegion, RNCamera) -> Unit)? = null
set(cb) {
view.onMapLoaded = cb
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.rngooglemapsplus.extensions

import com.google.android.gms.maps.model.AdvancedMarkerOptions
import com.rngooglemapsplus.RNMarkerCollisionBehavior

fun RNMarkerCollisionBehavior.toGoogleCollisionBehavior(): Int =
when (this) {
RNMarkerCollisionBehavior.REQUIRED -> {
AdvancedMarkerOptions.CollisionBehavior.REQUIRED
}

RNMarkerCollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL -> {
AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL
}

RNMarkerCollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY -> {
AdvancedMarkerOptions.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.rngooglemapsplus.extensions

import com.rngooglemapsplus.RNMarker
import com.rngooglemapsplus.RNMarkerCollisionBehavior

fun RNMarker.markerEquals(b: RNMarker): Boolean {
if (id != b.id) return false
Expand All @@ -16,10 +17,31 @@ fun RNMarker.markerEquals(b: RNMarker): Boolean {
if (rotation != b.rotation) return false
if (!markerInfoWindowStyleEquals(b)) return false
if (!markerStyleEquals(b)) return false
if (!advancedEquals(b)) return false

return true
}

fun RNMarker.advancedEquals(b: RNMarker): Boolean {
if (usesAdvancedMarker() != b.usesAdvancedMarker()) return false
if ((advancedOptions != null) != (b.advancedOptions != null)) return false
if (advancedMarkerCollisionBehavior() != b.advancedMarkerCollisionBehavior()) return false
return true
}

fun RNMarker.usesAdvancedMarker(): Boolean = advanced ?: false

fun RNMarker.advancedMarkerCollisionBehavior(): RNMarkerCollisionBehavior? {
if (!usesAdvancedMarker()) return null
return advancedOptions?.collisionBehavior ?: RNMarkerCollisionBehavior.REQUIRED
}

fun RNMarker.advancedMarkerConfigurationError(): String? =
when {
advancedOptions != null && !usesAdvancedMarker() -> "advancedOptions require advanced: true"
else -> null
}

fun RNMarker.coordinatesEquals(b: RNMarker): Boolean {
if (coordinate.latitude != b.coordinate.latitude) return false
if (coordinate.longitude != b.coordinate.longitude) return false
Expand Down
12 changes: 12 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { createStackNavigator } from '@react-navigation/stack';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

import { useAppTheme } from '@src/hooks/useAppTheme';
import AdvancedMarkersScreen from '@src/screens/AdvancedMarkersScreen';
import BasicMapScreen from '@src/screens/BasicMapScreen';
import BlankScreen from '@src/screens/BlankScreen';
import CameraTestScreen from '@src/screens/CameraTestScreen';
Expand All @@ -23,6 +24,7 @@ import HomeScreen from '@src/screens/HomeScreen';
import IndoorLevelMapScreen from '@src/screens/IndoorLevelMapScreen';
import KmlLayerScreen from '@src/screens/KmlLayerScreen';
import LocationScreen from '@src/screens/LocationScreen';
import MapIdScreen from '@src/screens/MapIdScreen';
import MarkersScreen from '@src/screens/MarkersScreen';
import PolygonsScreen from '@src/screens/PolygonsScreen';
import PolylinesScreen from '@src/screens/PolylinesScreen';
Expand Down Expand Up @@ -68,6 +70,11 @@ export default function App() {
component={BasicMapScreen}
options={{ title: 'Basic Map' }}
/>
<Stack.Screen
name="MapId"
component={MapIdScreen}
options={{ title: 'Map ID' }}
/>
<Stack.Screen
name="ScrollView"
component={ScrollViewScreen}
Expand All @@ -83,6 +90,11 @@ export default function App() {
component={SvgMarkersScreen}
options={{ title: 'SVG Markers' }}
/>
<Stack.Screen
name="AdvancedMarkers"
component={AdvancedMarkersScreen}
options={{ title: 'Advanced Markers' }}
/>
<Stack.Screen
name="Polygons"
component={PolygonsScreen}
Expand Down
5 changes: 5 additions & 0 deletions example/src/hooks/useMapCallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export function useMapCallbacks(
)
);

const onMapCapabilitiesChange = useNitroCallback(
props.onMapCapabilitiesChange
);

const onMapLoaded = useNitroCallback(
props.onMapLoaded,
useCallback(
Expand Down Expand Up @@ -207,6 +211,7 @@ export function useMapCallbacks(
hybridRef,
onMapError,
onMapReady,
onMapCapabilitiesChange,
onMapLoaded,
onMapPress,
onMapLongPress,
Expand Down
Loading
Loading