diff --git a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt index 86e26e7..bcb26fd 100644 --- a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt +++ b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt @@ -1,7 +1,7 @@ package com.rngooglemapsplus -import android.annotation.SuppressLint import android.location.Location +import android.widget.FrameLayout import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.uimanager.PixelUtil.dpToPx @@ -9,8 +9,8 @@ import com.facebook.react.uimanager.ThemedReactContext import com.google.android.gms.common.ConnectionResult import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.MapView -import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLngBounds @@ -28,25 +28,17 @@ class GoogleMapsViewImpl( val locationHandler: LocationHandler, val playServiceHandler: PlayServicesHandler, val markerOptions: com.rngooglemapsplus.MarkerOptions, -) : MapView(reactContext), +) : FrameLayout(reactContext), GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnCameraMoveListener, GoogleMap.OnCameraIdleListener, GoogleMap.OnMapClickListener, - OnMapReadyCallback, GoogleMap.OnMarkerClickListener, LifecycleEventListener { + private var initialized = false + private var mapReady = false private var googleMap: GoogleMap? = null - - private var pendingBuildingEnabled: Boolean? = null - private var pendingTrafficEnabled: Boolean? = null - private var pendingCustomMapStyle: MapStyleOptions? = null - private var pendingInitialCamera: CameraPosition? = null - private var pendingUserInterfaceStyle: Int? = null - private var pendingMinZoomLevel: Double? = null - private var pendingMaxZoomLevel: Double? = null - private var pendingMapPadding: RNMapPadding? = null - private var pendingMapType: Int? = null + private var mapView: MapView? = null private val pendingPolygons = mutableListOf>() private val pendingPolylines = mutableListOf>() private val pendingMarkers = mutableListOf>() @@ -61,10 +53,15 @@ class GoogleMapsViewImpl( init { reactContext.addLifecycleEventListener(this) - getMap() } - private fun getMap() { + fun initMapView( + mapId: String?, + liteMode: Boolean?, + cameraPosition: CameraPosition?, + ) { + if (initialized) return + initialized = true val result = playServiceHandler.playServicesAvailability() when (result) { @@ -93,8 +90,35 @@ class GoogleMapsViewImpl( onMapError?.invoke(RNMapErrorCode.UNKNOWN) } - onCreate(null) - getMapAsync(this@GoogleMapsViewImpl) + mapView = + MapView( + reactContext, + GoogleMapOptions().apply { + mapId?.let { mapId(it) } + liteMode?.let { liteMode(it) } + cameraPosition?.let { + camera(it) + } + }, + ) + + super.addView(mapView) + + mapView?.onCreate(null) + mapView?.getMapAsync { map -> + googleMap = map + googleMap?.setOnMapLoadedCallback { + googleMap?.setOnCameraMoveStartedListener(this@GoogleMapsViewImpl) + googleMap?.setOnCameraMoveListener(this@GoogleMapsViewImpl) + googleMap?.setOnCameraIdleListener(this@GoogleMapsViewImpl) + googleMap?.setOnMarkerClickListener(this@GoogleMapsViewImpl) + googleMap?.setOnMapClickListener(this@GoogleMapsViewImpl) + } + initLocationCallbacks() + applyPending() + } + mapReady = true + onMapReady?.invoke(true) } override fun onCameraMoveStarted(reason: Int) { @@ -185,22 +209,6 @@ class GoogleMapsViewImpl( ) } - @SuppressLint("PotentialBehaviorOverride") - override fun onMapReady(map: GoogleMap) { - googleMap = map - googleMap?.setOnMapLoadedCallback { - googleMap?.setOnCameraMoveStartedListener(this) - googleMap?.setOnCameraMoveListener(this) - googleMap?.setOnCameraIdleListener(this) - googleMap?.setOnMarkerClickListener(this) - googleMap?.setOnMapClickListener(this) - } - initLocationCallbacks() - applyPending() - - onMapReady?.invoke(true) - } - fun initLocationCallbacks() { locationHandler.onUpdate = { location -> // / only the coordinated are relevant right now @@ -225,7 +233,7 @@ class GoogleMapsViewImpl( fun applyPending() { onUi { - pendingMapPadding?.let { + mapPadding?.let { googleMap?.setPadding( it.left.dpToPx().toInt(), it.top.dpToPx().toInt(), @@ -233,30 +241,23 @@ class GoogleMapsViewImpl( it.bottom.dpToPx().toInt(), ) } - pendingInitialCamera?.let { - googleMap?.moveCamera( - CameraUpdateFactory.newCameraPosition( - it, - ), - ) - } - pendingBuildingEnabled?.let { + buildingEnabled?.let { googleMap?.isBuildingsEnabled = it } - pendingTrafficEnabled?.let { + trafficEnabled?.let { googleMap?.isTrafficEnabled = it } - googleMap?.setMapStyle(pendingCustomMapStyle) - pendingMapType?.let { + googleMap?.setMapStyle(customMapStyle) + mapType?.let { googleMap?.mapType = it } - pendingUserInterfaceStyle?.let { + userInterfaceStyle?.let { googleMap?.mapColorScheme = it } - pendingMinZoomLevel?.let { + minZoomLevel?.let { googleMap?.setMinZoomPreference(it.toFloat()) } - pendingMaxZoomLevel?.let { + maxZoomLevel?.let { googleMap?.setMaxZoomPreference(it.toFloat()) } } @@ -283,10 +284,9 @@ class GoogleMapsViewImpl( } } - var buildingEnabled: Boolean? - get() = googleMap?.isBuildingsEnabled ?: pendingBuildingEnabled + var buildingEnabled: Boolean? = null set(value) { - pendingBuildingEnabled = value + field = value onUi { value?.let { googleMap?.isBuildingsEnabled = it @@ -297,10 +297,9 @@ class GoogleMapsViewImpl( } } - var trafficEnabled: Boolean? - get() = googleMap?.isTrafficEnabled ?: pendingTrafficEnabled + var trafficEnabled: Boolean? = null set(value) { - pendingTrafficEnabled = value + field = value onUi { value?.let { googleMap?.isTrafficEnabled = it @@ -310,25 +309,17 @@ class GoogleMapsViewImpl( } } - var customMapStyle: MapStyleOptions? - get() = pendingCustomMapStyle + var customMapStyle: MapStyleOptions? = null set(value) { - pendingCustomMapStyle = value + field = value onUi { googleMap?.setMapStyle(value) } } - var initialCamera: CameraPosition? - get() = pendingInitialCamera + var userInterfaceStyle: Int? = null set(value) { - pendingInitialCamera = value - } - - var userInterfaceStyle: Int? - get() = pendingUserInterfaceStyle - set(value) { - pendingUserInterfaceStyle = value + field = value onUi { value?.let { googleMap?.mapColorScheme = it @@ -338,10 +329,9 @@ class GoogleMapsViewImpl( } } - var minZoomLevel: Double? - get() = pendingMinZoomLevel + var minZoomLevel: Double? = null set(value) { - pendingMinZoomLevel = value + field = value onUi { value?.let { googleMap?.setMinZoomPreference(it.toFloat()) @@ -351,10 +341,9 @@ class GoogleMapsViewImpl( } } - var maxZoomLevel: Double? - get() = pendingMaxZoomLevel + var maxZoomLevel: Double? = null set(value) { - pendingMaxZoomLevel = value + field = value onUi { value?.let { googleMap?.setMaxZoomPreference(it.toFloat()) @@ -364,10 +353,9 @@ class GoogleMapsViewImpl( } } - var mapPadding: RNMapPadding? - get() = pendingMapPadding + var mapPadding: RNMapPadding? = null set(value) { - pendingMapPadding = value + field = value value?.let { onUi { googleMap?.setPadding( @@ -382,10 +370,9 @@ class GoogleMapsViewImpl( } } - var mapType: Int? - get() = pendingMapType + var mapType: Int? = null set(value) { - pendingMapType = value + field = value onUi { value?.let { googleMap?.mapType = it @@ -470,8 +457,8 @@ class GoogleMapsViewImpl( val latSpan = bounds.northeast.latitude - bounds.southwest.latitude val lngSpan = bounds.northeast.longitude - bounds.southwest.longitude - val latPerPixel = latSpan / height - val lngPerPixel = lngSpan / width + val latPerPixel = latSpan / (mapView?.height ?: 0) + val lngPerPixel = lngSpan / (mapView?.width ?: 0) builder.include( LatLng( @@ -500,8 +487,10 @@ class GoogleMapsViewImpl( val paddedBounds = builder.build() - val adjustedWidth = (width - padding.left.dpToPx() - padding.right.dpToPx()).toInt() - val adjustedHeight = (height - padding.top.dpToPx() - padding.bottom.dpToPx()).toInt() + val adjustedWidth = + ((mapView?.width ?: 0) - padding.left.dpToPx() - padding.right.dpToPx()).toInt() + val adjustedHeight = + ((mapView?.height ?: 0) - padding.top.dpToPx() - padding.bottom.dpToPx()).toInt() val update = CameraUpdateFactory.newLatLngBounds( @@ -680,7 +669,7 @@ class GoogleMapsViewImpl( pendingPolygons.clear() } - fun clearAll() { + fun destroyInternal() { onUi { markerOptions.cancelAllJobs() clearMarkers() @@ -694,8 +683,14 @@ class GoogleMapsViewImpl( setOnMarkerClickListener(null) setOnMapClickListener(null) } - this@GoogleMapsViewImpl.onDestroy() googleMap = null + mapView?.apply { + onPause() + onStop() + onDestroy() + removeAllViews() + } + super.removeAllViews() reactContext.removeLifecycleEventListener(this) } } @@ -725,19 +720,19 @@ class GoogleMapsViewImpl( override fun onHostResume() { onUi { locationHandler.start() - this@GoogleMapsViewImpl.onResume() + mapView?.onResume() } } override fun onHostPause() { onUi { locationHandler.stop() - this@GoogleMapsViewImpl.onPause() + mapView?.onPause() } } override fun onHostDestroy() { - clearAll() + destroyInternal() } override fun onMarkerClick(marker: Marker): Boolean { diff --git a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt index 05a0288..55386f2 100644 --- a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt +++ b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt @@ -17,28 +17,34 @@ class RNGoogleMapsPlusView( private var permissionHandler = PermissionHandler(context) private var locationHandler = LocationHandler(context) private var playServiceHandler = PlayServicesHandler(context) + private val markerOptions = MarkerOptions() + private val polylineOptions = MapPolylineOptions() + private val polygonOptions = MapPolygonOptions() override val view = GoogleMapsViewImpl(context, locationHandler, playServiceHandler, markerOptions) - private val polylineOptions = MapPolylineOptions() - private val polygonOptions = MapPolygonOptions() + override var initialProps: RNInitialProps? = null + set(value) { + view.initMapView( + value?.mapId, + value?.liteMode, + mapCameraToCameraPosition(value?.initialCamera), + ) + } - override var buildingEnabled: Boolean? - get() = view.buildingEnabled + override var buildingEnabled: Boolean? = null set(value) { view.buildingEnabled = value } - override var trafficEnabled: Boolean? - get() = view.trafficEnabled + override var trafficEnabled: Boolean? = null set(value) { view.trafficEnabled = value } - override var customMapStyle: String? - get() = currentCustomMapStyle + override var customMapStyle: String? = null set(value) { currentCustomMapStyle = value value?.let { @@ -46,45 +52,34 @@ class RNGoogleMapsPlusView( } } - override var initialCamera: RNCamera? - get() = mapCameraPotionToCamera(view.initialCamera) - set(value) { - view.initialCamera = mapCameraToCameraPosition(value) - } - - override var userInterfaceStyle: RNUserInterfaceStyle? - get() = mapColorSchemeToUserInterfaceStyle(view.userInterfaceStyle) + override var userInterfaceStyle: RNUserInterfaceStyle? = null set(value) { view.userInterfaceStyle = userInterfaceStyleToMapColorScheme(value) } - override var minZoomLevel: Double? - get() = view.minZoomLevel + override var minZoomLevel: Double? = null set(value) { view.minZoomLevel = value } - override var maxZoomLevel: Double? - get() = view.maxZoomLevel + override var maxZoomLevel: Double? = null set(value) { view.maxZoomLevel = value } - override var mapPadding: RNMapPadding? - get() = view.mapPadding + override var mapPadding: RNMapPadding? = null set(value) { view.mapPadding = value } - override var mapType: RNMapType? - get() = RNMapType.entries.firstOrNull { it.value == view.mapType } + override var mapType: RNMapType? = null set(value) { value?.let { view.mapType = it.value } } - override var markers: Array? = emptyArray() + override var markers: Array? = null set(value) { val prevById = field?.associateBy { it.id } ?: emptyMap() val nextById = value?.associateBy { it.id } ?: emptyMap() @@ -134,7 +129,7 @@ class RNGoogleMapsPlusView( field = value } - override var polylines: Array? = emptyArray() + override var polylines: Array? = null set(value) { val prevById = field?.associateBy { it.id } ?: emptyMap() val nextById = value?.associateBy { it.id } ?: emptyMap() @@ -171,7 +166,7 @@ class RNGoogleMapsPlusView( field = value } - override var polygons: Array? = emptyArray() + override var polygons: Array? = null set(value) { val prevById = field?.associateBy { it.id } ?: emptyMap() val nextById = value?.associateBy { it.id } ?: emptyMap() @@ -203,55 +198,46 @@ class RNGoogleMapsPlusView( field = value } - override var onMapError: ((RNMapErrorCode) -> Unit)? - get() = view.onMapError + override var onMapError: ((RNMapErrorCode) -> Unit)? = null set(cb) { view.onMapError = cb } - override var onMapReady: ((Boolean) -> Unit)? - get() = view.onMapReady + override var onMapReady: ((Boolean) -> Unit)? = null set(cb) { view.onMapReady = cb } - override var onLocationUpdate: ((RNLocation) -> Unit)? - get() = view.onLocationUpdate + override var onLocationUpdate: ((RNLocation) -> Unit)? = null set(cb) { view.onLocationUpdate = cb } - override var onLocationError: ((RNLocationErrorCode) -> Unit)? - get() = view.onLocationError + override var onLocationError: ((RNLocationErrorCode) -> Unit)? = null set(cb) { view.onLocationError = cb } - override var onMapPress: ((RNLatLng) -> Unit)? - get() = view.onMapPress + override var onMapPress: ((RNLatLng) -> Unit)? = null set(cb) { view.onMapPress = cb } - override var onMarkerPress: ((String) -> Unit)? - get() = view.onMarkerPress + override var onMarkerPress: ((String) -> Unit)? = null set(cb) { view.onMarkerPress = cb } - override var onCameraChangeStart: ((RNRegion, RNCamera, Boolean) -> Unit)? - get() = view.onCameraChangeStart + override var onCameraChangeStart: ((RNRegion, RNCamera, Boolean) -> Unit)? = null set(cb) { view.onCameraChangeStart = cb } - override var onCameraChange: ((RNRegion, RNCamera, Boolean) -> Unit)? - get() = view.onCameraChange + override var onCameraChange: ((RNRegion, RNCamera, Boolean) -> Unit)? = null set(cb) { view.onCameraChange = cb } - override var onCameraChangeComplete: ((RNRegion, RNCamera, Boolean) -> Unit)? - get() = view.onCameraChangeComplete + override var onCameraChangeComplete: ((RNRegion, RNCamera, Boolean) -> Unit)? = null set(cb) { view.onCameraChangeComplete = cb } @@ -324,24 +310,6 @@ class RNGoogleMapsPlusView( return builder.build() } - - fun mapCameraPotionToCamera(cameraPosition: CameraPosition?): RNCamera? { - cameraPosition ?: return null - - return RNCamera( - center = RNLatLng(cameraPosition.target.latitude, cameraPosition.target.longitude), - zoom = cameraPosition.zoom.toDouble(), - bearing = cameraPosition.bearing.toDouble(), - tilt = cameraPosition.tilt.toDouble(), - ) - } - - fun mapColorSchemeToUserInterfaceStyle(value: Int?): RNUserInterfaceStyle = - when (value) { - MapColorScheme.LIGHT -> RNUserInterfaceStyle.LIGHT - MapColorScheme.DARK -> RNUserInterfaceStyle.DARK - else -> RNUserInterfaceStyle.DEFAULT - } } private inline fun onUi(crossinline block: () -> Unit) { diff --git a/example/src/App.tsx b/example/src/App.tsx index 7396507..b295ef3 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -353,6 +353,17 @@ export default function App() { mapRef.current = ref; }, }} + initialProps={{ + /// mapStyle not working with mapId + /// mapId: '111', + initialCamera: { + center: { + latitude: 37.7749, + longitude: -122.4194, + }, + zoom: 15, + }, + }} onMapReady={callback((ready) => console.log('Map is ready! ' + ready))} style={styles.map} buildingEnabled={true} @@ -360,13 +371,6 @@ export default function App() { customMapStyle={JSON.stringify( normalStyle ? standardMapStyle : silverMapStyle )} - initialCamera={{ - center: { - latitude: 37.7749, - longitude: -122.4194, - }, - zoom: 15, - }} userInterfaceStyle={'light'} mapType={'normal'} maxZoomLevel={20} diff --git a/ios/GoogleMapViewImpl.swift b/ios/GoogleMapViewImpl.swift index 185bdd1..3aa0e12 100644 --- a/ios/GoogleMapViewImpl.swift +++ b/ios/GoogleMapViewImpl.swift @@ -6,19 +6,10 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { private let locationHandler: LocationHandler private let markerOptions: MapMarkerOptions - private var mapView: GMSMapView! + private var mapView: GMSMapView? + private var initialized = false private var mapReady = false - private var pendingBuildingEnabled: Bool? - private var pendingTrafficEnabled: Bool? - private var pendingCustomMapStyle: GMSMapStyle? - private var pendingInitialCamera: GMSCameraPosition? - private var pendingUserInterfaceStyle: UIUserInterfaceStyle? - private var pendingMinZoomLevel: Double? - private var pendingMaxZoomLevel: Double? - private var pendingMapPadding: RNMapPadding? - private var pendingMapType: GMSMapViewType? - private var pendingPolygons: [(id: String, polygon: GMSPolygon)] = [] private var pendingPolylines: [(id: String, polyline: GMSPolyline)] = [] private var pendingMarkers: [(id: String, marker: GMSMarker)] = [] @@ -50,15 +41,6 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { self.markerOptions = markerOptions super.init(frame: frame) setupAppLifecycleObservers() - setupMap() - - /// wait 1 second if alle setter called - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in - self?.initLocationCallbacks() - self?.applyPending() - self?.onMapReady?(true) - self?.mapReady = true - } } private func setupAppLifecycleObservers() { @@ -81,14 +63,31 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { } @MainActor - private func setupMap() { + func initMapView(mapId: String?, liteMode: Bool?, camera: GMSCameraPosition?) { + if(initialized) {return} + initialized = true let options = GMSMapViewOptions() options.frame = bounds + if let mapId = mapId { + options.mapID = GMSMapID(identifier: mapId) + } + if let liteMode = liteMode { + /// not supported + } + if let camera = camera { + options.camera = camera + } mapView = GMSMapView.init(options: options) - mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - mapView.paddingAdjustmentBehavior = .never - mapView.delegate = self - addSubview(mapView) + mapView?.delegate = self + mapView?.autoresizingMask = [.flexibleWidth, .flexibleHeight] + mapView?.paddingAdjustmentBehavior = .never + if let mapView = mapView { + addSubview(mapView) + } + initLocationCallbacks() + applyPending() + onMapReady?(true) + mapReady = true } private func initLocationCallbacks() { @@ -119,8 +118,8 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { @MainActor private func applyPending() { - if let padding = pendingMapPadding { - mapView.padding = UIEdgeInsets( + if let padding = mapPadding { + mapView?.padding = UIEdgeInsets( top: padding.top, left: padding.left, bottom: padding.bottom, @@ -128,28 +127,28 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { ) } - if let style = pendingCustomMapStyle { - mapView.mapStyle = style + if let style = customMapStyle { + mapView?.mapStyle = style } - if let mapType = pendingMapType { - mapView.mapType = mapType + if let mapType = mapType { + mapView?.mapType = mapType } - if let buildings = pendingBuildingEnabled { - mapView.isBuildingsEnabled = buildings + if let buildings = buildingEnabled { + mapView?.isBuildingsEnabled = buildings } - if let traffic = pendingTrafficEnabled { - mapView.isTrafficEnabled = traffic + if let traffic = trafficEnabled { + mapView?.isTrafficEnabled = traffic } - if let uiStyle = pendingUserInterfaceStyle { - mapView.overrideUserInterfaceStyle = uiStyle + if let uiStyle = userInterfaceStyle { + mapView?.overrideUserInterfaceStyle = uiStyle } - if let minZoom = pendingMinZoomLevel, let maxZoom = pendingMaxZoomLevel { - mapView.setMinZoom(Float(minZoom), maxZoom: Float(maxZoom)) + if let minZoom = minZoomLevel, let maxZoom = maxZoomLevel { + mapView?.setMinZoom(Float(minZoom), maxZoom: Float(maxZoom)) } if !pendingMarkers.isEmpty { @@ -172,141 +171,98 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { } } - var currentCamera: GMSCameraPosition { - mapView.camera + var currentCamera: GMSCameraPosition? { + mapView?.camera } @MainActor var buildingEnabled: Bool? { - get { mapView.isBuildingsEnabled } - set { - pendingBuildingEnabled = newValue - if let value = newValue { - mapView.isBuildingsEnabled = value + didSet { + if let value = buildingEnabled { + mapView?.isBuildingsEnabled = value } } } @MainActor var trafficEnabled: Bool? { - get { mapView.isTrafficEnabled } - set { - pendingTrafficEnabled = newValue - if let value = newValue { - mapView.isTrafficEnabled = value + didSet { + if let value = trafficEnabled { + mapView?.isTrafficEnabled = value } } } @MainActor var customMapStyle: GMSMapStyle? { - get { pendingCustomMapStyle } - set { - pendingCustomMapStyle = newValue - if let style = newValue { - mapView.mapStyle = style - } - } - } - - @MainActor - var initialCamera: GMSCameraPosition? { - get { pendingInitialCamera } - set { - pendingInitialCamera = newValue - if let camera = newValue, !mapReady { - mapView.camera = camera + didSet { + if let style = customMapStyle { + mapView?.mapStyle = style } } } @MainActor var userInterfaceStyle: UIUserInterfaceStyle? { - get { pendingUserInterfaceStyle } - set { - pendingUserInterfaceStyle = newValue - if let style = newValue { - mapView.overrideUserInterfaceStyle = style + didSet { + if let style = userInterfaceStyle { + mapView?.overrideUserInterfaceStyle = style } } } @MainActor var minZoomLevel: Double? { - get { pendingMinZoomLevel } - set { - pendingMinZoomLevel = newValue - if let min = newValue, let max = pendingMaxZoomLevel { - mapView.setMinZoom(Float(min), maxZoom: Float(max)) + didSet { + if let min = minZoomLevel, let max = maxZoomLevel { + mapView?.setMinZoom(Float(min), maxZoom: Float(max)) } } } @MainActor var maxZoomLevel: Double? { - get { pendingMaxZoomLevel } - set { - pendingMaxZoomLevel = newValue - if let max = newValue, let min = pendingMinZoomLevel { - mapView.setMinZoom(Float(min), maxZoom: Float(max)) + didSet { + if let max = maxZoomLevel, let min = minZoomLevel { + mapView?.setMinZoom(Float(min), maxZoom: Float(max)) } } } @MainActor var mapPadding: RNMapPadding? { - get { pendingMapPadding } - set { - pendingMapPadding = newValue - if let padding = newValue { - mapView.padding = UIEdgeInsets( + didSet { + mapPadding + if let padding = mapPadding { + mapView?.padding = UIEdgeInsets( top: padding.top, left: padding.left, bottom: padding.bottom, right: padding.right ) } else { - mapView.padding = .zero + mapView?.padding = .zero } } } - @MainActor var mapType: Int32? { - get { pendingMapType.map { Int32($0.rawValue) } } - set { - pendingMapType = GMSMapViewType(rawValue: UInt(newValue ?? 1)) - mapView.mapType = pendingMapType ?? .normal + @MainActor var mapType: GMSMapViewType? { + didSet { + mapView?.mapType = mapType ?? .normal } } - func setCamera(camera: RNCamera, animated: Bool, durationMS: Double) { - let current = mapView.camera - - let zoom = Float(camera.zoom ?? Double(current.zoom)) - let bearing = camera.bearing ?? current.bearing - let viewingAngle = camera.bearing ?? current.viewingAngle - - let target = CLLocationCoordinate2D( - latitude: camera.center?.latitude ?? mapView.camera.target.latitude, - longitude: camera.center?.longitude ?? mapView.camera.target.longitude - ) - - let cam = GMSCameraPosition.camera( - withTarget: target, - zoom: zoom, - bearing: bearing, - viewingAngle: viewingAngle - ) + func setCamera(camera: GMSCameraPosition, animated: Bool, durationMS: Double) { if animated { withCATransaction( disableActions: false, duration: durationMS / 1000.0 ) { - mapView.animate(to: cam) + mapView?.animate(to: camera) } } else { - let update = GMSCameraUpdate.setCamera(cam) - mapView.moveCamera(update) + let update = GMSCameraUpdate.setCamera(camera) + mapView?.moveCamera(update) } } @@ -352,10 +308,10 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { disableActions: false, duration: durationMS / 1000.0 ) { - mapView.animate(with: update) + mapView?.animate(with: update) } } else { - mapView.moveCamera(update) + mapView?.moveCamera(update) } } @@ -464,19 +420,14 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { pendingPolygons.removeAll() } - func clearAll() { + func deinitInternal() { markerOptions.cancelAllIconTasks() clearMarkers() clearPolylines() clearPolygons() locationHandler.stop() - clearMap() - } - - @MainActor - func clearMap() { - mapView.clear() - mapView.delegate = nil + mapView?.clear() + mapView?.delegate = nil mapView = nil } @@ -504,7 +455,7 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate { deinit { NotificationCenter.default.removeObserver(self) - clearAll() + deinitInternal() } func mapView(_ mapView: GMSMapView, willMove gesture: Bool) { diff --git a/ios/RNGoogleMapsPlusView.swift b/ios/RNGoogleMapsPlusView.swift index c9cc5f0..6568395 100644 --- a/ios/RNGoogleMapsPlusView.swift +++ b/ios/RNGoogleMapsPlusView.swift @@ -8,17 +8,16 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { private let permissionHandler: PermissionHandler private let locationHandler: LocationHandler + private let markerOptions = MapMarkerOptions() + private let polylineOptions = MapPolylineOptions() + private let polygonOptions = MapPolygonOptions() + private let impl: GoogleMapsViewImpl var view: UIView { return impl } - private var currentCustomMapStyle: String? - private let markerOptions = MapMarkerOptions() - private let polylineOptions = MapPolylineOptions() - private let polygonOptions = MapPolygonOptions() - override init() { self.permissionHandler = PermissionHandler() self.locationHandler = LocationHandler() @@ -28,71 +27,73 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { ) } + /* + /// TODO: prepareForRecycle + override func prepareForRecycle() { + impl.clearAll() + } + */ + + @MainActor + var initialProps: RNInitialProps? { + didSet { + impl.initMapView( + mapId: initialProps?.mapId, + liteMode: initialProps?.liteMode, + camera: mapCameraToGMSCamera(initialProps?.initialCamera) + ) + } + } + @MainActor var buildingEnabled: Bool? { - get { impl.buildingEnabled } - set { impl.buildingEnabled = newValue } + didSet { impl.buildingEnabled = buildingEnabled } } @MainActor var trafficEnabled: Bool? { - get { impl.trafficEnabled } - set { impl.trafficEnabled = newValue } + didSet { impl.trafficEnabled = trafficEnabled } } @MainActor var customMapStyle: String? { - get { currentCustomMapStyle } - set { - currentCustomMapStyle = newValue - if let value = newValue { + didSet { + if let value = customMapStyle { impl.customMapStyle = try? GMSMapStyle(jsonString: value) } } } - @MainActor - var initialCamera: RNCamera? { - get { mapCameraPositionToCamera(impl.initialCamera) } - set { impl.initialCamera = mapCameraToGMSCamera(newValue) } - } - @MainActor var userInterfaceStyle: RNUserInterfaceStyle? { - get { mapUIUserInterfaceStyleToUserInterfaceStyle(impl.userInterfaceStyle) } - set { + didSet { impl.userInterfaceStyle = mapUserInterfaceStyleToUIUserInterfaceStyle( - newValue + userInterfaceStyle ) } } @MainActor var minZoomLevel: Double? { - get { impl.minZoomLevel } - set { impl.minZoomLevel = newValue } + didSet { impl.minZoomLevel = minZoomLevel } } @MainActor var maxZoomLevel: Double? { - get { impl.maxZoomLevel } - set { impl.maxZoomLevel = newValue } + didSet { impl.maxZoomLevel = maxZoomLevel } } @MainActor var mapPadding: RNMapPadding? { - get { impl.mapPadding } - set { impl.mapPadding = newValue } + didSet { impl.mapPadding = mapPadding } } @MainActor var mapType: RNMapType? { - get { - guard let value = impl.mapType else { return nil } - return RNMapType(rawValue: value) - } - set { - impl.mapType = newValue.map { Int32($0.rawValue) } + didSet { + impl.mapType = mapType.map { + GMSMapViewType(rawValue: UInt($0.rawValue)) ?? .normal + } } } @@ -197,9 +198,26 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { } func setCamera(camera: RNCamera, animated: Bool?, durationMS: Double?) { + let current = impl.currentCamera + + let zoom = Float(camera.zoom ?? Double(current?.zoom ?? 0)) + let bearing = camera.bearing ?? current?.bearing ?? 0 + let viewingAngle = camera.bearing ?? current?.viewingAngle ?? 0 + + let target = CLLocationCoordinate2D( + latitude: camera.center?.latitude ?? current?.target.latitude ?? 0, + longitude: camera.center?.longitude ?? current?.target.longitude ?? 0 + ) + + let cam = GMSCameraPosition.camera( + withTarget: target, + zoom: zoom, + bearing: bearing, + viewingAngle: viewingAngle + ) onMain { self.impl.setCamera( - camera: camera, + camera: cam, animated: animated ?? true, durationMS: durationMS ?? 3000 ) @@ -223,40 +241,31 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { } var onMapError: ((RNMapErrorCode) -> Void)? { - get { impl.onMapError } - set { impl.onMapError = newValue } + didSet { impl.onMapError = onMapError } } var onMapReady: ((Bool) -> Void)? { - get { impl.onMapReady } - set { impl.onMapReady = newValue } + didSet { impl.onMapReady = onMapReady } } var onLocationUpdate: ((RNLocation) -> Void)? { - get { impl.onLocationUpdate } - set { impl.onLocationUpdate = newValue } + didSet { impl.onLocationUpdate = onLocationUpdate } } var onLocationError: ((_ error: RNLocationErrorCode) -> Void)? { - get { impl.onLocationError } - set { impl.onLocationError = newValue } + didSet { impl.onLocationError = onLocationError } } var onMapPress: ((RNLatLng) -> Void)? { - get { impl.onMapPress } - set { impl.onMapPress = newValue } + didSet { impl.onMapPress = onMapPress } } var onMarkerPress: ((String) -> Void)? { - get { impl.onMarkerPress } - set { impl.onMarkerPress = newValue } + didSet { impl.onMarkerPress = onMarkerPress } } var onCameraChangeStart: ((RNRegion, RNCamera, Bool) -> Void)? { - get { impl.onCameraChangeStart } - set { impl.onCameraChangeStart = newValue } + didSet { impl.onCameraChangeStart = onCameraChangeStart } } var onCameraChange: ((RNRegion, RNCamera, Bool) -> Void)? { - get { impl.onCameraChange } - set { impl.onCameraChange = newValue } + didSet { impl.onCameraChange = onCameraChange } } var onCameraChangeComplete: ((RNRegion, RNCamera, Bool) -> Void)? { - get { impl.onCameraChangeComplete } - set { impl.onCameraChangeComplete = newValue } + didSet { impl.onCameraChangeComplete = onCameraChangeComplete } } func showLocationDialog() { @@ -282,12 +291,12 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { let current = impl.currentCamera let center = CLLocationCoordinate2D( - latitude: c.center?.latitude ?? current.target.latitude, - longitude: c.center?.longitude ?? current.target.longitude + latitude: c.center?.latitude ?? current?.target.latitude ?? 0, + longitude: c.center?.longitude ?? current?.target.longitude ?? 0 ) - let z = Float(c.zoom ?? Double(current.zoom)) - let b = c.bearing ?? current.bearing - let t = c.tilt ?? current.viewingAngle + let z = Float(c.zoom ?? Double(current?.zoom ?? 0)) + let b = c.bearing ?? current?.bearing ?? 0 + let t = c.tilt ?? current?.viewingAngle ?? 0 return GMSCameraPosition.camera( withTarget: center, @@ -297,21 +306,6 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { ) } - private func mapCameraPositionToCamera(_ cp: GMSCameraPosition?) - -> RNCamera? { - guard let cp = cp else { return nil } - - return RNCamera( - center: RNLatLng( - latitude: cp.target.latitude, - longitude: cp.target.longitude - ), - zoom: Double(cp.zoom), - bearing: cp.bearing, - tilt: cp.viewingAngle - ) - } - func mapUserInterfaceStyleToUIUserInterfaceStyle( _ style: RNUserInterfaceStyle? ) -> UIUserInterfaceStyle? { @@ -326,23 +320,6 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec { return .unspecified } } - - func mapUIUserInterfaceStyleToUserInterfaceStyle( - _ uiStyle: UIUserInterfaceStyle? - ) -> RNUserInterfaceStyle? { - guard let uiStyle = uiStyle else { return nil } - - switch uiStyle { - case .light: - return .light - case .dark: - return .dark - case .unspecified: - return .default - @unknown default: - return .default - } - } } extension UIUserInterfaceStyle { diff --git a/package.json b/package.json index 5b79780..25fb84b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "git:clean": "git clean -dfX", "release": "semantic-release", "build": "yarn nitrogen && bob build", - "nitrogen": "nitrogen --logLevel=\"debug\" && node scripts/post-script.js", + "nitrogen": "nitrogen --logLevel=\"debug\" && node scripts/nitrogen-patch.js", "prepare": "bob build" }, "keywords": [ diff --git a/scripts/post-script.js b/scripts/nitrogen-patch.js similarity index 54% rename from scripts/post-script.js rename to scripts/nitrogen-patch.js index c2f73b2..ce7832f 100644 --- a/scripts/post-script.js +++ b/scripts/nitrogen-patch.js @@ -1,16 +1,24 @@ /** * Recursively patches all generated Android files: * - Replaces 'com.margelo.nitro.rngooglemapsplus' -> 'com.rngooglemapsplus' - * - Replaces 'com/margelo/nitro/rngooglemapsplus' -> 'com/rngooglemapsplus' (for C++ descriptors) - * - Removes 'margelo/nitro/' in GoogleMapsPlusOnLoad.cpp + * - Replaces 'com/margelo/nitro/rngooglemapsplus' -> 'com/rngooglemapsplus' + * - Removes 'margelo/nitro/' in RNGoogleMapsPlusOnLoad.cpp + * - Inserts `prepareToRecycleView()` under `onDropViewInstance()` if missing */ -const path = require('node:path'); -const { readdir, readFile, writeFile } = require('node:fs/promises'); +import { fileURLToPath } from 'url'; +import { basename } from 'path'; +import path from 'node:path'; +import { readdir, readFile, writeFile } from 'node:fs/promises'; const ROOT_DIR = path.join(process.cwd(), 'nitrogen', 'generated', 'android'); console.log(ROOT_DIR); const ANDROID_ONLOAD_FILE = path.join(ROOT_DIR, 'RNGoogleMapsPlusOnLoad.cpp'); +const HYBRID_VIEW_MANAGER = path.join( + ROOT_DIR, + 'kotlin/com/margelo/nitro/rngooglemapsplus/views/HybridRNGoogleMapsPlusViewManager.kt' +); + const REPLACEMENTS = [ { regex: /com\.margelo\.nitro\.rngooglemapsplus/g, @@ -22,8 +30,19 @@ const REPLACEMENTS = [ }, ]; +const __filename = fileURLToPath(import.meta.url); +const filename = basename(__filename); + +const RECYCLE_METHOD = ` + /// added by ${filename} + override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? { + return null + } +`; + +// Patch-Routine async function processFile(filePath) { - const content = await readFile(filePath, { encoding: 'utf8' }); + let content = await readFile(filePath, 'utf8'); let updated = content; for (const { regex, replacement } of REPLACEMENTS) { @@ -34,8 +53,22 @@ async function processFile(filePath) { updated = updated.replace(/margelo\/nitro\//g, ''); } + console.log(filePath); + if (path.resolve(filePath) === path.resolve(HYBRID_VIEW_MANAGER)) { + if (!/override fun prepareToRecycleView/.test(updated)) { + const pattern = + /(override fun onDropViewInstance\(view: View\)\s*\{[^}]+\}\s*)/m; + + if (pattern.test(updated)) { + updated = updated.replace(pattern, `$1${RECYCLE_METHOD}\n`); + } else { + updated = updated.replace(/}\s*$/m, `${RECYCLE_METHOD}\n}\n`); + } + } + } + if (updated !== content) { - await writeFile(filePath, updated); + await writeFile(filePath, updated, 'utf8'); console.log(`✔ Updated: ${filePath}`); } } diff --git a/src/RNGoogleMapsPlusView.nitro.ts b/src/RNGoogleMapsPlusView.nitro.ts index 26cae3c..ec84c95 100644 --- a/src/RNGoogleMapsPlusView.nitro.ts +++ b/src/RNGoogleMapsPlusView.nitro.ts @@ -17,13 +17,14 @@ import type { RNLocation, RNMapErrorCode, RNMapType, + RNInitialProps, } from './types'; export interface RNGoogleMapsPlusViewProps extends HybridViewProps { + initialProps?: RNInitialProps; buildingEnabled?: boolean; trafficEnabled?: boolean; customMapStyle?: string; - initialCamera?: RNCamera; userInterfaceStyle?: RNUserInterfaceStyle; minZoomLevel?: number; maxZoomLevel?: number; diff --git a/src/types.ts b/src/types.ts index 0705ed0..ffc957b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,12 @@ import type { HybridView } from 'react-native-nitro-modules'; export type GoogleMapsViewRef = HybridView; +export type RNInitialProps = { + mapId?: string; + liteMode?: boolean; + initialCamera?: RNCamera; +}; + export type RNLatLng = { latitude: number; longitude: number }; export type RNBoundingBox = { northEast: RNLatLng; southWest: RNLatLng };