diff --git a/README.md b/README.md index 6ac6da49..0554cbce 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,8 @@ Vue({ * [`create(...)`](#create) +* [`update(...)`](#update) +* [`getOptions()`](#getoptions) * [`enableTouch()`](#enabletouch) * [`disableTouch()`](#disabletouch) * [`enableClustering(...)`](#enableclustering) @@ -406,6 +408,30 @@ create(options: CreateMapArgs, callback?: MapListenerCallback Promise +``` + +| Param | Type | +| ------------ | ----------------------------------------------------------- | +| **`config`** | GoogleMapConfig | + +-------------------- + + +### getOptions() + +```typescript +getOptions() => GoogleMapConfig | null +``` + +**Returns:** GoogleMapConfig | null + +-------------------- + + ### enableTouch() ```typescript @@ -971,42 +997,44 @@ setOnMyLocationClickListener(callback?: MapListenerCallbackstring | A unique identifier for the map instance. | | -| **`apiKey`** | string | The Google Maps SDK API Key. | | -| **`config`** | GoogleMapConfig | The initial configuration settings for the map. | | -| **`element`** | HTMLElement | The DOM element that the Google Map View will be mounted on which determines size and positioning. | | -| **`forceCreate`** | boolean | Destroy and re-create the map instance if a map with the supplied id already exists | false | -| **`region`** | string | The region parameter alters your application to serve different map tiles or bias the application (such as biasing geocoding results towards the region). Only available for web. | | -| **`language`** | string | The language parameter affects the names of controls, copyright notices, driving directions, and control labels, as well as the responses to service requests. Only available for web. | | +| Prop | Type | Description | Default | +| ----------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| **`id`** | string | A unique identifier for the map instance. | | +| **`apiKey`** | string | The Google Maps SDK API Key. | | +| **`config`** | GoogleMapCreateConfig | The initial configuration settings for the map. | | +| **`element`** | HTMLElement | The DOM element that the Google Map View will be mounted on which determines size and positioning. | | +| **`forceCreate`** | boolean | Destroy and re-create the map instance if a map with the supplied id already exists | false | +| **`region`** | string | The region parameter alters your application to serve different map tiles or bias the application (such as biasing geocoding results towards the region). Only available for web. | | +| **`language`** | string | The language parameter affects the names of controls, copyright notices, driving directions, and control labels, as well as the responses to service requests. Only available for web. | | -#### GoogleMapConfig +#### GoogleMapCreateConfig For web, all the javascript Google Maps options are available as -GoogleMapConfig extends google.maps.MapOptions. -For iOS and Android only the config options declared on GoogleMapConfig are available. - -| Prop | Type | Description | Default | Since | -| ---------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | -| **`width`** | number | Override width for native map. | | | -| **`height`** | number | Override height for native map. | | | -| **`x`** | number | Override absolute x coordinate position for native map. | | | -| **`y`** | number | Override absolute y coordinate position for native map. | | | -| **`center`** | LatLng | Default location on the Earth towards which the camera points. | | | -| **`zoom`** | number | Sets the zoom of the map. | | | -| **`androidLiteMode`** | boolean | Enables image-based lite mode on Android. | false | | -| **`devicePixelRatio`** | number | Override pixel ratio for native map. | | | -| **`styles`** | MapTypeStyle[] \| null | Styles to apply to each of the default map types. Note that for satellite, hybrid and terrain modes, these styles will only apply to labels and geometry. | | 4.3.0 | -| **`mapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Web. | | 5.4.0 | -| **`androidMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Android. | | 5.4.0 | -| **`iOSMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for iOS. | | 5.4.0 | -| **`maxZoom`** | number \| null | The maximum zoom level which will be displayed on the map. If omitted, or set to <code>null</code>, the maximum zoom from the current map type is used instead. Valid zoom values are numbers from zero up to the supported <a href="https://developers.google.com/maps/documentation/javascript/maxzoom">maximum zoom level</a>. | | | -| **`minZoom`** | number \| null | The minimum zoom level which will be displayed on the map. If omitted, or set to <code>null</code>, the minimum zoom from the current map type is used instead. Valid zoom values are numbers from zero up to the supported <a href="https://developers.google.com/maps/documentation/javascript/maxzoom">maximum zoom level</a>. | | | -| **`mapTypeId`** | string \| null | The initial Map mapTypeId. Defaults to <code>ROADMAP</code>. | | | -| **`heading`** | number \| null | The heading for aerial imagery in degrees measured clockwise from cardinal direction North. Headings are snapped to the nearest available angle for which imagery is available. | | | -| **`restriction`** | MapRestriction \| null | Defines a boundary that restricts the area of the map accessible to users. When set, a user can only pan and zoom while the camera view stays inside the limits of the boundary. | | | +GoogleMapCreateConfig extends google.maps.MapOptions. +For iOS and Android the following options from google.maps.MapOptions with the same signature are additionally available: +- gestureHandling ('none' | 'auto') +- heading +- mapTypeId +- maxZoom +- minZoom +- restriction +- styles +- zoom + +| Prop | Type | Description | Default | Since | +| ---------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ----- | +| **`width`** | number | Override width for native map. | | | +| **`height`** | number | Override height for native map. | | | +| **`x`** | number | Override absolute x coordinate position for native map. | | | +| **`y`** | number | Override absolute y coordinate position for native map. | | | +| **`center`** | LatLng | Default location on the Earth towards which the camera points. | | | +| **`zoom`** | number | Sets the zoom of the map. | | | +| **`androidLiteMode`** | boolean | Enables image-based lite mode on Android. | false | | +| **`devicePixelRatio`** | number | Override pixel ratio for native map. | | | +| **`mapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Web. | | 5.4.0 | +| **`androidMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Android. | | 5.4.0 | +| **`iOSMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for iOS. | | 5.4.0 | #### LatLng @@ -1026,6 +1054,46 @@ An interface representing a pair of latitude and longitude coordinates. | **`mapId`** | string | +#### GoogleMapConfig + +For web, all the javascript Google Maps options are available as +GoogleMapConfig extends google.maps.MapOptions. +For iOS and Android the following options from google.maps.MapOptions with the same signature are additionally available: +- gestureHandling ('none' | 'auto') +- heading +- mapTypeId +- maxZoom +- minZoom +- restriction +- styles + +| Prop | Type | Description | +| ------------------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`isAccessibilityElementsEnabled`** | boolean | Show accessibility elements for overlay objects, such as Marker and Polyline. Only available on iOS. | +| **`isCompassEnabled`** | boolean | Enables or disables the compass. | +| **`isMyLocationButtonEnabled`** | boolean | Sets whether a button should be displayed, which centers the camera to the users current position. | +| **`isMyLocationEnabled`** | boolean | Sets whether the My Location dot and accuracy circle is enabled. | +| **`isIndoorMapsEnabled`** | boolean | Sets whether indoor maps should be enabled. Only available on Android and iOS. | +| **`isRotateGesturesEnabled`** | boolean | Sets the preference for whether rotate gestures should be enabled or disabled. | +| **`isTiltGesturesEnabled`** | boolean | Sets the preference for whether tilt gestures should be enabled or disabled. | +| **`isToolbarEnabled`** | boolean | Sets the preference for whether the Map Toolbar should be enabled or disabled. Only available on Android. | +| **`isTrafficLayerEnabled`** | boolean | Turns the traffic layer on or off. | +| **`isZoomGesturesEnabled`** | boolean | Sets the preference for whether zoom gestures should be enabled or disabled. | +| **`padding`** | MapPadding | Padding on the 'visible' region of the view. | + + +#### MapPadding + +Controls for setting padding on the 'visible' region of the view. + +| Prop | Type | +| ------------ | ------------------- | +| **`top`** | number | +| **`left`** | number | +| **`right`** | number | +| **`bottom`** | number | + + #### TileOverlay A tile overlay is an image placed on top of your map at a specific zoom level. Available on iOS, Android and Web @@ -1153,18 +1221,6 @@ Configuration properties for a Google Map Camera | **`animationDuration`** | number | This configuration option is not being used. | | -#### MapPadding - -Controls for setting padding on the 'visible' region of the view. - -| Prop | Type | -| ------------ | ------------------- | -| **`top`** | number | -| **`left`** | number | -| **`right`** | number | -| **`bottom`** | number | - - #### CameraIdleCallbackData | Prop | Type | diff --git a/plugin/README.md b/plugin/README.md index 4e4e8a70..2375135f 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -338,6 +338,8 @@ Vue({ * [`create(...)`](#create) +* [`update(...)`](#update) +* [`getOptions()`](#getoptions) * [`enableTouch()`](#enabletouch) * [`disableTouch()`](#disabletouch) * [`enableClustering(...)`](#enableclustering) @@ -406,6 +408,30 @@ create(options: CreateMapArgs, callback?: MapListenerCallback Promise +``` + +| Param | Type | +| ------------ | ----------------------------------------------------------- | +| **`config`** | GoogleMapConfig | + +-------------------- + + +### getOptions() + +```typescript +getOptions() => GoogleMapConfig | null +``` + +**Returns:** GoogleMapConfig | null + +-------------------- + + ### enableTouch() ```typescript @@ -971,42 +997,44 @@ setOnMyLocationClickListener(callback?: MapListenerCallbackstring | A unique identifier for the map instance. | | -| **`apiKey`** | string | The Google Maps SDK API Key. | | -| **`config`** | GoogleMapConfig | The initial configuration settings for the map. | | -| **`element`** | HTMLElement | The DOM element that the Google Map View will be mounted on which determines size and positioning. | | -| **`forceCreate`** | boolean | Destroy and re-create the map instance if a map with the supplied id already exists | false | -| **`region`** | string | The region parameter alters your application to serve different map tiles or bias the application (such as biasing geocoding results towards the region). Only available for web. | | -| **`language`** | string | The language parameter affects the names of controls, copyright notices, driving directions, and control labels, as well as the responses to service requests. Only available for web. | | +| Prop | Type | Description | Default | +| ----------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| **`id`** | string | A unique identifier for the map instance. | | +| **`apiKey`** | string | The Google Maps SDK API Key. | | +| **`config`** | GoogleMapCreateConfig | The initial configuration settings for the map. | | +| **`element`** | HTMLElement | The DOM element that the Google Map View will be mounted on which determines size and positioning. | | +| **`forceCreate`** | boolean | Destroy and re-create the map instance if a map with the supplied id already exists | false | +| **`region`** | string | The region parameter alters your application to serve different map tiles or bias the application (such as biasing geocoding results towards the region). Only available for web. | | +| **`language`** | string | The language parameter affects the names of controls, copyright notices, driving directions, and control labels, as well as the responses to service requests. Only available for web. | | -#### GoogleMapConfig +#### GoogleMapCreateConfig For web, all the javascript Google Maps options are available as -GoogleMapConfig extends google.maps.MapOptions. -For iOS and Android only the config options declared on GoogleMapConfig are available. - -| Prop | Type | Description | Default | Since | -| ---------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | -| **`width`** | number | Override width for native map. | | | -| **`height`** | number | Override height for native map. | | | -| **`x`** | number | Override absolute x coordinate position for native map. | | | -| **`y`** | number | Override absolute y coordinate position for native map. | | | -| **`center`** | LatLng | Default location on the Earth towards which the camera points. | | | -| **`zoom`** | number | Sets the zoom of the map. | | | -| **`androidLiteMode`** | boolean | Enables image-based lite mode on Android. | false | | -| **`devicePixelRatio`** | number | Override pixel ratio for native map. | | | -| **`styles`** | MapTypeStyle[] \| null | Styles to apply to each of the default map types. Note that for satellite, hybrid and terrain modes, these styles will only apply to labels and geometry. | | 4.3.0 | -| **`mapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Web. | | 5.4.0 | -| **`androidMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Android. | | 5.4.0 | -| **`iOSMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for iOS. | | 5.4.0 | -| **`maxZoom`** | number \| null | The maximum zoom level which will be displayed on the map. If omitted, or set to <code>null</code>, the maximum zoom from the current map type is used instead. Valid zoom values are numbers from zero up to the supported <a href="https://developers.google.com/maps/documentation/javascript/maxzoom">maximum zoom level</a>. | | | -| **`minZoom`** | number \| null | The minimum zoom level which will be displayed on the map. If omitted, or set to <code>null</code>, the minimum zoom from the current map type is used instead. Valid zoom values are numbers from zero up to the supported <a href="https://developers.google.com/maps/documentation/javascript/maxzoom">maximum zoom level</a>. | | | -| **`mapTypeId`** | string \| null | The initial Map mapTypeId. Defaults to <code>ROADMAP</code>. | | | -| **`heading`** | number \| null | The heading for aerial imagery in degrees measured clockwise from cardinal direction North. Headings are snapped to the nearest available angle for which imagery is available. | | | -| **`restriction`** | MapRestriction \| null | Defines a boundary that restricts the area of the map accessible to users. When set, a user can only pan and zoom while the camera view stays inside the limits of the boundary. | | | +GoogleMapCreateConfig extends google.maps.MapOptions. +For iOS and Android the following options from google.maps.MapOptions with the same signature are additionally available: +- gestureHandling ('none' | 'auto') +- heading +- mapTypeId +- maxZoom +- minZoom +- restriction +- styles +- zoom + +| Prop | Type | Description | Default | Since | +| ---------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ----- | +| **`width`** | number | Override width for native map. | | | +| **`height`** | number | Override height for native map. | | | +| **`x`** | number | Override absolute x coordinate position for native map. | | | +| **`y`** | number | Override absolute y coordinate position for native map. | | | +| **`center`** | LatLng | Default location on the Earth towards which the camera points. | | | +| **`zoom`** | number | Sets the zoom of the map. | | | +| **`androidLiteMode`** | boolean | Enables image-based lite mode on Android. | false | | +| **`devicePixelRatio`** | number | Override pixel ratio for native map. | | | +| **`mapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Web. | | 5.4.0 | +| **`androidMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for Android. | | 5.4.0 | +| **`iOSMapId`** | string | A map id associated with a specific map style or feature. [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) Only for iOS. | | 5.4.0 | #### LatLng @@ -1026,6 +1054,46 @@ An interface representing a pair of latitude and longitude coordinates. | **`mapId`** | string | +#### GoogleMapConfig + +For web, all the javascript Google Maps options are available as +GoogleMapConfig extends google.maps.MapOptions. +For iOS and Android the following options from google.maps.MapOptions with the same signature are additionally available: +- gestureHandling ('none' | 'auto') +- heading +- mapTypeId +- maxZoom +- minZoom +- restriction +- styles + +| Prop | Type | Description | +| ------------------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`isAccessibilityElementsEnabled`** | boolean | Show accessibility elements for overlay objects, such as Marker and Polyline. Only available on iOS. | +| **`isCompassEnabled`** | boolean | Enables or disables the compass. | +| **`isMyLocationButtonEnabled`** | boolean | Sets whether a button should be displayed, which centers the camera to the users current position. | +| **`isMyLocationEnabled`** | boolean | Sets whether the My Location dot and accuracy circle is enabled. | +| **`isIndoorMapsEnabled`** | boolean | Sets whether indoor maps should be enabled. Only available on Android and iOS. | +| **`isRotateGesturesEnabled`** | boolean | Sets the preference for whether rotate gestures should be enabled or disabled. | +| **`isTiltGesturesEnabled`** | boolean | Sets the preference for whether tilt gestures should be enabled or disabled. | +| **`isToolbarEnabled`** | boolean | Sets the preference for whether the Map Toolbar should be enabled or disabled. Only available on Android. | +| **`isTrafficLayerEnabled`** | boolean | Turns the traffic layer on or off. | +| **`isZoomGesturesEnabled`** | boolean | Sets the preference for whether zoom gestures should be enabled or disabled. | +| **`padding`** | MapPadding | Padding on the 'visible' region of the view. | + + +#### MapPadding + +Controls for setting padding on the 'visible' region of the view. + +| Prop | Type | +| ------------ | ------------------- | +| **`top`** | number | +| **`left`** | number | +| **`right`** | number | +| **`bottom`** | number | + + #### TileOverlay A tile overlay is an image placed on top of your map at a specific zoom level. Available on iOS, Android and Web @@ -1153,18 +1221,6 @@ Configuration properties for a Google Map Camera | **`animationDuration`** | number | This configuration option is not being used. | | -#### MapPadding - -Controls for setting padding on the 'visible' region of the view. - -| Prop | Type | -| ------------ | ------------------- | -| **`top`** | number | -| **`left`** | number | -| **`right`** | number | -| **`bottom`** | number | - - #### CameraIdleCallbackData | Prop | Type | diff --git a/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt index c0bc416b..283dd11b 100644 --- a/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt +++ b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt @@ -102,42 +102,6 @@ class CapacitorGoogleMap( bridge.webView.bringToFront() bridge.webView.setBackgroundColor(Color.TRANSPARENT) - if (config.styles != null) { - googleMap?.setMapStyle(MapStyleOptions(config.styles!!)) - } - - if (config.maxZoom != null) { - googleMap?.setMaxZoomPreference(config.maxZoom!!.toFloat()) - } - - if (config.minZoom != null) { - googleMap?.setMinZoomPreference((config.minZoom!!.toFloat())) - } - - if (config.mapTypeId != null) { - when (config.mapTypeId!!) { - "hybrid" -> googleMap?.mapType = GoogleMap.MAP_TYPE_HYBRID - "roadmap" -> googleMap?.mapType = GoogleMap.MAP_TYPE_NORMAL - "satellite" -> googleMap?.mapType = GoogleMap.MAP_TYPE_SATELLITE - "terrain" -> googleMap?.mapType = GoogleMap.MAP_TYPE_TERRAIN - } - } - - if (config.restriction != null) { - googleMap?.setLatLngBoundsForCameraTarget(config.restriction!!.latLngBounds) - } - - if (config.heading != null) { - googleMap?.cameraPosition?.let { cameraPosition -> - googleMap?.animateCamera( - CameraUpdateFactory.newCameraPosition( - CameraPosition.Builder(cameraPosition) - .bearing(config.heading!!.toFloat()) - .build() - ) - ) - } - } } } } @@ -186,6 +150,178 @@ class CapacitorGoogleMap( } } + fun applyConfig(configObject: JSObject, callback: (error: GoogleMapsError?) -> Unit) { + try { + googleMap ?: throw GoogleMapNotAvailable() + + CoroutineScope(Dispatchers.Main).launch { + if (configObject.has("gestureHandling")) { + googleMap?.uiSettings?.setAllGesturesEnabled(configObject.getString("gestureHandling") != "none") + } + + if (configObject.has("heading")) { + googleMap?.cameraPosition?.let { cameraPosition -> + googleMap?.animateCamera( + CameraUpdateFactory.newCameraPosition( + CameraPosition.Builder(cameraPosition) + .bearing(configObject.getDouble("heading")!!.toFloat()) + .build() + ) + ) + } + } + + if (configObject.has("isCompassEnabled")) { + googleMap?.uiSettings?.isCompassEnabled = configObject.getBool("isCompassEnabled") == true + } + + if (configObject.has("isIndoorMapsEnabled")) { + googleMap?.isIndoorEnabled = configObject.getBool("isIndoorMapsEnabled") == true + } + + if (configObject.has("isMyLocationButtonEnabled")) { + googleMap?.uiSettings?.isMyLocationButtonEnabled = configObject.getBool("isMyLocationButtonEnabled") == true + } + + if (configObject.has("isMyLocationEnabled")) { + @SuppressLint("MissingPermission") + googleMap?.isMyLocationEnabled = configObject.getBool("isMyLocationEnabled") == true + } + + if (configObject.has("isRotateGesturesEnabled")) { + googleMap?.uiSettings?.isRotateGesturesEnabled = configObject.getBool("isRotateGesturesEnabled") == true + } + + if (configObject.has("isTiltGesturesEnabled")) { + googleMap?.uiSettings?.isTiltGesturesEnabled = configObject.getBool("isTiltGesturesEnabled") == true + } + + if (configObject.has("isToolbarEnabled")) { + googleMap?.uiSettings?.isMapToolbarEnabled = configObject.getBool("isToolbarEnabled") == true + } + + if (configObject.has("isTrafficLayerEnabled")) { + googleMap?.isTrafficEnabled = configObject.getBool("isTrafficLayerEnabled") == true + } + + if (configObject.has("isZoomGesturesEnabled")) { + googleMap?.uiSettings?.isZoomGesturesEnabled = configObject.getBool("isZoomGesturesEnabled") == true + } + + if (configObject.has("mapTypeId")) { + setMapType(configObject.getString("mapTypeId")) + } + + if (configObject.has("maxZoom")) { + setMaxZoom(configObject.getDouble("maxZoom").toFloat()) + } + + if (configObject.has("minZoom")) { + setMinZoom(configObject.getDouble("minZoom").toFloat()) + } + + if (configObject.has("padding")) { + setPadding(configObject.getJSObject("padding")) + } + + if (configObject.has("restriction")) { + setRestriction(configObject.getJSObject("restriction")) + } + + if (configObject.has("styles")) { + googleMap?.setMapStyle(configObject.getString("styles") + ?.let { MapStyleOptions(it) }) + } + + callback(null) + } + } catch (e: GoogleMapsError) { + callback(e) + } + } + + private fun setMapType(mapTypeId: String?) { + val mapTypeInt: Int = + when (mapTypeId?.lowercase()) { + "normal" -> MAP_TYPE_NORMAL + "hybrid" -> MAP_TYPE_HYBRID + "satellite" -> MAP_TYPE_SATELLITE + "terrain" -> MAP_TYPE_TERRAIN + "none" -> MAP_TYPE_NONE + else -> { + Log.w( + "CapacitorGoogleMaps", + "unknown mapView type '$mapTypeId' Defaulting to normal." + ) + MAP_TYPE_NORMAL + } + } + + googleMap?.mapType = mapTypeInt + } + + private fun setMaxZoom(maxZoom: Float) { + var minZoom = googleMap?.minZoomLevel + googleMap?.resetMinMaxZoomPreference() + if (minZoom != null) { + googleMap?.setMinZoomPreference(minZoom) + } + if (maxZoom != 0F) { + googleMap?.setMinZoomPreference(maxZoom) + } + } + + private fun setMinZoom(minZoom: Float) { + var maxZoom = googleMap?.maxZoomLevel + googleMap?.resetMinMaxZoomPreference() + if (maxZoom != null) { + googleMap?.setMaxZoomPreference(maxZoom) + } + if (minZoom != 0F) { + googleMap?.setMinZoomPreference(minZoom) + } + } + + private fun setPadding(paddingObj: JSObject?) { + if (paddingObj == null) { + googleMap?.setPadding(0, 0, 0, 0) + } else { + val padding = GoogleMapPadding(paddingObj) + val left = getScaledPixels(delegate.bridge, padding.left) + val top = getScaledPixels(delegate.bridge, padding.top) + val right = getScaledPixels(delegate.bridge, padding.right) + val bottom = getScaledPixels(delegate.bridge, padding.bottom) + googleMap?.setPadding(left, top, right, bottom) + } + } + + private fun setRestriction(restrictionObj: JSObject?) { + var latLngBounds = restrictionObj?.getJSObject("latLngBounds") + var bounds: LatLngBounds? = null + + if (latLngBounds != null) { + bounds = createLatLngBoundsFromGMSJS(latLngBounds) + } + + googleMap?.resetMinMaxZoomPreference() + googleMap?.setLatLngBoundsForCameraTarget(null) + + if (bounds != null) { + googleMap?.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0), + object : CancelableCallback { + override fun onFinish() { + val zoom = googleMap?.cameraPosition?.zoom + if (zoom != null) { + googleMap?.setMinZoomPreference(zoom) + } + googleMap?.setLatLngBoundsForCameraTarget(bounds) + } + override fun onCancel() {} + } + ) + } + } + fun destroy() { runBlocking { val job = @@ -653,104 +789,6 @@ class CapacitorGoogleMap( } } - fun getMapType(callback: (type: String, error: GoogleMapsError?) -> Unit) { - try { - googleMap ?: throw GoogleMapNotAvailable() - CoroutineScope(Dispatchers.Main).launch { - val mapType: String = when (googleMap?.mapType) { - MAP_TYPE_NORMAL -> "Normal" - MAP_TYPE_HYBRID -> "Hybrid" - MAP_TYPE_SATELLITE -> "Satellite" - MAP_TYPE_TERRAIN -> "Terrain" - MAP_TYPE_NONE -> "None" - else -> { - "Normal" - } - } - callback(mapType, null); - } - } catch (e: GoogleMapsError) { - callback("", e) - } - } - - fun setMapType(mapType: String, callback: (error: GoogleMapsError?) -> Unit) { - try { - googleMap ?: throw GoogleMapNotAvailable() - CoroutineScope(Dispatchers.Main).launch { - val mapTypeInt: Int = - when (mapType) { - "Normal" -> MAP_TYPE_NORMAL - "Hybrid" -> MAP_TYPE_HYBRID - "Satellite" -> MAP_TYPE_SATELLITE - "Terrain" -> MAP_TYPE_TERRAIN - "None" -> MAP_TYPE_NONE - else -> { - Log.w( - "CapacitorGoogleMaps", - "unknown mapView type '$mapType' Defaulting to normal." - ) - MAP_TYPE_NORMAL - } - } - - googleMap?.mapType = mapTypeInt - callback(null) - } - } catch (e: GoogleMapsError) { - callback(e) - } - } - - fun enableIndoorMaps(enabled: Boolean, callback: (error: GoogleMapsError?) -> Unit) { - try { - googleMap ?: throw GoogleMapNotAvailable() - CoroutineScope(Dispatchers.Main).launch { - googleMap?.isIndoorEnabled = enabled - callback(null) - } - } catch (e: GoogleMapsError) { - callback(e) - } - } - - fun enableTrafficLayer(enabled: Boolean, callback: (error: GoogleMapsError?) -> Unit) { - try { - googleMap ?: throw GoogleMapNotAvailable() - CoroutineScope(Dispatchers.Main).launch { - googleMap?.isTrafficEnabled = enabled - callback(null) - } - } catch (e: GoogleMapsError) { - callback(e) - } - } - - @SuppressLint("MissingPermission") - fun enableCurrentLocation(enabled: Boolean, callback: (error: GoogleMapsError?) -> Unit) { - try { - googleMap ?: throw GoogleMapNotAvailable() - CoroutineScope(Dispatchers.Main).launch { - googleMap?.isMyLocationEnabled = enabled - callback(null) - } - } catch (e: GoogleMapsError) { - callback(e) - } - } - - fun setPadding(padding: GoogleMapPadding, callback: (error: GoogleMapsError?) -> Unit) { - try { - googleMap ?: throw GoogleMapNotAvailable() - CoroutineScope(Dispatchers.Main).launch { - googleMap?.setPadding(padding.left, padding.top, padding.right, padding.bottom) - callback(null) - } - } catch (e: GoogleMapsError) { - callback(e) - } - } - fun getMapBounds(): Rect { return Rect( getScaledPixels(delegate.bridge, config.x), @@ -769,6 +807,20 @@ class CapacitorGoogleMap( googleMap?.animateCamera(cameraUpdate) } + private fun createLatLngBoundsFromGMSJS(boundsObject: JSObject): LatLngBounds { + val southwestLatLng = LatLng( + boundsObject.getDouble("south"), + boundsObject.getDouble("west") + ) + + val northeastLatLng = LatLng( + boundsObject.getDouble("north"), + boundsObject.getDouble("east") + ) + + return LatLngBounds(southwestLatLng, northeastLatLng) + } + private fun getScaledPixels(bridge: Bridge, pixels: Int): Int { // Get the screen's density scale val scale = bridge.activity.resources.displayMetrics.density diff --git a/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsPlugin.kt b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsPlugin.kt index 06ecab29..7a29d798 100644 --- a/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsPlugin.kt +++ b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsPlugin.kt @@ -9,7 +9,6 @@ import android.view.View import com.getcapacitor.* import com.getcapacitor.annotation.CapacitorPlugin import com.getcapacitor.annotation.Permission -import com.getcapacitor.annotation.PermissionCallback import com.google.android.gms.maps.MapsInitializer import com.google.android.gms.maps.OnMapsSdkInitializedCallback import com.google.android.gms.maps.model.LatLng @@ -154,7 +153,40 @@ class CapacitorGoogleMapsPlugin : Plugin(), OnMapsSdkInitializedCallback { val newMap = CapacitorGoogleMap(id, config, this) maps[id] = newMap - call.resolve() + newMap.applyConfig(configObject, { err -> + if (err != null) { + throw err + } + + call.resolve() + }) + } catch (e: GoogleMapsError) { + handleError(call, e) + } catch (e: Exception) { + handleError(call, e) + } + } + + @PluginMethod + fun update(call: PluginCall) { + try { + val id = call.getString("id") + id ?: throw InvalidMapIdError() + + val map = maps[id] + map ?: throw MapNotFoundError() + + val configObject = + call.getObject("config") + ?: throw InvalidArgumentsError("config object is missing") + + map.applyConfig(configObject, { err -> + if (err != null) { + throw err + } + + call.resolve() + }) } catch (e: GoogleMapsError) { handleError(call, e) } catch (e: Exception) { @@ -712,160 +744,6 @@ class CapacitorGoogleMapsPlugin : Plugin(), OnMapsSdkInitializedCallback { } } - @PluginMethod - fun getMapType(call: PluginCall) { - try { - val id = call.getString("id") - id ?: throw InvalidMapIdError() - - val map = maps[id] - map ?: throw MapNotFoundError() - - map.getMapType() { type, err -> - - if (err != null) { - throw err - } - val data = JSObject() - data.put("type", type) - call.resolve(data) - } - } catch (e: GoogleMapsError) { - handleError(call, e) - } catch (e: Exception) { - handleError(call, e) - } - } - - @PluginMethod - fun setMapType(call: PluginCall) { - try { - val id = call.getString("id") - id ?: throw InvalidMapIdError() - - val map = maps[id] - map ?: throw MapNotFoundError() - - val mapType = - call.getString("mapType") ?: throw InvalidArgumentsError("mapType is missing") - - map.setMapType(mapType) { err -> - if (err != null) { - throw err - } - - call.resolve() - } - } catch (e: GoogleMapsError) { - handleError(call, e) - } catch (e: Exception) { - handleError(call, e) - } - } - - @PluginMethod - fun enableIndoorMaps(call: PluginCall) { - try { - val id = call.getString("id") - id ?: throw InvalidMapIdError() - - val map = maps[id] - map ?: throw MapNotFoundError() - - val enabled = - call.getBoolean("enabled") ?: throw InvalidArgumentsError("enabled is missing") - - map.enableIndoorMaps(enabled) { err -> - if (err != null) { - throw err - } - - call.resolve() - } - } catch (e: GoogleMapsError) { - handleError(call, e) - } catch (e: Exception) { - handleError(call, e) - } - } - - @PluginMethod - fun enableTrafficLayer(call: PluginCall) { - try { - val id = call.getString("id") - id ?: throw InvalidMapIdError() - - val map = maps[id] - map ?: throw MapNotFoundError() - - val enabled = - call.getBoolean("enabled") ?: throw InvalidArgumentsError("enabled is missing") - - map.enableTrafficLayer(enabled) { err -> - if (err != null) { - throw err - } - - call.resolve() - } - } catch (e: GoogleMapsError) { - handleError(call, e) - } catch (e: Exception) { - handleError(call, e) - } - } - - @PluginMethod - fun enableCurrentLocation(call: PluginCall) { - if (getPermissionState(LOCATION) != PermissionState.GRANTED) { - requestAllPermissions(call, "enableCurrentLocationCallback") - } else { - internalEnableCurrentLocation(call) - } - } - - @PermissionCallback - fun enableCurrentLocationCallback(call: PluginCall) { - if (getPermissionState(LOCATION) == PermissionState.GRANTED) { - internalEnableCurrentLocation(call) - } else { - call.reject("location permission was denied") - } - } - - @PluginMethod - fun setPadding(call: PluginCall) { - try { - val id = call.getString("id") - id ?: throw InvalidMapIdError() - - val map = maps[id] - map ?: throw MapNotFoundError() - - val paddingObj = - call.getObject("padding") ?: throw InvalidArgumentsError("padding is missing") - - val padding = GoogleMapPadding(paddingObj) - - map.setPadding(padding) { err -> - if (err != null) { - throw err - } - - call.resolve() - } - } catch (e: GoogleMapsError) { - handleError(call, e) - } catch (e: Exception) { - handleError(call, e) - } - } - - @PluginMethod - fun enableAccessibilityElements(call: PluginCall) { - call.unavailable("this call is not available on android") - } - @PluginMethod fun onScroll(call: PluginCall) { try { @@ -1059,31 +937,6 @@ class CapacitorGoogleMapsPlugin : Plugin(), OnMapsSdkInitializedCallback { return LatLngBounds(southwestLatLng, northeastLatLng) } - private fun internalEnableCurrentLocation(call: PluginCall) { - try { - val id = call.getString("id") - id ?: throw InvalidMapIdError() - - val map = maps[id] - map ?: throw MapNotFoundError() - - val enabled = - call.getBoolean("enabled") ?: throw InvalidArgumentsError("enabled is missing") - - map.enableCurrentLocation(enabled) { err -> - if (err != null) { - throw err - } - - call.resolve() - } - } catch (e: GoogleMapsError) { - handleError(call, e) - } catch (e: Exception) { - handleError(call, e) - } - } - fun notify(event: String, data: JSObject) { notifyListeners(event, data) } diff --git a/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt index 696380c4..660588bc 100644 --- a/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt +++ b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt @@ -3,7 +3,6 @@ package com.capacitorjs.plugins.googlemaps import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng -import com.google.android.gms.maps.model.LatLngBounds import org.json.JSONObject class GoogleMapConfig(fromJSONObject: JSONObject) { @@ -11,17 +10,9 @@ class GoogleMapConfig(fromJSONObject: JSONObject) { var height: Int = 0 var x: Int = 0 var y: Int = 0 - var center: LatLng = LatLng(0.0, 0.0) var googleMapOptions: GoogleMapOptions? = null - var zoom: Int = 0 - var liteMode: Boolean = false var devicePixelRatio: Float = 1.00f - var styles: String? = null - var mapId: String? = null - var minZoom: Int? = null - var maxZoom: Int? = null var mapTypeId: String? = null - var restriction: GoogleMapConfigRestriction? = null var heading: Double? = null init { @@ -73,10 +64,6 @@ class GoogleMapConfig(fromJSONObject: JSONObject) { ) } - liteMode = - fromJSONObject.has("androidLiteMode") && - fromJSONObject.getBoolean("androidLiteMode") - width = fromJSONObject.getInt("width") height = fromJSONObject.getInt("height") x = fromJSONObject.getInt("x") @@ -90,28 +77,21 @@ class GoogleMapConfig(fromJSONObject: JSONObject) { tempMinZoom = tempMaxZoom.also { tempMaxZoom = tempMinZoom } } - minZoom = tempMinZoom - maxZoom = tempMaxZoom - - zoom = tempZoom.coerceIn( - tempMinZoom ?: Int.MIN_VALUE, - tempMaxZoom ?: Int.MAX_VALUE - ) - mapTypeId = fromJSONObject.optString("mapTypeId").takeIf { fromJSONObject.has("mapTypeId") } val lat = centerJSONObject.getDouble("lat") val lng = centerJSONObject.getDouble("lng") - center = LatLng(lat, lng) - + val center = LatLng(lat, lng) + val zoom = tempZoom.coerceIn( + tempMinZoom ?: Int.MIN_VALUE, + tempMaxZoom ?: Int.MAX_VALUE + ) + val mapId = fromJSONObject.getString("androidMapId") + val liteMode = + fromJSONObject.has("androidLiteMode") && + fromJSONObject.getBoolean("androidLiteMode") val cameraPosition = CameraPosition(center, zoom.toFloat(), 0.0F, 0.0F) - styles = fromJSONObject.getString("styles") - - mapId = fromJSONObject.getString("androidMapId") - - restriction = fromJSONObject.optJSONObject("restriction")?.let { GoogleMapConfigRestriction(it) } - heading = fromJSONObject.optDouble("heading").takeIf { fromJSONObject.has("heading") } googleMapOptions = GoogleMapOptions().camera(cameraPosition).liteMode(liteMode) @@ -120,46 +100,3 @@ class GoogleMapConfig(fromJSONObject: JSONObject) { } } } - -class GoogleMapConfigRestriction(fromJSONObject: JSONObject) { - var latLngBounds: LatLngBounds - - init { - if (!fromJSONObject.has("latLngBounds")) { - throw InvalidArgumentsError( - "GoogleMapConfigRestriction object is missing the required 'latLngBounds' property" - ) - } - val latLngBoundsObj = fromJSONObject.getJSONObject("latLngBounds") - - if (!latLngBoundsObj.has("north")) { - throw InvalidArgumentsError( - "GoogleMapConfigRestriction object is missing the required 'latLngBounds.north' property" - ) - } - if (!latLngBoundsObj.has("south")) { - throw InvalidArgumentsError( - "GoogleMapConfigRestriction object is missing the required 'latLngBounds.south' property" - ) - } - if (!latLngBoundsObj.has("east")) { - throw InvalidArgumentsError( - "GoogleMapConfigRestriction object is missing the required 'latLngBounds.east' property" - ) - } - if (!latLngBoundsObj.has("west")) { - throw InvalidArgumentsError( - "GoogleMapConfigRestriction object is missing the required 'latLngBounds.west' property" - ) - } - val north = latLngBoundsObj.getDouble("north") - val south = latLngBoundsObj.getDouble("south") - val east = latLngBoundsObj.getDouble("east") - val west = latLngBoundsObj.getDouble("west") - - latLngBounds = LatLngBounds( - LatLng(south, west), - LatLng(north, east) - ) - } -} diff --git a/plugin/e2e-tests/src/pages/Map/ConfigMap.tsx b/plugin/e2e-tests/src/pages/Map/ConfigMap.tsx index bf66c024..d1c49d81 100644 --- a/plugin/e2e-tests/src/pages/Map/ConfigMap.tsx +++ b/plugin/e2e-tests/src/pages/Map/ConfigMap.tsx @@ -1,6 +1,5 @@ import { useState } from 'react'; import { GoogleMap } from '@capacitor/google-maps'; -import { MapType } from '@capacitor/google-maps'; import { IonButton, IonTextarea } from '@ionic/react'; import BaseTestingPage from '../../components/BaseTestingPage'; @@ -87,10 +86,10 @@ const ConfigMapPage: React.FC = () => { setCommandOutput(""); try { const map1 = maps[0]; - await map1.setMapType(MapType.Terrain) + await map1.update({ mapTypeId: 'terrain' }) const map2 = maps[1]; - await map2.setMapType(MapType.Satellite) + await map2.update({ mapTypeId: 'satellite' }) } catch(err: any) { setCommandOutput(err.message); } @@ -111,7 +110,7 @@ const ConfigMapPage: React.FC = () => { animate: true, animationDuration: 50, }) - await map1.enableIndoorMaps(true); + await map1.update({ isIndoorMapsEnabled: true }); } catch(err: any) { setCommandOutput(err.message); } @@ -121,7 +120,7 @@ const ConfigMapPage: React.FC = () => { setCommandOutput(""); try { const map1 = maps[0]; - await map1.enableTrafficLayer(true); + await map1.update({ isTrafficLayerEnabled: true }); await map1.setCamera({ zoom: 10, animate: true, @@ -129,7 +128,7 @@ const ConfigMapPage: React.FC = () => { }) const map2 = maps[0]; - await map2.enableTrafficLayer(true); + await map2.update({ isTrafficLayerEnabled: true }); } catch(err: any) { setCommandOutput(err.message); } @@ -139,10 +138,10 @@ const ConfigMapPage: React.FC = () => { setCommandOutput(""); try { const map1 = maps[0]; - await map1.enableTrafficLayer(false); + await map1.update({ isTrafficLayerEnabled: false }); const map2 = maps[0]; - await map2.enableTrafficLayer(false); + await map2.update({ isTrafficLayerEnabled: false }); } catch(err: any) { setCommandOutput(err.message); } @@ -171,7 +170,7 @@ const ConfigMapPage: React.FC = () => { animate: true, animationDuration: 50, }) - await map1.enableCurrentLocation(true); + await map1.update({ isMyLocationEnabled: true }); const map2 = maps[1]; await map2.setCamera({ @@ -179,7 +178,7 @@ const ConfigMapPage: React.FC = () => { animate: true, animationDuration: 50, }); - await map2.enableCurrentLocation(true); + await map2.update({ isMyLocationEnabled: true }); } catch(err: any) { setCommandOutput(err.message); } @@ -189,10 +188,10 @@ const ConfigMapPage: React.FC = () => { setCommandOutput(""); try { const map1 = maps[0]; - await map1.enableCurrentLocation(false); + await map1.update({ isMyLocationEnabled: false }); const map2 = maps[1]; - await map2.enableCurrentLocation(false); + await map2.update({ isMyLocationEnabled: false }); } catch(err: any) { setCommandOutput(err.message); } @@ -202,10 +201,10 @@ const ConfigMapPage: React.FC = () => { setCommandOutput(""); try { const map1 = maps[0]; - await map1.enableAccessibilityElements(true); + await map1.update({ isAccessibilityElementsEnabled: true }); const map2 = maps[1]; - await map2.enableAccessibilityElements(true); + await map2.update({ isAccessibilityElementsEnabled: true }); } catch(err: any) { setCommandOutput(err.message); } @@ -215,10 +214,10 @@ const ConfigMapPage: React.FC = () => { setCommandOutput(""); try { const map1 = maps[0]; - await map1.enableAccessibilityElements(false); + await map1.update({ isAccessibilityElementsEnabled: false }); const map2 = maps[1]; - await map2.enableAccessibilityElements(false); + await map2.update({ isAccessibilityElementsEnabled: false }); } catch(err: any) { setCommandOutput(err.message); } diff --git a/plugin/e2e-tests/src/pages/Markers/MultipleMarkers.tsx b/plugin/e2e-tests/src/pages/Markers/MultipleMarkers.tsx index b0392e87..b753146b 100644 --- a/plugin/e2e-tests/src/pages/Markers/MultipleMarkers.tsx +++ b/plugin/e2e-tests/src/pages/Markers/MultipleMarkers.tsx @@ -210,7 +210,7 @@ const MultipleMarkers: React.FC = () => { animate: true, animationDuration: 50, }); - await map?.enableCurrentLocation(true); + await map?.update({ isMyLocationEnabled: true }); } async function showCurrentBounds() { diff --git a/plugin/ios/Sources/CapacitorGoogleMapsPlugin/CapacitorGoogleMapsPlugin.swift b/plugin/ios/Sources/CapacitorGoogleMapsPlugin/CapacitorGoogleMapsPlugin.swift index 55a830ed..e6c47e87 100644 --- a/plugin/ios/Sources/CapacitorGoogleMapsPlugin/CapacitorGoogleMapsPlugin.swift +++ b/plugin/ios/Sources/CapacitorGoogleMapsPlugin/CapacitorGoogleMapsPlugin.swift @@ -6,16 +6,16 @@ import GoogleMapsUtils extension GMSMapViewType { static func fromString(mapType: String) -> GMSMapViewType { - switch mapType { - case "Normal": + switch mapType.lowercased() { + case "normal": return .normal - case "Hybrid": + case "hybrid": return .hybrid - case "Satellite": + case "satellite": return .satellite - case "Terrain": + case "terrain": return .terrain - case "None": + case "none": return .none default: print("CapacitorGoogleMaps Warning: unknown mapView type '\(mapType)'. Defaulting to normal.") @@ -25,17 +25,17 @@ extension GMSMapViewType { static func toString(mapType: GMSMapViewType) -> String { switch mapType { case .normal: - return "Normal" + return "normal" case .hybrid: - return "Hybrid" + return "hybrid" case .satellite: - return "Satellite" + return "satellite" case .terrain: - return "Terrain" + return "terrain" case .none: - return "None" + return "none" default: - return "Normal" + return "normal" } } } @@ -69,6 +69,7 @@ public class CapacitorGoogleMapsPlugin: CAPPlugin, GMSMapViewDelegate, CAPBridge public let jsName = "CapacitorGoogleMaps" public let pluginMethods: [CAPPluginMethod] = [ CAPPluginMethod(name: "create", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "update", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "enableTouch", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "disableTouch", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "addTileOverlay", returnType: CAPPluginReturnPromise), @@ -87,13 +88,6 @@ public class CapacitorGoogleMapsPlugin: CAPPlugin, GMSMapViewDelegate, CAPBridge CAPPluginMethod(name: "disableClustering", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "destroy", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "setCamera", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "getMapType", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "setMapType", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "enableIndoorMaps", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "enableTrafficLayer", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "enableAccessibilityElements", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "enableCurrentLocation", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "setPadding", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "onScroll", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "onResize", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "onDisplay", returnType: CAPPluginReturnPromise), @@ -159,8 +153,31 @@ public class CapacitorGoogleMapsPlugin: CAPPlugin, GMSMapViewDelegate, CAPBridge DispatchQueue.main.sync { let newMap = Map(id: id, config: config, delegate: self) self.maps[id] = newMap + newMap.applyConfig(configObj: configObj) + } + + call.resolve() + } catch { + handleError(call, error: error) + } + } + + @objc func update(_ call: CAPPluginCall) { + do { + guard let id = call.getString("id") else { + throw GoogleMapErrors.invalidMapId + } + + guard let map = self.maps[id] else { + throw GoogleMapErrors.mapNotFound + } + + guard let configObj = call.getObject("config") else { + throw GoogleMapErrors.invalidArguments("config object is missing") } + map.applyConfig(configObj: configObj) + call.resolve() } catch { handleError(call, error: error) @@ -623,168 +640,6 @@ public class CapacitorGoogleMapsPlugin: CAPPlugin, GMSMapViewDelegate, CAPBridge } } - @objc func getMapType(_ call: CAPPluginCall) { - do { - guard let id = call.getString("id") else { - throw GoogleMapErrors.invalidMapId - } - - guard let map = self.maps[id] else { - throw GoogleMapErrors.mapNotFound - } - - let mapType = GMSMapViewType.toString(mapType: map.getMapType()) - - call.resolve([ - "type": mapType - ]) - } catch { - handleError(call, error: error) - } - } - - @objc func setMapType(_ call: CAPPluginCall) { - do { - guard let id = call.getString("id") else { - throw GoogleMapErrors.invalidMapId - } - - guard let map = self.maps[id] else { - throw GoogleMapErrors.mapNotFound - } - - guard let mapTypeString = call.getString("mapType") else { - throw GoogleMapErrors.invalidArguments("mapType is missing") - } - - let mapType = GMSMapViewType.fromString(mapType: mapTypeString) - - try map.setMapType(mapType: mapType) - - call.resolve() - } catch { - handleError(call, error: error) - } - } - - @objc func enableIndoorMaps(_ call: CAPPluginCall) { - do { - guard let id = call.getString("id") else { - throw GoogleMapErrors.invalidMapId - } - - guard let map = self.maps[id] else { - throw GoogleMapErrors.mapNotFound - } - - guard let enabled = call.getBool("enabled") else { - throw GoogleMapErrors.invalidArguments("enabled is missing") - } - - try map.enableIndoorMaps(enabled: enabled) - - call.resolve() - } catch { - handleError(call, error: error) - } - } - - @objc func enableTrafficLayer(_ call: CAPPluginCall) { - do { - guard let id = call.getString("id") else { - throw GoogleMapErrors.invalidMapId - } - - guard let map = self.maps[id] else { - throw GoogleMapErrors.mapNotFound - } - - guard let enabled = call.getBool("enabled") else { - throw GoogleMapErrors.invalidArguments("enabled is missing") - } - - try map.enableTrafficLayer(enabled: enabled) - - call.resolve() - } catch { - handleError(call, error: error) - } - } - - @objc func enableAccessibilityElements(_ call: CAPPluginCall) { - do { - guard let id = call.getString("id") else { - throw GoogleMapErrors.invalidMapId - } - - guard let map = self.maps[id] else { - throw GoogleMapErrors.mapNotFound - } - - guard let enabled = call.getBool("enabled") else { - throw GoogleMapErrors.invalidArguments("enabled is missing") - } - - try map.enableAccessibilityElements(enabled: enabled) - - call.resolve() - } catch { - handleError(call, error: error) - } - } - - @objc func setPadding(_ call: CAPPluginCall) { - do { - guard let id = call.getString("id") else { - throw GoogleMapErrors.invalidMapId - } - - guard let map = self.maps[id] else { - throw GoogleMapErrors.mapNotFound - } - - guard let configObj = call.getObject("padding") else { - throw GoogleMapErrors.invalidArguments("padding is missing") - } - - let padding = try GoogleMapPadding.init(fromJSObject: configObj) - - try map.setPadding(padding: padding) - - call.resolve() - } catch { - handleError(call, error: error) - } - } - - @objc func enableCurrentLocation(_ call: CAPPluginCall) { - do { - guard let id = call.getString("id") else { - throw GoogleMapErrors.invalidMapId - } - - guard let map = self.maps[id] else { - throw GoogleMapErrors.mapNotFound - } - - guard let enabled = call.getBool("enabled") else { - throw GoogleMapErrors.invalidArguments("enabled is missing") - } - - let locationStatus = checkLocationPermission() - - if enabled && !(locationStatus == "granted" || locationStatus == "prompt") { - throw GoogleMapErrors.permissionsDeniedLocation - } - - try map.enableCurrentLocation(enabled: enabled) - - call.resolve() - } catch { - handleError(call, error: error) - } - } - @objc func enableClustering(_ call: CAPPluginCall) { do { guard let id = call.getString("id") else { diff --git a/plugin/ios/Sources/CapacitorGoogleMapsPlugin/GoogleMapConfig.swift b/plugin/ios/Sources/CapacitorGoogleMapsPlugin/GoogleMapConfig.swift index a1086744..8d5a0cd1 100644 --- a/plugin/ios/Sources/CapacitorGoogleMapsPlugin/GoogleMapConfig.swift +++ b/plugin/ios/Sources/CapacitorGoogleMapsPlugin/GoogleMapConfig.swift @@ -1,6 +1,5 @@ import Foundation import Capacitor -import GoogleMaps public struct GoogleMapConfig: Codable { let width: Double @@ -9,13 +8,7 @@ public struct GoogleMapConfig: Codable { let y: Double let center: LatLng let zoom: Double - let styles: String? var mapId: String? - let mapTypeId: String? - let maxZoom: Double? - let minZoom: Double? - let restriction: GoogleMapConfigRestriction? - let heading: Double? init(fromJSObject: JSObject) throws { guard let width = fromJSObject["width"] as? Double else { @@ -51,23 +44,14 @@ public struct GoogleMapConfig: Codable { self.x = x self.y = y self.center = LatLng(lat: lat, lng: lng) - if let stylesArray = fromJSObject["styles"] as? JSArray, let jsonData = try? JSONSerialization.data(withJSONObject: stylesArray, options: []) { - self.styles = String(data: jsonData, encoding: .utf8) - } else { - self.styles = nil - } self.mapId = fromJSObject["iOSMapId"] as? String - self.mapTypeId = fromJSObject["mapTypeId"] as? String - var maxZoom = fromJSObject["maxZoom"] as? Double var minZoom = fromJSObject["minZoom"] as? Double if let unwrappedMinZoom = minZoom, let unwrappedMaxZoom = maxZoom, unwrappedMinZoom > unwrappedMaxZoom { swap(&minZoom, &maxZoom) } - self.minZoom = minZoom - self.maxZoom = maxZoom if let maxZoom, zoom > maxZoom { self.zoom = maxZoom @@ -76,75 +60,5 @@ public struct GoogleMapConfig: Codable { } else { self.zoom = zoom } - - if let restrictionObj = fromJSObject["restriction"] as? JSObject { - self.restriction = try GoogleMapConfigRestriction(fromJSObject: restrictionObj) - } else { - self.restriction = nil - } - - self.heading = fromJSObject["heading"] as? Double - } -} - -public struct GoogleMapConfigRestriction: Codable { - let latLngBounds: GMSCoordinateBounds - - init(fromJSObject: JSObject) throws { - guard let latLngBoundsObj = fromJSObject["latLngBounds"] as? JSObject else { - throw GoogleMapErrors.invalidArguments("GoogleMapConfigRestriction object is missing the required 'latLngBounds' property") - } - - guard let north = latLngBoundsObj["north"] as? Double else { - throw GoogleMapErrors.invalidArguments("GoogleMapConfigRestriction object is missing the required 'latLngBounds.north' property") - } - - guard let south = latLngBoundsObj["south"] as? Double else { - throw GoogleMapErrors.invalidArguments("GoogleMapConfigRestriction object is missing the required 'latLngBounds.south' property") - } - - guard let east = latLngBoundsObj["east"] as? Double else { - throw GoogleMapErrors.invalidArguments("GoogleMapConfigRestriction object is missing the required 'latLngBounds.east' property") - } - - guard let west = latLngBoundsObj["west"] as? Double else { - throw GoogleMapErrors.invalidArguments("GoogleMapConfigRestriction object is missing the required 'latLngBounds.west' property") - } - - let southWest = CLLocationCoordinate2D(latitude: south, longitude: west) - let northEast = CLLocationCoordinate2D(latitude: north, longitude: east) - self.latLngBounds = GMSCoordinateBounds(coordinate: southWest, coordinate: northEast) - } - - enum CodingKeys: String, CodingKey { - case latLngBounds - } - - struct LatLngBounds: Codable { - let north: CLLocationDegrees - let south: CLLocationDegrees - let east: CLLocationDegrees - let west: CLLocationDegrees - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - let latLngBounds = LatLngBounds( - north: self.latLngBounds.northEast.latitude, - south: self.latLngBounds.southWest.latitude, - east: self.latLngBounds.northEast.longitude, - west: self.latLngBounds.southWest.longitude - ) - try container.encode(latLngBounds, forKey: .latLngBounds) - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let latLngBounds = try container.decode(LatLngBounds.self, forKey: .latLngBounds) - let southWest = CLLocationCoordinate2D(latitude: latLngBounds.south, longitude: latLngBounds.west) - let northEast = CLLocationCoordinate2D(latitude: latLngBounds.north, longitude: latLngBounds.east) - self.latLngBounds = GMSCoordinateBounds(coordinate: southWest, coordinate: northEast) } } diff --git a/plugin/ios/Sources/CapacitorGoogleMapsPlugin/Map.swift b/plugin/ios/Sources/CapacitorGoogleMapsPlugin/Map.swift index 85a8953d..c401f357 100644 --- a/plugin/ios/Sources/CapacitorGoogleMapsPlugin/Map.swift +++ b/plugin/ios/Sources/CapacitorGoogleMapsPlugin/Map.swift @@ -96,6 +96,7 @@ public class Map { self.delegate = delegate self.mapViewController = GMViewController() self.mapViewController.mapId = config.mapId + self.mapViewController.onViewDidLoad = { [weak self] in self?.finishMapConfiguration() } @@ -132,41 +133,6 @@ public class Map { DispatchQueue.main.async { self.mapViewController.GMapView.delegate = self.delegate - if let styles = self.config.styles { - do { - self.mapViewController.GMapView.mapStyle = try GMSMapStyle(jsonString: styles) - } catch { - CAPLog.print("Invalid Google Maps styles") - } - } - - let minZoom = self.config.minZoom.map { Float($0) } ?? self.mapViewController.GMapView.minZoom - let maxZoom = self.config.maxZoom.map { Float($0) } ?? self.mapViewController.GMapView.maxZoom - self.mapViewController.GMapView.setMinZoom(minZoom, maxZoom: maxZoom) - - if let mapTypeId = self.config.mapTypeId { - switch mapTypeId { - case "hybrid": - self.mapViewController.GMapView.mapType = .hybrid - case "roadmap": - self.mapViewController.GMapView.mapType = .normal - case "satellite": - self.mapViewController.GMapView.mapType = .satellite - case "terrain": - self.mapViewController.GMapView.mapType = .terrain - default: - break - } - } - - if let restriction = self.config.restriction { - self.mapViewController.GMapView.cameraTargetBounds = restriction.latLngBounds - } - - if let heading = self.config.heading { - self.mapViewController.GMapView.animate(toBearing: heading) - } - self.delegate.notifyListeners("onMapReady", data: [ "mapId": self.id ]) @@ -233,6 +199,120 @@ public class Map { return nil } + func applyConfig(configObj: JSObject) { + DispatchQueue.main.async { + if (configObj as [String: Any]).keys.contains("gestureHandling"), let gestureHandling = configObj["gestureHandling"] as? String { + self.mapViewController.GMapView.settings.consumesGesturesInView = gestureHandling != "none" + } + + if (configObj as [String: Any]).keys.contains("heading"), let heading = configObj["heading"] as? Float { + self.mapViewController.GMapView.animate(toBearing: CLLocationDirection(heading)) + } + + if (configObj as [String: Any]).keys.contains("isAccessibilityElementsEnabled"), let isAccessibilityElementsEnabled = configObj["isAccessibilityElementsEnabled"] as? Bool { + self.mapViewController.GMapView.accessibilityElementsHidden = isAccessibilityElementsEnabled + } + + if (configObj as [String: Any]).keys.contains("isCompassEnabled"), let isCompassEnabled = configObj["isCompassEnabled"] as? Bool { + self.mapViewController.GMapView.settings.compassButton = isCompassEnabled + } + + if (configObj as [String: Any]).keys.contains("isIndoorMapsEnabled"), let isIndoorMapsEnabled = configObj["isIndoorMapsEnabled"] as? Bool { + self.mapViewController.GMapView.isIndoorEnabled = isIndoorMapsEnabled + } + + if (configObj as [String: Any]).keys.contains("isMyLocationButtonEnabled"), let isMyLocationButtonEnabled = configObj["isMyLocationButtonEnabled"] as? Bool { + self.mapViewController.GMapView.settings.myLocationButton = isMyLocationButtonEnabled + } + + if (configObj as [String: Any]).keys.contains("isMyLocationEnabled"), let isMyLocationEnabled = configObj["isMyLocationEnabled"] as? Bool { + self.mapViewController.GMapView.isMyLocationEnabled = isMyLocationEnabled + } + + if (configObj as [String: Any]).keys.contains("isRotateGesturesEnabled"), let isRotateGesturesEnabled = configObj["isRotateGesturesEnabled"] as? Bool { + self.mapViewController.GMapView.settings.rotateGestures = isRotateGesturesEnabled + } + + if (configObj as [String: Any]).keys.contains("isTiltGesturesEnabled"), let isTiltGesturesEnabled = configObj["isTiltGesturesEnabled"] as? Bool { + self.mapViewController.GMapView.settings.tiltGestures = isTiltGesturesEnabled + } + + if (configObj as [String: Any]).keys.contains("isTrafficLayerEnabled"), let isTrafficLayerEnabled = configObj["isTrafficLayerEnabled"] as? Bool { + self.mapViewController.GMapView.isTrafficEnabled = isTrafficLayerEnabled + } + + if (configObj as [String: Any]).keys.contains("isZoomGesturesEnabled"), let isZoomGesturesEnabled = configObj["isZoomGesturesEnabled"] as? Bool { + self.mapViewController.GMapView.settings.zoomGestures = isZoomGesturesEnabled + } + + if (configObj as [String: Any]).keys.contains("mapTypeId"), let mapTypeId = configObj["mapTypeId"] as? String { + self.setMapType(mapTypeId: mapTypeId) + } + + if (configObj as [String: Any]).keys.contains("maxZoom"), let maxZoom = configObj["maxZoom"] as? Float { + self.mapViewController.GMapView.setMinZoom(self.mapViewController.GMapView.minZoom, maxZoom: maxZoom) + } + + if (configObj as [String: Any]).keys.contains("minZoom"), let minZoom = configObj["minZoom"] as? Float { + self.mapViewController.GMapView.setMinZoom(minZoom, maxZoom: self.mapViewController.GMapView.maxZoom) + } + + if (configObj as [String: Any]).keys.contains("padding"), let paddingObj = configObj["padding"] as? JSObject { + self.setPadding(paddingObj: paddingObj) + } + + if (configObj as [String: Any]).keys.contains("restriction") { + let restrictionObj = (configObj as [String: Any])["restriction"] as? JSObject + self.setRestriction(restrictionObj: restrictionObj) + } + + if (configObj as [String: Any]).keys.contains("styles"), let stylesObj = (configObj as [String: Any])["styles"] as? JSArray { + self.setStyle(stylesObj: stylesObj) + } + } + } + + private func setMapType(mapTypeId: String) { + let mapType = GMSMapViewType.fromString(mapType: mapTypeId) + self.mapViewController.GMapView.mapType = mapType + } + + private func setPadding(paddingObj: JSObject) { + if let padding = try? GoogleMapPadding.init(fromJSObject: paddingObj) { + let mapInsets = UIEdgeInsets(top: CGFloat(padding.top), left: CGFloat(padding.left), bottom: CGFloat(padding.bottom), right: CGFloat(padding.right)) + self.mapViewController.GMapView.padding = mapInsets + } + } + + private func setRestriction(restrictionObj: JSObject?) { + let latLngBounds = restrictionObj?["latLngBounds"] as? JSObject + var bounds: GMSCoordinateBounds? = nil + + if latLngBounds != nil { + bounds = try? getGMSCoordinateBoundsFromGMSJS(latLngBounds!) + } + + self.mapViewController.GMapView.setMinZoom(kGMSMinZoomLevel, maxZoom: kGMSMaxZoomLevel) + self.mapViewController.GMapView.cameraTargetBounds = nil + + if (bounds != nil) { + CATransaction.begin() + CATransaction.setCompletionBlock({ + self.mapViewController.GMapView.setMinZoom(self.mapViewController.GMapView.camera.zoom, maxZoom: kGMSMaxZoomLevel) + self.mapViewController.GMapView.cameraTargetBounds = bounds + }) + self.mapViewController.GMapView.animate(with: GMSCameraUpdate.fit(bounds!)) + CATransaction.commit() + } + } + + private func setStyle(stylesObj: JSArray) { + if let jsonData = try? JSONSerialization.data(withJSONObject: stylesObj, options: []) { + let stylesString = String(data: jsonData, encoding: .utf8) + self.mapViewController.GMapView.mapStyle = try? GMSMapStyle(jsonString: stylesString!) + } + } + func destroy() { DispatchQueue.main.async { self.mapViewController.GMapView = nil @@ -500,47 +580,6 @@ public class Map { } - func getMapType() -> GMSMapViewType { - return self.mapViewController.GMapView.mapType - } - - func setMapType(mapType: GMSMapViewType) throws { - DispatchQueue.main.sync { - self.mapViewController.GMapView.mapType = mapType - } - } - - func enableIndoorMaps(enabled: Bool) throws { - DispatchQueue.main.sync { - self.mapViewController.GMapView.isIndoorEnabled = enabled - } - } - - func enableTrafficLayer(enabled: Bool) throws { - DispatchQueue.main.sync { - self.mapViewController.GMapView.isTrafficEnabled = enabled - } - } - - func enableAccessibilityElements(enabled: Bool) throws { - DispatchQueue.main.sync { - self.mapViewController.GMapView.accessibilityElementsHidden = enabled - } - } - - func enableCurrentLocation(enabled: Bool) throws { - DispatchQueue.main.sync { - self.mapViewController.GMapView.isMyLocationEnabled = enabled - } - } - - func setPadding(padding: GoogleMapPadding) throws { - DispatchQueue.main.sync { - let mapInsets = UIEdgeInsets(top: CGFloat(padding.top), left: CGFloat(padding.left), bottom: CGFloat(padding.bottom), right: CGFloat(padding.right)) - self.mapViewController.GMapView.padding = mapInsets - } - } - func removeMarkers(ids: [Int]) throws { DispatchQueue.main.sync { var markers: [GMSMarker] = [] @@ -569,6 +608,17 @@ public class Map { } } + private func getGMSCoordinateBoundsFromGMSJS(_ bounds: JSObject) throws -> GMSCoordinateBounds { + guard let south = bounds["south"] as? Double, let west = bounds["west"] as? Double, let north = bounds["north"] as? Double, let east = bounds["east"] as? Double else { + throw GoogleMapErrors.unhandledError("Bounds not formatted properly.") + } + + return GMSCoordinateBounds( + coordinate: CLLocationCoordinate2D(latitude: south, longitude: west), + coordinate: CLLocationCoordinate2D(latitude: north, longitude: east) + ) + } + private func getFrameOverflowBounds(frame: CGRect, mapBounds: CGRect) -> [CGRect] { var intersections: [CGRect] = [] diff --git a/plugin/rollup.config.js b/plugin/rollup.config.js index 055120cd..07b8e87a 100644 --- a/plugin/rollup.config.js +++ b/plugin/rollup.config.js @@ -18,5 +18,9 @@ export default { inlineDynamicImports: true, }, ], - external: ['@capacitor/core', '@googlemaps/js-api-loader', '@googlemaps/markerclusterer'], + external: [ + '@capacitor/core', + '@googlemaps/js-api-loader', + '@googlemaps/markerclusterer', + ], }; diff --git a/plugin/src/definitions.ts b/plugin/src/definitions.ts index 5d0a716e..90990e5c 100644 --- a/plugin/src/definitions.ts +++ b/plugin/src/definitions.ts @@ -147,9 +147,82 @@ export interface StyleSpan { /** * For web, all the javascript Google Maps options are available as * GoogleMapConfig extends google.maps.MapOptions. - * For iOS and Android only the config options declared on GoogleMapConfig are available. + * For iOS and Android the following options from google.maps.MapOptions with the same signature are additionally available: + * - gestureHandling ('none' | 'auto') + * - heading + * - mapTypeId + * - maxZoom + * - minZoom + * - restriction + * - styles */ export interface GoogleMapConfig extends google.maps.MapOptions { + /** + * Show accessibility elements for overlay objects, such as Marker and Polyline. + * + * Only available on iOS. + */ + isAccessibilityElementsEnabled?: boolean; + /** + * Enables or disables the compass. + */ + isCompassEnabled?: boolean; + /** + * Sets whether a button should be displayed, which centers the camera to the users current position. + */ + isMyLocationButtonEnabled?: boolean; + /** + * Sets whether the My Location dot and accuracy circle is enabled. + */ + isMyLocationEnabled?: boolean; + /** + * Sets whether indoor maps should be enabled. + * + * Only available on Android and iOS. + */ + isIndoorMapsEnabled?: boolean; + /** + * Sets the preference for whether rotate gestures should be enabled or disabled. + */ + isRotateGesturesEnabled?: boolean; + /** + * Sets the preference for whether tilt gestures should be enabled or disabled. + */ + isTiltGesturesEnabled?: boolean; + /** + * Sets the preference for whether the Map Toolbar should be enabled or disabled. + * + * Only available on Android. + */ + isToolbarEnabled?: boolean; + /** + * Turns the traffic layer on or off. + */ + isTrafficLayerEnabled?: boolean; + /** + * Sets the preference for whether zoom gestures should be enabled or disabled. + */ + isZoomGesturesEnabled?: boolean; + /** + * Padding on the 'visible' region of the view. + */ + padding?: MapPadding; +} + +/** + * For web, all the javascript Google Maps options are available as + * GoogleMapCreateConfig extends google.maps.MapOptions. + * For iOS and Android the following options from google.maps.MapOptions with the same signature are additionally available: + * - gestureHandling ('none' | 'auto') + * - heading + * - mapTypeId + * - maxZoom + * - minZoom + * - restriction + * - styles + * - zoom + */ +export interface GoogleMapCreateConfig extends GoogleMapConfig { /** * Override width for native map. */ @@ -184,14 +257,6 @@ export interface GoogleMapConfig extends google.maps.MapOptions { * Override pixel ratio for native map. */ devicePixelRatio?: number; - /** - * Styles to apply to each of the default map types. Note that for - * satellite, hybrid and terrain modes, - * these styles will only apply to labels and geometry. - * - * @since 4.3.0 - */ - styles?: google.maps.MapTypeStyle[] | null; /** * A map id associated with a specific map style or feature. * @@ -222,40 +287,6 @@ export interface GoogleMapConfig extends google.maps.MapOptions { * @since 5.4.0 */ iOSMapId?: string; - /** - * The maximum zoom level which will be displayed on the map. If omitted, or - * set to null, the maximum zoom from the current map type is - * used instead. Valid zoom values are numbers from zero up to the supported - * maximum - * zoom level. - */ - maxZoom?: number | null; - /** - * The minimum zoom level which will be displayed on the map. If omitted, or - * set to null, the minimum zoom from the current map type is - * used instead. Valid zoom values are numbers from zero up to the supported - * maximum - * zoom level. - */ - minZoom?: number | null; - /** - * The initial Map mapTypeId. Defaults to ROADMAP. - */ - mapTypeId?: string | null; - /** - * The heading for aerial imagery in degrees measured clockwise from - * cardinal direction North. Headings are snapped to the nearest available - * angle for which imagery is available. - */ - heading?: number | null; - /** - * Defines a boundary that restricts the area of the map accessible to - * users. When set, a user can only pan and zoom while the camera view stays - * inside the limits of the boundary. - */ - restriction?: google.maps.MapRestriction | null; } /** diff --git a/plugin/src/implementation.ts b/plugin/src/implementation.ts index 357f0d10..2b5fd4c5 100644 --- a/plugin/src/implementation.ts +++ b/plugin/src/implementation.ts @@ -7,12 +7,11 @@ import type { GoogleMapConfig, LatLng, LatLngBounds, - MapPadding, - MapType, Marker, Polygon, Polyline, TileOverlay, + GoogleMapCreateConfig, } from './definitions'; /** @@ -30,7 +29,7 @@ export interface CreateMapArgs { /** * The initial configuration settings for the map. */ - config: GoogleMapConfig; + config: GoogleMapCreateConfig; /** * The DOM element that the Google Map View will be mounted on which determines size and positioning. */ @@ -55,6 +54,11 @@ export interface CreateMapArgs { language?: string; } +export interface UpdateMapArgs { + id: string; + config: GoogleMapConfig; +} + export interface DestroyMapArgs { id: string; } @@ -108,16 +112,6 @@ export interface CameraArgs { config: CameraConfig; } -export interface MapTypeArgs { - id: string; - mapType: MapType; -} - -export interface IndoorMapArgs { - id: string; - enabled: boolean; -} - export interface RemoveTileOverlayArgs { id: string; tileOverlayId: string; @@ -128,25 +122,11 @@ export interface AddTileOverlayArgs { tileOverlay: TileOverlay; } -export interface TrafficLayerArgs { - id: string; - enabled: boolean; -} - export interface AccElementsArgs { id: string; enabled: boolean; } -export interface PaddingArgs { - id: string; - padding: MapPadding; -} - -export interface CurrentLocArgs { - id: string; - enabled: boolean; -} export interface AddMarkersArgs { id: string; markers: Marker[]; @@ -182,6 +162,7 @@ export interface FitBoundsArgs { export interface CapacitorGoogleMapsPlugin extends Plugin { create(options: CreateMapArgs): Promise; + update(options: UpdateMapArgs): Promise; enableTouch(args: { id: string }): Promise; disableTouch(args: { id: string }): Promise; addTileOverlay(args: AddTileOverlayArgs): Promise<{ id: string }>; @@ -200,28 +181,26 @@ export interface CapacitorGoogleMapsPlugin extends Plugin { disableClustering(args: { id: string }): Promise; destroy(args: DestroyMapArgs): Promise; setCamera(args: CameraArgs): Promise; - getMapType(args: { id: string }): Promise<{ type: string }>; - setMapType(args: MapTypeArgs): Promise; - enableIndoorMaps(args: IndoorMapArgs): Promise; - enableTrafficLayer(args: TrafficLayerArgs): Promise; - enableAccessibilityElements(args: AccElementsArgs): Promise; - enableCurrentLocation(args: CurrentLocArgs): Promise; - setPadding(args: PaddingArgs): Promise; onScroll(args: MapBoundsArgs): Promise; onResize(args: MapBoundsArgs): Promise; onDisplay(args: MapBoundsArgs): Promise; dispatchMapEvent(args: { id: string; focus: boolean }): Promise; getMapBounds(args: { id: string }): Promise; fitBounds(args: FitBoundsArgs): Promise; - mapBoundsContains(args: MapBoundsContainsArgs): Promise<{ contains: boolean }>; + mapBoundsContains( + args: MapBoundsContainsArgs, + ): Promise<{ contains: boolean }>; mapBoundsExtend(args: MapBoundsExtendArgs): Promise<{ bounds: LatLngBounds }>; } -const CapacitorGoogleMaps = registerPlugin('CapacitorGoogleMaps', { - web: () => import('./web').then((m) => new m.CapacitorGoogleMapsWeb()), -}); +const CapacitorGoogleMaps = registerPlugin( + 'CapacitorGoogleMaps', + { + web: () => import('./web').then(m => new m.CapacitorGoogleMapsWeb()), + }, +); -CapacitorGoogleMaps.addListener('isMapInFocus', (data) => { +CapacitorGoogleMaps.addListener('isMapInFocus', data => { const x = data.x; const y = data.y; diff --git a/plugin/src/index.ts b/plugin/src/index.ts index 1900cdad..aaf94947 100644 --- a/plugin/src/index.ts +++ b/plugin/src/index.ts @@ -1,8 +1,25 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { LatLngBounds, MapType, Marker, Polygon, Circle, Polyline, StyleSpan } from './definitions'; +import { + LatLngBounds, + MapType, + Marker, + Polygon, + Circle, + Polyline, + StyleSpan, +} from './definitions'; import { GoogleMap } from './map'; -export { GoogleMap, LatLngBounds, MapType, Marker, Polygon, Circle, Polyline, StyleSpan }; +export { + GoogleMap, + LatLngBounds, + MapType, + Marker, + Polygon, + Circle, + Polyline, + StyleSpan, +}; declare global { export namespace JSX { diff --git a/plugin/src/map.ts b/plugin/src/map.ts index 73f064b4..917c928a 100644 --- a/plugin/src/map.ts +++ b/plugin/src/map.ts @@ -20,13 +20,19 @@ import type { Polyline, PolylineCallbackData, TileOverlay, + GoogleMapConfig, } from './definitions'; import { LatLngBounds, MapType } from './definitions'; import type { CreateMapArgs } from './implementation'; import { CapacitorGoogleMaps } from './implementation'; export interface GoogleMapInterface { - create(options: CreateMapArgs, callback?: MapListenerCallback): Promise; + create( + options: CreateMapArgs, + callback?: MapListenerCallback, + ): Promise; + update(config: GoogleMapConfig): Promise; + getOptions(): GoogleMapConfig | null; enableTouch(): Promise; disableTouch(): Promise; enableClustering( @@ -52,13 +58,32 @@ export interface GoogleMapInterface { setCamera(config: CameraConfig): Promise; /** * Get current map type + * @deprecated This method will be removed in v7. Use {@link #update()} instead. */ getMapType(): Promise; + /** + * @deprecated This method will be removed in v7. Use {@link #update()} instead. + */ setMapType(mapType: MapType): Promise; + /** + * @deprecated This method will be removed in v7. Use {@link #update()} instead. + */ enableIndoorMaps(enabled: boolean): Promise; + /** + * @deprecated This method will be removed in v7. Use {@link #update()} instead. + */ enableTrafficLayer(enabled: boolean): Promise; + /** + * @deprecated This method will be removed in v7. Use {@link #update()} instead. + */ enableAccessibilityElements(enabled: boolean): Promise; + /** + * @deprecated This method will be removed in v7. Use {@link #update()} instead. + */ enableCurrentLocation(enabled: boolean): Promise; + /** + * @deprecated This method will be removed in v7. Use {@link #update()} instead. + */ setPadding(padding: MapPadding): Promise; /** * Get the map's current viewport latitude and longitude bounds. @@ -72,22 +97,54 @@ export interface GoogleMapInterface { * @param padding Optional padding to apply in pixels. The bounds will be fit in the part of the map that remains after padding is removed. */ fitBounds(bounds: LatLngBounds, padding?: number): Promise; - setOnBoundsChangedListener(callback?: MapListenerCallback): Promise; - setOnCameraIdleListener(callback?: MapListenerCallback): Promise; - setOnCameraMoveStartedListener(callback?: MapListenerCallback): Promise; - setOnClusterClickListener(callback?: MapListenerCallback): Promise; - setOnClusterInfoWindowClickListener(callback?: MapListenerCallback): Promise; - setOnInfoWindowClickListener(callback?: MapListenerCallback): Promise; - setOnMapClickListener(callback?: MapListenerCallback): Promise; - setOnMarkerClickListener(callback?: MapListenerCallback): Promise; - setOnPolygonClickListener(callback?: MapListenerCallback): Promise; - setOnCircleClickListener(callback?: MapListenerCallback): Promise; - setOnPolylineClickListener(callback?: MapListenerCallback): Promise; - setOnMarkerDragStartListener(callback?: MapListenerCallback): Promise; - setOnMarkerDragListener(callback?: MapListenerCallback): Promise; - setOnMarkerDragEndListener(callback?: MapListenerCallback): Promise; - setOnMyLocationButtonClickListener(callback?: MapListenerCallback): Promise; - setOnMyLocationClickListener(callback?: MapListenerCallback): Promise; + setOnBoundsChangedListener( + callback?: MapListenerCallback, + ): Promise; + setOnCameraIdleListener( + callback?: MapListenerCallback, + ): Promise; + setOnCameraMoveStartedListener( + callback?: MapListenerCallback, + ): Promise; + setOnClusterClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnClusterInfoWindowClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnInfoWindowClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnMapClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnMarkerClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnPolygonClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnCircleClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnPolylineClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnMarkerDragStartListener( + callback?: MapListenerCallback, + ): Promise; + setOnMarkerDragListener( + callback?: MapListenerCallback, + ): Promise; + setOnMarkerDragEndListener( + callback?: MapListenerCallback, + ): Promise; + setOnMyLocationButtonClickListener( + callback?: MapListenerCallback, + ): Promise; + setOnMyLocationClickListener( + callback?: MapListenerCallback, + ): Promise; } class MapCustomElement extends HTMLElement { @@ -116,6 +173,7 @@ export class GoogleMap { private id: string; private element: HTMLElement | null = null; private resizeObserver: ResizeObserver | null = null; + private config: GoogleMapConfig | null = null; private onBoundsChangedListener?: PluginListenerHandle; private onCameraIdleListener?: PluginListenerHandle; @@ -149,6 +207,7 @@ export class GoogleMap { callback?: MapListenerCallback, ): Promise { const newMap = new GoogleMap(options.id); + newMap.config = options.config; if (!options.element) { throw new Error('container element is required'); @@ -176,7 +235,8 @@ export class GoogleMap { (options.element as any) = {}; const getMapBounds = () => { - const mapRect = newMap.element?.getBoundingClientRect() ?? ({} as DOMRect); + const mapRect = + newMap.element?.getBoundingClientRect() ?? ({} as DOMRect); return { x: mapRect.x, y: mapRect.y, @@ -228,7 +288,10 @@ export class GoogleMap { if (Capacitor.getPlatform() === 'ios' && !ionicPage) { onDisplay(); } - } else if (lastState.width !== mapRect.width || lastState.height !== mapRect.height) { + } else if ( + lastState.width !== mapRect.width || + lastState.height !== mapRect.height + ) { onResize(); } } @@ -254,19 +317,24 @@ export class GoogleMap { }); if (callback) { - const onMapReadyListener = await CapacitorGoogleMaps.addListener('onMapReady', (data: MapReadyCallbackData) => { - if (data.mapId == newMap.id) { - callback(data); - onMapReadyListener.remove(); - } - }); + const onMapReadyListener = await CapacitorGoogleMaps.addListener( + 'onMapReady', + (data: MapReadyCallbackData) => { + if (data.mapId == newMap.id) { + callback(data); + onMapReadyListener.remove(); + } + }, + ); } return newMap; } - private static async getElementBounds(element: HTMLElement): Promise { - return new Promise((resolve) => { + private static async getElementBounds( + element: HTMLElement, + ): Promise { + return new Promise(resolve => { let elementBounds = element.getBoundingClientRect(); if (elementBounds.width == 0) { let retries = 0; @@ -288,6 +356,39 @@ export class GoogleMap { }); } + /** + * Update map options + * + * @returns void + */ + async update(config: GoogleMapConfig): Promise { + Object.assign(this.config as any, config); + + // Convert restriction latLngBounds to LatLngBoundsLiteral if its in LatLngBounds format + if ( + config.restriction?.latLngBounds && + (config.restriction.latLngBounds as any)?.toJSON + ) { + config.restriction.latLngBounds = ( + config.restriction.latLngBounds as google.maps.LatLngBounds + ).toJSON(); + } + + return CapacitorGoogleMaps.update({ + id: this.id, + config, + }); + } + + /** + * Get map options + * + * @returns void + */ + getOptions(): GoogleMapConfig | null { + return this.config; + } + /** * Enable touch events on native map * @@ -500,52 +601,66 @@ export class GoogleMap { }); } + /** + * @deprecated This method will be removed in v7. Use {@link #update()} instead. + */ async getMapType(): Promise { - const { type } = await CapacitorGoogleMaps.getMapType({ id: this.id }); - return MapType[type as keyof typeof MapType]; + return Promise.resolve( + MapType[this.getOptions()?.mapTypeId as keyof typeof MapType], + ); } /** * Sets the type of map tiles that should be displayed. + * @deprecated This method will be removed in v7. Use {@link #update()} instead. * * @param mapType * @returns */ async setMapType(mapType: MapType): Promise { - return CapacitorGoogleMaps.setMapType({ + return CapacitorGoogleMaps.update({ id: this.id, - mapType, + config: { + mapTypeId: mapType, + }, }); } /** * Sets whether indoor maps are shown, where available. + * @deprecated This method will be removed in v7. Use {@link #update()} instead. * * @param enabled * @returns */ async enableIndoorMaps(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableIndoorMaps({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isIndoorMapsEnabled: enabled, + }, }); } /** * Controls whether the map is drawing traffic data, if available. + * @deprecated This method will be removed in v7. Use {@link #update()} instead. * * @param enabled * @returns */ async enableTrafficLayer(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableTrafficLayer({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isTrafficLayerEnabled: enabled, + }, }); } /** * Show accessibility elements for overlay objects, such as Marker and Polyline. + * @deprecated This method will be removed in v7. Use {@link #update()} instead. * * Only available on iOS. * @@ -553,35 +668,43 @@ export class GoogleMap { * @returns */ async enableAccessibilityElements(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableAccessibilityElements({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isAccessibilityElementsEnabled: enabled, + }, }); } /** * Set whether the My Location dot and accuracy circle is enabled. + * @deprecated This method will be removed in v7. Use {@link #update()} instead. * * @param enabled * @returns */ async enableCurrentLocation(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableCurrentLocation({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isMyLocationEnabled: enabled, + }, }); } /** * Set padding on the 'visible' region of the view. + * @deprecated This method will be removed in v7. Use {@link #update()} instead. * * @param padding * @returns */ async setPadding(padding: MapPadding): Promise { - return CapacitorGoogleMaps.setPadding({ + return CapacitorGoogleMaps.update({ id: this.id, - padding, + config: { + padding, + }, }); } @@ -685,7 +808,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnCameraIdleListener(callback?: MapListenerCallback): Promise { + async setOnCameraIdleListener( + callback?: MapListenerCallback, + ): Promise { if (this.onCameraIdleListener) { this.onCameraIdleListener.remove(); } @@ -706,7 +831,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnBoundsChangedListener(callback?: MapListenerCallback): Promise { + async setOnBoundsChangedListener( + callback?: MapListenerCallback, + ): Promise { if (this.onBoundsChangedListener) { this.onBoundsChangedListener.remove(); } @@ -727,7 +854,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnCameraMoveStartedListener(callback?: MapListenerCallback): Promise { + async setOnCameraMoveStartedListener( + callback?: MapListenerCallback, + ): Promise { if (this.onCameraMoveStartedListener) { this.onCameraMoveStartedListener.remove(); } @@ -748,7 +877,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnClusterClickListener(callback?: MapListenerCallback): Promise { + async setOnClusterClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onClusterClickListener) { this.onClusterClickListener.remove(); } @@ -769,16 +900,19 @@ export class GoogleMap { * @param callback * @returns */ - async setOnClusterInfoWindowClickListener(callback?: MapListenerCallback): Promise { + async setOnClusterInfoWindowClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onClusterInfoWindowClickListener) { this.onClusterInfoWindowClickListener.remove(); } if (callback) { - this.onClusterInfoWindowClickListener = await CapacitorGoogleMaps.addListener( - 'onClusterInfoWindowClick', - this.generateCallback(callback), - ); + this.onClusterInfoWindowClickListener = + await CapacitorGoogleMaps.addListener( + 'onClusterInfoWindowClick', + this.generateCallback(callback), + ); } else { this.onClusterInfoWindowClickListener = undefined; } @@ -790,7 +924,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnInfoWindowClickListener(callback?: MapListenerCallback): Promise { + async setOnInfoWindowClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onInfoWindowClickListener) { this.onInfoWindowClickListener.remove(); } @@ -811,13 +947,18 @@ export class GoogleMap { * @param callback * @returns */ - async setOnMapClickListener(callback?: MapListenerCallback): Promise { + async setOnMapClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onMapClickListener) { this.onMapClickListener.remove(); } if (callback) { - this.onMapClickListener = await CapacitorGoogleMaps.addListener('onMapClick', this.generateCallback(callback)); + this.onMapClickListener = await CapacitorGoogleMaps.addListener( + 'onMapClick', + this.generateCallback(callback), + ); } else { this.onMapClickListener = undefined; } @@ -829,7 +970,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnPolygonClickListener(callback?: MapListenerCallback): Promise { + async setOnPolygonClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onPolygonClickListener) { this.onPolygonClickListener.remove(); } @@ -850,7 +993,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnCircleClickListener(callback?: MapListenerCallback): Promise { + async setOnCircleClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onCircleClickListener) [this.onCircleClickListener.remove()]; if (callback) { @@ -869,7 +1014,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnMarkerClickListener(callback?: MapListenerCallback): Promise { + async setOnMarkerClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onMarkerClickListener) { this.onMarkerClickListener.remove(); } @@ -889,7 +1036,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnPolylineClickListener(callback?: MapListenerCallback): Promise { + async setOnPolylineClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onPolylineClickListener) { this.onPolylineClickListener.remove(); } @@ -910,7 +1059,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnMarkerDragStartListener(callback?: MapListenerCallback): Promise { + async setOnMarkerDragStartListener( + callback?: MapListenerCallback, + ): Promise { if (this.onMarkerDragStartListener) { this.onMarkerDragStartListener.remove(); } @@ -931,7 +1082,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnMarkerDragListener(callback?: MapListenerCallback): Promise { + async setOnMarkerDragListener( + callback?: MapListenerCallback, + ): Promise { if (this.onMarkerDragListener) { this.onMarkerDragListener.remove(); } @@ -952,7 +1105,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnMarkerDragEndListener(callback?: MapListenerCallback): Promise { + async setOnMarkerDragEndListener( + callback?: MapListenerCallback, + ): Promise { if (this.onMarkerDragEndListener) { this.onMarkerDragEndListener.remove(); } @@ -981,10 +1136,11 @@ export class GoogleMap { } if (callback) { - this.onMyLocationButtonClickListener = await CapacitorGoogleMaps.addListener( - 'onMyLocationButtonClick', - this.generateCallback(callback), - ); + this.onMyLocationButtonClickListener = + await CapacitorGoogleMaps.addListener( + 'onMyLocationButtonClick', + this.generateCallback(callback), + ); } else { this.onMyLocationButtonClickListener = undefined; } @@ -996,7 +1152,9 @@ export class GoogleMap { * @param callback * @returns */ - async setOnMyLocationClickListener(callback?: MapListenerCallback): Promise { + async setOnMyLocationClickListener( + callback?: MapListenerCallback, + ): Promise { if (this.onMyLocationClickListener) { this.onMyLocationClickListener.remove(); } @@ -1097,7 +1255,9 @@ export class GoogleMap { } } - private generateCallback(callback: MapListenerCallback): MapListenerCallback { + private generateCallback( + callback: MapListenerCallback, + ): MapListenerCallback { const mapId = this.id; return (data: any) => { if (data.mapId == mapId) { diff --git a/plugin/src/web.ts b/plugin/src/web.ts index 9af9a7c9..dc126f8c 100644 --- a/plugin/src/web.ts +++ b/plugin/src/web.ts @@ -1,9 +1,20 @@ import { WebPlugin } from '@capacitor/core'; -import type { Cluster, onClusterClickHandler } from '@googlemaps/markerclusterer'; -import { MarkerClusterer, SuperClusterAlgorithm } from '@googlemaps/markerclusterer'; +import type { + Cluster, + onClusterClickHandler, +} from '@googlemaps/markerclusterer'; +import { + MarkerClusterer, + SuperClusterAlgorithm, +} from '@googlemaps/markerclusterer'; -import type { Marker, TileOverlay } from './definitions'; -import { MapType, LatLngBounds } from './definitions'; +import type { + Marker, + MapPadding, + GoogleMapConfig, + TileOverlay, +} from './definitions'; +import { LatLngBounds } from './definitions'; import type { AddTileOverlayArgs, AddMarkerArgs, @@ -11,12 +22,8 @@ import type { AddMarkersArgs, CapacitorGoogleMapsPlugin, CreateMapArgs, - CurrentLocArgs, DestroyMapArgs, - MapTypeArgs, - PaddingArgs, RemoveMarkerArgs, - TrafficLayerArgs, RemoveMarkersArgs, MapBoundsContainsArgs, EnableClusteringArgs, @@ -29,35 +36,42 @@ import type { AddPolylinesArgs, RemovePolylinesArgs, RemoveTileOverlayArgs, + UpdateMapArgs, } from './implementation'; -export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogleMapsPlugin { - private gMapsRef: google.maps.MapsLibrary | undefined = undefined; - private AdvancedMarkerElement: typeof google.maps.marker.AdvancedMarkerElement | undefined = undefined; - private PinElement: typeof google.maps.marker.PinElement | undefined = undefined; - private maps: { - [id: string]: { - element: HTMLElement; - map: google.maps.Map; - markers: { - [id: string]: google.maps.marker.AdvancedMarkerElement; - }; - tileOverlays: { - [id: string]: google.maps.ImageMapType; - }; - polygons: { - [id: string]: google.maps.Polygon; - }; - circles: { - [id: string]: google.maps.Circle; - }; - polylines: { - [id: string]: google.maps.Polyline; - }; - markerClusterer?: MarkerClusterer; - trafficLayer?: google.maps.TrafficLayer; - }; +class MapInstance { + element!: HTMLElement; + map!: google.maps.Map; + markers: { + [id: string]: google.maps.marker.AdvancedMarkerElement; + } = {}; + tileOverlays: { + [id: string]: google.maps.ImageMapType; + } = {}; + polygons: { + [id: string]: google.maps.Polygon; + } = {}; + circles: { + [id: string]: google.maps.Circle; } = {}; + polylines: { + [id: string]: google.maps.Polyline; + } = {}; + markerClusterer?: MarkerClusterer; + trafficLayer?: google.maps.TrafficLayer; +} + +export class CapacitorGoogleMapsWeb + extends WebPlugin + implements CapacitorGoogleMapsPlugin +{ + private gMapsRef: google.maps.MapsLibrary | undefined = undefined; + private AdvancedMarkerElement: + | typeof google.maps.marker.AdvancedMarkerElement + | undefined = undefined; + private PinElement: typeof google.maps.marker.PinElement | undefined = + undefined; + private maps: { [id: string]: MapInstance } = {}; private currMarkerId = 0; private currTileOverlayId = 0; private currPolygonId = 0; @@ -109,7 +123,10 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle return ''; } - private getIdFromMarker(mapId: string, marker: google.maps.marker.AdvancedMarkerElement): string { + private getIdFromMarker( + mapId: string, + marker: google.maps.marker.AdvancedMarkerElement, + ): string { for (const id in this.maps[mapId].markers) { if (this.maps[mapId].markers[id] == marker) { return id; @@ -119,7 +136,11 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle return ''; } - private async importGoogleLib(apiKey: string, region?: string, language?: string) { + private async importGoogleLib( + apiKey: string, + region?: string, + language?: string, + ) { if (this.gMapsRef === undefined) { const lib = await import('@googlemaps/js-api-loader'); const loader = new lib.Loader({ @@ -131,9 +152,10 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle this.gMapsRef = await loader.importLibrary('maps'); // Import marker library once - const { AdvancedMarkerElement, PinElement } = (await google.maps.importLibrary( - 'marker', - )) as google.maps.MarkerLibrary; + const { AdvancedMarkerElement, PinElement } = + (await google.maps.importLibrary( + 'marker', + )) as google.maps.MarkerLibrary; this.AdvancedMarkerElement = AdvancedMarkerElement; this.PinElement = PinElement; @@ -159,82 +181,10 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle }); } - async getMapType(_args: { id: string }): Promise<{ type: string }> { - let type = this.maps[_args.id].map.getMapTypeId(); - if (type !== undefined) { - if (type === 'roadmap') { - type = MapType.Normal; - } - return { type: `${type.charAt(0).toUpperCase()}${type.slice(1)}` }; - } - throw new Error('Map type is undefined'); - } - - async setMapType(_args: MapTypeArgs): Promise { - let mapType = _args.mapType.toLowerCase(); - if (_args.mapType === MapType.Normal) { - mapType = 'roadmap'; - } - this.maps[_args.id].map.setMapTypeId(mapType); - } - - async enableIndoorMaps(): Promise { - throw new Error('Method not supported on web.'); - } - - async enableTrafficLayer(_args: TrafficLayerArgs): Promise { - const trafficLayer = this.maps[_args.id].trafficLayer ?? new google.maps.TrafficLayer(); - - if (_args.enabled) { - trafficLayer.setMap(this.maps[_args.id].map); - this.maps[_args.id].trafficLayer = trafficLayer; - } else if (this.maps[_args.id].trafficLayer) { - trafficLayer.setMap(null); - this.maps[_args.id].trafficLayer = undefined; - } - } - - async enableAccessibilityElements(): Promise { - throw new Error('Method not supported on web.'); - } - dispatchMapEvent(): Promise { throw new Error('Method not supported on web.'); } - async enableCurrentLocation(_args: CurrentLocArgs): Promise { - if (_args.enabled) { - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - (position: GeolocationPosition) => { - const pos = { - lat: position.coords.latitude, - lng: position.coords.longitude, - }; - - this.maps[_args.id].map.setCenter(pos); - - this.notifyListeners('onMyLocationButtonClick', {}); - - this.notifyListeners('onMyLocationClick', {}); - }, - () => { - throw new Error('Geolocation not supported on web browser.'); - }, - ); - } else { - throw new Error('Geolocation not supported on web browser.'); - } - } - } - async setPadding(_args: PaddingArgs): Promise { - const bounds = this.maps[_args.id].map.getBounds(); - - if (bounds !== undefined) { - this.maps[_args.id].map.fitBounds(bounds, _args.padding); - } - } - async getMapBounds(_args: { id: string }): Promise { const bounds = this.maps[_args.id].map.getBounds(); @@ -273,7 +223,10 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle const customMapOverlay = new google.maps.ImageMapType({ getTileUrl: function (coord, zoom) { - return tileOverlay.url.replace('{x}', `${coord.x}`).replace('{y}', `${coord.y}`).replace('{z}', `${zoom}`); + return tileOverlay.url + .replace('{x}', `${coord.x}`) + .replace('{y}', `${coord.y}`) + .replace('{z}', `${zoom}`); }, tileSize: new google.maps.Size(256, 256), opacity: tileOverlay.opacity, @@ -295,7 +248,10 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle } for (let i = 0; i < map.overlayMapTypes.getLength(); i++) { - if (map.overlayMapTypes.getAt(i) === this.maps[_args.id].tileOverlays[_args.tileOverlayId]) { + if ( + map.overlayMapTypes.getAt(i) === + this.maps[_args.id].tileOverlays[_args.tileOverlayId] + ) { map.overlayMapTypes.removeAt(i); delete this.maps[_args.id].tileOverlays[_args.tileOverlayId]; break; @@ -323,7 +279,10 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle } async addMarker(_args: AddMarkerArgs): Promise<{ id: string }> { - const advancedMarker = this.buildMarkerOpts(_args.marker, this.maps[_args.id].map); + const advancedMarker = this.buildMarkerOpts( + _args.marker, + this.maps[_args.id].map, + ); const id = '' + this.currMarkerId; @@ -493,7 +452,7 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle config.mapId = `capacitor_map_${this.currMapId++}`; } - this.maps[_args.id] = { + const mapInstance = { map: new window.google.maps.Map(_args.element, config), element: _args.element, markers: {}, @@ -502,9 +461,85 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle circles: {}, polylines: {}, }; + this.applyConfig(mapInstance, config); + this.maps[_args.id] = mapInstance; this.setMapListeners(_args.id); } + async update(_args: UpdateMapArgs): Promise { + const mapInstance = this.maps[_args.id]; + mapInstance.map.setOptions(_args.config); + + this.applyConfig(mapInstance, _args.config); + } + + private applyConfig(mapInstance: MapInstance, config: GoogleMapConfig): void { + if (config.isMyLocationEnabled) { + this.enableMyLocation(mapInstance); + } + + if (config.isTrafficLayerEnabled !== undefined) { + this.setTrafficLayer(mapInstance, config.isTrafficLayerEnabled); + } + + if (config.mapTypeId !== undefined) { + this.setMapTypeId(mapInstance, config.mapTypeId as string); + } + + if (config.padding !== undefined) { + this.setPadding(mapInstance, config.padding); + } + } + + private enableMyLocation(mapInstance: MapInstance): void { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position: GeolocationPosition) => { + const pos = { + lat: position.coords.latitude, + lng: position.coords.longitude, + }; + + mapInstance.map.setCenter(pos); + + this.notifyListeners('onMyLocationButtonClick', {}); + + this.notifyListeners('onMyLocationClick', {}); + }, + () => { + throw new Error('Geolocation not supported on web browser.'); + }, + ); + } else { + throw new Error('Geolocation not supported on web browser.'); + } + } + + private setTrafficLayer(mapInstance: MapInstance, enabled: boolean): void { + const trafficLayer = + mapInstance.trafficLayer ?? new google.maps.TrafficLayer(); + + if (enabled) { + trafficLayer.setMap(mapInstance.map); + mapInstance.trafficLayer = trafficLayer; + } else if (mapInstance.trafficLayer) { + trafficLayer.setMap(null); + mapInstance.trafficLayer = undefined; + } + } + + private setMapTypeId(mapInstance: MapInstance, typeId: string): void { + mapInstance.map.setMapTypeId(typeId); + } + + private setPadding(mapInstance: MapInstance, padding: MapPadding): void { + const bounds = mapInstance.map.getBounds(); + + if (bounds !== undefined) { + mapInstance.map.fitBounds(bounds, padding); + } + } + async destroy(_args: DestroyMapArgs): Promise { console.log(`Destroy map: ${_args.id}`); const mapItem = this.maps[_args.id]; @@ -513,13 +548,17 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle delete this.maps[_args.id]; } - async mapBoundsContains(_args: MapBoundsContainsArgs): Promise<{ contains: boolean }> { + async mapBoundsContains( + _args: MapBoundsContainsArgs, + ): Promise<{ contains: boolean }> { const bounds = this.getLatLngBounds(_args.bounds); const point = new google.maps.LatLng(_args.point.lat, _args.point.lng); return { contains: bounds.contains(point) }; } - async mapBoundsExtend(_args: MapBoundsExtendArgs): Promise<{ bounds: LatLngBounds }> { + async mapBoundsExtend( + _args: MapBoundsExtendArgs, + ): Promise<{ bounds: LatLngBounds }> { const bounds = this.getLatLngBounds(_args.bounds); const point = new google.maps.LatLng(_args.point.lat, _args.point.lng); bounds.extend(point); @@ -547,7 +586,11 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle ); } - async setCircleListeners(mapId: string, circleId: string, circle: google.maps.Circle): Promise { + async setCircleListeners( + mapId: string, + circleId: string, + circle: google.maps.Circle, + ): Promise { circle.addListener('click', () => { this.notifyListeners('onCircleClick', { mapId: mapId, @@ -557,7 +600,11 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle }); } - async setPolygonListeners(mapId: string, polygonId: string, polygon: google.maps.Polygon): Promise { + async setPolygonListeners( + mapId: string, + polygonId: string, + polygon: google.maps.Polygon, + ): Promise { polygon.addListener('click', () => { this.notifyListeners('onPolygonClick', { mapId: mapId, @@ -567,7 +614,11 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle }); } - async setPolylineListeners(mapId: string, polylineId: string, polyline: google.maps.Polyline): Promise { + async setPolylineListeners( + mapId: string, + polylineId: string, + polyline: google.maps.Polyline, + ): Promise { polyline.addListener('click', () => { this.notifyListeners('onPolylineClick', { mapId: mapId, @@ -669,20 +720,26 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle }); }); - map.addListener('click', (e: google.maps.MapMouseEvent | google.maps.IconMouseEvent) => { - this.notifyListeners('onMapClick', { - mapId: mapId, - latitude: e.latLng?.lat(), - longitude: e.latLng?.lng(), - }); - }); + map.addListener( + 'click', + (e: google.maps.MapMouseEvent | google.maps.IconMouseEvent) => { + this.notifyListeners('onMapClick', { + mapId: mapId, + latitude: e.latLng?.lat(), + longitude: e.latLng?.lng(), + }); + }, + ); this.notifyListeners('onMapReady', { mapId: mapId, }); } - private buildMarkerOpts(marker: Marker, map: google.maps.Map): google.maps.marker.AdvancedMarkerElement { + private buildMarkerOpts( + marker: Marker, + map: google.maps.Map, + ): google.maps.marker.AdvancedMarkerElement { if (!this.AdvancedMarkerElement || !this.PinElement) { throw new Error('Marker library not loaded'); }