Skip to content

Commit cb265a7

Browse files
authored
chore: merge dev into main
2 parents 9a86cf5 + 9fab0f8 commit cb265a7

File tree

66 files changed

+4803
-988
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+4803
-988
lines changed

android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt

Lines changed: 444 additions & 479 deletions
Large diffs are not rendered by default.

android/src/main/java/com/rngooglemapsplus/LocationHandler.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ class LocationHandler(
3838
private var priority: Int = PRIORITY_DEFAULT
3939
private var interval: Long = INTERVAL_DEFAULT
4040
private var minUpdateInterval: Long = MIN_UPDATE_INTERVAL
41-
private var lastSubmittedLocation: Location? = null
4241
private var isActive = false
4342

4443
var onUpdate: ((Location) -> Unit)? = null
@@ -57,6 +56,8 @@ class LocationHandler(
5756
this.interval = interval ?: INTERVAL_DEFAULT
5857
this.minUpdateInterval = minUpdateInterval ?: MIN_UPDATE_INTERVAL
5958
buildLocationRequest(this.priority, this.interval, this.minUpdateInterval)
59+
stop()
60+
start()
6061
}
6162

6263
fun showLocationDialog() {
@@ -143,9 +144,8 @@ class LocationHandler(
143144
fusedLocationClientProviderClient.lastLocation
144145
.addOnSuccessListener(
145146
OnSuccessListener { location ->
146-
if (location != null && location != lastSubmittedLocation) {
147+
if (location != null) {
147148
onUpdate?.invoke(location)
148-
lastSubmittedLocation = location
149149
}
150150
},
151151
).addOnFailureListener { e ->
@@ -157,11 +157,8 @@ class LocationHandler(
157157
override fun onLocationResult(locationResult: LocationResult) {
158158
val location = locationResult.lastLocation
159159
if (location != null) {
160-
if (location != lastSubmittedLocation) {
161-
lastSubmittedLocation = location
162-
listener?.onLocationChanged(location)
163-
onUpdate?.invoke(location)
164-
}
160+
listener?.onLocationChanged(location)
161+
onUpdate?.invoke(location)
165162
} else {
166163
onError?.invoke(RNLocationErrorCode.POSITION_UNAVAILABLE)
167164
}

android/src/main/java/com/rngooglemapsplus/MapCircleBuilder.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.graphics.Color
44
import com.facebook.react.uimanager.PixelUtil.dpToPx
55
import com.google.android.gms.maps.model.Circle
66
import com.google.android.gms.maps.model.CircleOptions
7+
import com.rngooglemapsplus.extensions.onUi
78
import com.rngooglemapsplus.extensions.toColor
89
import com.rngooglemapsplus.extensions.toLatLng
910

@@ -23,7 +24,7 @@ class MapCircleBuilder {
2324
prev: RNCircle,
2425
next: RNCircle,
2526
circle: Circle,
26-
) {
27+
) = onUi {
2728
if (prev.center.latitude != next.center.latitude ||
2829
prev.center.longitude != next.center.longitude
2930
) {

android/src/main/java/com/rngooglemapsplus/MapHeatmapBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class MapHeatmapBuilder {
1919
heatmap.gradient?.let {
2020
val colors = it.colors.map { c -> c.toColor() }.toIntArray()
2121
val startPoints = it.startPoints.map { p -> p.toFloat() }.toFloatArray()
22-
gradient(Gradient(colors, startPoints))
22+
gradient(Gradient(colors, startPoints, it.colorMapSize.toInt()))
2323
}
2424
}.build()
2525

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.rngooglemapsplus.extensions
2+
3+
import com.facebook.react.bridge.UiThreadUtil
4+
import kotlinx.coroutines.CompletableDeferred
5+
import kotlinx.coroutines.runBlocking
6+
7+
inline fun onUi(crossinline block: () -> Unit) {
8+
if (UiThreadUtil.isOnUiThread()) {
9+
block()
10+
} else {
11+
UiThreadUtil.runOnUiThread { block() }
12+
}
13+
}
14+
15+
inline fun <T> onUiSync(crossinline block: () -> T): T {
16+
if (UiThreadUtil.isOnUiThread()) return block()
17+
val result = CompletableDeferred<T>()
18+
UiThreadUtil.runOnUiThread {
19+
runCatching(block).onSuccess(result::complete).onFailure(result::completeExceptionally)
20+
}
21+
return runBlocking { result.await() }
22+
}

android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
package com.rngooglemapsplus
22

3+
import MarkerTag
34
import android.graphics.Bitmap
5+
import android.graphics.BitmapFactory
46
import android.graphics.Canvas
7+
import android.graphics.Typeface
8+
import android.graphics.drawable.PictureDrawable
9+
import android.util.Base64
510
import android.util.LruCache
11+
import android.widget.ImageView
12+
import android.widget.LinearLayout
613
import androidx.core.graphics.createBitmap
714
import com.caverock.androidsvg.SVG
15+
import com.caverock.androidsvg.SVGExternalFileResolver
816
import com.facebook.react.uimanager.PixelUtil.dpToPx
17+
import com.facebook.react.uimanager.ThemedReactContext
918
import com.google.android.gms.maps.model.BitmapDescriptor
1019
import com.google.android.gms.maps.model.BitmapDescriptorFactory
1120
import com.google.android.gms.maps.model.Marker
1221
import com.google.android.gms.maps.model.MarkerOptions
1322
import com.rngooglemapsplus.extensions.markerStyleEquals
23+
import com.rngooglemapsplus.extensions.onUi
1424
import com.rngooglemapsplus.extensions.styleHash
1525
import com.rngooglemapsplus.extensions.toLatLng
1626
import kotlinx.coroutines.CoroutineScope
@@ -20,9 +30,14 @@ import kotlinx.coroutines.SupervisorJob
2030
import kotlinx.coroutines.ensureActive
2131
import kotlinx.coroutines.launch
2232
import kotlinx.coroutines.withContext
33+
import java.net.HttpURLConnection
34+
import java.net.URL
35+
import java.net.URLDecoder
36+
import java.util.concurrent.ConcurrentHashMap
2337
import kotlin.coroutines.coroutineContext
2438

2539
class MapMarkerBuilder(
40+
val context: ThemedReactContext,
2641
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
2742
) {
2843
private val iconCache =
@@ -33,7 +48,103 @@ class MapMarkerBuilder(
3348
): Int = 1
3449
}
3550

36-
private val jobsById = mutableMapOf<String, Job>()
51+
private val jobsById = ConcurrentHashMap<String, Job>()
52+
53+
init {
54+
// / TODO: refactor with androidsvg 1.5 release
55+
SVG.registerExternalFileResolver(
56+
object : SVGExternalFileResolver() {
57+
override fun resolveImage(filename: String?): Bitmap? {
58+
if (filename.isNullOrBlank()) return null
59+
60+
return runCatching {
61+
when {
62+
filename.startsWith("data:image/svg+xml") -> {
63+
val svgContent =
64+
if ("base64," in filename) {
65+
val base64 = filename.substringAfter("base64,")
66+
String(Base64.decode(base64, Base64.DEFAULT), Charsets.UTF_8)
67+
} else {
68+
URLDecoder.decode(filename.substringAfter(","), "UTF-8")
69+
}
70+
71+
val svg = SVG.getFromString(svgContent)
72+
val width = (svg.documentWidth.takeIf { it > 0 } ?: 128f).toInt()
73+
val height = (svg.documentHeight.takeIf { it > 0 } ?: 128f).toInt()
74+
75+
createBitmap(width, height).apply {
76+
Canvas(this).also(svg::renderToCanvas)
77+
}
78+
}
79+
80+
filename.startsWith("http://") || filename.startsWith("https://") -> {
81+
val conn =
82+
(URL(filename).openConnection() as HttpURLConnection).apply {
83+
connectTimeout = 5000
84+
readTimeout = 5000
85+
requestMethod = "GET"
86+
instanceFollowRedirects = true
87+
}
88+
conn.connect()
89+
90+
val contentType = conn.contentType ?: ""
91+
val result =
92+
if (contentType.contains("svg") || filename.endsWith(".svg")) {
93+
val svgText = conn.inputStream.bufferedReader().use { it.readText() }
94+
val innerSvg = SVG.getFromString(svgText)
95+
val w = innerSvg.documentWidth.takeIf { it > 0 } ?: 128f
96+
val h = innerSvg.documentHeight.takeIf { it > 0 } ?: 128f
97+
val bmp = createBitmap(w.toInt(), h.toInt())
98+
val canvas = Canvas(bmp)
99+
innerSvg.renderToCanvas(canvas)
100+
bmp
101+
} else {
102+
conn.inputStream.use { BitmapFactory.decodeStream(it) }
103+
}
104+
105+
conn.disconnect()
106+
result
107+
}
108+
109+
else -> null
110+
}
111+
}.getOrNull()
112+
}
113+
114+
override fun resolveFont(
115+
fontFamily: String?,
116+
fontWeight: Int,
117+
fontStyle: String?,
118+
): Typeface? {
119+
if (fontFamily.isNullOrBlank()) return null
120+
121+
return runCatching {
122+
val assetManager = context.assets
123+
124+
val candidates =
125+
listOf(
126+
"fonts/$fontFamily.ttf",
127+
"fonts/$fontFamily.otf",
128+
)
129+
130+
for (path in candidates) {
131+
try {
132+
return Typeface.createFromAsset(assetManager, path)
133+
} catch (_: Throwable) {
134+
// / ignore
135+
}
136+
}
137+
138+
Typeface.create(fontFamily, Typeface.NORMAL)
139+
}.getOrElse {
140+
Typeface.create(fontFamily, fontWeight)
141+
}
142+
}
143+
144+
override fun isFormatSupported(mimeType: String?): Boolean = mimeType?.startsWith("image/") == true
145+
},
146+
)
147+
}
37148

38149
fun build(
39150
m: RNMarker,
@@ -57,7 +168,7 @@ class MapMarkerBuilder(
57168
prev: RNMarker,
58169
next: RNMarker,
59170
marker: Marker,
60-
) {
171+
) = onUi {
61172
if (prev.coordinate.latitude != next.coordinate.latitude ||
62173
prev.coordinate.longitude != next.coordinate.longitude
63174
) {
@@ -132,6 +243,10 @@ class MapMarkerBuilder(
132243
if (prev.zIndex != next.zIndex) {
133244
marker.zIndex = next.zIndex?.toFloat() ?: 0f
134245
}
246+
247+
if (prev.infoWindowIconSvg != next.infoWindowIconSvg) {
248+
marker.tag = MarkerTag(id = next.id, iconSvg = next.infoWindowIconSvg)
249+
}
135250
}
136251

137252
fun buildIconAsync(
@@ -189,6 +304,31 @@ class MapMarkerBuilder(
189304
iconCache.evictAll()
190305
}
191306

307+
fun buildInfoWindow(iconSvg: RNMarkerSvg?): ImageView? {
308+
val iconSvg = iconSvg ?: return null
309+
310+
val svgView =
311+
ImageView(context).apply {
312+
layoutParams =
313+
LinearLayout.LayoutParams(
314+
iconSvg.width.dpToPx().toInt(),
315+
iconSvg.height.dpToPx().toInt(),
316+
)
317+
}
318+
319+
try {
320+
val svg = SVG.getFromString(iconSvg.svgString)
321+
svg.setDocumentWidth(iconSvg.width.dpToPx())
322+
svg.setDocumentHeight(iconSvg.height.dpToPx())
323+
val drawable = PictureDrawable(svg.renderToPicture())
324+
svgView.setImageDrawable(drawable)
325+
} catch (e: Exception) {
326+
return null
327+
}
328+
329+
return svgView
330+
}
331+
192332
private suspend fun renderBitmap(m: RNMarker): Bitmap? {
193333
m.iconSvg ?: return null
194334

android/src/main/java/com/rngooglemapsplus/MapPolygonBuilder.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.graphics.Color
44
import com.facebook.react.uimanager.PixelUtil.dpToPx
55
import com.google.android.gms.maps.model.Polygon
66
import com.google.android.gms.maps.model.PolygonOptions
7+
import com.rngooglemapsplus.extensions.onUi
78
import com.rngooglemapsplus.extensions.toColor
89
import com.rngooglemapsplus.extensions.toLatLng
910

@@ -30,7 +31,7 @@ class MapPolygonBuilder {
3031
prev: RNPolygon,
3132
next: RNPolygon,
3233
poly: Polygon,
33-
) {
34+
) = onUi {
3435
val coordsChanged =
3536
prev.coordinates.size != next.coordinates.size ||
3637
!prev.coordinates.zip(next.coordinates).all { (a, b) ->

android/src/main/java/com/rngooglemapsplus/MapPolylineBuilder.kt.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.google.android.gms.maps.model.Polyline
99
import com.google.android.gms.maps.model.PolylineOptions
1010
import com.google.android.gms.maps.model.RoundCap
1111
import com.google.android.gms.maps.model.SquareCap
12+
import com.rngooglemapsplus.extensions.onUi
1213
import com.rngooglemapsplus.extensions.toColor
1314
import com.rngooglemapsplus.extensions.toLatLng
1415

@@ -34,7 +35,7 @@ class MapPolylineBuilder {
3435
prev: RNPolyline,
3536
next: RNPolyline,
3637
polyline: Polyline,
37-
) {
38+
) = onUi {
3839
val coordsChanged =
3940
prev.coordinates.size != next.coordinates.size ||
4041
!prev.coordinates.zip(next.coordinates).all { (a, b) ->
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.rngooglemapsplus
2+
3+
import com.google.android.gms.maps.model.TileOverlayOptions
4+
import com.google.android.gms.maps.model.UrlTileProvider
5+
import java.net.URL
6+
7+
class MapUrlTileOverlayBuilder {
8+
fun build(t: RNUrlTileOverlay): TileOverlayOptions {
9+
val provider =
10+
object : UrlTileProvider(
11+
t.tileSize.toInt(),
12+
t.tileSize.toInt(),
13+
) {
14+
override fun getTileUrl(
15+
x: Int,
16+
y: Int,
17+
zoom: Int,
18+
): URL? {
19+
val url =
20+
t.url
21+
.replace("{x}", x.toString())
22+
.replace("{y}", y.toString())
23+
.replace("{z}", zoom.toString())
24+
25+
return try {
26+
URL(url)
27+
} catch (e: Exception) {
28+
null
29+
}
30+
}
31+
}
32+
33+
val opts = TileOverlayOptions().tileProvider(provider)
34+
35+
t.fadeIn?.let { opts.fadeIn(it) }
36+
t.zIndex?.let { opts.zIndex(it.toFloat()) }
37+
t.opacity?.let { opts.transparency(1f - it.toFloat()) }
38+
return opts
39+
}
40+
}

0 commit comments

Comments
 (0)