react-native-google-maps-plus 1.10.0 → 1.10.1-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,8 +41,6 @@ import com.google.android.gms.maps.model.TileOverlayOptions
41
41
  import com.google.maps.android.data.kml.KmlLayer
42
42
  import com.margelo.nitro.core.Promise
43
43
  import com.rngooglemapsplus.extensions.encode
44
- import com.rngooglemapsplus.extensions.onUi
45
- import com.rngooglemapsplus.extensions.onUiSync
46
44
  import com.rngooglemapsplus.extensions.toGooglePriority
47
45
  import com.rngooglemapsplus.extensions.toLatLng
48
46
  import com.rngooglemapsplus.extensions.toLocationErrorCode
@@ -53,7 +51,6 @@ import com.rngooglemapsplus.extensions.toRnCamera
53
51
  import com.rngooglemapsplus.extensions.toRnLatLng
54
52
  import com.rngooglemapsplus.extensions.toRnLocation
55
53
  import com.rngooglemapsplus.extensions.toRnRegion
56
- import com.rngooglemapsplus.extensions.withPaddingPixels
57
54
  import idTag
58
55
  import tagData
59
56
  import java.io.ByteArrayInputStream
@@ -451,27 +448,25 @@ class GoogleMapsViewImpl(
451
448
  ) = onUi {
452
449
  if (coordinates.isEmpty()) return@onUi
453
450
 
454
- val w = mapView?.width ?: 0
455
- val h = mapView?.height ?: 0
451
+ val bounds =
452
+ LatLngBounds
453
+ .builder()
454
+ .apply {
455
+ coordinates.forEach { include(it.toLatLng()) }
456
+ }.build()
456
457
 
457
- val builder = LatLngBounds.builder()
458
- coordinates.forEach { coord -> builder.include(coord.toLatLng()) }
458
+ val previousMapPadding = mapPadding
459
+ mapPadding = padding
459
460
 
460
- val baseBounds = builder.build()
461
- val paddedBounds = baseBounds.withPaddingPixels(w, h, padding)
462
-
463
- val adjustedWidth =
464
- (w - padding.left.dpToPx() - padding.right.dpToPx()).toInt().coerceAtLeast(0)
465
- val adjustedHeight =
466
- (h - padding.top.dpToPx() - padding.bottom.dpToPx()).toInt().coerceAtLeast(0)
467
-
468
- val update = CameraUpdateFactory.newLatLngBounds(paddedBounds, adjustedWidth, adjustedHeight, 0)
461
+ val update = CameraUpdateFactory.newLatLngBounds(bounds, 0)
469
462
 
470
463
  if (animated) {
471
464
  googleMap?.animateCamera(update, durationMs, null)
472
465
  } else {
473
466
  googleMap?.moveCamera(update)
474
467
  }
468
+
469
+ mapPadding = previousMapPadding
475
470
  }
476
471
 
477
472
  fun setCameraBounds(bounds: LatLngBounds?) =
@@ -752,7 +747,7 @@ class GoogleMapsViewImpl(
752
747
  kmlLayersById[id] = layer
753
748
  layer.addLayerToMap()
754
749
  } catch (_: Exception) {
755
- // ignore
750
+ mapsLog("kml layer parse failed: id=$id")
756
751
  }
757
752
  }
758
753
 
@@ -837,9 +832,15 @@ class GoogleMapsViewImpl(
837
832
  setOnMyLocationClickListener(null)
838
833
  setOnMyLocationButtonClickListener(null)
839
834
  setInfoWindowAdapter(null)
835
+ isTrafficEnabled = false
836
+ isIndoorEnabled = false
837
+ myLocationEnabled = false
838
+ setLocationSource(null)
839
+ setLatLngBoundsForCameraTarget(null)
840
840
  }
841
841
  googleMap = null
842
842
  mapView?.removeAllViews()
843
+ mapView = null
843
844
  super.removeAllViews()
844
845
  reactContext.unregisterComponentCallbacks(componentCallbacks)
845
846
  }
