diff --git a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt index cb9e084..0f4f13c 100644 --- a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt +++ b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt @@ -32,7 +32,7 @@ class GoogleMapsViewImpl( val reactContext: ThemedReactContext, val locationHandler: LocationHandler, val playServiceHandler: PlayServicesHandler, - val markerBuilder: MarkerBuilder, + val markerBuilder: MapMarkerBuilder, ) : FrameLayout(reactContext), GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnCameraMoveListener, diff --git a/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt b/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt index b47584e..dcf70f6 100644 --- a/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt +++ b/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt @@ -10,6 +10,7 @@ import com.google.android.gms.maps.model.BitmapDescriptor import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions +import com.rngooglemapsplus.extensions.styleHash import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -19,7 +20,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.coroutines.coroutineContext -class MarkerBuilder( +class MapMarkerBuilder( private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default), ) { private val iconCache = @@ -34,7 +35,7 @@ class MarkerBuilder( fun build( m: RNMarker, - icon: BitmapDescriptor, + icon: BitmapDescriptor?, ): MarkerOptions = MarkerOptions().apply { position(LatLng(m.coordinate.latitude, m.coordinate.longitude)) @@ -46,10 +47,13 @@ class MarkerBuilder( fun buildIconAsync( id: String, m: RNMarker, - onReady: (BitmapDescriptor) -> Unit, + onReady: (BitmapDescriptor?) -> Unit, ) { jobsById[id]?.cancel() - + if (m.iconSvg == null) { + onReady(null) + return + } val key = m.styleHash() iconCache.get(key)?.let { cached -> onReady(cached) @@ -98,17 +102,28 @@ class MarkerBuilder( private suspend fun renderBitmap(m: RNMarker): Bitmap? { var bmp: Bitmap? = null + if (m.iconSvg == null) { + return null + } try { coroutineContext.ensureActive() - val svg = SVG.getFromString(m.iconSvg) + val svg = SVG.getFromString(m.iconSvg.svgString) coroutineContext.ensureActive() - svg.setDocumentWidth(m.width.dpToPx()) - svg.setDocumentHeight(m.height.dpToPx()) + svg.setDocumentWidth(m.iconSvg.width.dpToPx()) + svg.setDocumentHeight(m.iconSvg.height.dpToPx()) coroutineContext.ensureActive() bmp = - createBitmap(m.width.dpToPx().toInt(), m.height.dpToPx().toInt(), Bitmap.Config.ARGB_8888) + createBitmap( + m.iconSvg.width + .dpToPx() + .toInt(), + m.iconSvg.height + .dpToPx() + .toInt(), + Bitmap.Config.ARGB_8888, + ) coroutineContext.ensureActive() val canvas = Canvas(bmp) @@ -125,22 +140,3 @@ class MarkerBuilder( } } } - -fun RNMarker.markerEquals(b: RNMarker): Boolean = - id == b.id && - zIndex == b.zIndex && - coordinate == b.coordinate && - anchor == b.anchor && - markerStyleEquals(b) - -fun RNMarker.markerStyleEquals(b: RNMarker): Boolean = - width == b.width && - height == b.height && - iconSvg == b.iconSvg - -fun RNMarker.styleHash(): Int = - arrayOf( - width, - height, - iconSvg, - ).contentHashCode() diff --git a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt index bc2983b..572b1ba 100644 --- a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt +++ b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt @@ -8,6 +8,8 @@ import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MapStyleOptions import com.margelo.nitro.core.Promise import com.rngooglemapsplus.extensions.circleEquals +import com.rngooglemapsplus.extensions.markerEquals +import com.rngooglemapsplus.extensions.markerStyleEquals import com.rngooglemapsplus.extensions.polygonEquals import com.rngooglemapsplus.extensions.polylineEquals import com.rngooglemapsplus.extensions.toCameraPosition @@ -23,7 +25,7 @@ class RNGoogleMapsPlusView( private var locationHandler = LocationHandler(context) private var playServiceHandler = PlayServicesHandler(context) - private val markerBuilder = MarkerBuilder() + private val markerBuilder = MapMarkerBuilder() private val polylineBuilder = MapPolylineBuilder() private val polygonBuilder = MapPolygonBuilder() private val circleBuilder = MapCircleBuilder() diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt new file mode 100644 index 0000000..01fde5c --- /dev/null +++ b/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt @@ -0,0 +1,17 @@ +package com.rngooglemapsplus.extensions + +import com.rngooglemapsplus.RNMarker + +fun RNMarker.markerEquals(b: RNMarker): Boolean = + id == b.id && + zIndex == b.zIndex && + coordinate == b.coordinate && + anchor == b.anchor && + markerStyleEquals(b) + +fun RNMarker.markerStyleEquals(b: RNMarker): Boolean = iconSvg == b.iconSvg + +fun RNMarker.styleHash(): Int = + arrayOf( + iconSvg, + ).contentHashCode() diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 7fae52d..aae64d8 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2356,7 +2356,7 @@ PODS: - React-perflogger (= 0.82.0-rc.4) - React-utils (= 0.82.0-rc.4) - SocketRocket - - RNGoogleMapsPlus (1.0.3-dev.0): + - RNGoogleMapsPlus (1.1.0-dev.1): - boost - DoubleConversion - fast_float @@ -2708,7 +2708,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 1202b833d8cca6c917dabcf679837c34a9ca5723 ReactCodegen: 19febbd1fdc8928493972f8d5290f2952e14c9d2 ReactCommon: 2caf7281b37aa1ca389e18839dd594099efb1489 - RNGoogleMapsPlus: f9d77f78190272ae61db89e5e424d735342fcddc + RNGoogleMapsPlus: 45deb9a3ba72d6557af3bfbf970f51de523364a1 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SVGKit: 1ad7513f8c74d9652f94ed64ddecda1a23864dea Yoga: 2fb906b2084fd388a52edae73c54c39c3f50e86c diff --git a/example/src/App.tsx b/example/src/App.tsx index 6ccf5a8..5052dc0 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -251,9 +251,14 @@ export const makeMarker = (id: number): RNMarker => ({ zIndex: id, coordinate: randomCoordinates(37.7749, -122.4194, 0.2), anchor: { x: 0.5, y: 1.0 }, - width: (64 / 100) * 50, - height: (88 / 100) * 50, - iconSvg: makeSvgIcon(64, 88), + iconSvg: + id % 2 === 0 + ? { + width: (64 / 100) * 50, + height: (88 / 100) * 50, + svgString: makeSvgIcon(64, 88), + } + : undefined, }); export default function App() { diff --git a/ios/MapMarkerBuilder.swift b/ios/MapMarkerBuilder.swift index 2c431a4..4ee13be 100644 --- a/ios/MapMarkerBuilder.swift +++ b/ios/MapMarkerBuilder.swift @@ -11,7 +11,7 @@ final class MapMarkerBuilder { attributes: .concurrent ) - func build(_ m: RNMarker, icon: UIImage) -> GMSMarker { + func build(_ m: RNMarker, icon: UIImage?) -> GMSMarker { let marker = GMSMarker( position: CLLocationCoordinate2D( latitude: m.coordinate.latitude, @@ -40,6 +40,11 @@ final class MapMarkerBuilder { ) { tasks[id]?.cancel() + if m.iconSvg == nil { + onReady(nil) + return + } + let key = m.styleHash() if let cached = iconCache.object(forKey: key) { onReady(cached) @@ -85,7 +90,6 @@ final class MapMarkerBuilder { if !prev.markerStyleEquals(next) { buildIconAsync(next.id, next) { img in - guard let img else { return } m.tracksViewChanges = true m.icon = img if prev.anchor?.x != next.anchor?.x || prev.anchor?.y != next.anchor?.y { @@ -117,21 +121,26 @@ final class MapMarkerBuilder { } private func renderUIImage(_ m: RNMarker) async -> UIImage? { - await withTaskCancellationHandler( + guard let iconSvg = m.iconSvg else { + return nil + } + + return await withTaskCancellationHandler( operation: { - await withCheckedContinuation { cont in + await withCheckedContinuation { + (cont: CheckedContinuation) in queue.async { if Task.isCancelled { cont.resume(returning: nil) return } - let targetW = max(1, Int(CGFloat(m.width))) - let targetH = max(1, Int(CGFloat(m.height))) + let targetW = max(1, Int(CGFloat(iconSvg.width))) + let targetH = max(1, Int(CGFloat(iconSvg.height))) let size = CGSize(width: targetW, height: targetH) guard - let data = m.iconSvg.data(using: .utf8), + let data = iconSvg.svgString.data(using: .utf8), let svgImg = SVGKImage(data: data) else { cont.resume(returning: nil) diff --git a/ios/RNGoogleMapsPlusView.swift b/ios/RNGoogleMapsPlusView.swift index 631be21..be6ed92 100644 --- a/ios/RNGoogleMapsPlusView.swift +++ b/ios/RNGoogleMapsPlusView.swift @@ -140,7 +140,6 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { } } else { markerBuilder.buildIconAsync(next.id, next) { icon in - guard let icon else { return } let marker = self.markerBuilder.build(next, icon: icon) self.impl.addMarker(id: id, marker: marker) } diff --git a/ios/extensions/RNMarker+Extension.swift b/ios/extensions/RNMarker+Extension.swift index a039853..e3636e1 100644 --- a/ios/extensions/RNMarker+Extension.swift +++ b/ios/extensions/RNMarker+Extension.swift @@ -10,15 +10,16 @@ extension RNMarker { } func markerStyleEquals(_ b: RNMarker) -> Bool { - width == b.width && height == b.height - && iconSvg == b.iconSvg + iconSvg?.width == b.iconSvg?.width && iconSvg?.height == b.iconSvg?.height + && iconSvg?.svgString == b.iconSvg?.svgString + } func styleHash() -> NSString { var hasher = Hasher() - hasher.combine(width) - hasher.combine(height) - hasher.combine(iconSvg) + hasher.combine(iconSvg?.width) + hasher.combine(iconSvg?.height) + hasher.combine(iconSvg?.svgString) return String(hasher.finalize()) as NSString } } diff --git a/src/types.ts b/src/types.ts index 59b6952..8373399 100644 --- a/src/types.ts +++ b/src/types.ts @@ -131,9 +131,13 @@ export type RNMarker = { zIndex?: number; coordinate: RNLatLng; anchor?: RNPosition; + iconSvg?: RNMarkerSvg; +}; + +export type RNMarkerSvg = { width: number; height: number; - iconSvg: string; + svgString: string; }; export type RNPolygon = {