From 8f4f9a270a8e30ad4e6e504579bf1ac09dfb4c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 30 Oct 2024 13:04:07 +0100 Subject: [PATCH] Adds Custom Tile Overlay Feature https://github.com/ionic-team/capacitor-plugins/pull/1970 From https://github.com/LaravelFreelancerNL/capacitor-plugins/tree/main --- .npmrc | 1 - package.json | 4 +- plugin/README.md | 28 ++ .../plugins/googlemaps/CapacitorGoogleMap.kt | 79 ++++- .../googlemaps/CapacitorGoogleMapsPlugin.kt | 39 +++ .../CapacitorGoogleMapsTileOverlay.kt | 29 ++ plugin/e2e-tests/.ionic/wdio.config.ts | 325 +++++++++--------- plugin/e2e-tests/ios/.gitignore | 2 +- plugin/e2e-tests/public/index.html | 1 - .../src/serviceWorkerRegistration.ts | 5 +- plugin/e2e-tests/src/setupTests.ts | 12 +- plugin/e2e-tests/src/theme/variables.css | 45 ++- .../map/create-and-destroy.page.ts | 20 +- .../markers/add-and-remove.page.ts | 29 +- .../markers/marker-customization.page.ts | 2 +- .../markers/multiple-markers.page.ts | 40 +-- .../specs/map/create-and-destroy.spec.ts | 15 +- .../specs/markers/add-and-remove.spec.ts | 17 +- .../markers/marker-customization.spec.ts | 30 +- .../specs/markers/multiple-markers.spec.ts | 110 +++--- plugin/package.json | 9 +- plugin/src/definitions.ts | 12 + plugin/src/implementation.ts | 10 + plugin/src/map.ts | 12 + plugin/src/web.ts | 74 ++++ 25 files changed, 597 insertions(+), 353 deletions(-) delete mode 100644 .npmrc create mode 100644 plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsTileOverlay.kt diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 16919e7..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -provenance=true \ No newline at end of file diff --git a/package.json b/package.json index 6bd1111..9b7c0d8 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "node": ">=20", "pnpm": ">=8" }, - "dependencies": {}, "workspaces": [ "plugin" - ] + ], + "packageManager": "pnpm@9.12.3+sha1.4fb96925d434c491285182acf29803bb9aa2e6e5" } diff --git a/plugin/README.md b/plugin/README.md index 2964128..4f27b4a 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -300,6 +300,7 @@ export default MyMap; * [`disableTouch()`](#disabletouch) * [`enableClustering(...)`](#enableclustering) * [`disableClustering()`](#disableclustering) +* [`addTileOverlay(...)`](#addtileoverlay) * [`addMarker(...)`](#addmarker) * [`addMarkers(...)`](#addmarkers) * [`removeMarker(...)`](#removemarker) @@ -402,6 +403,19 @@ disableClustering() => Promise -------------------- +### addTileOverlay(...) + +```typescript +addTileOverlay(tiles: TileOverlay) => Promise +``` + +| Param | Type | +| ----------- | --------------------------------------------------- | +| **`tiles`** | TileOverlay | + +-------------------- + + ### addMarker(...) ```typescript @@ -949,6 +963,20 @@ An interface representing a pair of latitude and longitude coordinates. | **`mapId`** | string | +#### TileOverlay + +For web, all the javascript TileOverlay options are available as +For iOS and Android only the config options declared on TileOverlay are available. + +| Prop | Type | +| ------------- | -------------------------------------------------------------- | +| **`getTile`** | (x: number, y: number, zoom: number) => string | +| **`opacity`** | number | +| **`visible`** | boolean | +| **`zIndex`** | number | +| **`debug`** | boolean | + + #### Marker A marker is an icon placed at a particular point on the map's surface. 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 a22aa22..59176db 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 @@ -19,6 +19,7 @@ import com.google.maps.android.clustering.ClusterManager import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import java.io.InputStream +import java.net.HttpURLConnection import java.net.URL @@ -45,7 +46,7 @@ class CapacitorGoogleMap( private val markers = HashMap() private val polygons = HashMap() private val circles = HashMap() - private val polylines = HashMap() + private val polylines = HashMap() private val markerIcons = HashMap() private var clusterManager: ClusterManager? = null @@ -173,6 +174,82 @@ class CapacitorGoogleMap( } } + fun addTileOverlay(tileOverlay: CapacitorGoogleMapsTileOverlay, callback: (result: Result) -> Unit) { + try { + googleMap ?: throw GoogleMapNotAvailable() + var tileOverlayId: String + + val bitmapFunc = CoroutineScope(Dispatchers.IO).async { + val url = URL(tileOverlay.imageSrc) + val connection: HttpURLConnection = url.openConnection() as HttpURLConnection + + connection.doInput = true + connection.connect() + + val input: InputStream = connection.inputStream + + BitmapFactory.decodeStream(input) + } + + CoroutineScope(Dispatchers.Main).launch { + /* + var tileProvider: TileProvider = object : UrlTileProvider(256, 256) { + override fun getTileUrl(x: Int, y: Int, zoom: Int): URL? { + /* Define the URL pattern for the tile images */ + val url = "https://avatars.githubusercontent.com/u/103097039?v=4" + return if (!checkTileExists(x, y, zoom)) { + null + } else try { + URL(url) + } catch (e: MalformedURLException) { + throw AssertionError(e) + } + } + /* + * Check that the tile server supports the requested x, y and zoom. + * Complete this stub according to the tile range you support. + * If you support a limited range of tiles at different zoom levels, then you + * need to define the supported x, y range at each zoom level. + */ + private fun checkTileExists(x: Int, y: Int, zoom: Int): Boolean { + val minZoom = 1 + val maxZoom = 16 + return zoom in minZoom..maxZoom + } + } + Log.d("TileOverlay ^^^ ", "tileProvider") + val tileOverlay = googleMap?.addTileOverlay( + TileOverlayOptions() + .tileProvider(tileProvider) + ) + */ + + val bitmap = bitmapFunc.await() + + // Now you can safely use the bitmap + if (bitmap != null) { + val imageDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap) + + val groundOverlay = googleMap?.addGroundOverlay( + GroundOverlayOptions() + .image(imageDescriptor) + .positionFromBounds(tileOverlay.imageBounds) + .transparency(tileOverlay.opacity) + .zIndex(tileOverlay.zIndex) + .visible(tileOverlay.visible) + ) + + tileOverlay.googleMapsTileOverlay = groundOverlay + tileOverlayId = groundOverlay!!.id + + callback(Result.success(tileOverlayId)) + } + } + } catch (e: GoogleMapsError) { + callback(Result.failure(e)) + } + } + fun addMarkers( newMarkers: List, callback: (ids: Result>) -> Unit 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 143eca8..c46a561 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 @@ -237,6 +237,45 @@ class CapacitorGoogleMapsPlugin : Plugin(), OnMapsSdkInitializedCallback { } } + @PluginMethod + fun addTileOverlay(call: PluginCall) { + try { + val id = call.getString("id") + id ?: throw InvalidMapIdError() + + val imageBoundsObj = call.getObject("imageBounds") ?: throw InvalidArgumentsError("imageBounds object is missing") + + val imageSrc = call.getString("imageSrc") + val opacity = call.getFloat("opacity", 1.0f) + val zIndex = call.getFloat("zIndex", 0.0f) + val visible = call.getBoolean("visible", true) + + val tileOverlayConfig = JSONObject() + tileOverlayConfig.put("imageBounds", imageBoundsObj) + tileOverlayConfig.put("imageSrc", imageSrc) + tileOverlayConfig.put("opacity", opacity) + tileOverlayConfig.put("zIndex", zIndex) + tileOverlayConfig.put("visible", visible) + + val map = maps[id] + map ?: throw MapNotFoundError() + + val tileOptions = CapacitorGoogleMapsTileOverlay(tileOverlayConfig) + + map.addTileOverlay(tileOptions) { result -> + val tileOverlayId = result.getOrThrow() + + val res = JSObject() + res.put("id", tileOverlayId) + call.resolve(res) + } + } catch (e: GoogleMapsError) { + handleError(call, e) + } catch (e: Exception) { + handleError(call, e) + } + } + @PluginMethod fun addMarkers(call: PluginCall) { try { diff --git a/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsTileOverlay.kt b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsTileOverlay.kt new file mode 100644 index 0000000..b757dfc --- /dev/null +++ b/plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMapsTileOverlay.kt @@ -0,0 +1,29 @@ +package com.capacitorjs.plugins.googlemaps + +import com.google.android.gms.maps.model.GroundOverlay +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import org.json.JSONObject + +class CapacitorGoogleMapsTileOverlay(fromJSONObject: JSONObject) { + var imageBounds: LatLngBounds + var imageSrc: String? = null + var opacity: Float = 1.0f + var zIndex: Float = 0.0f + var visible: Boolean = true + var googleMapsTileOverlay: GroundOverlay? = null + + init { + val latLngObj = fromJSONObject.getJSONObject("imageBounds") + val north = latLngObj.optDouble("north", 0.0) + val south = latLngObj.optDouble("south", 0.0) + val east = latLngObj.optDouble("east", 0.0) + val west = latLngObj.optDouble("west", 0.0) + + imageBounds = LatLngBounds(LatLng(south, west), LatLng(north, east)) + imageSrc = fromJSONObject.optString("imageSrc", null) + zIndex = fromJSONObject.optLong("zIndex", 0).toFloat() + visible = fromJSONObject.optBoolean("visible", true) + opacity = 1.0f - fromJSONObject.optDouble("opacity", 1.0).toFloat() + } +} \ No newline at end of file diff --git a/plugin/e2e-tests/.ionic/wdio.config.ts b/plugin/e2e-tests/.ionic/wdio.config.ts index d0c57fe..efd1f3f 100644 --- a/plugin/e2e-tests/.ionic/wdio.config.ts +++ b/plugin/e2e-tests/.ionic/wdio.config.ts @@ -1,174 +1,165 @@ exports.config = { - "autoCompileOpts": { - "autoCompile": true, - "tsNodeOpts": { - "transpileOnly": true - }, - "tsConfigPathsOpts": { - "paths": {}, - "baseUrl": "./" - } + autoCompileOpts: { + autoCompile: true, + tsNodeOpts: { + transpileOnly: true, }, - "runner": "local", - "specs": [ - [ - "./tests/specs/**/*.spec.ts" - ] - ], - "logLevel": "trace", - "bail": 0, - "waitforTimeout": 45000, - "connectionRetryTimeout": 120000, - "connectionRetryCount": 3, - "framework": "mocha", - "reporters": [ - "spec" - ], - "mochaOpts": { - "timeout": 1200000 + tsConfigPathsOpts: { + paths: {}, + baseUrl: './', }, - "services": [ - [ - "appium", - { - "command": "appium", - "args": { - "relaxedSecurity": true - } - } - ], - [ - "chromedriver", - { - "args": [ - "--use-fake-ui-for-media-stream", - "--use-fake-device-for-media-stream" - ] - } - ] + }, + runner: 'local', + specs: [['./tests/specs/**/*.spec.ts']], + logLevel: 'trace', + bail: 0, + waitforTimeout: 45000, + connectionRetryTimeout: 120000, + connectionRetryCount: 3, + framework: 'mocha', + reporters: ['spec'], + mochaOpts: { + timeout: 1200000, + }, + services: [ + [ + 'appium', + { + command: 'appium', + args: { + relaxedSecurity: true, + }, + }, ], - "port": 4723, - "ios:simulator": { - "platformName": "iOS", - "maxInstances": 1, - "appium:isHeadless": true, - "appium:deviceName": "iPhone 13 Pro Max", - "appium:platformVersion": "15.2", - "appium:orientation": "PORTRAIT", - "appium:automationName": "XCUITest", - "appium:app": "./.ionic/App-ios-simulator.zip", - "appium:newCommandTimeout": 240, - "appium:platformName": "iOS", - "appium:wdaLaunchTimeout": 600000 - }, - "ios:device": { - "platformName": "iOS", - "maxInstances": 1, - "appium:isHeadless": false, - "appium:deviceName": "iPhone 12 Pro Max", - "appium:platformVersion": "15.2", - "appium:orientation": "PORTRAIT", - "appium:automationName": "XCUITest", - "appium:app": "./.ionic/App-ios-simulator.zip", - "appium:newCommandTimeout": 240 - }, - "ios:browser": { - "browserName": "safari", - "platformName": "iOS", - "maxInstances": 1, - "appium:isHeadless": false, - "appium:deviceName": "iPhone 13 Pro Max", - "appium:platformVersion": "15.0", - "appium:orientation": "PORTRAIT", - "appium:automationName": "XCUITest", - "appium:newCommandTimeout": 240 - }, - "android:emulator": { - "platformName": "Android", - "maxInstances": 1, - "appium:isHeadless": true, - "appium:deviceName": "e2eDevice", - "appium:platformVersion": "11.0", - "appium:orientation": "PORTRAIT", - "appium:automationName": "UiAutomator2", - "appium:app": "./.ionic/app-debug.apk", - "appium:appWaitActivity": "io.ionic.starter.MainActivity", - "appium:newCommandTimeout": 300, - "appium:platformName": "Android", - "appium:avd": "e2eDevice", - "appium:appPackage": "io.ionic.starter", - "appium:autoGrantPermissions": true, - "appium:allowTestPackages": true, - "appium:appWaitDuration": 60000, - "appium:adbExecTimeout": 300000, - "appium:deviceReadyTimeout": 3000, - "appium:androidDeviceReadyTimeout": 3000, - "appium:avdLaunchTimeout": 300000, - "appium:avdReadyTimeout": 300000, - "appium:appWaitForLaunch": false, - "appium:avdArgs": "-no-window -noaudio -verbose -accel on -no-boot-anim -no-snapshot-save" - }, - "android:device": { - "platformName": "Android", - "maxInstances": 1, - "appium:isHeadless": false, - "appium:deviceName": "G8X", - "appium:platformVersion": "11", - "appium:orientation": "PORTRAIT", - "appium:automationName": "UiAutomator2", - "appium:app": "./.ionic/app-debug.apk", - "appium:appWaitActivity": "io.ionic.starter.MainActivity", - "appium:newCommandTimeout": 240 - }, - "android:browser": { - "platformName": "Android", - "browserName": "chrome", - "maxInstances": 1, - "appium:isHeadless": false, - "appium:deviceName": "e2eDevice", - "appium:platformVersion": "12", - "appium:orientation": "PORTRAIT", - "appium:newCommandTimeout": 240 + [ + 'chromedriver', + { + args: ['--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream'], + }, + ], + ], + port: 4723, + 'ios:simulator': { + platformName: 'iOS', + maxInstances: 1, + 'appium:isHeadless': true, + 'appium:deviceName': 'iPhone 13 Pro Max', + 'appium:platformVersion': '15.2', + 'appium:orientation': 'PORTRAIT', + 'appium:automationName': 'XCUITest', + 'appium:app': './.ionic/App-ios-simulator.zip', + 'appium:newCommandTimeout': 240, + 'appium:platformName': 'iOS', + 'appium:wdaLaunchTimeout': 600000, + }, + 'ios:device': { + platformName: 'iOS', + maxInstances: 1, + 'appium:isHeadless': false, + 'appium:deviceName': 'iPhone 12 Pro Max', + 'appium:platformVersion': '15.2', + 'appium:orientation': 'PORTRAIT', + 'appium:automationName': 'XCUITest', + 'appium:app': './.ionic/App-ios-simulator.zip', + 'appium:newCommandTimeout': 240, + }, + 'ios:browser': { + browserName: 'safari', + platformName: 'iOS', + maxInstances: 1, + 'appium:isHeadless': false, + 'appium:deviceName': 'iPhone 13 Pro Max', + 'appium:platformVersion': '15.0', + 'appium:orientation': 'PORTRAIT', + 'appium:automationName': 'XCUITest', + 'appium:newCommandTimeout': 240, + }, + 'android:emulator': { + platformName: 'Android', + maxInstances: 1, + 'appium:isHeadless': true, + 'appium:deviceName': 'e2eDevice', + 'appium:platformVersion': '11.0', + 'appium:orientation': 'PORTRAIT', + 'appium:automationName': 'UiAutomator2', + 'appium:app': './.ionic/app-debug.apk', + 'appium:appWaitActivity': 'io.ionic.starter.MainActivity', + 'appium:newCommandTimeout': 300, + 'appium:platformName': 'Android', + 'appium:avd': 'e2eDevice', + 'appium:appPackage': 'io.ionic.starter', + 'appium:autoGrantPermissions': true, + 'appium:allowTestPackages': true, + 'appium:appWaitDuration': 60000, + 'appium:adbExecTimeout': 300000, + 'appium:deviceReadyTimeout': 3000, + 'appium:androidDeviceReadyTimeout': 3000, + 'appium:avdLaunchTimeout': 300000, + 'appium:avdReadyTimeout': 300000, + 'appium:appWaitForLaunch': false, + 'appium:avdArgs': '-no-window -noaudio -verbose -accel on -no-boot-anim -no-snapshot-save', + }, + 'android:device': { + platformName: 'Android', + maxInstances: 1, + 'appium:isHeadless': false, + 'appium:deviceName': 'G8X', + 'appium:platformVersion': '11', + 'appium:orientation': 'PORTRAIT', + 'appium:automationName': 'UiAutomator2', + 'appium:app': './.ionic/app-debug.apk', + 'appium:appWaitActivity': 'io.ionic.starter.MainActivity', + 'appium:newCommandTimeout': 240, + }, + 'android:browser': { + platformName: 'Android', + browserName: 'chrome', + maxInstances: 1, + 'appium:isHeadless': false, + 'appium:deviceName': 'e2eDevice', + 'appium:platformVersion': '12', + 'appium:orientation': 'PORTRAIT', + 'appium:newCommandTimeout': 240, + }, + 'web:chrome': { + maxInstances: 1, + browserName: 'chrome', + 'wdio:devtoolsOptions': { + headless: true, }, - "web:chrome": { - "maxInstances": 1, - "browserName": "chrome", - "wdio:devtoolsOptions": { - "headless": true + 'goog:chromeOptions': { + prefs: { + 'profile.default_content_setting_values.media_stream_camera': 1, + 'profile.default_content_setting_values.media_stream_mic': 1, + 'profile.default_content_setting_values.notifications': 1, }, - "goog:chromeOptions": { - "prefs": { - "profile.default_content_setting_values.media_stream_camera": 1, - "profile.default_content_setting_values.media_stream_mic": 1, - "profile.default_content_setting_values.notifications": 1 - } - } }, - "capabilities": [ - { - "platformName": "Android", - "maxInstances": 1, - "appium:isHeadless": true, - "appium:deviceName": "e2eDevice", - "appium:platformVersion": "11.0", - "appium:orientation": "PORTRAIT", - "appium:automationName": "UiAutomator2", - "appium:app": "./.ionic/app-debug.apk", - "appium:appWaitActivity": "io.ionic.starter.MainActivity", - "appium:newCommandTimeout": 300, - "appium:platformName": "Android", - "appium:avd": "e2eDevice", - "appium:appPackage": "io.ionic.starter", - "appium:autoGrantPermissions": true, - "appium:allowTestPackages": true, - "appium:appWaitDuration": 60000, - "appium:adbExecTimeout": 300000, - "appium:deviceReadyTimeout": 3000, - "appium:androidDeviceReadyTimeout": 3000, - "appium:avdLaunchTimeout": 300000, - "appium:avdReadyTimeout": 300000, - "appium:appWaitForLaunch": false, - "appium:avdArgs": "-no-window -noaudio -verbose -accel on -no-boot-anim -no-snapshot-save" - } - ] - } \ No newline at end of file + }, + capabilities: [ + { + platformName: 'Android', + maxInstances: 1, + 'appium:isHeadless': true, + 'appium:deviceName': 'e2eDevice', + 'appium:platformVersion': '11.0', + 'appium:orientation': 'PORTRAIT', + 'appium:automationName': 'UiAutomator2', + 'appium:app': './.ionic/app-debug.apk', + 'appium:appWaitActivity': 'io.ionic.starter.MainActivity', + 'appium:newCommandTimeout': 300, + 'appium:platformName': 'Android', + 'appium:avd': 'e2eDevice', + 'appium:appPackage': 'io.ionic.starter', + 'appium:autoGrantPermissions': true, + 'appium:allowTestPackages': true, + 'appium:appWaitDuration': 60000, + 'appium:adbExecTimeout': 300000, + 'appium:deviceReadyTimeout': 3000, + 'appium:androidDeviceReadyTimeout': 3000, + 'appium:avdLaunchTimeout': 300000, + 'appium:avdReadyTimeout': 300000, + 'appium:appWaitForLaunch': false, + 'appium:avdArgs': '-no-window -noaudio -verbose -accel on -no-boot-anim -no-snapshot-save', + }, + ], +}; diff --git a/plugin/e2e-tests/ios/.gitignore b/plugin/e2e-tests/ios/.gitignore index 01ad520..75e8c5a 100644 --- a/plugin/e2e-tests/ios/.gitignore +++ b/plugin/e2e-tests/ios/.gitignore @@ -1,9 +1,9 @@ App/build App/Pods +App/Podfile.lock App/App/public DerivedData xcuserdata # Cordova plugins for Capacitor capacitor-cordova-ios-plugins - diff --git a/plugin/e2e-tests/public/index.html b/plugin/e2e-tests/public/index.html index 25b753b..80d0b3a 100644 --- a/plugin/e2e-tests/public/index.html +++ b/plugin/e2e-tests/public/index.html @@ -27,5 +27,4 @@
- diff --git a/plugin/e2e-tests/src/serviceWorkerRegistration.ts b/plugin/e2e-tests/src/serviceWorkerRegistration.ts index efbf2ac..bdd1d51 100644 --- a/plugin/e2e-tests/src/serviceWorkerRegistration.ts +++ b/plugin/e2e-tests/src/serviceWorkerRegistration.ts @@ -109,10 +109,7 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { + if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { diff --git a/plugin/e2e-tests/src/setupTests.ts b/plugin/e2e-tests/src/setupTests.ts index 87988d6..0d55309 100644 --- a/plugin/e2e-tests/src/setupTests.ts +++ b/plugin/e2e-tests/src/setupTests.ts @@ -5,10 +5,12 @@ import '@testing-library/jest-dom/extend-expect'; // Mock matchmedia -window.matchMedia = window.matchMedia || function() { - return { +window.matchMedia = + window.matchMedia || + function () { + return { matches: false, - addListener: function() {}, - removeListener: function() {} + addListener: function () {}, + removeListener: function () {}, + }; }; -}; diff --git a/plugin/e2e-tests/src/theme/variables.css b/plugin/e2e-tests/src/theme/variables.css index 9b1948e..aba28d8 100644 --- a/plugin/e2e-tests/src/theme/variables.css +++ b/plugin/e2e-tests/src/theme/variables.css @@ -92,65 +92,65 @@ body { body { --ion-color-primary: #428cff; - --ion-color-primary-rgb: 66,140,255; + --ion-color-primary-rgb: 66, 140, 255; --ion-color-primary-contrast: #ffffff; - --ion-color-primary-contrast-rgb: 255,255,255; + --ion-color-primary-contrast-rgb: 255, 255, 255; --ion-color-primary-shade: #3a7be0; --ion-color-primary-tint: #5598ff; --ion-color-secondary: #50c8ff; - --ion-color-secondary-rgb: 80,200,255; + --ion-color-secondary-rgb: 80, 200, 255; --ion-color-secondary-contrast: #ffffff; - --ion-color-secondary-contrast-rgb: 255,255,255; + --ion-color-secondary-contrast-rgb: 255, 255, 255; --ion-color-secondary-shade: #46b0e0; --ion-color-secondary-tint: #62ceff; --ion-color-tertiary: #6a64ff; - --ion-color-tertiary-rgb: 106,100,255; + --ion-color-tertiary-rgb: 106, 100, 255; --ion-color-tertiary-contrast: #ffffff; - --ion-color-tertiary-contrast-rgb: 255,255,255; + --ion-color-tertiary-contrast-rgb: 255, 255, 255; --ion-color-tertiary-shade: #5d58e0; --ion-color-tertiary-tint: #7974ff; --ion-color-success: #2fdf75; - --ion-color-success-rgb: 47,223,117; + --ion-color-success-rgb: 47, 223, 117; --ion-color-success-contrast: #000000; - --ion-color-success-contrast-rgb: 0,0,0; + --ion-color-success-contrast-rgb: 0, 0, 0; --ion-color-success-shade: #29c467; --ion-color-success-tint: #44e283; --ion-color-warning: #ffd534; - --ion-color-warning-rgb: 255,213,52; + --ion-color-warning-rgb: 255, 213, 52; --ion-color-warning-contrast: #000000; - --ion-color-warning-contrast-rgb: 0,0,0; + --ion-color-warning-contrast-rgb: 0, 0, 0; --ion-color-warning-shade: #e0bb2e; --ion-color-warning-tint: #ffd948; --ion-color-danger: #ff4961; - --ion-color-danger-rgb: 255,73,97; + --ion-color-danger-rgb: 255, 73, 97; --ion-color-danger-contrast: #ffffff; - --ion-color-danger-contrast-rgb: 255,255,255; + --ion-color-danger-contrast-rgb: 255, 255, 255; --ion-color-danger-shade: #e04055; --ion-color-danger-tint: #ff5b71; --ion-color-dark: #f4f5f8; - --ion-color-dark-rgb: 244,245,248; + --ion-color-dark-rgb: 244, 245, 248; --ion-color-dark-contrast: #000000; - --ion-color-dark-contrast-rgb: 0,0,0; + --ion-color-dark-contrast-rgb: 0, 0, 0; --ion-color-dark-shade: #d7d8da; --ion-color-dark-tint: #f5f6f9; --ion-color-medium: #989aa2; - --ion-color-medium-rgb: 152,154,162; + --ion-color-medium-rgb: 152, 154, 162; --ion-color-medium-contrast: #000000; - --ion-color-medium-contrast-rgb: 0,0,0; + --ion-color-medium-contrast-rgb: 0, 0, 0; --ion-color-medium-shade: #86888f; --ion-color-medium-tint: #a2a4ab; --ion-color-light: #222428; - --ion-color-light-rgb: 34,36,40; + --ion-color-light-rgb: 34, 36, 40; --ion-color-light-contrast: #ffffff; - --ion-color-light-contrast-rgb: 255,255,255; + --ion-color-light-contrast-rgb: 255, 255, 255; --ion-color-light-shade: #1e2023; --ion-color-light-tint: #383a3e; } @@ -162,10 +162,10 @@ body { .ios body { --ion-background-color: #000000; - --ion-background-color-rgb: 0,0,0; + --ion-background-color-rgb: 0, 0, 0; --ion-text-color: #ffffff; - --ion-text-color-rgb: 255,255,255; + --ion-text-color-rgb: 255, 255, 255; --ion-color-step-50: #0d0d0d; --ion-color-step-100: #1a1a1a; @@ -198,7 +198,6 @@ body { --ion-toolbar-border-color: var(--ion-color-step-250); } - /* * Material Design Dark Theme * ------------------------------------------- @@ -206,10 +205,10 @@ body { .md body { --ion-background-color: #121212; - --ion-background-color-rgb: 18,18,18; + --ion-background-color-rgb: 18, 18, 18; --ion-text-color: #ffffff; - --ion-text-color-rgb: 255,255,255; + --ion-text-color-rgb: 255, 255, 255; --ion-border-color: #222222; diff --git a/plugin/e2e-tests/tests/pageobjects/map/create-and-destroy.page.ts b/plugin/e2e-tests/tests/pageobjects/map/create-and-destroy.page.ts index e207d9e..2bc3520 100644 --- a/plugin/e2e-tests/tests/pageobjects/map/create-and-destroy.page.ts +++ b/plugin/e2e-tests/tests/pageobjects/map/create-and-destroy.page.ts @@ -3,15 +3,15 @@ import { IonicButton, IonicTextarea } from '@ionic/e2e-components-ionic'; import Page from '../page'; class CreateAndDestroyMapPage extends Page { - get createMapButton() { - return new IonicButton("#createMapButton"); - } - get destroyMapButton() { - return new IonicButton("#destroyMapButton"); - } - get commandOutputTextarea() { - return new IonicTextarea('#commandOutput'); - } + get createMapButton() { + return new IonicButton('#createMapButton'); + } + get destroyMapButton() { + return new IonicButton('#destroyMapButton'); + } + get commandOutputTextarea() { + return new IonicTextarea('#commandOutput'); + } } -export default new CreateAndDestroyMapPage(); \ No newline at end of file +export default new CreateAndDestroyMapPage(); diff --git a/plugin/e2e-tests/tests/pageobjects/markers/add-and-remove.page.ts b/plugin/e2e-tests/tests/pageobjects/markers/add-and-remove.page.ts index 7f6c47d..40e5352 100644 --- a/plugin/e2e-tests/tests/pageobjects/markers/add-and-remove.page.ts +++ b/plugin/e2e-tests/tests/pageobjects/markers/add-and-remove.page.ts @@ -1,21 +1,20 @@ import { IonicButton, IonicTextarea } from '@ionic/e2e-components-ionic'; -import Page from "../page"; +import Page from '../page'; class AddAndRemoveMarkers extends Page { - get createMapButton() { - return new IonicButton("#createMapButton") - } - get addMarkerButton() { - return new IonicButton("#addMarkerButton") - } - get removeMarkerButton() { - return new IonicButton("#removeMarkerButton") - } - get commandOutputTextarea() { - return new IonicTextarea('#commandOutput'); - } + get createMapButton() { + return new IonicButton('#createMapButton'); + } + get addMarkerButton() { + return new IonicButton('#addMarkerButton'); + } + get removeMarkerButton() { + return new IonicButton('#removeMarkerButton'); + } + get commandOutputTextarea() { + return new IonicTextarea('#commandOutput'); + } } - -export default new AddAndRemoveMarkers(); \ No newline at end of file +export default new AddAndRemoveMarkers(); diff --git a/plugin/e2e-tests/tests/pageobjects/markers/marker-customization.page.ts b/plugin/e2e-tests/tests/pageobjects/markers/marker-customization.page.ts index 96925a3..6434251 100644 --- a/plugin/e2e-tests/tests/pageobjects/markers/marker-customization.page.ts +++ b/plugin/e2e-tests/tests/pageobjects/markers/marker-customization.page.ts @@ -33,7 +33,7 @@ class MarkerCustomizations extends Page { get commandOutputTextarea() { return new IonicTextarea('#commandOutput'); -} + } } export default new MarkerCustomizations(); diff --git a/plugin/e2e-tests/tests/pageobjects/markers/multiple-markers.page.ts b/plugin/e2e-tests/tests/pageobjects/markers/multiple-markers.page.ts index 79f237c..e1d1ab5 100644 --- a/plugin/e2e-tests/tests/pageobjects/markers/multiple-markers.page.ts +++ b/plugin/e2e-tests/tests/pageobjects/markers/multiple-markers.page.ts @@ -1,26 +1,26 @@ import { IonicButton, IonicTextarea } from '@ionic/e2e-components-ionic'; -import Page from "../page"; +import Page from '../page'; class MultipleMarkers extends Page { - get createMapButton() { - return new IonicButton("#createMapButton") - } - get addMarkersButton() { - return new IonicButton("#addMarkersButton") - } - get enableClusteringButton() { - return new IonicButton("#enableClusteringButton") - } - get disableClusteringButton() { - return new IonicButton("#disableClusteringButton") - } - get removeMarkersButton() { - return new IonicButton("#removeMarkersButton") - } - get commandOutputTextarea() { - return new IonicTextarea('#commandOutput'); - } + get createMapButton() { + return new IonicButton('#createMapButton'); + } + get addMarkersButton() { + return new IonicButton('#addMarkersButton'); + } + get enableClusteringButton() { + return new IonicButton('#enableClusteringButton'); + } + get disableClusteringButton() { + return new IonicButton('#disableClusteringButton'); + } + get removeMarkersButton() { + return new IonicButton('#removeMarkersButton'); + } + get commandOutputTextarea() { + return new IonicTextarea('#commandOutput'); + } } -export default new MultipleMarkers(); \ No newline at end of file +export default new MultipleMarkers(); diff --git a/plugin/e2e-tests/tests/specs/map/create-and-destroy.spec.ts b/plugin/e2e-tests/tests/specs/map/create-and-destroy.spec.ts index 13aaf26..e6ee51a 100644 --- a/plugin/e2e-tests/tests/specs/map/create-and-destroy.spec.ts +++ b/plugin/e2e-tests/tests/specs/map/create-and-destroy.spec.ts @@ -3,7 +3,6 @@ import { waitForLoad, pause, setDevice, switchToWeb, url } from '@ionic/e2e'; import CreateAndDestroyMapPage from '../../pageobjects/map/create-and-destroy.page'; - describe('Google Maps - Create and Destroy Map', function () { before(async function () { await waitForLoad(); @@ -24,12 +23,12 @@ describe('Google Maps - Create and Destroy Map', function () { await pause(500); }); - it('should create and destroy a map', async function() { + it('should create and destroy a map', async function () { const createMapButton = await CreateAndDestroyMapPage.createMapButton; const destroyMapButton = await CreateAndDestroyMapPage.destroyMapButton; - const getCommandOutputText = async function() { + const getCommandOutputText = async function () { return (await CreateAndDestroyMapPage.commandOutputTextarea).getValue(); - } + }; await createMapButton.tap(); await pause(500); @@ -40,16 +39,14 @@ describe('Google Maps - Create and Destroy Map', function () { await expect(await getCommandOutputText()).toBe('Maps destroyed'); }); - it('should throw when attempting to destroy a non-existent map', async function() { + it('should throw when attempting to destroy a non-existent map', async function () { const destroyMapButton = await CreateAndDestroyMapPage.destroyMapButton; - const getCommandOutputText = async function() { + const getCommandOutputText = async function () { return (await CreateAndDestroyMapPage.commandOutputTextarea).getValue(); - } + }; await destroyMapButton.tap(); await pause(100); await expect(await getCommandOutputText()).toBe('Map not found for provided id.'); }); - - }); diff --git a/plugin/e2e-tests/tests/specs/markers/add-and-remove.spec.ts b/plugin/e2e-tests/tests/specs/markers/add-and-remove.spec.ts index 7b398cc..0761797 100644 --- a/plugin/e2e-tests/tests/specs/markers/add-and-remove.spec.ts +++ b/plugin/e2e-tests/tests/specs/markers/add-and-remove.spec.ts @@ -3,9 +3,8 @@ import { waitForLoad, pause, setDevice, switchToWeb, url } from '@ionic/e2e'; import AddAndRemoveMarkers from '../../pageobjects/markers/add-and-remove.page'; - describe('Google Maps - Add and Remove Marker', function () { - let createdMarkerId = ""; + let createdMarkerId = ''; before(async function () { await waitForLoad(); @@ -26,12 +25,12 @@ describe('Google Maps - Add and Remove Marker', function () { await pause(500); }); - it("should create a map and add a marker", async function() { + it('should create a map and add a marker', async function () { const createMapButton = await AddAndRemoveMarkers.createMapButton; const addMarkerButton = await AddAndRemoveMarkers.addMarkerButton; - const getCommandOutputText = async function() { + const getCommandOutputText = async function () { return (await AddAndRemoveMarkers.commandOutputTextarea).getValue(); - } + }; await createMapButton.tap(); await pause(500); @@ -41,13 +40,13 @@ describe('Google Maps - Add and Remove Marker', function () { await pause(500); await expect(await getCommandOutputText()).toContain('Marker added: '); - const markerId = (await getCommandOutputText()).replace("Marker added: ", ""); + const markerId = (await getCommandOutputText()).replace('Marker added: ', ''); await expect(markerId).not.toBeFalsy(); createdMarkerId = markerId; }); - it("should remove the created marker", async function() { + it('should remove the created marker', async function () { const removeMarkerButton = await AddAndRemoveMarkers.removeMarkerButton; const commandOutput = await $((await AddAndRemoveMarkers.commandOutputTextarea).selector).$('textarea'); @@ -56,7 +55,7 @@ describe('Google Maps - Add and Remove Marker', function () { await expect(commandOutput).toHaveValueContaining(`Marker removed: ${createdMarkerId}`); }); - it("should throw when attempting to remove a non-existent marker", async function() { + it('should throw when attempting to remove a non-existent marker', async function () { const removeMarkerButton = await AddAndRemoveMarkers.removeMarkerButton; const commandOutput = await $((await AddAndRemoveMarkers.commandOutputTextarea).selector).$('textarea'); @@ -64,4 +63,4 @@ describe('Google Maps - Add and Remove Marker', function () { await pause(500); await expect(commandOutput).toHaveValueContaining(`Marker not found for provided id.`); }); -}); \ No newline at end of file +}); diff --git a/plugin/e2e-tests/tests/specs/markers/marker-customization.spec.ts b/plugin/e2e-tests/tests/specs/markers/marker-customization.spec.ts index 4d4a80c..4359681 100644 --- a/plugin/e2e-tests/tests/specs/markers/marker-customization.spec.ts +++ b/plugin/e2e-tests/tests/specs/markers/marker-customization.spec.ts @@ -27,11 +27,7 @@ describe('Google Maps - Marker Customization', function () { const createMapButton = await MarkerCustomizations.createMapButton; const removeAllMarkersButton = await MarkerCustomizations.removeAllMarkersButton; const addColorMarkerButton = await MarkerCustomizations.addMarkerColorButton; - const commandOutput = await $( - ( - await MarkerCustomizations.commandOutputTextarea - ).selector, - ).$('textarea'); + const commandOutput = await $((await MarkerCustomizations.commandOutputTextarea).selector).$('textarea'); await createMapButton.tap(); await pause(500); @@ -50,11 +46,7 @@ describe('Google Maps - Marker Customization', function () { const createMapButton = await MarkerCustomizations.createMapButton; const removeAllMarkersButton = await MarkerCustomizations.removeAllMarkersButton; const addImageMarkerButton = await MarkerCustomizations.addMarkerImageButton; - const commandOutput = await $( - ( - await MarkerCustomizations.commandOutputTextarea - ).selector, - ).$('textarea'); + const commandOutput = await $((await MarkerCustomizations.commandOutputTextarea).selector).$('textarea'); await createMapButton.tap(); await pause(500); @@ -72,11 +64,7 @@ describe('Google Maps - Marker Customization', function () { it('should add 1 image marker', async function () { const removeAllMarkersButton = await MarkerCustomizations.removeAllMarkersButton; const addImageMarkerButton = await MarkerCustomizations.addMarkerImageButton; - const commandOutput = await $( - ( - await MarkerCustomizations.commandOutputTextarea - ).selector, - ).$('textarea'); + const commandOutput = await $((await MarkerCustomizations.commandOutputTextarea).selector).$('textarea'); await addImageMarkerButton.tap(); await pause(500); @@ -90,11 +78,7 @@ describe('Google Maps - Marker Customization', function () { it('should add 4 image markers', async function () { const removeAllMarkersButton = await MarkerCustomizations.removeAllMarkersButton; const addMultipleImageMarkersButton = await MarkerCustomizations.addMultipleImageMarkersButton; - const commandOutput = await $( - ( - await MarkerCustomizations.commandOutputTextarea - ).selector, - ).$('textarea'); + const commandOutput = await $((await MarkerCustomizations.commandOutputTextarea).selector).$('textarea'); await addMultipleImageMarkersButton.tap(); await pause(500); @@ -108,11 +92,7 @@ describe('Google Maps - Marker Customization', function () { it('should add 4 color markers', async function () { const removeAllMarkersButton = await MarkerCustomizations.removeAllMarkersButton; const addMultipleColorMarkersButton = await MarkerCustomizations.addMultipleColorMarkersButton; - const commandOutput = await $( - ( - await MarkerCustomizations.commandOutputTextarea - ).selector, - ).$('textarea'); + const commandOutput = await $((await MarkerCustomizations.commandOutputTextarea).selector).$('textarea'); await addMultipleColorMarkersButton.tap(); await pause(500); diff --git a/plugin/e2e-tests/tests/specs/markers/multiple-markers.spec.ts b/plugin/e2e-tests/tests/specs/markers/multiple-markers.spec.ts index f1d2b3e..5b35fb8 100644 --- a/plugin/e2e-tests/tests/specs/markers/multiple-markers.spec.ts +++ b/plugin/e2e-tests/tests/specs/markers/multiple-markers.spec.ts @@ -4,68 +4,68 @@ import { waitForLoad, pause, setDevice, switchToWeb, url } from '@ionic/e2e'; import MultipleMarkers from '../../pageobjects/markers/multiple-markers.page'; describe('Google Maps - Multiple Markers', function () { - before(async function () { - await waitForLoad(); - await switchToWeb(); - await url('/markers/multiple-markers'); - await pause(500); - }); - - beforeEach(async function () { - await setDevice(IonicE2E.Device.Mobile); - await switchToWeb(); - await MultipleMarkers.hideToolBars(); - }); - - after(async function () { - await switchToWeb(); - await MultipleMarkers.showToolBars(); - await pause(500); - }); + before(async function () { + await waitForLoad(); + await switchToWeb(); + await url('/markers/multiple-markers'); + await pause(500); + }); - it("should create a map and add 4 markers", async function() { - const createMapButton = await MultipleMarkers.createMapButton; - const addMarkersButton = await MultipleMarkers.addMarkersButton; - const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); + beforeEach(async function () { + await setDevice(IonicE2E.Device.Mobile); + await switchToWeb(); + await MultipleMarkers.hideToolBars(); + }); - await createMapButton.tap(); - await pause(500); - await expect(commandOutput).toHaveValue('Map created'); + after(async function () { + await switchToWeb(); + await MultipleMarkers.showToolBars(); + await pause(500); + }); - await addMarkersButton.tap(); - await pause(500); - await expect(commandOutput).toHaveValueContaining('4 markers added'); - }) + it('should create a map and add 4 markers', async function () { + const createMapButton = await MultipleMarkers.createMapButton; + const addMarkersButton = await MultipleMarkers.addMarkersButton; + const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); - it("should enable and disable clustering", async function() { - const enableClusteringButton = await MultipleMarkers.enableClusteringButton; - const disableClusteringButton = await MultipleMarkers.disableClusteringButton; - const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); + await createMapButton.tap(); + await pause(500); + await expect(commandOutput).toHaveValue('Map created'); - await enableClusteringButton.tap(); - await pause(500); - await expect(commandOutput).toHaveValue('marker clustering enabled'); + await addMarkersButton.tap(); + await pause(500); + await expect(commandOutput).toHaveValueContaining('4 markers added'); + }); - await disableClusteringButton.tap(); - await pause(500); - await expect(commandOutput).toHaveValue('marker clustering disabled'); - }) + it('should enable and disable clustering', async function () { + const enableClusteringButton = await MultipleMarkers.enableClusteringButton; + const disableClusteringButton = await MultipleMarkers.disableClusteringButton; + const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); - it("should remove 4 markers", async function() { - const removeMarkersButton = await MultipleMarkers.removeMarkersButton; - const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); + await enableClusteringButton.tap(); + await pause(500); + await expect(commandOutput).toHaveValue('marker clustering enabled'); - await removeMarkersButton.tap() - await pause(500); - await expect(commandOutput).toHaveValue('4 markers removed'); - }) + await disableClusteringButton.tap(); + await pause(500); + await expect(commandOutput).toHaveValue('marker clustering disabled'); + }); - it("should throw error when attempting to remove no markers", async function() { - const removeMarkersButton = await MultipleMarkers.removeMarkersButton; - const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); + it('should remove 4 markers', async function () { + const removeMarkersButton = await MultipleMarkers.removeMarkersButton; + const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); - await removeMarkersButton.tap() - await pause(500); - await expect(commandOutput).toHaveValue('Invalid Arguments Provided: markerIds requires at least one marker id.'); - }) -}) \ No newline at end of file + await removeMarkersButton.tap(); + await pause(500); + await expect(commandOutput).toHaveValue('4 markers removed'); + }); + + it('should throw error when attempting to remove no markers', async function () { + const removeMarkersButton = await MultipleMarkers.removeMarkersButton; + const commandOutput = await $((await MultipleMarkers.commandOutputTextarea).selector).$('textarea'); + + await removeMarkersButton.tap(); + await pause(500); + await expect(commandOutput).toHaveValue('Invalid Arguments Provided: markerIds requires at least one marker id.'); + }); +}); diff --git a/plugin/package.json b/plugin/package.json index fcb50ba..4e316d6 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -1,5 +1,5 @@ { - "name": "@capacitor/google-maps", + "name": "@flossyweb/capacitor-google-maps", "version": "6.0.1", "description": "Google maps on Capacitor", "main": "dist/plugin.cjs.js", @@ -20,14 +20,14 @@ "ios/Plugin/", "CapacitorGoogleMaps.podspec" ], - "author": "Ionic ", + "author": "François Simon (SNS Solutions)", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/ionic-team/capacitor-google-maps.git" + "url": "git+https://github.com/FlossyWeb/capacitor-google-maps.git" }, "bugs": { - "url": "https://github.com/ionic-team/capacitor-google-maps/issues" + "url": "https://github.com/FlossyWeb/capacitor-google-maps/issues" }, "keywords": [ "capacitor", @@ -41,6 +41,7 @@ "verify:android": "cd android && ./gradlew clean build test && cd ..", "verify:web": "pnpm run build", "lint": "pnpm eslint . --ext ts && pnpm prettier \"./**/*.{css,html,ts,js,java}\" --check && pnpm node-swiftlint lint", + "lint:fix": "pnpm prettier \"./**/*.{css,html,ts,js,java}\" --write", "fmt": "pnpm eslint . --ext ts --fix && pnpm prettier \"./**/*.{css,html,ts,js,java}\" --write && pnpm node-swiftlint --fix --format", "docgen": "docgen --api GoogleMapInterface --output-readme README.md --output-json dist/docs.json", "build": "pnpm run clean && pnpm run docgen && tsc && rollup -c rollup.config.js && pnpm run downleveldts", diff --git a/plugin/src/definitions.ts b/plugin/src/definitions.ts index 6fa9125..7bb3bc9 100644 --- a/plugin/src/definitions.ts +++ b/plugin/src/definitions.ts @@ -65,6 +65,18 @@ export interface Point { y: number; } +/** + * For web, all the javascript TileOverlay options are available as + * For iOS and Android only the config options declared on TileOverlay are available. + */ +export interface TileOverlay { + getTile: (x: number, y: number, zoom: number) => string; + opacity?: number; + visible?: boolean; + zIndex?: number; + debug?: boolean; +} + /** * For web, all the javascript Polygon options are available as * Polygon extends google.maps.PolygonOptions. diff --git a/plugin/src/implementation.ts b/plugin/src/implementation.ts index a7ff453..860f8a6 100644 --- a/plugin/src/implementation.ts +++ b/plugin/src/implementation.ts @@ -58,6 +58,15 @@ export interface DestroyMapArgs { id: string; } +export interface AddTileOverlayArgs { + id: string; + getTile: (x: number, y: number, zoom: number) => string; + opacity?: number; + debug?: boolean; + visible?: boolean; + zIndex?: number; +} + export interface RemoveMarkerArgs { id: string; markerId: string; @@ -173,6 +182,7 @@ export interface CapacitorGoogleMapsPlugin extends Plugin { create(options: CreateMapArgs): Promise; enableTouch(args: { id: string }): Promise; disableTouch(args: { id: string }): Promise; + addTileOverlay(args: AddTileOverlayArgs): Promise; addMarker(args: AddMarkerArgs): Promise<{ id: string }>; addMarkers(args: AddMarkersArgs): Promise<{ ids: string[] }>; removeMarker(args: RemoveMarkerArgs): Promise; diff --git a/plugin/src/map.ts b/plugin/src/map.ts index 977c032..840112b 100644 --- a/plugin/src/map.ts +++ b/plugin/src/map.ts @@ -19,6 +19,7 @@ import type { CircleClickCallbackData, Polyline, PolylineCallbackData, + TileOverlay, } from './definitions'; import { LatLngBounds, MapType } from './definitions'; import type { CreateMapArgs } from './implementation'; @@ -35,6 +36,7 @@ export interface GoogleMapInterface { minClusterSize?: number ): Promise; disableClustering(): Promise; + addTileOverlay(tiles: TileOverlay): Promise; addMarker(marker: Marker): Promise; addMarkers(markers: Marker[]): Promise; removeMarker(id: string): Promise; @@ -333,6 +335,16 @@ export class GoogleMap { }); } + /** + * Adds a TileOverlay to the map + */ + async addTileOverlay(tiles: TileOverlay): Promise { + return await CapacitorGoogleMaps.addTileOverlay({ + id: this.id, + ...tiles, + }); + } + /** * Adds a marker to the map * diff --git a/plugin/src/web.ts b/plugin/src/web.ts index 87cea93..871b7c9 100644 --- a/plugin/src/web.ts +++ b/plugin/src/web.ts @@ -5,6 +5,7 @@ import { MarkerClusterer, SuperClusterAlgorithm } from '@googlemaps/markercluste import type { Marker } from './definitions'; import { MapType, LatLngBounds } from './definitions'; import type { + AddTileOverlayArgs, AddMarkerArgs, CameraArgs, AddMarkersArgs, @@ -29,6 +30,38 @@ import type { RemovePolylinesArgs, } from './implementation'; +class CoordMapType implements google.maps.MapType { + tileSize: google.maps.Size; + alt: string | null = null; + maxZoom = 17; + minZoom = 0; + name: string | null = null; + projection: google.maps.Projection | null = null; + radius = 6378137; + + constructor(tileSize: google.maps.Size) { + this.tileSize = tileSize; + } + getTile(coord: google.maps.Point, zoom: number, ownerDocument: Document): HTMLElement { + const div = ownerDocument.createElement('div'); + const pElement = ownerDocument.createElement('p'); + pElement.innerHTML = `x = ${coord.x}, y = ${coord.y}, zoom = ${zoom}`; + pElement.style.color = 'rgba(0, 0, 0, 0.5)'; + pElement.style.padding = '0 20px'; + div.appendChild(pElement); + + div.style.width = this.tileSize.width + 'px'; + div.style.height = this.tileSize.height + 'px'; + div.style.fontSize = '10'; + div.style.borderStyle = 'solid'; + div.style.borderWidth = '1px'; + div.style.borderColor = 'rgba(0, 0, 0, 0.5)'; + return div; + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + releaseTile(): void {} +} + export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogleMapsPlugin { private gMapsRef: typeof google.maps | undefined = undefined; private maps: { @@ -246,6 +279,47 @@ export class CapacitorGoogleMapsWeb extends WebPlugin implements CapacitorGoogle map.fitBounds(bounds, _args.padding); } + async addTileOverlay(_args: AddTileOverlayArgs): Promise { + const map = this.maps[_args.id].map; + + const tileSize = new google.maps.Size(256, 256); // Create a google.maps.Size instance + const coordMapType = new CoordMapType(tileSize); + + // Create a TileOverlay object + const customMapOverlay = new google.maps.ImageMapType({ + getTileUrl: function (coord, zoom) { + return _args.getTile(coord.x, coord.y, zoom); + }, + tileSize: new google.maps.Size(256, 256), + opacity: _args?.opacity, + name: 'tileoverlay', + }); + + // Draw Tiles + map.overlayMapTypes.insertAt(0, coordMapType); // insert coordMapType at the first position + + // Add the TileOverlay to the map + map.overlayMapTypes.push(customMapOverlay); + + // Optionally, you can set debug mode if needed + if (_args?.debug) { + map.addListener('mousemove', function (event: any) { + console.log('Mouse Coordinates: ', event.latLng.toString()); + }); + } + + // Set visibility based on the 'visible' property + if (!_args?.visible) { + map.overlayMapTypes.pop(); // Remove the last overlay (customMapOverlay) from the stack + } + + // Set zIndex based on the 'zIndex' property + if (_args?.zIndex !== undefined) { + // Move the customMapOverlay to the specified index in the overlay stack + map.overlayMapTypes.setAt(map.overlayMapTypes.getLength() - 1, customMapOverlay); + } + } + async addMarkers(_args: AddMarkersArgs): Promise<{ ids: string[] }> { const markerIds: string[] = []; const map = this.maps[_args.id];