@@ -968,5 +969,5 @@ class GoogleMapsViewImpl(
968
969
 
969
970
  override fun getInfoContents(marker: Marker): View? = null
970
971
 
971
- override fun getInfoWindow(marker: Marker): View? = markerBuilder.buildInfoWindow(marker.tagData.iconSvg)
972
+ override fun getInfoWindow(marker: Marker): View? = markerBuilder.buildInfoWindow(marker.tagData)
972
973
  }
@@ -64,8 +64,8 @@ class LocationHandler(
64
64
  }
65
65
 
66
66
  fun showLocationDialog() {
67
- UiThreadUtil.runOnUiThread {
68
- val activity = context.currentActivity ?: run { return@runOnUiThread }
67
+ onUi {
68
+ val activity = context.currentActivity ?: run { return@onUi }
69
69
 
70
70
  val lr =
71
71
  if (Build.VERSION.SDK_INT >= 31) {
@@ -5,7 +5,6 @@ import com.facebook.react.uimanager.PixelUtil.dpToPx
5
5
  import com.google.android.gms.maps.model.Circle
6
6
  import com.google.android.gms.maps.model.CircleOptions
7
7
  import com.rngooglemapsplus.extensions.centerEquals
8
- import com.rngooglemapsplus.extensions.onUi
9
8
  import com.rngooglemapsplus.extensions.toColor
10
9
  import com.rngooglemapsplus.extensions.toLatLng
11
10
 
@@ -1,4 +1,4 @@
1
- package com.rngooglemapsplus.extensions
1
+ package com.rngooglemapsplus
2
2
 
3
3
  import com.facebook.react.bridge.UiThreadUtil
4
4
  import kotlinx.coroutines.CompletableDeferred
@@ -20,3 +20,16 @@ inline fun <T> onUiSync(crossinline block: () -> T): T {
20
20
  }
21
21
  return runBlocking { result.await() }
22
22
  }
23
+
24
+ private const val MAPS_LOG_TAG = "react-native-google-maps-plus"
25
+
26
+ fun mapsLog(msg: String) {
27
+ android.util.Log.w(MAPS_LOG_TAG, msg)
28
+ }
29
+
30
+ fun mapsLog(
31
+ msg: String,
32
+ t: Throwable,
33
+ ) {
34
+ android.util.Log.w(MAPS_LOG_TAG, msg, t)
35
+ }
@@ -13,6 +13,7 @@ import android.widget.LinearLayout
13
13
  import androidx.core.graphics.createBitmap
14
14
  import com.caverock.androidsvg.SVG
15
15
  import com.caverock.androidsvg.SVGExternalFileResolver
16
+ import com.caverock.androidsvg.SVGParseException
16
17
  import com.facebook.react.uimanager.PixelUtil.dpToPx
17
18
  import com.facebook.react.uimanager.ThemedReactContext
18
19
  import com.google.android.gms.maps.model.BitmapDescriptor
@@ -24,13 +25,13 @@ import com.rngooglemapsplus.extensions.coordinatesEquals
24
25
  import com.rngooglemapsplus.extensions.infoWindowAnchorEquals
25
26
  import com.rngooglemapsplus.extensions.markerInfoWindowStyleEquals
26
27
  import com.rngooglemapsplus.extensions.markerStyleEquals
27
- import com.rngooglemapsplus.extensions.onUi
28
28
  import com.rngooglemapsplus.extensions.styleHash
29
29
  import com.rngooglemapsplus.extensions.toLatLng
30
30
  import kotlinx.coroutines.CoroutineScope
31
31
  import kotlinx.coroutines.Dispatchers
32
32
  import kotlinx.coroutines.Job
33
33
  import kotlinx.coroutines.SupervisorJob
34
+ import kotlinx.coroutines.currentCoroutineContext
34
35
  import kotlinx.coroutines.ensureActive
35
36
  import kotlinx.coroutines.launch
36
37
  import kotlinx.coroutines.withContext
@@ -38,7 +39,7 @@ import java.net.HttpURLConnection
38
39
  import java.net.URL
39
40
  import java.net.URLDecoder
40
41
  import java.util.concurrent.ConcurrentHashMap
41
- import kotlin.coroutines.coroutineContext
42
+ import kotlin.coroutines.cancellation.CancellationException
42
43
 
43
44
  class MapMarkerBuilder(
44
45
  val context: ThemedReactContext,
@@ -117,6 +118,8 @@ class MapMarkerBuilder(
117
118
 
118
119
  else -> null
119
120
  }
121
+ }.onFailure {
122
+ mapsLog("external svg resolve failed")
120
123
  }.getOrNull()
121
124
  }
122
125
 
@@ -140,7 +143,7 @@ class MapMarkerBuilder(
140
143
  try {
141
144
  return Typeface.createFromAsset(assetManager, path)
142
145
  } catch (_: Throwable) {
143
- // / ignore
146
+ mapsLog("font resolve failed: $path")
144
147
  }
145
148
  }
146
149
 
@@ -264,32 +267,40 @@ class MapMarkerBuilder(
264
267
  scope.launch {
265
268
  try {
266
269
  ensureActive()
267
- val bmp = renderBitmap(m)
270
+ val renderResult = renderBitmap(m.iconSvg, m.id)
268
271
 
269
- if (bmp == null) {
270
- withContext(Dispatchers.Main) { onReady(null) }
272
+ if (renderResult?.bitmap == null) {
273
+ withContext(Dispatchers.Main) {
274
+ ensureActive()
275
+ onReady(createFallbackDescriptor())
276
+ }
271
277
  return@launch
272
278
  }
279
+
273
280
  ensureActive()
274
- val desc = BitmapDescriptorFactory.fromBitmap(bmp)
281
+ val desc = BitmapDescriptorFactory.fromBitmap(renderResult.bitmap)
275
282
 
276
- iconCache.put(key, desc)
277
- bmp.recycle()
283
+ if (!renderResult.isFallback) {
284
+ iconCache.put(key, desc)
285
+ }
286
+ renderResult.bitmap.recycle()
278
287
 
279
288
  withContext(Dispatchers.Main) {
280
289
  ensureActive()
281
290
  onReady(desc)
282
291
  }
283
292
  } catch (_: OutOfMemoryError) {
293
+ mapsLog("markerId=${m.id} buildIconAsync out of memory")
284
294
  clearIconCache()
285
295
  withContext(Dispatchers.Main) {
286
296
  ensureActive()
287
- onReady(null)
297
+ onReady(createFallbackDescriptor())
288
298
  }
289
299
  } catch (_: Throwable) {
300
+ mapsLog("markerId=${m.id} buildIconAsync failed")
290
301
  withContext(Dispatchers.Main) {
291
302
  ensureActive()
292
- onReady(null)
303
+ onReady(createFallbackDescriptor())
293
304
  }
294
305
  } finally {
295
306
  jobsById.remove(m.id)
@@ -317,8 +328,22 @@ class MapMarkerBuilder(
317
328
  iconCache.evictAll()
318
329
  }
319
330
 
320
- fun buildInfoWindow(iconSvg: RNMarkerSvg?): ImageView? {
321
- val iconSvg = iconSvg ?: return null
331
+ fun buildInfoWindow(markerTag: MarkerTag): ImageView? {
332
+ val iconSvg = markerTag.iconSvg ?: return null
333
+
334
+ val wPx =
335
+ markerTag.iconSvg.width
336
+ .dpToPx()
337
+ .toInt()
338
+ val hPx =
339
+ markerTag.iconSvg.height
340
+ .dpToPx()
341
+ .toInt()
342
+
343
+ if (wPx <= 0 || hPx <= 0) {
344
+ mapsLog("markerId=${markerTag.id} invalid svg size")
345
+ return ImageView(context)
346
+ }
322
347
 
323
348
  val svgView =
324
349
  ImageView(context).apply {
@@ -330,40 +355,73 @@ class MapMarkerBuilder(
330
355
  }
331
356
 
332
357
  try {
333
- val svg = SVG.getFromString(iconSvg.svgString)
334
- svg.setDocumentWidth(iconSvg.width.dpToPx())
335
- svg.setDocumentHeight(iconSvg.height.dpToPx())
358
+ val svg =
359
+ SVG.getFromString(iconSvg.svgString).apply {
360
+ documentWidth = wPx.toFloat()
361
+ documentHeight = hPx.toFloat()
362
+ }
336
363
  val drawable = PictureDrawable(svg.renderToPicture())
337
364
  svgView.setImageDrawable(drawable)
338
- } catch (e: Exception) {
339
- return null
365
+ } catch (_: Exception) {
366
+ mapsLog("markerId=${markerTag.id} infoWindow: svg render failed")
367
+ return ImageView(context)
340
368
  }
341
369
 
342
370
  return svgView
343
371
  }
344
372
 
345
- private suspend fun renderBitmap(m: RNMarker): Bitmap? {
346
- m.iconSvg ?: return null
373
+ private fun createFallbackBitmap(): Bitmap =
374
+ createBitmap(1, 1, Bitmap.Config.ARGB_8888).apply {
375
+ setHasAlpha(true)
376
+ }
377
+
378
+ private fun createFallbackDescriptor(): BitmapDescriptor {
379
+ val bmp = createFallbackBitmap()
380
+ return BitmapDescriptorFactory.fromBitmap(bmp).also {
381
+ bmp.recycle()
382
+ }
383
+ }
384
+
385
+ private data class RenderBitmapResult(
386
+ val bitmap: Bitmap,
387
+ val isFallback: Boolean,
388
+ )
389
+
390
+ private suspend fun renderBitmap(
391
+ iconSvg: RNMarkerSvg,
392
+ markerId: String,
393
+ ): RenderBitmapResult? {
394
+ val wPx =
395
+ iconSvg.width
396
+ .dpToPx()
397
+ .toInt()
398
+ val hPx =
399
+ iconSvg.height
400
+ .dpToPx()
401
+ .toInt()
402
+
403
+ if (wPx <= 0 || hPx <= 0) {
404
+ mapsLog("markerId=$markerId invalid svg size")
405
+ return RenderBitmapResult(createFallbackBitmap(), true)
406
+ }
347
407
 
348
408
  var bmp: Bitmap? = null
349
409
  try {
350
- coroutineContext.ensureActive()
351
- val svg = SVG.getFromString(m.iconSvg.svgString)
352
-
353
- val wPx =
354
- m.iconSvg.width
355
- .dpToPx()
356
- .toInt()
357
- val hPx =
358
- m.iconSvg.height
359
- .dpToPx()
360
- .toInt()
361
-
362
- coroutineContext.ensureActive()
363
- svg.setDocumentWidth(wPx.toFloat())
364
- svg.setDocumentHeight(hPx.toFloat())
365
-
366
- coroutineContext.ensureActive()
410
+ val svg =
411
+ try {
412
+ SVG.getFromString(iconSvg.svgString).apply {
413
+ documentWidth = wPx.toFloat()
414
+ documentHeight = hPx.toFloat()
415
+ }
416
+ } catch (_: SVGParseException) {
417
+ mapsLog("markerId=$markerId icon: svg parse failed")
418
+ return RenderBitmapResult(createFallbackBitmap(), true)
419
+ } catch (_: IllegalArgumentException) {
420
+ mapsLog("markerId=$markerId icon: svg invalid")
421
+ return RenderBitmapResult(createFallbackBitmap(), true)
422
+ }
423
+
424
+ currentCoroutineContext().ensureActive()
367
425
  bmp =
368
426
  createBitmap(wPx, hPx, Bitmap.Config.ARGB_8888).apply {
369
427
  density = context.resources.displayMetrics.densityDpi
@@ -372,13 +430,13 @@ class MapMarkerBuilder(
372
430
  }
373
431
  }
374
432
 
375
- return bmp
376
- } catch (t: Throwable) {
377
- try {
378
- bmp?.recycle()
379
- } catch (_: Throwable) {
380
- }
381
- throw t
433
+ currentCoroutineContext().ensureActive()
434
+
435
+ return RenderBitmapResult(bmp, false)
436
+ } catch (e: Exception) {
437
+ if (e is CancellationException) throw e
438
+ bmp?.recycle()
439
+ throw e
382
440
  }
383
441
  }
384
442
  }
@@ -6,7 +6,6 @@ import com.google.android.gms.maps.model.Polygon
6
6
  import com.google.android.gms.maps.model.PolygonOptions
7
7
  import com.rngooglemapsplus.extensions.coordinatesEquals
8
8
  import com.rngooglemapsplus.extensions.holesEquals
9
- import com.rngooglemapsplus.extensions.onUi
10
9
  import com.rngooglemapsplus.extensions.toColor
11
10
  import com.rngooglemapsplus.extensions.toLatLng
12
11
  import com.rngooglemapsplus.extensions.toMapsPolygonHoles
@@ -5,7 +5,6 @@ import com.facebook.react.uimanager.PixelUtil.dpToPx
5
5
  import com.google.android.gms.maps.model.Polyline
6
6
  import com.google.android.gms.maps.model.PolylineOptions
7
7
  import com.rngooglemapsplus.extensions.coordinatesEquals
8
- import com.rngooglemapsplus.extensions.onUi
9
8
  import com.rngooglemapsplus.extensions.toColor
10
9
  import com.rngooglemapsplus.extensions.toLatLng
11
10
  import com.rngooglemapsplus.extensions.toMapJointType
@@ -25,6 +25,7 @@ class MapUrlTileOverlayBuilder {
25
25
  return try {
26
26
  URL(url)
27
27
  } catch (e: Exception) {
28
+ mapsLog("tile url invalid: $url", e)
28
29
  null
29
30
  }
30
31
  }
@@ -5,6 +5,7 @@ import android.graphics.Bitmap
5
5
  import android.util.Base64
6
6
  import android.util.Size
7
7
  import androidx.core.graphics.scale
8
+ import com.rngooglemapsplus.mapsLog
8
9
  import java.io.ByteArrayOutputStream
9
10
  import java.io.File
10
11
  import java.io.FileOutputStream
@@ -30,6 +31,7 @@ fun Bitmap.encode(
30
31
  } else {
31
32
  "data:image/$format;base64," + Base64.encodeToString(bytes, Base64.NO_WRAP)
32
33
  }
33
- } catch (_: Exception) {
34
+ } catch (e: Exception) {
35
+ mapsLog("snapshot export failed", e)
34
36
  null
35
37
  }
@@ -1,41 +1,10 @@
1
1
  package com.rngooglemapsplus.extensions
2
2
 
3
- import com.facebook.react.uimanager.PixelUtil.dpToPx
4
- import com.google.android.gms.maps.model.LatLng
5
3
  import com.google.android.gms.maps.model.LatLngBounds
6
4
  import com.rngooglemapsplus.RNLatLngBounds
7
- import com.rngooglemapsplus.RNMapPadding
8
5
 
9
6
  fun LatLngBounds.toRnLatLngBounds(): RNLatLngBounds =
10
7
  RNLatLngBounds(
11
8
  northeast = northeast.toRnLatLng(),
12
9
  southwest = southwest.toRnLatLng(),
13
10
  )
14
-
15
- fun LatLngBounds.withPaddingPixels(
16
- mapWidthPx: Int,
17
- mapHeightPx: Int,
18
- padding: RNMapPadding,
19
- ): LatLngBounds {
20
- val latSpan = northeast.latitude - southwest.latitude
21
- val lngSpan = northeast.longitude - southwest.longitude
22
- if (latSpan == 0.0 && lngSpan == 0.0) return this
23
-
24
- val latPerPixel = if (mapHeightPx != 0) latSpan / mapHeightPx else 0.0
25
- val lngPerPixel = if (mapWidthPx != 0) lngSpan / mapWidthPx else 0.0
26
-
27
- val builder = LatLngBounds.builder()
28
- builder.include(
29
- LatLng(
30
- northeast.latitude + (padding.top.dpToPx() * latPerPixel),
31
- northeast.longitude + (padding.right.dpToPx() * lngPerPixel),
32
- ),
33
- )
34
- builder.include(
35
- LatLng(
36
- southwest.latitude - (padding.bottom.dpToPx() * latPerPixel),
37
- southwest.longitude - (padding.left.dpToPx() * lngPerPixel),
38
- ),
39
- )
40
- return builder.build()
41
- }