diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4d4f57220..87cb6d948 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -14,4 +14,4 @@
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
-.github/ @googlemaps/dpe
+.github/ @googlemaps/admin
diff --git a/.github/autoapproval.yml b/.github/autoapproval.yml
index ff85d0dbe..1a97fd172 100644
--- a/.github/autoapproval.yml
+++ b/.github/autoapproval.yml
@@ -1,3 +1,17 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
from_owner:
- dependabot
diff --git a/.github/blunderbuss.yml b/.github/blunderbuss.yml
index 334554212..34a1073e6 100644
--- a/.github/blunderbuss.yml
+++ b/.github/blunderbuss.yml
@@ -1,4 +1,18 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
assign_issues:
- - arriolac
+ - barbeau
assign_prs:
- - arriolac
+ - barbeau
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6f0dac88e..ea94225d9 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,16 +1,30 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
version: 2
updates:
- package-ecosystem: gradle
directory: "/./app"
schedule:
- interval: daily
+ interval: "weekly"
open-pull-requests-limit: 10
commit-message:
prefix: chore(deps)
- package-ecosystem: gradle
directory: "/./maps-compose"
schedule:
- interval: daily
+ interval: "weekly"
open-pull-requests-limit: 10
commit-message:
prefix: chore(deps)
diff --git a/.github/stale.yml b/.github/stale.yml
index 8ed0e080f..1d39e65d7 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -1,3 +1,17 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml
new file mode 100644
index 000000000..a7b2d39c0
--- /dev/null
+++ b/.github/sync-repo-settings.yaml
@@ -0,0 +1,44 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings
+
+rebaseMergeAllowed: true
+squashMergeAllowed: true
+mergeCommitAllowed: false
+deleteBranchOnMerge: true
+branchProtectionRules:
+- pattern: main
+ isAdminEnforced: false
+ requiresStrictStatusChecks: false
+ requiredStatusCheckContexts:
+ - 'cla/google'
+ - 'test'
+ - 'snippet-bot check'
+ - 'header-check'
+ requiredApprovingReviewCount: 1
+ requiresCodeOwnerReviews: true
+- pattern: master
+ isAdminEnforced: false
+ requiresStrictStatusChecks: false
+ requiredStatusCheckContexts:
+ - 'cla/google'
+ - 'test'
+ - 'snippet-bot check'
+ - 'header-check'
+ requiredApprovingReviewCount: 1
+ requiresCodeOwnerReviews: true
+permissionRules:
+ - team: admin
+ permission: admin
diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml
new file mode 100644
index 000000000..6b46ebbbf
--- /dev/null
+++ b/.github/workflows/dependabot.yml
@@ -0,0 +1,32 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Dependabot
+on: pull_request
+
+permissions:
+ contents: write
+
+jobs:
+ dependabot:
+ runs-on: ubuntu-latest
+ if: ${{ github.actor == 'dependabot[bot]' }}
+ env:
+ PR_URL: ${{github.event.pull_request.html_url}}
+ GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}}
+ steps:
+ - name: approve
+ run: gh pr review --approve "$PR_URL"
+ - name: merge
+ run: gh pr merge --auto --squash --delete-branch "$PR_URL"
diff --git a/.github/workflows/instrumentation-test.yml b/.github/workflows/instrumentation-test.yml
new file mode 100644
index 000000000..294355125
--- /dev/null
+++ b/.github/workflows/instrumentation-test.yml
@@ -0,0 +1,69 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A workflow that runs tests on every new pull request
+name: Run instrumentation tests
+
+on:
+ repository_dispatch:
+ types: [test]
+ push:
+ branches-ignore: ['gh-pages']
+ pull_request:
+ branches-ignore: ['gh-pages']
+ workflow_dispatch:
+
+jobs:
+ run-instrumentation-test:
+ runs-on: macOS-latest # enables hardware acceleration in the virtual machine
+ timeout-minutes: 30
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v2
+
+ - name: Gradle Wrapper Validation
+ uses: gradle/wrapper-validation-action@v1.0.4
+
+ - name: Set up JDK 11
+ uses: actions/setup-java@v2.3.1
+ with:
+ java-version: '11'
+ distribution: 'adopt'
+
+ - name: Inject Maps API Key
+ run: |
+ # Injecting the key directly into the manifest as secrets-gradle-plugin
+ # isn't picking up the key when creating a local.properties file here
+ sed -i -e "s,\${MAPS_API_KEY},$MAPS_API_KEY,g" ./app/src/main/AndroidManifest.xml
+ env:
+ MAPS_API_KEY: ${{ secrets.GMP_API_KEY }}
+
+ - name: Build debug
+ run: ./gradlew assembleDebug
+
+ - name: Run instrumentation tests
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: 29
+ target: google_apis
+ arch: x86
+ disable-animations: true
+ script: ./gradlew :app:connectedCheck --stacktrace
+
+ - name: Upload test reports
+ if: always()
+ uses: actions/upload-artifact@v2
+ with:
+ name: test-reports
+ path: ./app/build/reports
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1fb92912f..049db65b8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }}
- uses: gradle/wrapper-validation-action@v1.0.4
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 74abaa9e0..4fc6ad0e1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -25,22 +25,22 @@ on:
workflow_dispatch:
jobs:
- run-unit-test:
+ test:
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout Repo
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Gradle Wrapper Validation
uses: gradle/wrapper-validation-action@v1.0.4
- name: Set up JDK 11
- uses: actions/setup-java@v2.3.1
+ uses: actions/setup-java@v3
with:
java-version: '11'
- distribution: 'adopt'
+ distribution: 'temurin'
- name: Build modules
run: ./gradlew build jacocoTestReport --stacktrace
diff --git a/README.md b/README.md
index d6b4079bf..ceddfb994 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ This repository contains [Jetpack Compose][jetpack-compose] components for the M
## Requirements
* Kotlin-enabled project
-* Jetpack Compose-enabled project
+* Jetpack Compose-enabled project (see [releases](https://github.com/googlemaps/android-maps-compose/releases) for the required version of Jetpack Compose)
* An [API key][api-key]
* API level 21+
@@ -21,50 +21,76 @@ Adding a map to your app looks like the following:
```kotlin
val singapore = LatLng(1.35, 103.87)
+val cameraPositionState = rememberCameraPositionState {
+ position = CameraPosition.fromLatLngZoom(singapore, 10f)
+}
GoogleMap(
modifier = Modifier.fillMaxSize(),
- googleMapOptionsFactory = {
- GoogleMapOptions().camera(CameraPosition.fromLatLngZoom(singapore, 10f))
- }
+ cameraPositionState = cameraPositionState
)
```
### Creating and configuring a map
-Configuring the map can be done either by passing a `GoogleMapOptions` instance
-to initialize the map, or by passing a `MapProperties` object into the `GoogleMap`
-composable.
+Configuring the map can be done by passing a `MapProperties` object into the
+`GoogleMap` composable, or for UI-related configurations, use `MapUiSettings`.
+`MapProperties` and `MapUiSettings` should be your first go-to for configuring
+the map. For any other configuration not present in those two classes, use
+`googleMapOptionsFactory` to provide a `GoogleMapOptions` instance instead.
+Typically, anything that can only be provided once (i.e. when the map is
+created)—like map ID—should be provided via `googleMapOptionsFactory`.
```kotlin
-// Initialize map by providing a googleMapOptionsFactory
-GoogleMap(
- googleMapOptionsFactory = {
- GoogleMapOptions().mapId("MyMapId")
- }
-)
-
-// ...or set properties using MapProperties which you can use to recompose the map
+// Set properties using MapProperties which you can use to recompose the map
var mapProperties by remember {
mutableStateOf(
MapProperties(maxZoomPreference = 10f, minZoomPreference = 5f)
)
}
+var mapUiSettings by remember {
+ mutableStateOf(
+ MapUiSettings(mapToolbarEnabled = false)
+ )
+}
Box(Modifier.fillMaxSize()) {
- GoogleMap(properties = mapProperties)
- Button(onClick = {
- mapProperties = mapProperties.copy(
- isBuildingEnabled = !mapProperties.isBuildingEnabled
- )
- }) {
- Text(text = "Toggle isBuildingEnabled")
+ GoogleMap(properties = mapProperties, uiSettings = mapUiSettings)
+ Column {
+ Button(onClick = {
+ mapProperties = mapProperties.copy(
+ isBuildingEnabled = !mapProperties.isBuildingEnabled
+ )
+ }) {
+ Text(text = "Toggle isBuildingEnabled")
+ }
+ Button(onClick = {
+ mapUiSettings = mapUiSettings.copy(
+ mapToolbarEnabled = !mapUiSettings.mapToolbarEnabled
+ )
+ }) {
+ Text(text = "Toggle mapToolbarEnabled")
+ }
}
}
+
+// ...or initialize the map by providing a googleMapOptionsFactory
+// This should only be used for values that do not recompose the map such as
+// map ID.
+GoogleMap(
+ googleMapOptionsFactory = {
+ GoogleMapOptions().mapId("MyMapId")
+ }
+)
+
```
### Controlling a map's camera
Camera changes and updates can be observed and controlled via `CameraPositionState`.
+**Note**: `CameraPositionState` is the source of truth for anything camera
+related. So, providing a camera position in `GoogleMapOptions` will be
+overridden by `CameraPosition`.
+
```kotlin
val singapore = LatLng(1.35, 103.87)
val cameraPositionState: CameraPositionState = rememberCameraPositionState {
@@ -90,8 +116,14 @@ composable elements to the content of the `GoogleMap`.
GoogleMap(
//...
) {
- Marker(position = LatLng(-34, 151), title = "Marker in Sydney")
- Marker(position = LatLng(35.66, 139.6), title = "Marker in Tokyo")
+ Marker(
+ state = MarkerState(position = LatLng(-34, 151)),
+ title = "Marker in Sydney"
+ )
+ Marker(
+ state = MarkerState(position = LatLng(35.66, 139.6)),
+ title = "Marker in Tokyo"
+ )
}
```
@@ -134,10 +166,13 @@ To run it, you'll have to:
```groovy
dependencies {
- implementation 'com.google.maps.android:maps-compose:1.2.0'
+ implementation 'com.google.maps.android:maps-compose:2.2.1'
// Make sure to also include the latest version of the Maps SDK for Android
implementation 'com.google.android.gms:play-services-maps:18.0.2'
+
+ // Also include Compose version `1.2.0-alpha03` or higher - for example:
+ implementation "androidx.compose.foundation:foundation:2.2.1-alpha03"
}
```
diff --git a/app/build.gradle b/app/build.gradle
index 64d7d8c95..6ae0c4b50 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,7 +27,7 @@ android {
}
buildFeatures {
- buildConfig false
+ buildConfig true
compose true
}
@@ -36,22 +36,40 @@ android {
}
}
+// [START maps_android_compose_dependency]
dependencies {
- implementation project(':maps-compose')
+ // [START_EXCLUDE silent]
implementation 'androidx.activity:activity-compose:1.4.0'
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
- implementation 'androidx.core:core-ktx:1.7.0'
- implementation 'com.google.android.gms:play-services-maps:18.0.2'
implementation 'com.google.android.material:material:1.5.0'
implementation 'com.google.maps.android:maps-ktx:3.3.0'
- implementation "androidx.core:core-ktx:1.7.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+ androidTestImplementation "androidx.test:core:$androidx_test_version"
+ androidTestImplementation "androidx.test:rules:$androidx_test_version"
+ androidTestImplementation "androidx.test:runner:$androidx_test_version"
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
+ androidTestImplementation 'junit:junit:4.13.2'
+ androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
+ androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
+
+ // For debugging purposes, uncomment the implementation project declaration
+ // so that the sample app can use the local state of the `maps-compose`
+ // module.
+ // However, this should remain uncommented on the `main` branch so that
+ // the maven declaration of Maps Compose can be used as a snippet.
+ // implementation project(':maps-compose')
+ // [END_EXCLUDE]
+ implementation "com.google.maps.android:maps-compose:2.2.0"
+ implementation 'com.google.android.gms:play-services-maps:18.0.2'
}
+// [END maps_android_compose_dependency]
secrets {
// To add your Maps API key to this project:
// 1. Add this line to your local.properties file, where YOUR_API_KEY is your API key:
// MAPS_API_KEY=YOUR_API_KEY
defaultPropertiesFileName 'local.defaults.properties'
-}
\ No newline at end of file
+}
diff --git a/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt b/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
new file mode 100644
index 000000000..46b72c211
--- /dev/null
+++ b/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
@@ -0,0 +1,234 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.maps.android.compose
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import com.google.android.gms.maps.model.CameraPosition
+import com.google.android.gms.maps.model.LatLng
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+class GoogleMapViewTests {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val startingZoom = 10f
+ private val startingPosition = LatLng(1.23, 4.56)
+ private lateinit var cameraPositionState: CameraPositionState
+
+ private fun initMap(content: @Composable () -> Unit = {}) {
+ val countDownLatch = CountDownLatch(1)
+ composeTestRule.setContent {
+ GoogleMapView(
+ modifier = Modifier.fillMaxSize(),
+ cameraPositionState = cameraPositionState,
+ onMapLoaded = {
+ countDownLatch.countDown()
+ }
+ ) {
+ content.invoke()
+ }
+ }
+ val mapLoaded = countDownLatch.await(30, TimeUnit.SECONDS)
+ assertTrue("Map loaded", mapLoaded)
+ }
+
+ @Before
+ fun setUp() {
+ cameraPositionState = CameraPositionState(
+ position = CameraPosition.fromLatLngZoom(
+ startingPosition,
+ startingZoom
+ )
+ )
+ }
+
+ @Test
+ fun testStartingCameraPosition() {
+ initMap()
+ startingPosition.assertEquals(cameraPositionState.position.target)
+ }
+
+ @Test
+ fun testCameraReportsMoving() {
+ initMap()
+ zoom(shouldAnimate = true, zoomIn = true) {
+ composeTestRule.waitUntil(1000) {
+ cameraPositionState.isMoving
+ }
+ assertTrue(cameraPositionState.isMoving)
+ }
+ }
+
+ @Test
+ fun testCameraReportsNotMoving() {
+ initMap()
+ zoom(shouldAnimate = true, zoomIn = true) {
+ composeTestRule.waitUntil(1000) {
+ cameraPositionState.isMoving
+ }
+ composeTestRule.waitUntil(5000) {
+ !cameraPositionState.isMoving
+ }
+ assertFalse(cameraPositionState.isMoving)
+ }
+ }
+
+ @Test
+ fun testCameraZoomInAnimation() {
+ initMap()
+ zoom(shouldAnimate = true, zoomIn = true) {
+ composeTestRule.waitUntil(1000) {
+ cameraPositionState.isMoving
+ }
+ composeTestRule.waitUntil(3000) {
+ !cameraPositionState.isMoving
+ }
+ assertEquals(
+ startingZoom + 1f,
+ cameraPositionState.position.zoom,
+ assertRoundingError.toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testCameraZoomIn() {
+ initMap()
+ zoom(shouldAnimate = false, zoomIn = true) {
+ composeTestRule.waitUntil(1000) {
+ cameraPositionState.isMoving
+ }
+ composeTestRule.waitUntil(3000) {
+ !cameraPositionState.isMoving
+ }
+ assertEquals(
+ startingZoom + 1f,
+ cameraPositionState.position.zoom,
+ assertRoundingError.toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testCameraZoomOut() {
+ initMap()
+ zoom(shouldAnimate = false, zoomIn = false) {
+ composeTestRule.waitUntil(1000) {
+ cameraPositionState.isMoving
+ }
+ composeTestRule.waitUntil(3000) {
+ !cameraPositionState.isMoving
+ }
+ assertEquals(
+ startingZoom - 1f,
+ cameraPositionState.position.zoom,
+ assertRoundingError.toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testCameraZoomOutAnimation() {
+ initMap()
+ zoom(shouldAnimate = true, zoomIn = false) {
+ composeTestRule.waitUntil(1000) {
+ cameraPositionState.isMoving
+ }
+ composeTestRule.waitUntil(3000) {
+ !cameraPositionState.isMoving
+ }
+ assertEquals(
+ startingZoom - 1f,
+ cameraPositionState.position.zoom,
+ assertRoundingError.toFloat()
+ )
+ }
+ }
+
+ @Test
+ fun testLatLngInVisibleRegion() {
+ initMap()
+ composeTestRule.runOnUiThread {
+ val projection = cameraPositionState.projection
+ assertNotNull(projection)
+ assertTrue(
+ projection!!.visibleRegion.latLngBounds.contains(startingPosition)
+ )
+ }
+ }
+
+ @Test
+ fun testLatLngNotInVisibleRegion() {
+ initMap()
+ composeTestRule.runOnUiThread {
+ val projection = cameraPositionState.projection
+ assertNotNull(projection)
+ val latLng = LatLng(23.4, 25.6)
+ assertFalse(
+ projection!!.visibleRegion.latLngBounds.contains(latLng)
+ )
+ }
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun testMarkerStateCannotBeReused() {
+ initMap {
+ val markerState = rememberMarkerState()
+ Marker(
+ state = markerState
+ )
+ Marker(
+ state = markerState
+ )
+ }
+ }
+
+ @Test
+ fun testCameraPositionStateMapClears() {
+ initMap()
+ composeTestRule.onNodeWithTag("toggleMapVisibility")
+ .performClick()
+ .performClick()
+ }
+
+ private fun zoom(
+ shouldAnimate: Boolean,
+ zoomIn: Boolean,
+ assertionBlock: () -> Unit
+ ) {
+ if (!shouldAnimate) {
+ composeTestRule.onNodeWithTag("cameraAnimations")
+ .assertIsDisplayed()
+ .performClick()
+ }
+ composeTestRule.onNodeWithText(if (zoomIn) "+" else "-")
+ .assertIsDisplayed()
+ .performClick()
+
+ assertionBlock()
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/google/maps/android/compose/MapInColumnTests.kt b/app/src/androidTest/java/com/google/maps/android/compose/MapInColumnTests.kt
new file mode 100644
index 000000000..e944944c6
--- /dev/null
+++ b/app/src/androidTest/java/com/google/maps/android/compose/MapInColumnTests.kt
@@ -0,0 +1,141 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.maps.android.compose
+
+import android.util.Log
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.*
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.android.gms.maps.model.CameraPosition
+import com.google.android.gms.maps.model.LatLng
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+private const val TAG = "MapInColumnTests"
+
+class MapInColumnTests {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val startingZoom = 10f
+ private val startingPosition = LatLng(1.23, 4.56)
+ private lateinit var cameraPositionState: CameraPositionState
+
+ private fun initMap(content: @Composable () -> Unit = {}) {
+ val countDownLatch = CountDownLatch(1)
+ composeTestRule.setContent {
+ var scrollingEnabled by remember { mutableStateOf(true) }
+
+ LaunchedEffect(cameraPositionState.isMoving) {
+ if (!cameraPositionState.isMoving) {
+ scrollingEnabled = true
+ Log.d(TAG, "Map camera stopped moving - Enabling column scrolling...")
+ }
+ }
+
+ MapInColumn(
+ modifier = Modifier.fillMaxSize(),
+ cameraPositionState,
+ columnScrollingEnabled = scrollingEnabled,
+ onMapTouched = {
+ scrollingEnabled = false
+ Log.d(
+ TAG,
+ "User touched map - Disabling column scrolling after user touched this Box..."
+ )
+ },
+ onMapLoaded = {
+ countDownLatch.countDown()
+ }
+ )
+ }
+ val mapLoaded = countDownLatch.await(30, TimeUnit.SECONDS)
+ assertTrue("Map loaded", mapLoaded)
+ }
+
+ @Before
+ fun setUp() {
+ cameraPositionState = CameraPositionState(
+ position = CameraPosition.fromLatLngZoom(
+ startingPosition,
+ startingZoom
+ )
+ )
+ }
+
+ @Test
+ fun testStartingCameraPosition() {
+ initMap()
+ startingPosition.assertEquals(cameraPositionState.position.target)
+ }
+
+ @Test
+ fun testLatLngInVisibleRegion() {
+ initMap()
+ composeTestRule.runOnUiThread {
+ val projection = cameraPositionState.projection
+ assertNotNull(projection)
+ assertTrue(
+ projection!!.visibleRegion.latLngBounds.contains(startingPosition)
+ )
+ }
+ }
+
+ @Test
+ fun testLatLngNotInVisibleRegion() {
+ initMap()
+ composeTestRule.runOnUiThread {
+ val projection = cameraPositionState.projection
+ assertNotNull(projection)
+ val latLng = LatLng(23.4, 25.6)
+ assertFalse(
+ projection!!.visibleRegion.latLngBounds.contains(latLng)
+ )
+ }
+ }
+
+ @Test
+ fun testScrollColumn_MapCameraRemainsSame() {
+ initMap()
+ // Check that the column scrolls to the last item
+ composeTestRule.onRoot().performTouchInput { swipeUp() }
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithTag("Item 1").assertIsNotDisplayed()
+
+ // Check that the map didn't change
+ startingPosition.assertEquals(cameraPositionState.position.target)
+ }
+
+// @Test
+// fun testPanMapUp_MapCameraChangesColumnDoesNotScroll() {
+// initMap()
+// // Swipe the map up
+// // FIXME - for some reason this scrolls the entire column instead of just the map
+// composeTestRule.onNodeWithTag("Map").performTouchInput { swipeUp() }
+// composeTestRule.waitForIdle()
+//
+// // Make sure that the map changed (i.e., we can scroll the map in the column)
+// startingPosition.assertNotEquals(cameraPositionState.position.target)
+//
+// // Check to make sure column didn't scroll
+// composeTestRule.onNodeWithTag("Item 1").assertIsDisplayed()
+// }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/google/maps/android/compose/TestUtils.kt b/app/src/androidTest/java/com/google/maps/android/compose/TestUtils.kt
new file mode 100644
index 000000000..9ff769a6b
--- /dev/null
+++ b/app/src/androidTest/java/com/google/maps/android/compose/TestUtils.kt
@@ -0,0 +1,17 @@
+package com.google.maps.android.compose
+
+import com.google.android.gms.maps.model.LatLng
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+
+const val assertRoundingError: Double = 0.01
+
+fun LatLng.assertEquals(other: LatLng) {
+ assertEquals(latitude, other.latitude, assertRoundingError)
+ assertEquals(longitude, other.longitude, assertRoundingError)
+}
+
+fun LatLng.assertNotEquals(other: LatLng) {
+ assertNotEquals(latitude, other.latitude, assertRoundingError)
+ assertNotEquals(longitude, other.longitude, assertRoundingError)
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b2d82c36d..0120d5149 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -33,14 +33,22 @@
android:value="${MAPS_API_KEY}" />
+
+
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/google/maps/android/compose/MapSampleActivity.kt b/app/src/main/java/com/google/maps/android/compose/BasicMapActivity.kt
similarity index 53%
rename from app/src/main/java/com/google/maps/android/compose/MapSampleActivity.kt
rename to app/src/main/java/com/google/maps/android/compose/BasicMapActivity.kt
index afde47747..d8871816c 100644
--- a/app/src/main/java/com/google/maps/android/compose/MapSampleActivity.kt
+++ b/app/src/main/java/com/google/maps/android/compose/BasicMapActivity.kt
@@ -24,52 +24,45 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.CircularProgressIndicator
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Switch
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.*
+import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import com.google.android.gms.maps.CameraUpdateFactory
-import com.google.android.gms.maps.GoogleMapOptions
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import kotlinx.coroutines.launch
-private const val TAG = "MapSampleActivity"
+private const val TAG = "BasicMapActivity"
-class MapSampleActivity : ComponentActivity() {
+private val singapore = LatLng(1.35, 103.87)
+private val singapore2 = LatLng(1.40, 103.77)
+private val singapore3 = LatLng(1.45, 103.77)
+private val defaultCameraPosition = CameraPosition.fromLatLngZoom(singapore, 11f)
+
+class BasicMapActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var isMapLoaded by remember { mutableStateOf(false) }
+ // Observing and controlling the camera's state can be done with a CameraPositionState
+ val cameraPositionState = rememberCameraPositionState {
+ position = defaultCameraPosition
+ }
Box(Modifier.fillMaxSize()) {
GoogleMapView(
modifier = Modifier.matchParentSize(),
+ cameraPositionState = cameraPositionState,
onMapLoaded = {
isMapLoaded = true
- }
+ },
)
if (!isMapLoaded) {
AnimatedVisibility(
@@ -92,77 +85,99 @@ class MapSampleActivity : ComponentActivity() {
}
@Composable
-private fun GoogleMapView(modifier: Modifier, onMapLoaded: () -> Unit) {
- val singapore = LatLng(1.35, 103.87)
- val singapore2 = LatLng(1.40, 103.77)
-
- // Observing and controlling the camera's state can be done with a CameraPositionState
- val cameraPositionState = rememberCameraPositionState {
- position = CameraPosition.fromLatLngZoom(singapore, 11f)
+fun GoogleMapView(
+ modifier: Modifier,
+ cameraPositionState: CameraPositionState,
+ onMapLoaded: () -> Unit,
+ content: @Composable () -> Unit = {}
+) {
+ val singaporeState = rememberMarkerState(position = singapore)
+ val singapore2State = rememberMarkerState(position = singapore2)
+ val singapore3State = rememberMarkerState(position = singapore3)
+ var circleCenter by remember { mutableStateOf(singapore) }
+ if (singaporeState.dragState == DragState.END) {
+ circleCenter = singaporeState.position
}
+ var uiSettings by remember { mutableStateOf(MapUiSettings(compassEnabled = false)) }
+ var shouldAnimateZoom by remember { mutableStateOf(true) }
+ var ticker by remember { mutableStateOf(0) }
var mapProperties by remember {
mutableStateOf(MapProperties(mapType = MapType.NORMAL))
}
- var uiSettings by remember {
- mutableStateOf(
- MapUiSettings(compassEnabled = false)
- )
- }
- var shouldAnimateZoom by remember { mutableStateOf(true) }
- var ticker by remember { mutableStateOf(0) }
+ var mapVisible by remember { mutableStateOf(true) }
- GoogleMap(
- modifier = modifier,
- cameraPositionState = cameraPositionState,
- properties = mapProperties,
- uiSettings = uiSettings,
- onMapLoaded = onMapLoaded,
- googleMapOptionsFactory = {
- GoogleMapOptions().camera(
- CameraPosition.fromLatLngZoom(
- singapore,
- 11f
- )
- )
- },
- onPOIClick = {
- Log.d(TAG, "POI clicked: ${it.name}")
- }
- ) {
- // Drawing on the map is accomplished with a child-based API
- val markerClick: (Marker) -> Boolean = {
- Log.d(TAG, "${it.title} was clicked")
- false
- }
- MarkerInfoWindowContent(
- position = singapore,
- title = "Zoom in has been tapped $ticker times.",
- onClick = markerClick,
- ) {
- Text(it.title ?: "Title", color = Color.Red)
- }
- MarkerInfoWindowContent(
- position = singapore2,
- title = "Marker with custom info window.\nZoom in has been tapped $ticker times.",
- icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE),
- onClick = markerClick,
+ if (mapVisible) {
+ GoogleMap(
+ modifier = modifier,
+ cameraPositionState = cameraPositionState,
+ properties = mapProperties,
+ uiSettings = uiSettings,
+ onMapLoaded = onMapLoaded,
+ onPOIClick = {
+ Log.d(TAG, "POI clicked: ${it.name}")
+ }
) {
- Text(it.title ?: "Title", color = Color.Blue)
+ // Drawing on the map is accomplished with a child-based API
+ val markerClick: (Marker) -> Boolean = {
+ Log.d(TAG, "${it.title} was clicked")
+ cameraPositionState.projection?.let { projection ->
+ Log.d(TAG, "The current projection is: $projection")
+ }
+ false
+ }
+ MarkerInfoWindowContent(
+ state = singaporeState,
+ title = "Zoom in has been tapped $ticker times.",
+ onClick = markerClick,
+ draggable = true,
+ ) {
+ Text(it.title ?: "Title", color = Color.Red)
+ }
+ MarkerInfoWindowContent(
+ state = singapore2State,
+ title = "Marker with custom info window.\nZoom in has been tapped $ticker times.",
+ icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE),
+ onClick = markerClick,
+ ) {
+ Text(it.title ?: "Title", color = Color.Blue)
+ }
+ Marker(
+ state = singapore3State,
+ title = "Marker in Singapore",
+ onClick = markerClick
+ )
+ Circle(
+ center = circleCenter,
+ fillColor = MaterialTheme.colors.secondary,
+ strokeColor = MaterialTheme.colors.secondaryVariant,
+ radius = 1000.0,
+ )
+ content()
}
- Circle(
- center = singapore,
- fillColor = MaterialTheme.colors.secondary,
- strokeColor = MaterialTheme.colors.secondaryVariant,
- radius = 1000.0,
- )
- }
+ }
Column {
MapTypeControls(onMapTypeClick = {
Log.d("GoogleMap", "Selected map type $it")
mapProperties = mapProperties.copy(mapType = it)
})
+ Row {
+ MapButton(
+ text = "Reset Map",
+ onClick = {
+ mapProperties = mapProperties.copy(mapType = MapType.NORMAL)
+ cameraPositionState.position = defaultCameraPosition
+ singaporeState.position = singapore
+ singaporeState.hideInfoWindow()
+ }
+ )
+ MapButton(
+ text = "Toggle Map",
+ onClick = { mapVisible = !mapVisible },
+ modifier = Modifier.testTag("toggleMapVisibility"),
+ )
+ }
val coroutineScope = rememberCoroutineScope()
ZoomControls(
shouldAnimateZoom,
@@ -193,7 +208,7 @@ private fun GoogleMapView(modifier: Modifier, onMapLoaded: () -> Unit) {
uiSettings = uiSettings.copy(zoomControlsEnabled = it)
}
)
- DebugView(cameraPositionState)
+ DebugView(cameraPositionState, singaporeState)
}
}
@@ -214,18 +229,8 @@ private fun MapTypeControls(
}
@Composable
-private fun MapTypeButton(type: MapType, onClick: () -> Unit) {
- Button(
- modifier = Modifier.padding(4.dp),
- colors = ButtonDefaults.buttonColors(
- backgroundColor = MaterialTheme.colors.onPrimary,
- contentColor = MaterialTheme.colors.primary
- ),
- onClick = onClick
- ) {
- Text(text = type.toString(), style = MaterialTheme.typography.body1)
- }
-}
+private fun MapTypeButton(type: MapType, onClick: () -> Unit) =
+ MapButton(text = type.toString(), onClick = onClick)
@Composable
private fun ZoomControls(
@@ -240,40 +245,40 @@ private fun ZoomControls(
MapButton("-", onClick = { onZoomOut() })
MapButton("+", onClick = { onZoomIn() })
Column(verticalArrangement = Arrangement.Center) {
- Row(horizontalArrangement = Arrangement.Center) {
- Text(text = "Camera Animations On?")
- Switch(
- isCameraAnimationChecked,
- onCheckedChange = onCameraAnimationCheckedChange
- )
- }
- Row(horizontalArrangement = Arrangement.Center) {
- Text(text = "Zoom Controls On?")
- Switch(
- isZoomControlsEnabledChecked,
- onCheckedChange = onZoomControlsCheckedChange
- )
- }
+ Text(text = "Camera Animations On?")
+ Switch(
+ isCameraAnimationChecked,
+ onCheckedChange = onCameraAnimationCheckedChange,
+ modifier = Modifier.testTag("cameraAnimations"),
+ )
+ Text(text = "Zoom Controls On?")
+ Switch(
+ isZoomControlsEnabledChecked,
+ onCheckedChange = onZoomControlsCheckedChange
+ )
}
}
}
@Composable
-private fun MapButton(text: String, onClick: () -> Unit) {
+private fun MapButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
Button(
- modifier = Modifier.padding(8.dp),
+ modifier = modifier.padding(4.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.onPrimary,
contentColor = MaterialTheme.colors.primary
),
onClick = onClick
) {
- Text(text = text, style = MaterialTheme.typography.h5)
+ Text(text = text, style = MaterialTheme.typography.body1)
}
}
@Composable
-private fun DebugView(cameraPositionState: CameraPositionState) {
+private fun DebugView(
+ cameraPositionState: CameraPositionState,
+ markerState: MarkerState
+) {
Column(
Modifier
.fillMaxWidth(),
@@ -283,5 +288,10 @@ private fun DebugView(cameraPositionState: CameraPositionState) {
if (cameraPositionState.isMoving) "moving" else "not moving"
Text(text = "Camera is $moving")
Text(text = "Camera position is ${cameraPositionState.position}")
+ Spacer(modifier = Modifier.height(4.dp))
+ val dragging =
+ if (markerState.dragState == DragState.DRAG) "dragging" else "not dragging"
+ Text(text = "Marker is $dragging")
+ Text(text = "Marker position is ${markerState.position}")
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/google/maps/android/compose/MainActivity.kt b/app/src/main/java/com/google/maps/android/compose/MainActivity.kt
new file mode 100644
index 000000000..02c49847c
--- /dev/null
+++ b/app/src/main/java/com/google/maps/android/compose/MainActivity.kt
@@ -0,0 +1,74 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.maps.android.compose
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+
+private const val TAG = "MapSampleActivity"
+
+class MainActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colors.background
+ ) {
+ val context = LocalContext.current
+ Column(
+ Modifier
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.padding(10.dp))
+ Text(
+ text = getString(R.string.main_activity_title),
+ style = MaterialTheme.typography.h5
+ )
+ Spacer(modifier = Modifier.padding(10.dp))
+ Button(
+ onClick = {
+ context.startActivity(Intent(context, BasicMapActivity::class.java))
+ }) {
+ Text(getString(R.string.basic_map_activity))
+ }
+ Spacer(modifier = Modifier.padding(5.dp))
+ Button(
+ onClick = {
+ context.startActivity(Intent(context, MapInColumnActivity::class.java))
+ }) {
+ Text(getString(R.string.map_in_column_activity))
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/google/maps/android/compose/MapInColumnActivity.kt b/app/src/main/java/com/google/maps/android/compose/MapInColumnActivity.kt
new file mode 100644
index 000000000..8aad53c2a
--- /dev/null
+++ b/app/src/main/java/com/google/maps/android/compose/MapInColumnActivity.kt
@@ -0,0 +1,220 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.maps.android.compose
+
+import android.os.Bundle
+import android.util.Log
+import android.view.MotionEvent
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.dp
+import com.google.android.gms.maps.model.CameraPosition
+import com.google.android.gms.maps.model.LatLng
+import com.google.android.gms.maps.model.Marker
+
+private const val TAG = "ScrollingMapActivity"
+
+private val singapore = LatLng(1.35, 103.87)
+private val defaultCameraPosition = CameraPosition.fromLatLngZoom(singapore, 11f)
+
+class MapInColumnActivity : ComponentActivity() {
+
+ @OptIn(ExperimentalComposeUiApi::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ // Observing and controlling the camera's state can be done with a CameraPositionState
+ val cameraPositionState = rememberCameraPositionState {
+ position = defaultCameraPosition
+ }
+ var columnScrollingEnabled by remember { mutableStateOf(true) }
+
+ // Use a LaunchedEffect keyed on the camera moving state to enable column scrolling when the camera stops moving
+ LaunchedEffect(cameraPositionState.isMoving) {
+ if (!cameraPositionState.isMoving) {
+ columnScrollingEnabled = true
+ Log.d(TAG, "Map camera stopped moving - Enabling column scrolling...")
+ }
+ }
+
+ MapInColumn(
+ modifier = Modifier.fillMaxSize(),
+ cameraPositionState,
+ columnScrollingEnabled = columnScrollingEnabled,
+ onMapTouched = {
+ columnScrollingEnabled = false
+ Log.d(
+ TAG,
+ "User touched map - Disabling column scrolling after user touched this Box..."
+ )
+ },
+ onMapLoaded = { }
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun MapInColumn(
+ modifier: Modifier = Modifier,
+ cameraPositionState: CameraPositionState,
+ columnScrollingEnabled: Boolean,
+ onMapTouched: () -> Unit,
+ onMapLoaded: () -> Unit,
+) {
+ Surface(
+ modifier = modifier,
+ color = MaterialTheme.colors.background
+ ) {
+ var isMapLoaded by remember { mutableStateOf(false) }
+
+ Column(
+ Modifier
+ .fillMaxSize()
+ .verticalScroll(
+ rememberScrollState(),
+ columnScrollingEnabled
+ ),
+ horizontalAlignment = Alignment.Start
+ ) {
+ Spacer(modifier = Modifier.padding(10.dp))
+ for (i in 1..20) {
+ Text(
+ text = "Item $i",
+ modifier = Modifier
+ .padding(start = 10.dp)
+ .testTag("Item $i")
+ )
+ }
+ Spacer(modifier = Modifier.padding(10.dp))
+
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ ) {
+ GoogleMapViewInColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .testTag("Map")
+ .pointerInteropFilter(
+ onTouchEvent = {
+ when (it.action) {
+ MotionEvent.ACTION_DOWN -> {
+ onMapTouched()
+ false
+ }
+ else -> {
+ Log.d(
+ TAG,
+ "MotionEvent ${it.action} - this never triggers."
+ )
+ true
+ }
+ }
+ }
+ ),
+ cameraPositionState = cameraPositionState,
+ onMapLoaded = {
+ isMapLoaded = true
+ onMapLoaded()
+ },
+ )
+ if (!isMapLoaded) {
+ androidx.compose.animation.AnimatedVisibility(
+ modifier = Modifier
+ .fillMaxSize(),
+ visible = !isMapLoaded,
+ enter = EnterTransition.None,
+ exit = fadeOut()
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .background(MaterialTheme.colors.background)
+ .wrapContentSize()
+ )
+ }
+ }
+ }
+ Spacer(modifier = Modifier.padding(10.dp))
+ for (i in 21..40) {
+ Text(
+ text = "Item $i",
+ modifier = Modifier
+ .padding(start = 10.dp)
+ .testTag("Item $i")
+ )
+ }
+ Spacer(modifier = Modifier.padding(10.dp))
+ }
+ }
+}
+
+@Composable
+private fun GoogleMapViewInColumn(
+ modifier: Modifier,
+ cameraPositionState: CameraPositionState,
+ onMapLoaded: () -> Unit,
+) {
+ val singaporeState = rememberMarkerState(position = singapore)
+
+ var uiSettings by remember { mutableStateOf(MapUiSettings(compassEnabled = false)) }
+ var mapProperties by remember {
+ mutableStateOf(MapProperties(mapType = MapType.NORMAL))
+ }
+
+ GoogleMap(
+ modifier = modifier,
+ cameraPositionState = cameraPositionState,
+ properties = mapProperties,
+ uiSettings = uiSettings,
+ onMapLoaded = onMapLoaded
+ ) {
+ // Drawing on the map is accomplished with a child-based API
+ val markerClick: (Marker) -> Boolean = {
+ Log.d(TAG, "${it.title} was clicked")
+ cameraPositionState.projection?.let { projection ->
+ Log.d(TAG, "The current projection is: $projection")
+ }
+ false
+ }
+ MarkerInfoWindowContent(
+ state = singaporeState,
+ title = "Singapore",
+ onClick = markerClick,
+ draggable = true,
+ ) {
+ Text(it.title ?: "Title", color = Color.Red)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 246066311..c8fb093ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,4 +16,7 @@
android-maps-compose
+ "Maps Compose Demos \uD83D\uDDFA"
+ Basic Map Activity
+ Map In Column Activity
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b0c1d0815..291e5fcf1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.6.10'
- ext.compose_version = '1.2.0-alpha02'
+ ext.kotlin_version = '1.6.21'
+ ext.compose_version = '1.2.0-beta02'
+ ext.androidx_test_version = '1.4.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.4"
- classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.0"
+ classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.5.0'
classpath 'com.hiya:jacoco-android:0.2'
@@ -29,7 +30,7 @@ ext.projectArtifactId = { project ->
allprojects {
group = 'com.google.maps.android'
- version = '1.2.0'
+ version = '2.2.1'
project.ext.artifactId = rootProject.ext.projectArtifactId(project)
}
@@ -124,8 +125,10 @@ subprojects { project ->
repositories {
maven {
+ def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+ def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
name = "mavencentral"
- url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+ url = project.version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username sonatypeUsername
password sonatypePassword
diff --git a/maps-compose/build.gradle b/maps-compose/build.gradle
index 90ac6bc38..0e2dd7d6d 100644
--- a/maps-compose/build.gradle
+++ b/maps-compose/build.gradle
@@ -29,6 +29,7 @@ android {
kotlinOptions {
jvmTarget = '1.8'
+ freeCompilerArgs += '-Xexplicit-api=strict'
}
}
diff --git a/maps-compose/src/androidTest/java/com/google/maps/android/compose/ExampleInstrumentedTest.kt b/maps-compose/src/androidTest/java/com/google/maps/android/compose/ExampleInstrumentedTest.kt
index 4acacd6fb..10c71948f 100644
--- a/maps-compose/src/androidTest/java/com/google/maps/android/compose/ExampleInstrumentedTest.kt
+++ b/maps-compose/src/androidTest/java/com/google/maps/android/compose/ExampleInstrumentedTest.kt
@@ -28,7 +28,7 @@ import org.junit.Assert.*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
+internal class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/CameraPositionState.kt b/maps-compose/src/main/java/com/google/maps/android/compose/CameraPositionState.kt
index 2e28740e2..57bdae868 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/CameraPositionState.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/CameraPositionState.kt
@@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue
import com.google.android.gms.maps.CameraUpdate
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
+import com.google.android.gms.maps.Projection
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import kotlinx.coroutines.CancellableContinuation
@@ -32,6 +33,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.suspendCancellableCoroutine
+import java.lang.Integer.MAX_VALUE
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -41,7 +43,7 @@ import kotlin.coroutines.resumeWithException
* initial state.
*/
@Composable
-inline fun rememberCameraPositionState(
+public inline fun rememberCameraPositionState(
key: String? = null,
crossinline init: CameraPositionState.() -> Unit = {}
): CameraPositionState = rememberSaveable(key = key, saver = CameraPositionState.Saver) {
@@ -55,16 +57,23 @@ inline fun rememberCameraPositionState(
*
* @param position the initial camera position
*/
-class CameraPositionState(
+public class CameraPositionState(
position: CameraPosition = CameraPosition(LatLng(0.0, 0.0), 0f, 0f, 0f)
) {
/**
* Whether the camera is currently moving or not. This includes any kind of movement:
* panning, zooming, or rotation.
*/
- var isMoving by mutableStateOf(false)
+ public var isMoving: Boolean by mutableStateOf(false)
internal set
+ /**
+ * Returns the current [Projection] to be used for converting between screen
+ * coordinates and lat/lng.
+ */
+ public val projection: Projection?
+ get() = map?.projection
+
/**
* Local source of truth for the current camera position.
* While [map] is non-null this reflects the current position of [map] as it changes.
@@ -76,7 +85,7 @@ class CameraPositionState(
/**
* Current position of the camera on the map.
*/
- var position: CameraPosition
+ public var position: CameraPosition
get() = rawPosition
set(value) {
synchronized(lock) {
@@ -165,9 +174,14 @@ class CameraPositionState(
* suspend until a map is bound and animation will begin.
*
* This method should only be called from a dispatcher bound to the map's UI thread.
+ *
+ * @param update the change that should be applied to the camera
+ * @param durationMs The duration of the animation in milliseconds. If [Int.MAX_VALUE] is
+ * provided, the default animation duration will be used. Otherwise, the value provided must be
+ * strictly positive, otherwise an [IllegalArgumentException] will be thrown.
*/
@UiThread
- suspend fun animate(update: CameraUpdate) {
+ public suspend fun animate(update: CameraUpdate, durationMs: Int = MAX_VALUE) {
val myJob = currentCoroutineContext()[Job]
try {
suspendCancellableCoroutine { continuation ->
@@ -187,7 +201,7 @@ class CameraPositionState(
"internal error; no GoogleMap available to animate position"
)
}
- performAnimateCameraLocked(newMap, update, continuation)
+ performAnimateCameraLocked(newMap, update, durationMs, continuation)
}
override fun onCancelLocked() {
@@ -208,7 +222,7 @@ class CameraPositionState(
}
}
} else {
- performAnimateCameraLocked(map, update, continuation)
+ performAnimateCameraLocked(map, update, durationMs, continuation)
}
}
}
@@ -227,9 +241,10 @@ class CameraPositionState(
private fun performAnimateCameraLocked(
map: GoogleMap,
update: CameraUpdate,
+ durationMs: Int,
continuation: CancellableContinuation
) {
- map.animateCamera(update, object : GoogleMap.CancelableCallback {
+ val cancelableCallback = object : GoogleMap.CancelableCallback {
override fun onCancel() {
continuation.resumeWithException(CancellationException("Animation cancelled"))
}
@@ -237,7 +252,12 @@ class CameraPositionState(
override fun onFinish() {
continuation.resume(Unit)
}
- })
+ }
+ if (durationMs == MAX_VALUE) {
+ map.animateCamera(update, cancelableCallback)
+ } else {
+ map.animateCamera(update, durationMs, cancelableCallback)
+ }
doOnMapChangedLocked {
check(it == null) {
"New GoogleMap unexpectedly set while an animation was still running"
@@ -256,7 +276,7 @@ class CameraPositionState(
* This method must be called from the map's UI thread.
*/
@UiThread
- fun move(update: CameraUpdate) {
+ public fun move(update: CameraUpdate) {
synchronized(lock) {
val map = map
movementOwner = null
@@ -269,11 +289,11 @@ class CameraPositionState(
}
}
- companion object {
+ public companion object {
/**
* The default saver implementation for [CameraPositionState]
*/
- val Saver = Saver(
+ public val Saver: Saver = Saver(
save = { it.position },
restore = { CameraPositionState(it) }
)
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/Circle.kt b/maps-compose/src/main/java/com/google/maps/android/compose/Circle.kt
index 4958d1745..237af0450 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/Circle.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/Circle.kt
@@ -50,7 +50,8 @@ internal class CircleNode(
* @param onClick a lambda invoked when the circle is clicked
*/
@Composable
-fun Circle(
+@GoogleMapComposable
+public fun Circle(
center: LatLng,
clickable: Boolean = false,
fillColor: Color = Color.Transparent,
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/ComposeInfoWindowAdapter.kt b/maps-compose/src/main/java/com/google/maps/android/compose/ComposeInfoWindowAdapter.kt
index 5fa063348..28571c5c7 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/ComposeInfoWindowAdapter.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/ComposeInfoWindowAdapter.kt
@@ -1,3 +1,17 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.google.maps.android.compose
import android.view.View
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
index 7f0a2bcb2..0111a53ca 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
@@ -15,20 +15,18 @@
package com.google.maps.android.compose
import android.content.ComponentCallbacks
-import android.content.Context
import android.content.res.Configuration
import android.location.Location
import android.os.Bundle
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.LinearLayout
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
@@ -38,7 +36,6 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
-import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.GoogleMapOptions
import com.google.android.gms.maps.LocationSource
import com.google.android.gms.maps.MapView
@@ -66,10 +63,12 @@ import kotlinx.coroutines.awaitCancellation
* @param onMyLocationButtonClick lambda invoked when the my location button is clicked
* @param onMyLocationClick lambda invoked when the my location dot is clicked
* @param onPOIClick lambda invoked when a POI is clicked
+ * @param contentPadding the padding values used to signal that portions of the map around the edges
+ * may be obscured. The map will move the Google logo, etc. to avoid overlapping the padding.
* @param content the content of the map
*/
@Composable
-fun GoogleMap(
+public fun GoogleMap(
modifier: Modifier = Modifier,
cameraPositionState: CameraPositionState = rememberCameraPositionState(),
contentDescription: String? = null,
@@ -85,7 +84,7 @@ fun GoogleMap(
onMyLocationClick: (Location) -> Unit = {},
onPOIClick: (PointOfInterest) -> Unit = {},
contentPadding: PaddingValues = NoPadding,
- content: (@Composable () -> Unit)? = null,
+ content: (@Composable @GoogleMapComposable () -> Unit)? = null,
) {
val context = LocalContext.current
val mapView = remember { MapView(context, googleMapOptionsFactory()) }
@@ -160,8 +159,9 @@ private suspend inline fun MapView.newComposition(
private fun MapLifecycle(mapView: MapView) {
val context = LocalContext.current
val lifecycle = LocalLifecycleOwner.current.lifecycle
+ val previousState = remember { mutableStateOf(Lifecycle.Event.ON_CREATE) }
DisposableEffect(context, lifecycle, mapView) {
- val mapLifecycleObserver = mapView.lifecycleObserver()
+ val mapLifecycleObserver = mapView.lifecycleObserver(previousState)
val callbacks = mapView.componentCallbacks()
lifecycle.addObserver(mapLifecycleObserver)
@@ -174,10 +174,18 @@ private fun MapLifecycle(mapView: MapView) {
}
}
-private fun MapView.lifecycleObserver(): LifecycleEventObserver =
+private fun MapView.lifecycleObserver(previousState: MutableState): LifecycleEventObserver =
LifecycleEventObserver { _, event ->
+ event.targetState
when (event) {
- Lifecycle.Event.ON_CREATE -> this.onCreate(Bundle())
+ Lifecycle.Event.ON_CREATE -> {
+ // Skip calling mapView.onCreate if the lifecycle did not go through onDestroy - in
+ // this case the GoogleMap composable also doesn't leave the composition. So,
+ // recreating the map does not restore state properly which must be avoided.
+ if (previousState.value != Lifecycle.Event.ON_STOP) {
+ this.onCreate(Bundle())
+ }
+ }
Lifecycle.Event.ON_START -> this.onStart()
Lifecycle.Event.ON_RESUME -> this.onResume()
Lifecycle.Event.ON_PAUSE -> this.onPause()
@@ -185,6 +193,7 @@ private fun MapView.lifecycleObserver(): LifecycleEventObserver =
Lifecycle.Event.ON_DESTROY -> this.onDestroy()
else -> throw IllegalStateException()
}
+ previousState.value = event
}
private fun MapView.componentCallbacks(): ComponentCallbacks =
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMapComposable.kt b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMapComposable.kt
new file mode 100644
index 000000000..56f3f04c9
--- /dev/null
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMapComposable.kt
@@ -0,0 +1,21 @@
+package com.google.maps.android.compose
+
+import androidx.compose.runtime.ComposableTargetMarker
+
+/**
+ * An annotation that can be used to mark a composable function as being expected to be use in a
+ * composable function that is also marked or inferred to be marked as a [GoogleMapComposable].
+ *
+ * This will produce build warnings when [GoogleMapComposable] composable functions are used outside
+ * of a [GoogleMapComposable] content lambda, and vice versa.
+ */
+@Retention(AnnotationRetention.BINARY)
+@ComposableTargetMarker(description = "Google Map Composable")
+@Target(
+ AnnotationTarget.FILE,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.TYPE,
+ AnnotationTarget.TYPE_PARAMETER,
+)
+public annotation class GoogleMapComposable
\ No newline at end of file
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/GroundOverlay.kt b/maps-compose/src/main/java/com/google/maps/android/compose/GroundOverlay.kt
index 9e0d1a774..89828218d 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/GroundOverlay.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/GroundOverlay.kt
@@ -40,18 +40,18 @@ internal class GroundOverlayNode(
*
* Use one of the [create] methods to construct an instance of this class.
*/
-class GroundOverlayPosition private constructor(
- val latLngBounds: LatLngBounds? = null,
- val location: LatLng? = null,
- val width: Float? = null,
- val height: Float? = null,
+public class GroundOverlayPosition private constructor(
+ public val latLngBounds: LatLngBounds? = null,
+ public val location: LatLng? = null,
+ public val width: Float? = null,
+ public val height: Float? = null,
) {
- companion object {
- fun create(latLngBounds: LatLngBounds) : GroundOverlayPosition {
+ public companion object {
+ public fun create(latLngBounds: LatLngBounds) : GroundOverlayPosition {
return GroundOverlayPosition(latLngBounds = latLngBounds)
}
- fun create(location: LatLng, width: Float, height: Float? = null) : GroundOverlayPosition {
+ public fun create(location: LatLng, width: Float, height: Float? = null) : GroundOverlayPosition {
return GroundOverlayPosition(
location = location,
width = width,
@@ -76,7 +76,8 @@ class GroundOverlayPosition private constructor(
* @param onClick a lambda invoked when the ground overlay is clicked
*/
@Composable
-fun GroundOverlay(
+@GoogleMapComposable
+public fun GroundOverlay(
position: GroundOverlayPosition,
image: BitmapDescriptor,
anchor: Offset = Offset(0.5f, 0.5f),
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt
index cacc5c490..2940046d4 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt
@@ -15,7 +15,6 @@
package com.google.maps.android.compose
import androidx.compose.runtime.AbstractApplier
-import androidx.compose.ui.platform.ComposeView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.model.Circle
@@ -27,6 +26,7 @@ import com.google.android.gms.maps.model.Polyline
internal interface MapNode {
fun onAttached() {}
fun onRemoved() {}
+ fun onCleared() {}
}
private object MapNodeRoot : MapNode
@@ -44,6 +44,8 @@ internal class MapApplier(
override fun onClear() {
map.clear()
+ decorations.forEach { it.onCleared() }
+ decorations.clear()
}
override fun insertBottomUp(index: Int, instance: MapNode) {
@@ -112,21 +114,24 @@ internal class MapApplier(
}
map.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener {
override fun onMarkerDrag(marker: Marker) {
- val markerDragState =
- decorations.nodeForMarker(marker)?.markerDragState
- markerDragState?.dragState = DragState.DRAG
+ with(decorations.nodeForMarker(marker)) {
+ this?.markerState?.position = marker.position
+ this?.markerState?.dragState = DragState.DRAG
+ }
}
override fun onMarkerDragEnd(marker: Marker) {
- val markerDragState =
- decorations.nodeForMarker(marker)?.markerDragState
- markerDragState?.dragState = DragState.END
+ with(decorations.nodeForMarker(marker)) {
+ this?.markerState?.position = marker.position
+ this?.markerState?.dragState = DragState.END
+ }
}
override fun onMarkerDragStart(marker: Marker) {
- val markerDragState =
- decorations.nodeForMarker(marker)?.markerDragState
- markerDragState?.dragState = DragState.START
+ with(decorations.nodeForMarker(marker)) {
+ this?.markerState?.position = marker.position
+ this?.markerState?.dragState = DragState.START
+ }
}
})
map.setInfoWindowAdapter(
@@ -139,18 +144,18 @@ internal class MapApplier(
}
private fun MutableList.nodeForCircle(circle: Circle): CircleNode? =
- first { it is CircleNode && it.circle == circle } as? CircleNode
+ firstOrNull { it is CircleNode && it.circle == circle } as? CircleNode
private fun MutableList.nodeForMarker(marker: Marker): MarkerNode? =
- first { it is MarkerNode && it.marker == marker } as? MarkerNode
+ firstOrNull { it is MarkerNode && it.marker == marker } as? MarkerNode
private fun MutableList.nodeForPolygon(polygon: Polygon): PolygonNode? =
- first { it is PolygonNode && it.polygon == polygon } as? PolygonNode
+ firstOrNull { it is PolygonNode && it.polygon == polygon } as? PolygonNode
private fun MutableList.nodeForPolyline(polyline: Polyline): PolylineNode? =
- first { it is PolylineNode && it.polyline == polyline } as? PolylineNode
+ firstOrNull { it is PolylineNode && it.polyline == polyline } as? PolylineNode
private fun MutableList.nodeForGroundOverlay(
groundOverlay: GroundOverlay
): GroundOverlayNode? =
- first { it is GroundOverlayNode && it.groundOverlay == groundOverlay } as? GroundOverlayNode
+ firstOrNull { it is GroundOverlayNode && it.groundOverlay == groundOverlay } as? GroundOverlayNode
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapClickListeners.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapClickListeners.kt
index fea55144a..1ffea9478 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/MapClickListeners.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapClickListeners.kt
@@ -1,3 +1,17 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.google.maps.android.compose
import android.location.Location
@@ -12,22 +26,22 @@ import com.google.android.gms.maps.model.PointOfInterest
* Default implementation of [IndoorStateChangeListener] with no-op
* implementations.
*/
-object DefaultIndoorStateChangeListener : IndoorStateChangeListener
+public object DefaultIndoorStateChangeListener : IndoorStateChangeListener
/**
* Interface definition for building indoor level state changes.
*/
-interface IndoorStateChangeListener {
+public interface IndoorStateChangeListener {
/**
* Callback invoked when an indoor building comes to focus.
*/
- fun onIndoorBuildingFocused() {}
+ public fun onIndoorBuildingFocused() {}
/**
* Callback invoked when a level for a building is activated.
* @param building the activated building
*/
- fun onIndoorLevelActivated(building: IndoorBuilding) {}
+ public fun onIndoorLevelActivated(building: IndoorBuilding) {}
}
/**
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapProperties.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapProperties.kt
index 48d93039d..fcdd0604d 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/MapProperties.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapProperties.kt
@@ -1,3 +1,17 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.google.maps.android.compose
import com.google.android.gms.maps.model.LatLngBounds
@@ -13,16 +27,16 @@ internal val DefaultMapProperties = MapProperties()
* compatibility on future changes.
* See: https://jakewharton.com/public-api-challenges-in-kotlin/
*/
-class MapProperties(
- val isBuildingEnabled: Boolean = false,
- val isIndoorEnabled: Boolean = false,
- val isMyLocationEnabled: Boolean = false,
- val isTrafficEnabled: Boolean = false,
- val latLngBoundsForCameraTarget: LatLngBounds? = null,
- val mapStyleOptions: MapStyleOptions? = null,
- val mapType: MapType = MapType.NORMAL,
- val maxZoomPreference: Float = 21.0f,
- val minZoomPreference: Float = 3.0f,
+public class MapProperties(
+ public val isBuildingEnabled: Boolean = false,
+ public val isIndoorEnabled: Boolean = false,
+ public val isMyLocationEnabled: Boolean = false,
+ public val isTrafficEnabled: Boolean = false,
+ public val latLngBoundsForCameraTarget: LatLngBounds? = null,
+ public val mapStyleOptions: MapStyleOptions? = null,
+ public val mapType: MapType = MapType.NORMAL,
+ public val maxZoomPreference: Float = 21.0f,
+ public val minZoomPreference: Float = 3.0f,
) {
override fun toString(): String = "MapProperties(" +
"isBuildingEnabled=$isBuildingEnabled, isIndoorEnabled=$isIndoorEnabled, " +
@@ -54,7 +68,7 @@ class MapProperties(
minZoomPreference
)
- fun copy(
+ public fun copy(
isBuildingEnabled: Boolean = this.isBuildingEnabled,
isIndoorEnabled: Boolean = this.isIndoorEnabled,
isMyLocationEnabled: Boolean = this.isMyLocationEnabled,
@@ -64,7 +78,7 @@ class MapProperties(
mapType: MapType = this.mapType,
maxZoomPreference: Float = this.maxZoomPreference,
minZoomPreference: Float = this.minZoomPreference,
- ) = MapProperties(
+ ): MapProperties = MapProperties(
isBuildingEnabled = isBuildingEnabled,
isIndoorEnabled = isIndoorEnabled,
isMyLocationEnabled = isMyLocationEnabled,
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapType.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapType.kt
index 9382e7f55..0d292faf2 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/MapType.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapType.kt
@@ -20,7 +20,7 @@ import androidx.compose.runtime.Immutable
* Enumerates the different types of map tiles.
*/
@Immutable
-enum class MapType(val value: Int) {
+public enum class MapType(public val value: Int) {
NONE(com.google.android.gms.maps.GoogleMap.MAP_TYPE_NONE),
NORMAL(com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL),
SATELLITE(com.google.android.gms.maps.GoogleMap.MAP_TYPE_SATELLITE),
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapUiSettings.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapUiSettings.kt
index 4299a124c..5f1870eae 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/MapUiSettings.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapUiSettings.kt
@@ -25,17 +25,17 @@ internal val DefaultMapUiSettings = MapUiSettings()
* compatibility on future changes.
* See: https://jakewharton.com/public-api-challenges-in-kotlin/
*/
-class MapUiSettings(
- val compassEnabled: Boolean = true,
- val indoorLevelPickerEnabled: Boolean = true,
- val mapToolbarEnabled: Boolean = true,
- val myLocationButtonEnabled: Boolean = true,
- val rotationGesturesEnabled: Boolean = true,
- val scrollGesturesEnabled: Boolean = true,
- val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true,
- val tiltGesturesEnabled: Boolean = true,
- val zoomControlsEnabled: Boolean = true,
- val zoomGesturesEnabled: Boolean = true,
+public class MapUiSettings(
+ public val compassEnabled: Boolean = true,
+ public val indoorLevelPickerEnabled: Boolean = true,
+ public val mapToolbarEnabled: Boolean = true,
+ public val myLocationButtonEnabled: Boolean = true,
+ public val rotationGesturesEnabled: Boolean = true,
+ public val scrollGesturesEnabled: Boolean = true,
+ public val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true,
+ public val tiltGesturesEnabled: Boolean = true,
+ public val zoomControlsEnabled: Boolean = true,
+ public val zoomGesturesEnabled: Boolean = true,
) {
override fun toString(): String = "MapUiSettings(" +
"compassEnabled=$compassEnabled, indoorLevelPickerEnabled=$indoorLevelPickerEnabled, " +
@@ -70,7 +70,7 @@ class MapUiSettings(
zoomGesturesEnabled
)
- fun copy(
+ public fun copy(
compassEnabled: Boolean = this.compassEnabled,
indoorLevelPickerEnabled: Boolean = this.indoorLevelPickerEnabled,
mapToolbarEnabled: Boolean = this.mapToolbarEnabled,
@@ -81,7 +81,7 @@ class MapUiSettings(
tiltGesturesEnabled: Boolean = this.tiltGesturesEnabled,
zoomControlsEnabled: Boolean = this.zoomControlsEnabled,
zoomGesturesEnabled: Boolean = this.zoomGesturesEnabled
- ) = MapUiSettings(
+ ): MapUiSettings = MapUiSettings(
compassEnabled = compassEnabled,
indoorLevelPickerEnabled = indoorLevelPickerEnabled,
mapToolbarEnabled = mapToolbarEnabled,
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt
index eb5942f53..dca9fb4ba 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt
@@ -1,3 +1,17 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.google.maps.android.compose
import android.annotation.SuppressLint
@@ -80,6 +94,10 @@ internal class MapPropertiesNode(
override fun onRemoved() {
cameraPositionState.setMap(null)
}
+
+ override fun onCleared() {
+ cameraPositionState.setMap(null)
+ }
}
internal val NoPadding = PaddingValues()
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt b/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt
index 722d22c95..e61c88fa5 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt
@@ -21,8 +21,9 @@ import androidx.compose.runtime.Immutable
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import com.google.android.gms.maps.model.BitmapDescriptor
@@ -33,7 +34,7 @@ import com.google.maps.android.ktx.addMarker
internal class MarkerNode(
val compositionContext: CompositionContext,
val marker: Marker,
- var markerDragState: MarkerDragState?,
+ val markerState: MarkerState,
var onMarkerClick: (Marker) -> Boolean,
var onInfoWindowClick: (Marker) -> Unit,
var onInfoWindowClose: (Marker) -> Unit,
@@ -41,39 +42,92 @@ internal class MarkerNode(
var infoWindow: (@Composable (Marker) -> Unit)?,
var infoContent: (@Composable (Marker) -> Unit)?,
) : MapNode {
+ override fun onAttached() {
+ markerState.marker = marker
+ }
override fun onRemoved() {
+ markerState.marker = null
+ marker.remove()
+ }
+
+ override fun onCleared() {
+ markerState.marker = null
marker.remove()
}
}
@Immutable
-enum class DragState {
+public enum class DragState {
START, DRAG, END
}
/**
- * A state object for observing marker drag events.
+ * A state object that can be hoisted to control and observe the marker state.
+ *
+ * @param position the initial marker position
*/
-class MarkerDragState {
+public class MarkerState(
+ position: LatLng = LatLng(0.0, 0.0)
+) {
/**
- * State of the marker drag.
+ * Current position of the marker.
*/
- var dragState: DragState by mutableStateOf(DragState.END)
+ public var position: LatLng by mutableStateOf(position)
+
+ /**
+ * Current [DragState] of the marker.
+ */
+ public var dragState: DragState by mutableStateOf(DragState.END)
internal set
+
+ // The marker associated with this MarkerState.
+ internal var marker: Marker? = null
+ set(value) {
+ if (field == null && value == null) return
+ if (field != null && value != null) {
+ error("MarkerState may only be associated with one Marker at a time.")
+ }
+ field = value
+ }
+
+ /**
+ * Shows the info window for the underlying marker
+ */
+ public fun showInfoWindow() {
+ marker?.showInfoWindow()
+ }
+
+ /**
+ * Hides the info window for the underlying marker
+ */
+ public fun hideInfoWindow() {
+ marker?.hideInfoWindow()
+ }
+
+ public companion object {
+ /**
+ * The default saver implementation for [MarkerState]
+ */
+ public val Saver: Saver = Saver(
+ save = { it.position },
+ restore = { MarkerState(it) }
+ )
+ }
}
-/**
- * Creates and [remember] a [MarkerDragState].
- */
@Composable
-fun rememberMarkerDragState(): MarkerDragState = remember {
- MarkerDragState()
+public fun rememberMarkerState(
+ key: String? = null,
+ position: LatLng = LatLng(0.0, 0.0)
+): MarkerState = rememberSaveable(key = key, saver = MarkerState.Saver) {
+ MarkerState(position)
}
/**
* A composable for a marker on the map.
*
- * @param position the position of the marker
+ * @param state the [MarkerState] to be used to control or observe the marker
+ * state such as its position and info window
* @param alpha the alpha (opacity) of the marker
* @param anchor the anchor for the marker image
* @param draggable sets the draggability for the marker
@@ -86,15 +140,15 @@ fun rememberMarkerDragState(): MarkerDragState = remember {
* @param title the title for the marker
* @param visible the visibility of the marker
* @param zIndex the z-index of the marker
- * @param markerDragState a [MarkerDragState] to be used for observing marker drag events
* @param onClick a lambda invoked when the marker is clicked
* @param onInfoWindowClick a lambda invoked when the marker's info window is clicked
* @param onInfoWindowClose a lambda invoked when the marker's info window is closed
* @param onInfoWindowLongClick a lambda invoked when the marker's info window is long clicked
*/
@Composable
-fun Marker(
- position: LatLng,
+@GoogleMapComposable
+public fun Marker(
+ state: MarkerState = rememberMarkerState(),
alpha: Float = 1.0f,
anchor: Offset = Offset(0.5f, 1.0f),
draggable: Boolean = false,
@@ -107,14 +161,13 @@ fun Marker(
title: String? = null,
visible: Boolean = true,
zIndex: Float = 0.0f,
- markerDragState: MarkerDragState? = null,
onClick: (Marker) -> Boolean = { false },
onInfoWindowClick: (Marker) -> Unit = {},
onInfoWindowClose: (Marker) -> Unit = {},
onInfoWindowLongClick: (Marker) -> Unit = {},
) {
MarkerImpl(
- position = position,
+ state = state,
alpha = alpha,
anchor = anchor,
draggable = draggable,
@@ -127,7 +180,6 @@ fun Marker(
title = title,
visible = visible,
zIndex = zIndex,
- markerDragState = markerDragState,
onClick = onClick,
onInfoWindowClick = onInfoWindowClick,
onInfoWindowClose = onInfoWindowClose,
@@ -140,7 +192,8 @@ fun Marker(
* customized. If this customization is not required, use
* [com.google.maps.android.compose.Marker].
*
- * @param position the position of the marker
+ * @param state the [MarkerState] to be used to control or observe the marker
+ * state such as its position and info window
* @param alpha the alpha (opacity) of the marker
* @param anchor the anchor for the marker image
* @param draggable sets the draggability for the marker
@@ -153,7 +206,6 @@ fun Marker(
* @param title the title for the marker
* @param visible the visibility of the marker
* @param zIndex the z-index of the marker
- * @param markerDragState a [MarkerDragState] to be used for observing marker drag events
* @param onClick a lambda invoked when the marker is clicked
* @param onInfoWindowClick a lambda invoked when the marker's info window is clicked
* @param onInfoWindowClose a lambda invoked when the marker's info window is closed
@@ -162,8 +214,9 @@ fun Marker(
* info window's content
*/
@Composable
-fun MarkerInfoWindow(
- position: LatLng,
+@GoogleMapComposable
+public fun MarkerInfoWindow(
+ state: MarkerState = rememberMarkerState(),
alpha: Float = 1.0f,
anchor: Offset = Offset(0.5f, 1.0f),
draggable: Boolean = false,
@@ -176,7 +229,6 @@ fun MarkerInfoWindow(
title: String? = null,
visible: Boolean = true,
zIndex: Float = 0.0f,
- markerDragState: MarkerDragState? = null,
onClick: (Marker) -> Boolean = { false },
onInfoWindowClick: (Marker) -> Unit = {},
onInfoWindowClose: (Marker) -> Unit = {},
@@ -184,7 +236,7 @@ fun MarkerInfoWindow(
content: (@Composable (Marker) -> Unit)? = null
) {
MarkerImpl(
- position = position,
+ state = state,
alpha = alpha,
anchor = anchor,
draggable = draggable,
@@ -197,7 +249,6 @@ fun MarkerInfoWindow(
title = title,
visible = visible,
zIndex = zIndex,
- markerDragState = markerDragState,
onClick = onClick,
onInfoWindowClick = onInfoWindowClick,
onInfoWindowClose = onInfoWindowClose,
@@ -211,7 +262,8 @@ fun MarkerInfoWindow(
* customized. If this customization is not required, use
* [com.google.maps.android.compose.Marker].
*
- * @param position the position of the marker
+ * @param state the [MarkerState] to be used to control or observe the marker
+ * state such as its position and info window
* @param alpha the alpha (opacity) of the marker
* @param anchor the anchor for the marker image
* @param draggable sets the draggability for the marker
@@ -224,7 +276,6 @@ fun MarkerInfoWindow(
* @param title the title for the marker
* @param visible the visibility of the marker
* @param zIndex the z-index of the marker
- * @param markerDragState a [MarkerDragState] to be used for observing marker drag events
* @param onClick a lambda invoked when the marker is clicked
* @param onInfoWindowClick a lambda invoked when the marker's info window is clicked
* @param onInfoWindowClose a lambda invoked when the marker's info window is closed
@@ -233,8 +284,9 @@ fun MarkerInfoWindow(
* info window's content
*/
@Composable
-fun MarkerInfoWindowContent(
- position: LatLng,
+@GoogleMapComposable
+public fun MarkerInfoWindowContent(
+ state: MarkerState = rememberMarkerState(),
alpha: Float = 1.0f,
anchor: Offset = Offset(0.5f, 1.0f),
draggable: Boolean = false,
@@ -247,7 +299,6 @@ fun MarkerInfoWindowContent(
title: String? = null,
visible: Boolean = true,
zIndex: Float = 0.0f,
- markerDragState: MarkerDragState? = null,
onClick: (Marker) -> Boolean = { false },
onInfoWindowClick: (Marker) -> Unit = {},
onInfoWindowClose: (Marker) -> Unit = {},
@@ -255,7 +306,7 @@ fun MarkerInfoWindowContent(
content: (@Composable (Marker) -> Unit)? = null
) {
MarkerImpl(
- position = position,
+ state = state,
alpha = alpha,
anchor = anchor,
draggable = draggable,
@@ -268,7 +319,6 @@ fun MarkerInfoWindowContent(
title = title,
visible = visible,
zIndex = zIndex,
- markerDragState = markerDragState,
onClick = onClick,
onInfoWindowClick = onInfoWindowClick,
onInfoWindowClose = onInfoWindowClose,
@@ -280,7 +330,8 @@ fun MarkerInfoWindowContent(
/**
* Internal implementation for a marker on a Google map.
*
- * @param position the position of the marker
+ * @param state the [MarkerState] to be used to control or observe the marker
+ * state such as its position and info window
* @param alpha the alpha (opacity) of the marker
* @param anchor the anchor for the marker image
* @param draggable sets the draggability for the marker
@@ -293,7 +344,6 @@ fun MarkerInfoWindowContent(
* @param title the title for the marker
* @param visible the visibility of the marker
* @param zIndex the z-index of the marker
- * @param markerDragState a [MarkerDragState] to be used for observing marker drag events
* @param onClick a lambda invoked when the marker is clicked
* @param onInfoWindowClick a lambda invoked when the marker's info window is clicked
* @param onInfoWindowClose a lambda invoked when the marker's info window is closed
@@ -305,8 +355,9 @@ fun MarkerInfoWindowContent(
* the info window's content. If this value is non-null, [infoWindow] must be null.
*/
@Composable
+@GoogleMapComposable
private fun MarkerImpl(
- position: LatLng,
+ state: MarkerState = rememberMarkerState(),
alpha: Float = 1.0f,
anchor: Offset = Offset(0.5f, 1.0f),
draggable: Boolean = false,
@@ -319,7 +370,6 @@ private fun MarkerImpl(
title: String? = null,
visible: Boolean = true,
zIndex: Float = 0.0f,
- markerDragState: MarkerDragState? = null,
onClick: (Marker) -> Boolean = { false },
onInfoWindowClick: (Marker) -> Unit = {},
onInfoWindowClose: (Marker) -> Unit = {},
@@ -338,7 +388,7 @@ private fun MarkerImpl(
flat(flat)
icon(icon)
infoWindowAnchor(infoWindowAnchor.x, infoWindowAnchor.y)
- position(position)
+ position(state.position)
rotation(rotation)
snippet(snippet)
title(title)
@@ -349,7 +399,7 @@ private fun MarkerImpl(
MarkerNode(
compositionContext = compositionContext,
marker = marker,
- markerDragState = markerDragState,
+ markerState = state,
onMarkerClick = onClick,
onInfoWindowClick = onInfoWindowClick,
onInfoWindowClose = onInfoWindowClose,
@@ -359,7 +409,6 @@ private fun MarkerImpl(
)
},
update = {
- update(markerDragState) { this.markerDragState = it }
update(onClick) { this.onMarkerClick = it }
update(onInfoWindowClick) { this.onInfoWindowClick = it }
update(onInfoWindowClose) { this.onInfoWindowClose = it }
@@ -373,7 +422,7 @@ private fun MarkerImpl(
set(flat) { this.marker.isFlat = it }
set(icon) { this.marker.setIcon(it) }
set(infoWindowAnchor) { this.marker.setInfoWindowAnchor(it.x, it.y) }
- set(position) { this.marker.position = it }
+ set(state.position) { this.marker.position = it }
set(rotation) { this.marker.rotation = it }
set(snippet) {
this.marker.snippet = it
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/Polygon.kt b/maps-compose/src/main/java/com/google/maps/android/compose/Polygon.kt
index aa0d53aba..368cd5e01 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/Polygon.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/Polygon.kt
@@ -52,7 +52,8 @@ internal class PolygonNode(
* @param onClick a lambda invoked when the polygon is clicked
*/
@Composable
-fun Polygon(
+@GoogleMapComposable
+public fun Polygon(
points: List,
clickable: Boolean = false,
fillColor: Color = Color.Black,
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/Polyline.kt b/maps-compose/src/main/java/com/google/maps/android/compose/Polyline.kt
index 7eca6b27a..242f0862e 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/Polyline.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/Polyline.kt
@@ -54,7 +54,8 @@ internal class PolylineNode(
* @param onClick a lambda invoked when the polyline is clicked
*/
@Composable
-fun Polyline(
+@GoogleMapComposable
+public fun Polyline(
points: List,
clickable: Boolean = false,
color: Color = Color.Black,
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/TileOverlay.kt b/maps-compose/src/main/java/com/google/maps/android/compose/TileOverlay.kt
index 0f1e4f2c5..462018923 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/TileOverlay.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/TileOverlay.kt
@@ -41,7 +41,8 @@ private class TileOverlayNode(
* @param onClick a lambda invoked when the tile overlay is clicked
*/
@Composable
-fun TileOverlay(
+@GoogleMapComposable
+public fun TileOverlay(
tileProvider: TileProvider,
fadeIn: Boolean = true,
transparency: Float = 0f,