11package com.rngooglemapsplus
22
33import android.annotation.SuppressLint
4+ import android.graphics.Bitmap
45import android.location.Location
6+ import android.util.Base64
7+ import android.util.Size
58import android.widget.FrameLayout
9+ import androidx.core.graphics.scale
610import com.facebook.react.bridge.LifecycleEventListener
711import com.facebook.react.bridge.UiThreadUtil
812import com.facebook.react.uimanager.PixelUtil.dpToPx
@@ -15,6 +19,7 @@ import com.google.android.gms.maps.MapView
1519import com.google.android.gms.maps.model.CameraPosition
1620import com.google.android.gms.maps.model.Circle
1721import com.google.android.gms.maps.model.CircleOptions
22+ import com.google.android.gms.maps.model.IndoorBuilding
1823import com.google.android.gms.maps.model.LatLng
1924import com.google.android.gms.maps.model.LatLngBounds
2025import com.google.android.gms.maps.model.MapColorScheme
@@ -28,9 +33,15 @@ import com.google.android.gms.maps.model.PolylineOptions
2833import com.google.android.gms.maps.model.TileOverlay
2934import com.google.android.gms.maps.model.TileOverlayOptions
3035import com.google.maps.android.data.kml.KmlLayer
36+ import com.margelo.nitro.core.Promise
3137import com.rngooglemapsplus.extensions.toGooglePriority
3238import com.rngooglemapsplus.extensions.toLocationErrorCode
39+ import com.rngooglemapsplus.extensions.toRNIndoorBuilding
40+ import com.rngooglemapsplus.extensions.toRNIndoorLevel
3341import java.io.ByteArrayInputStream
42+ import java.io.ByteArrayOutputStream
43+ import java.io.File
44+ import java.io.FileOutputStream
3445import java.nio.charset.StandardCharsets
3546
3647class GoogleMapsViewImpl (
@@ -47,6 +58,8 @@ class GoogleMapsViewImpl(
4758 GoogleMap .OnPolylineClickListener ,
4859 GoogleMap .OnPolygonClickListener ,
4960 GoogleMap .OnCircleClickListener ,
61+ GoogleMap .OnMarkerDragListener ,
62+ GoogleMap .OnIndoorStateChangeListener ,
5063 LifecycleEventListener {
5164 private var initialized = false
5265 private var mapReady = false
@@ -136,12 +149,13 @@ class GoogleMapsViewImpl(
136149 googleMap?.setOnPolygonClickListener(this @GoogleMapsViewImpl)
137150 googleMap?.setOnCircleClickListener(this @GoogleMapsViewImpl)
138151 googleMap?.setOnMapClickListener(this @GoogleMapsViewImpl)
152+ googleMap?.setOnMarkerDragListener(this @GoogleMapsViewImpl)
139153 }
140154 initLocationCallbacks()
141155 applyPending()
156+ mapReady = true
157+ onMapReady?.invoke(true )
142158 }
143- mapReady = true
144- onMapReady?.invoke(true )
145159 }
146160
147161 override fun onCameraMoveStarted (reason : Int ) {
@@ -182,6 +196,8 @@ class GoogleMapsViewImpl(
182196 if (cameraPosition == lastSubmittedCameraPosition) {
183197 return
184198 }
199+ lastSubmittedCameraPosition = cameraPosition
200+
185201 val isGesture = GoogleMap .OnCameraMoveStartedListener .REASON_GESTURE == cameraMoveReason
186202
187203 val latDelta = bounds.northeast.latitude - bounds.southwest.latitude
@@ -201,7 +217,6 @@ class GoogleMapsViewImpl(
201217 ),
202218 isGesture,
203219 )
204- lastSubmittedCameraPosition = cameraPosition
205220 }
206221
207222 override fun onCameraIdle () {
@@ -357,6 +372,8 @@ class GoogleMapsViewImpl(
357372 }
358373 }
359374
375+ var initialProps: RNInitialProps ? = null
376+
360377 var uiSettings: RNMapUiSettings ? = null
361378 set(value) {
362379 field = value
@@ -481,18 +498,23 @@ class GoogleMapsViewImpl(
481498 var onLocationUpdate: ((RNLocation ) -> Unit )? = null
482499 var onLocationError: ((RNLocationErrorCode ) -> Unit )? = null
483500 var onMapPress: ((RNLatLng ) -> Unit )? = null
484- var onMarkerPress: ((String ) -> Unit )? = null
485- var onPolylinePress: ((String ) -> Unit )? = null
486- var onPolygonPress: ((String ) -> Unit )? = null
487- var onCirclePress: ((String ) -> Unit )? = null
501+ var onMarkerPress: ((String? ) -> Unit )? = null
502+ var onPolylinePress: ((String? ) -> Unit )? = null
503+ var onPolygonPress: ((String? ) -> Unit )? = null
504+ var onCirclePress: ((String? ) -> Unit )? = null
505+ var onMarkerDragStart: ((String? , RNLatLng ) -> Unit )? = null
506+ var onMarkerDrag: ((String? , RNLatLng ) -> Unit )? = null
507+ var onMarkerDragEnd: ((String? , RNLatLng ) -> Unit )? = null
508+ var onIndoorBuildingFocused: ((RNIndoorBuilding ) -> Unit )? = null
509+ var onIndoorLevelActivated: ((RNIndoorLevel ) -> Unit )? = null
488510 var onCameraChangeStart: ((RNRegion , RNCamera , Boolean ) -> Unit )? = null
489511 var onCameraChange: ((RNRegion , RNCamera , Boolean ) -> Unit )? = null
490512 var onCameraChangeComplete: ((RNRegion , RNCamera , Boolean ) -> Unit )? = null
491513
492514 fun setCamera (
493515 cameraPosition : CameraPosition ,
494516 animated : Boolean ,
495- durationMS : Int ,
517+ durationMs : Int ,
496518 ) {
497519 onUi {
498520 val current = googleMap?.cameraPosition
@@ -503,7 +525,7 @@ class GoogleMapsViewImpl(
503525 val update = CameraUpdateFactory .newCameraPosition(cameraPosition)
504526
505527 if (animated) {
506- googleMap?.animateCamera(update, durationMS , null )
528+ googleMap?.animateCamera(update, durationMs , null )
507529 } else {
508530 googleMap?.moveCamera(update)
509531 }
@@ -514,7 +536,7 @@ class GoogleMapsViewImpl(
514536 coordinates : Array <RNLatLng >,
515537 padding : RNMapPadding ,
516538 animated : Boolean ,
517- durationMS : Int ,
539+ durationMs : Int ,
518540 ) {
519541 if (coordinates.isEmpty()) {
520542 return
@@ -572,13 +594,85 @@ class GoogleMapsViewImpl(
572594 0 ,
573595 )
574596 if (animated) {
575- googleMap?.animateCamera(update, durationMS , null )
597+ googleMap?.animateCamera(update, durationMs , null )
576598 } else {
577599 googleMap?.moveCamera(update)
578600 }
579601 }
580602 }
581603
604+ fun setCameraBounds (bounds : LatLngBounds ? ) {
605+ onUi {
606+ googleMap?.setLatLngBoundsForCameraTarget(bounds)
607+ }
608+ }
609+
610+ fun animateToBounds (
611+ bounds : LatLngBounds ,
612+ padding : Int ,
613+ durationMs : Int ,
614+ lockBounds : Boolean ,
615+ ) {
616+ onUi {
617+ if (lockBounds) {
618+ googleMap?.setLatLngBoundsForCameraTarget(bounds)
619+ }
620+ val update =
621+ CameraUpdateFactory .newLatLngBounds(
622+ bounds,
623+ padding,
624+ )
625+ googleMap?.animateCamera(update, durationMs, null )
626+ }
627+ }
628+
629+ fun snapshot (
630+ size : Size ? ,
631+ format : String ,
632+ compressFormat : Bitmap .CompressFormat ,
633+ quality : Double ,
634+ resultIsFile : Boolean ,
635+ ): Promise <String ?> {
636+ val promise = Promise <String ?>()
637+ onUi {
638+ googleMap?.snapshot { bitmap ->
639+ try {
640+ if (bitmap == null ) {
641+ promise.resolve(null )
642+ return @snapshot
643+ }
644+
645+ val scaledBitmap =
646+ size?.let {
647+ bitmap.scale(it.width, it.height)
648+ } ? : bitmap
649+
650+ val output = ByteArrayOutputStream ()
651+ scaledBitmap.compress(compressFormat, (quality * 100 ).toInt().coerceIn(0 , 100 ), output)
652+ val bytes = output.toByteArray()
653+
654+ if (resultIsFile) {
655+ val file = File (context.cacheDir, " map_snapshot_${System .currentTimeMillis()} .$format " )
656+ FileOutputStream (file).use { it.write(bytes) }
657+ promise.resolve(file.absolutePath)
658+ } else {
659+ val base64 = Base64 .encodeToString(bytes, Base64 .NO_WRAP )
660+ promise.resolve(" data:image/$format ;base64,$base64 " )
661+ }
662+
663+ if (scaledBitmap != bitmap) {
664+ scaledBitmap.recycle()
665+ }
666+ bitmap.recycle()
667+ } catch (e: Exception ) {
668+ promise.resolve(null )
669+ }
670+ }
671+ }
672+
673+ return promise
674+ }
675+
582676 fun addMarker (
583677 id : String ,
584678 opts : MarkerOptions ,
@@ -883,14 +977,14 @@ class GoogleMapsViewImpl(
883977
884978 fun destroyInternal () {
885979 onUi {
980+ locationHandler.stop()
886981 markerBuilder.cancelAllJobs()
887982 clearMarkers()
888983 clearPolylines()
889984 clearPolygons()
890985 clearCircles()
891986 clearHeatmaps()
892987 clearKmlLayer()
893- locationHandler.stop()
894988 googleMap?.apply {
895989 setOnCameraMoveStartedListener(null )
896990 setOnCameraMoveListener(null )
@@ -900,6 +994,7 @@ class GoogleMapsViewImpl(
900994 setOnPolygonClickListener(null )
901995 setOnCircleClickListener(null )
902996 setOnMapClickListener(null )
997+ setOnMarkerDragListener(null )
903998 }
904999 googleMap = null
9051000 mapView?.apply {
@@ -910,6 +1005,7 @@ class GoogleMapsViewImpl(
9101005 }
9111006 super .removeAllViews()
9121007 reactContext.removeLifecycleEventListener(this )
1008+ initialized = false
9131009 }
9141010 }
9151011
@@ -954,27 +1050,64 @@ class GoogleMapsViewImpl(
9541050 }
9551051
9561052 override fun onMarkerClick (marker : Marker ): Boolean {
957- onMarkerPress?.invoke(marker.tag?.toString() ? : " unknown" )
1053+ marker.showInfoWindow()
1054+ onMarkerPress?.invoke(marker.tag?.toString())
9581055 return true
9591056 }
9601057
9611058 override fun onPolylineClick (polyline : Polyline ) {
962- onPolylinePress?.invoke(polyline.tag?.toString() ? : " unknown " )
1059+ onPolylinePress?.invoke(polyline.tag?.toString())
9631060 }
9641061
9651062 override fun onPolygonClick (polygon : Polygon ) {
966- onPolygonPress?.invoke(polygon.tag?.toString() ? : " unknown " )
1063+ onPolygonPress?.invoke(polygon.tag?.toString())
9671064 }
9681065
9691066 override fun onCircleClick (circle : Circle ) {
970- onCirclePress?.invoke(circle.tag?.toString() ? : " unknown " )
1067+ onCirclePress?.invoke(circle.tag?.toString())
9711068 }
9721069
9731070 override fun onMapClick (coordinates : LatLng ) {
9741071 onMapPress?.invoke(
9751072 RNLatLng (coordinates.latitude, coordinates.longitude),
9761073 )
9771074 }
1075+
1076+ override fun onMarkerDragStart (marker : Marker ) {
1077+ onMarkerDragStart?.invoke(
1078+ marker.tag?.toString(),
1079+ RNLatLng (marker.position.latitude, marker.position.longitude),
1080+ )
1081+ }
1082+
1083+ override fun onMarkerDrag (marker : Marker ) {
1084+ onMarkerDrag?.invoke(
1085+ marker.tag?.toString(),
1086+ RNLatLng (marker.position.latitude, marker.position.longitude),
1087+ )
1088+ }
1089+
1090+ override fun onMarkerDragEnd (marker : Marker ) {
1091+ onMarkerDragEnd?.invoke(
1092+ marker.tag?.toString(),
1093+ RNLatLng (marker.position.latitude, marker.position.longitude),
1094+ )
1095+ }
1096+
1097+ override fun onIndoorBuildingFocused () {
1098+ val building = googleMap?.focusedBuilding ? : return
1099+ onIndoorBuildingFocused?.invoke(building.toRNIndoorBuilding())
1100+ }
1101+
1102+ override fun onIndoorLevelActivated (indoorBuilding : IndoorBuilding ) {
1103+ val activeLevel = indoorBuilding.levels.getOrNull(indoorBuilding.activeLevelIndex) ? : return
1104+ onIndoorLevelActivated?.invoke(
1105+ activeLevel.toRNIndoorLevel(
1106+ indoorBuilding.activeLevelIndex,
1107+ true ,
1108+ ),
1109+ )
1110+ }
9781111}
9791112
9801113private inline fun onUi (crossinline block : () -> Unit ) {
0 commit comments