From 06a8a4609b50cdc943efb5bc09bfe180631e5b92 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 22 Jun 2022 17:43:56 -0400 Subject: [PATCH 1/5] feat: Add the reason for map camera movement --- .../android/compose/LocationTrackingActivity.kt | 17 ++++++++++++++++- .../maps/android/compose/CameraPositionState.kt | 14 ++++++++------ .../google/maps/android/compose/MapUpdater.kt | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt b/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt index 923562c4..70f0694b 100644 --- a/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt +++ b/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt @@ -67,7 +67,7 @@ class LocationTrackingActivity : AppCompatActivity() { setContent { var isMapLoaded by remember { mutableStateOf(false) } - // To control the map camera + // To control and observe the map camera val cameraPositionState = rememberCameraPositionState { position = defaultCameraPosition } @@ -88,6 +88,21 @@ class LocationTrackingActivity : AppCompatActivity() { cameraPositionState.animate(CameraUpdateFactory.newCameraPosition(cameraPosition), 1_000) } + // Detect when the map starts moving and print the reason + LaunchedEffect(cameraPositionState.isMoving) { + if (cameraPositionState.isMoving) { + val reason = when(cameraPositionState.cameraMoveStartedReason) { + // See https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnCameraMoveStartedListener#constants + -1 -> "DEFAULT_NO_MOVEMENT_YET" + 1 -> "REASON_GESTURE" + 2 -> "REASON_API_ANIMATION" + 3 -> "REASON_DEVELOPER_ANIMATION" + else -> "UNKNOWN" + } + Log.d(TAG, "Map camera started moving due to $reason") + } + } + Box(Modifier.fillMaxSize()) { GoogleMap( modifier = Modifier.matchParentSize(), 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 57bdae86..0ac9f435 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 @@ -27,12 +27,7 @@ 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 -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.* import java.lang.Integer.MAX_VALUE import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -67,6 +62,13 @@ public class CameraPositionState( public var isMoving: Boolean by mutableStateOf(false) internal set + /** + * The reason for the start of the most recent camera moment, or -1 if the camera hasn't moved + * yet. See constant definitions at https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnCameraMoveStartedListener#constants. + */ + public var cameraMoveStartedReason: Int by mutableStateOf(-1) + internal set + /** * Returns the current [Projection] to be used for converting between screen * coordinates and lat/lng. 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 dca9fb4b..c59432c4 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 @@ -69,6 +69,7 @@ internal class MapPropertiesNode( cameraPositionState.isMoving = false } map.setOnCameraMoveStartedListener { + cameraPositionState.cameraMoveStartedReason = it cameraPositionState.isMoving = true } map.setOnCameraMoveListener { From 1aa711a7eee39c2079d54506ac1ca70cb0d3f2b9 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 22 Jun 2022 17:47:52 -0400 Subject: [PATCH 2/5] chore: Add unit test --- .../java/com/google/maps/android/compose/GoogleMapViewTests.kt | 3 +++ 1 file changed, 3 insertions(+) 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 index 46b72c21..35d4324e 100644 --- a/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt +++ b/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt @@ -22,6 +22,7 @@ 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.GoogleMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import org.junit.Assert.* @@ -75,11 +76,13 @@ class GoogleMapViewTests { @Test fun testCameraReportsMoving() { initMap() + assertEquals(-1, cameraPositionState.cameraMoveStartedReason) zoom(shouldAnimate = true, zoomIn = true) { composeTestRule.waitUntil(1000) { cameraPositionState.isMoving } assertTrue(cameraPositionState.isMoving) + assertEquals(REASON_DEVELOPER_ANIMATION, cameraPositionState.cameraMoveStartedReason) } } From 819179ec6d98e27b54740a8b6c0eefa5ae4dcee3 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 23 Jun 2022 11:01:09 -0400 Subject: [PATCH 3/5] refactor: Convert reason from Int to Enum --- .../android/compose/GoogleMapViewTests.kt | 5 +- .../compose/LocationTrackingActivity.kt | 10 +--- .../compose/CameraMoveStartedReason.kt | 52 +++++++++++++++++++ .../android/compose/CameraPositionState.kt | 9 ++-- .../google/maps/android/compose/MapUpdater.kt | 2 +- 5 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 maps-compose/src/main/java/com/google/maps/android/compose/CameraMoveStartedReason.kt 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 index 35d4324e..fa5b8375 100644 --- a/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt +++ b/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt @@ -22,7 +22,6 @@ 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.GoogleMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import org.junit.Assert.* @@ -76,13 +75,13 @@ class GoogleMapViewTests { @Test fun testCameraReportsMoving() { initMap() - assertEquals(-1, cameraPositionState.cameraMoveStartedReason) + assertEquals(CameraMoveStartedReason.NO_MOVEMENT_YET, cameraPositionState.cameraMoveStartedReason) zoom(shouldAnimate = true, zoomIn = true) { composeTestRule.waitUntil(1000) { cameraPositionState.isMoving } assertTrue(cameraPositionState.isMoving) - assertEquals(REASON_DEVELOPER_ANIMATION, cameraPositionState.cameraMoveStartedReason) + assertEquals(CameraMoveStartedReason.DEVELOPER_ANIMATION, cameraPositionState.cameraMoveStartedReason) } } diff --git a/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt b/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt index 70f0694b..2967f563 100644 --- a/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt +++ b/app/src/main/java/com/google/maps/android/compose/LocationTrackingActivity.kt @@ -91,15 +91,7 @@ class LocationTrackingActivity : AppCompatActivity() { // Detect when the map starts moving and print the reason LaunchedEffect(cameraPositionState.isMoving) { if (cameraPositionState.isMoving) { - val reason = when(cameraPositionState.cameraMoveStartedReason) { - // See https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnCameraMoveStartedListener#constants - -1 -> "DEFAULT_NO_MOVEMENT_YET" - 1 -> "REASON_GESTURE" - 2 -> "REASON_API_ANIMATION" - 3 -> "REASON_DEVELOPER_ANIMATION" - else -> "UNKNOWN" - } - Log.d(TAG, "Map camera started moving due to $reason") + Log.d(TAG, "Map camera started moving due to ${cameraPositionState.cameraMoveStartedReason.name}") } } diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/CameraMoveStartedReason.kt b/maps-compose/src/main/java/com/google/maps/android/compose/CameraMoveStartedReason.kt new file mode 100644 index 00000000..4edf1b8d --- /dev/null +++ b/maps-compose/src/main/java/com/google/maps/android/compose/CameraMoveStartedReason.kt @@ -0,0 +1,52 @@ +// 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.runtime.Immutable +import com.google.maps.android.compose.CameraMoveStartedReason.Companion.fromInt +import com.google.maps.android.compose.CameraMoveStartedReason.NO_MOVEMENT_YET +import com.google.maps.android.compose.CameraMoveStartedReason.UNKNOWN + +/** + * Enumerates the different reasons why the map camera started to move. + * + * Based on enum values from https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnCameraMoveStartedListener#constants. + * + * [NO_MOVEMENT_YET] is used as the initial state before any map movement has been observed. + * + * [UNKNOWN] is used to represent when an unsupported integer value is provided to [fromInt] - this + * may be a new constant value from the Maps SDK that isn't supported by maps-compose yet, in which + * case this library should be updated to include a new enum value for that constant. + */ +@Immutable +public enum class CameraMoveStartedReason(public val value: Int) { + UNKNOWN(-2), + NO_MOVEMENT_YET(-1), + GESTURE(com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE), + API_ANIMATION(com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener.REASON_API_ANIMATION), + DEVELOPER_ANIMATION(com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION); + + public companion object { + /** + * Converts from the Maps SDK [com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener] + * constants to [CameraMoveStartedReason], or returns [UNKNOWN] if there is no such + * [CameraMoveStartedReason] for the given [value]. + * + * See https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnCameraMoveStartedListener#constants. + */ + public fun fromInt(value: Int): CameraMoveStartedReason { + return values().firstOrNull { it.value == value } ?: return UNKNOWN + } + } +} \ No newline at end of file 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 0ac9f435..5c467258 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 @@ -63,10 +63,13 @@ public class CameraPositionState( internal set /** - * The reason for the start of the most recent camera moment, or -1 if the camera hasn't moved - * yet. See constant definitions at https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnCameraMoveStartedListener#constants. + * The reason for the start of the most recent camera moment, or + * [CameraMoveStartedReason.NO_MOVEMENT_YET] if the camera hasn't moved yet or + * [CameraMoveStartedReason.UNKNOWN] if an unknown constant is received from the Maps SDK. */ - public var cameraMoveStartedReason: Int by mutableStateOf(-1) + public var cameraMoveStartedReason: CameraMoveStartedReason by mutableStateOf( + CameraMoveStartedReason.NO_MOVEMENT_YET + ) internal set /** 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 c59432c4..56ccc7f2 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 @@ -69,7 +69,7 @@ internal class MapPropertiesNode( cameraPositionState.isMoving = false } map.setOnCameraMoveStartedListener { - cameraPositionState.cameraMoveStartedReason = it + cameraPositionState.cameraMoveStartedReason = CameraMoveStartedReason.fromInt(it) cameraPositionState.isMoving = true } map.setOnCameraMoveListener { From 7b3fef628898e536a9229e6aa866a2cb9c2c5356 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 23 Jun 2022 13:59:25 -0400 Subject: [PATCH 4/5] chore: Don't use wildcard imports --- .../com/google/maps/android/compose/CameraPositionState.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 5c467258..b766ae81 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 @@ -27,7 +27,11 @@ 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.* +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.suspendCancellableCoroutine import java.lang.Integer.MAX_VALUE import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException From 12215c4d93d919ef1978081ba1bca03752c32efb Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 23 Jun 2022 14:01:50 -0400 Subject: [PATCH 5/5] chore: Restore import that was previously removed --- .../java/com/google/maps/android/compose/CameraPositionState.kt | 1 + 1 file changed, 1 insertion(+) 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 b766ae81..0e6c893b 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 @@ -30,6 +30,7 @@ import com.google.android.gms.maps.model.LatLng import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.suspendCancellableCoroutine import java.lang.Integer.MAX_VALUE