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 40d30ffe..46b72c21 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
@@ -14,22 +14,17 @@
package com.google.maps.android.compose
-import androidx.activity.ComponentActivity
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.createAndroidComposeRule
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.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
+import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -42,8 +37,6 @@ class GoogleMapViewTests {
private val startingZoom = 10f
private val startingPosition = LatLng(1.23, 4.56)
- private val assertRoundingError = 0.01
-
private lateinit var cameraPositionState: CameraPositionState
private fun initMap(content: @Composable () -> Unit = {}) {
@@ -97,7 +90,7 @@ class GoogleMapViewTests {
composeTestRule.waitUntil(1000) {
cameraPositionState.isMoving
}
- composeTestRule.waitUntil(3000) {
+ composeTestRule.waitUntil(5000) {
!cameraPositionState.isMoving
}
assertFalse(cameraPositionState.isMoving)
@@ -238,9 +231,4 @@ class GoogleMapViewTests {
assertionBlock()
}
-
- private fun LatLng.assertEquals(other: LatLng) {
- assertEquals(latitude, other.latitude, assertRoundingError)
- assertEquals(longitude, other.longitude, assertRoundingError)
- }
}
\ 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 00000000..e944944c
--- /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 00000000..9ff769a6
--- /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 fa396824..0120d514 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -33,17 +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 88%
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 75fd7a54..d8871816 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,28 +24,9 @@ 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.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-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
@@ -57,14 +38,14 @@ 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"
-val singapore = LatLng(1.35, 103.87)
-val singapore2 = LatLng(1.40, 103.77)
-val singapore3 = LatLng(1.45, 103.77)
-val defaultCameraPosition = CameraPosition.fromLatLngZoom(singapore, 11f)
+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 MapSampleActivity : ComponentActivity() {
+class BasicMapActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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 00000000..02c49847
--- /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 00000000..8aad53c2
--- /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 24606631..c8fb093c 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