diff --git a/google-maps/README.md b/google-maps/README.md index 0d858c28b..8681456f8 100644 --- a/google-maps/README.md +++ b/google-maps/README.md @@ -284,6 +284,8 @@ export default MyMap; * [`create(...)`](#create) * [`enableTouch()`](#enabletouch) * [`disableTouch()`](#disabletouch) +* [`update(...)`](#update) +* [`getOptions()`](#getoptions) * [`enableClustering(...)`](#enableclustering) * [`disableClustering()`](#disableclustering) * [`addMarker(...)`](#addmarker) @@ -362,6 +364,31 @@ enableTouch() => Promise disableTouch() => Promise ``` + + +### update(...) + +```typescript +update(config: GoogleMapConfig) => any +``` + +| Param | Type | +| ------------ | ----------------------------------------------------------- | +| **`config`** | GoogleMapConfig | + +**Returns:** any + +-------------------- + + +### getOptions() + +```typescript +getOptions() => GoogleMapConfig | null +``` + +**Returns:** GoogleMapConfig | null + -------------------- @@ -871,37 +898,43 @@ 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 | +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') +- 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 | The Map center. | | | +| **`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 @@ -921,6 +954,45 @@ 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') +- 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 | + + #### Marker A marker is an icon placed at a particular point on the map's surface. @@ -1030,18 +1102,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/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt b/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt index a22aa2208..122fec677 100644 --- a/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt +++ b/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt @@ -102,9 +102,6 @@ class CapacitorGoogleMap( bridge.webView.bringToFront() bridge.webView.setBackgroundColor(Color.TRANSPARENT) - if (config.styles != null) { - googleMap?.setMapStyle(MapStyleOptions(config.styles!!)) - } } } } @@ -153,6 +150,166 @@ 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("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) { + "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 = @@ -562,104 +719,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), @@ -678,6 +737,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/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsPlugin.kt b/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsPlugin.kt index 143eca887..7bae8c401 100644 --- a/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsPlugin.kt +++ b/google-maps/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) { @@ -659,160 +691,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 { @@ -1006,31 +884,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/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt b/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt index bc29ae08f..3d3d7158b 100644 --- a/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt +++ b/google-maps/android/src/main/java/com/capacitorjs/plugins/googlemaps/GoogleMapConfig.kt @@ -1,5 +1,6 @@ package com.capacitorjs.plugins.googlemaps +import com.getcapacitor.JSObject import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng @@ -15,7 +16,6 @@ class GoogleMapConfig(fromJSONObject: JSONObject) { var zoom: Int = 0 var liteMode: Boolean = false var devicePixelRatio: Float = 1.00f - var styles: String? = null var mapId: String? = null init { @@ -83,8 +83,6 @@ class GoogleMapConfig(fromJSONObject: JSONObject) { val cameraPosition = CameraPosition(center, zoom.toFloat(), 0.0F, 0.0F) - styles = fromJSONObject.getString("styles") - mapId = fromJSONObject.getString("androidMapId") googleMapOptions = GoogleMapOptions().camera(cameraPosition).liteMode(liteMode) diff --git a/google-maps/e2e-tests/src/pages/Map/ConfigMap.tsx b/google-maps/e2e-tests/src/pages/Map/ConfigMap.tsx index b73b65e2f..5b7f15fb1 100644 --- a/google-maps/e2e-tests/src/pages/Map/ConfigMap.tsx +++ b/google-maps/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/google-maps/e2e-tests/src/pages/Markers/MultipleMarkers.tsx b/google-maps/e2e-tests/src/pages/Markers/MultipleMarkers.tsx index 0108d8aab..b580abdde 100644 --- a/google-maps/e2e-tests/src/pages/Markers/MultipleMarkers.tsx +++ b/google-maps/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/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.m b/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.m index 4ae033d08..aeee0e300 100644 --- a/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.m +++ b/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.m @@ -7,6 +7,7 @@ CAP_PLUGIN_METHOD(create, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(enableTouch, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(disableTouch, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(update, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(addMarker, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(addMarkers, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(addPolygons, CAPPluginReturnPromise); @@ -21,13 +22,6 @@ CAP_PLUGIN_METHOD(disableClustering, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(destroy, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(setCamera, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(getMapType, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(setMapType, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(enableIndoorMaps, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(enableTrafficLayer, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(enableAccessibilityElements, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(enableCurrentLocation, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(setPadding, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(onScroll, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(onResize, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(onDisplay, CAPPluginReturnPromise); diff --git a/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.swift b/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.swift index 59af66ba8..fb0775fbf 100644 --- a/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.swift +++ b/google-maps/ios/Plugin/CapacitorGoogleMapsPlugin.swift @@ -121,6 +121,7 @@ public class CapacitorGoogleMapsPlugin: CAPPlugin, GMSMapViewDelegate { DispatchQueue.main.sync { let newMap = Map(id: id, config: config, delegate: self) self.maps[id] = newMap + newMap.applyConfig(configObj: configObj) } call.resolve() @@ -129,6 +130,28 @@ public class CapacitorGoogleMapsPlugin: CAPPlugin, GMSMapViewDelegate { } } + @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) + } + } + @objc func destroy(_ call: CAPPluginCall) { do { guard let id = call.getString("id") else { @@ -535,166 +558,6 @@ public class CapacitorGoogleMapsPlugin: CAPPlugin, GMSMapViewDelegate { } } - @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") - } - - if enabled && checkLocationPermission() != "granted" { - 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/google-maps/ios/Plugin/GoogleMapConfig.swift b/google-maps/ios/Plugin/GoogleMapConfig.swift index ef97fe77b..cf034a6c4 100644 --- a/google-maps/ios/Plugin/GoogleMapConfig.swift +++ b/google-maps/ios/Plugin/GoogleMapConfig.swift @@ -8,7 +8,6 @@ public struct GoogleMapConfig: Codable { let y: Double let center: LatLng let zoom: Double - let styles: String? var mapId: String? init(fromJSObject: JSObject) throws { @@ -46,12 +45,6 @@ public struct GoogleMapConfig: Codable { self.y = y self.zoom = zoom 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 } } diff --git a/google-maps/ios/Plugin/Map.swift b/google-maps/ios/Plugin/Map.swift index 71e4a7de9..5bf681166 100644 --- a/google-maps/ios/Plugin/Map.swift +++ b/google-maps/ios/Plugin/Map.swift @@ -194,6 +194,116 @@ 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("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 { @@ -427,47 +537,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] = [] @@ -496,6 +565,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/google-maps/src/definitions.ts b/google-maps/src/definitions.ts index 6fa91254a..2abfdf4ad 100644 --- a/google-maps/src/definitions.ts +++ b/google-maps/src/definitions.ts @@ -146,9 +146,80 @@ 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') + * - 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') + * - mapTypeId + * - maxZoom + * - minZoom + * - restriction + * - styles + * - zoom + */ +export interface GoogleMapCreateConfig extends GoogleMapConfig { /** * Override width for native map. */ @@ -166,7 +237,7 @@ export interface GoogleMapConfig extends google.maps.MapOptions { */ y?: number; /** - * Default location on the Earth towards which the camera points. + * The Map center. */ center: LatLng; /** @@ -183,14 +254,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. * diff --git a/google-maps/src/implementation.ts b/google-maps/src/implementation.ts index 76555ada0..f2a8d54dd 100644 --- a/google-maps/src/implementation.ts +++ b/google-maps/src/implementation.ts @@ -7,11 +7,10 @@ import type { GoogleMapConfig, LatLng, LatLngBounds, - MapPadding, - MapType, Marker, Polygon, Polyline, + GoogleMapCreateConfig, } from './definitions'; /** @@ -29,7 +28,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. */ @@ -54,6 +53,11 @@ export interface CreateMapArgs { language?: string; } +export interface UpdateMapArgs { + id: string; + config: GoogleMapConfig; +} + export interface DestroyMapArgs { id: string; } @@ -107,35 +111,11 @@ export interface CameraArgs { config: CameraConfig; } -export interface MapTypeArgs { - id: string; - mapType: MapType; -} - -export interface IndoorMapArgs { - id: string; - enabled: boolean; -} - -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[]; @@ -173,6 +153,7 @@ export interface CapacitorGoogleMapsPlugin extends Plugin { create(options: CreateMapArgs): Promise; enableTouch(args: { id: string }): Promise; disableTouch(args: { id: string }): Promise; + update(options: UpdateMapArgs): Promise; addMarker(args: AddMarkerArgs): Promise<{ id: string }>; addMarkers(args: AddMarkersArgs): Promise<{ ids: string[] }>; removeMarker(args: RemoveMarkerArgs): Promise; @@ -187,13 +168,6 @@ 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; diff --git a/google-maps/src/map.ts b/google-maps/src/map.ts index 67b6324e6..b8fcc0cbd 100644 --- a/google-maps/src/map.ts +++ b/google-maps/src/map.ts @@ -19,6 +19,7 @@ import type { CircleClickCallbackData, Polyline, PolylineCallbackData, + GoogleMapConfig, } from './definitions'; import { LatLngBounds, MapType } from './definitions'; import type { CreateMapArgs } from './implementation'; @@ -31,6 +32,8 @@ export interface GoogleMapInterface { ): Promise; enableTouch(): Promise; disableTouch(): Promise; + update(config: GoogleMapConfig): Promise; + getOptions(): GoogleMapConfig | null; enableClustering( /** * The minimum number of markers that can be clustered together. The default is 4 markers. @@ -52,13 +55,32 @@ export interface GoogleMapInterface { setCamera(config: CameraConfig): Promise; /** * Get current map type + * @deprecated This function will be removed in v6. Use {@link #update()} instead. */ getMapType(): Promise; + /** + * @deprecated This function will be removed in v6. Use {@link #update()} instead. + */ setMapType(mapType: MapType): Promise; + /** + * @deprecated This function will be removed in v6. Use {@link #update()} instead. + */ enableIndoorMaps(enabled: boolean): Promise; + /** + * @deprecated This function will be removed in v6. Use {@link #update()} instead. + */ enableTrafficLayer(enabled: boolean): Promise; + /** + * @deprecated This function will be removed in v6. Use {@link #update()} instead. + */ enableAccessibilityElements(enabled: boolean): Promise; + /** + * @deprecated This function will be removed in v6. Use {@link #update()} instead. + */ enableCurrentLocation(enabled: boolean): Promise; + /** + * @deprecated This function will be removed in v6. Use {@link #update()} instead. + */ setPadding(padding: MapPadding): Promise; /** * Sets the map viewport to contain the given bounds. @@ -142,6 +164,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; @@ -175,6 +198,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'); @@ -345,6 +369,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 marker clustering * @@ -507,9 +564,13 @@ export class GoogleMap { }); } + /** + * @deprecated This function will be removed in v6. 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], + ); } /** @@ -517,11 +578,14 @@ export class GoogleMap { * * @param mapType * @returns + * @deprecated This function will be removed in v6. Use {@link #update()} instead. */ async setMapType(mapType: MapType): Promise { - return CapacitorGoogleMaps.setMapType({ + return CapacitorGoogleMaps.update({ id: this.id, - mapType, + config: { + mapTypeId: mapType, + }, }); } @@ -530,11 +594,14 @@ export class GoogleMap { * * @param enabled * @returns + * @deprecated This function will be removed in v6. Use {@link #update()} instead. */ async enableIndoorMaps(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableIndoorMaps({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isIndoorMapsEnabled: enabled, + }, }); } @@ -543,11 +610,14 @@ export class GoogleMap { * * @param enabled * @returns + * @deprecated This function will be removed in v6. Use {@link #update()} instead. */ async enableTrafficLayer(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableTrafficLayer({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isTrafficLayerEnabled: enabled, + }, }); } @@ -558,11 +628,14 @@ export class GoogleMap { * * @param enabled * @returns + * @deprecated This function will be removed in v6. Use {@link #update()} instead. */ async enableAccessibilityElements(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableAccessibilityElements({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isAccessibilityElementsEnabled: enabled, + }, }); } @@ -571,11 +644,14 @@ export class GoogleMap { * * @param enabled * @returns + * @deprecated This function will be removed in v6. Use {@link #update()} instead. */ async enableCurrentLocation(enabled: boolean): Promise { - return CapacitorGoogleMaps.enableCurrentLocation({ + return CapacitorGoogleMaps.update({ id: this.id, - enabled, + config: { + isMyLocationEnabled: enabled, + }, }); } @@ -584,11 +660,14 @@ export class GoogleMap { * * @param padding * @returns + * @deprecated This function will be removed in v6. Use {@link #update()} instead. */ async setPadding(padding: MapPadding): Promise { - return CapacitorGoogleMaps.setPadding({ + return CapacitorGoogleMaps.update({ id: this.id, - padding, + config: { + padding, + }, }); } diff --git a/google-maps/src/web.ts b/google-maps/src/web.ts index 5b82addec..2ea904abe 100644 --- a/google-maps/src/web.ts +++ b/google-maps/src/web.ts @@ -8,20 +8,16 @@ import { SuperClusterAlgorithm, } from '@googlemaps/markerclusterer'; -import type { Marker } from './definitions'; -import { MapType, LatLngBounds } from './definitions'; +import type { Marker, MapPadding, GoogleMapConfig } from './definitions'; +import { LatLngBounds } from './definitions'; import type { AddMarkerArgs, CameraArgs, AddMarkersArgs, CapacitorGoogleMapsPlugin, CreateMapArgs, - CurrentLocArgs, DestroyMapArgs, - MapTypeArgs, - PaddingArgs, RemoveMarkerArgs, - TrafficLayerArgs, RemoveMarkersArgs, MapBoundsContainsArgs, EnableClusteringArgs, @@ -33,33 +29,34 @@ import type { RemoveCirclesArgs, AddPolylinesArgs, RemovePolylinesArgs, + UpdateMapArgs, } from './implementation'; +class MapInstance { + element!: HTMLElement; + map!: google.maps.Map; + markers: { + [id: string]: google.maps.Marker; + } = {}; + 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: typeof google.maps | undefined = undefined; - private maps: { - [id: string]: { - element: HTMLElement; - map: google.maps.Map; - markers: { - [id: string]: google.maps.Marker; - }; - polygons: { - [id: string]: google.maps.Polygon; - }; - circles: { - [id: string]: google.maps.Circle; - }; - polylines: { - [id: string]: google.maps.Polyline; - }; - markerClusterer?: MarkerClusterer; - trafficLayer?: google.maps.TrafficLayer; - }; - } = {}; + private maps: { [id: string]: MapInstance } = {}; private currMarkerId = 0; private currPolygonId = 0; private currCircleId = 0; @@ -154,83 +151,10 @@ export class CapacitorGoogleMapsWeb }); } - 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(); @@ -436,8 +360,7 @@ export class CapacitorGoogleMapsWeb async create(_args: CreateMapArgs): Promise { console.log(`Create map: ${_args.id}`); await this.importGoogleLib(_args.apiKey, _args.region, _args.language); - - this.maps[_args.id] = { + const mapInstance = { map: new window.google.maps.Map(_args.element, { ..._args.config }), element: _args.element, markers: {}, @@ -445,9 +368,85 @@ export class CapacitorGoogleMapsWeb circles: {}, polylines: {}, }; + this.applyConfig(mapInstance, _args.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];