diff --git a/maps-app/src/androidTest/java/com/google/maps/android/compose/MapsInLazyColumnTest.kt b/maps-app/src/androidTest/java/com/google/maps/android/compose/MapsInLazyColumnTest.kt new file mode 100644 index 00000000..2e501e0c --- /dev/null +++ b/maps-app/src/androidTest/java/com/google/maps/android/compose/MapsInLazyColumnTest.kt @@ -0,0 +1,113 @@ +package com.google.maps.android.compose + +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeUp +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class MapsInLazyColumnTests { + @get:Rule + val composeTestRule = createComposeRule() + + private val mapItems = listOf( + MapListItem(id = "1", location = LatLng(1.23, 4.56), zoom = 10f, title = "Item 1"), + MapListItem(id = "2", location = LatLng(7.89, 0.12), zoom = 12f, title = "Item 2"), + MapListItem(id = "3", location = LatLng(3.45, 6.78), zoom = 11f, title = "Item 3"), + MapListItem(id = "4", location = LatLng(9.01, 2.34), zoom = 13f, title = "Item 4"), + MapListItem(id = "5", location = LatLng(5.67, 8.90), zoom = 9f, title = "Item 5"), + MapListItem(id = "6", location = LatLng(4.32, 7.65), zoom = 14f, title = "Item 6"), + MapListItem(id = "7", location = LatLng(8.76, 1.23), zoom = 10f, title = "Item 7"), + MapListItem(id = "8", location = LatLng(2.98, 6.54), zoom = 12f, title = "Item 8"), + MapListItem(id = "9", location = LatLng(7.65, 3.21), zoom = 11f, title = "Item 9"), + MapListItem(id = "10", location = LatLng(0.12, 9.87), zoom = 13f, title = "Item 10"), + ) + + + private lateinit var cameraPositionStates: Map + + private fun initMaps() { + check(hasValidApiKey) { "Maps API key not specified" } + + composeTestRule.setContent { + val lazyListState = rememberLazyListState() + val visibleMapCount = remember { mutableStateOf(0) } + + val visibleItems by remember { + derivedStateOf { + lazyListState.layoutInfo.visibleItemsInfo.size + } + } + + LaunchedEffect(visibleItems) { + visibleMapCount.value = visibleItems + } + + val countDownLatch = CountDownLatch(visibleMapCount.value) + + MapsInLazyColumn( + mapItems, + lazyListState = lazyListState, + onMapLoaded = { + countDownLatch.countDown() + } + ) + + LaunchedEffect(Unit) { + val mapsLoaded = countDownLatch.await(30, TimeUnit.SECONDS) + assertTrue("Visible maps loaded", mapsLoaded) + } + } + } + + @Before + fun setUp() { + cameraPositionStates = mapItems.associate { item -> + item.id to CameraPositionState( + position = CameraPosition.fromLatLngZoom(item.location, item.zoom) + ) + } + } + + @Test + fun testStartingCameraPositions() { + initMaps() + mapItems.forEach { item -> + item.location.assertEquals(cameraPositionStates[item.id]?.position?.target!!) + } + } + + @Test + fun testLazyColumnScrolls_MapPositionsRemain() { + initMaps() + composeTestRule.onRoot().performTouchInput { swipeUp() } + composeTestRule.waitForIdle() + + mapItems.forEach { item -> + item.location.assertEquals(cameraPositionStates[item.id]?.position?.target!!) + } + } + + @Test + fun testScrollToBottom() { + initMaps() + composeTestRule.onRoot().performTouchInput { swipeUp(durationMillis = 1000) } + composeTestRule.waitForIdle() + //We do not need to check anything on the test, just to make sure the scroll down doesnt crash + } +} diff --git a/maps-app/src/main/java/com/google/maps/android/compose/MapsInLazyColumnActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/MapsInLazyColumnActivity.kt index 36459b15..6e8c98a8 100644 --- a/maps-app/src/main/java/com/google/maps/android/compose/MapsInLazyColumnActivity.kt +++ b/maps-app/src/main/java/com/google/maps/android/compose/MapsInLazyColumnActivity.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState @@ -40,7 +41,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -51,12 +52,16 @@ import com.google.android.gms.maps.model.LatLng private data class CountryLocation(val name: String, val latLng: LatLng, val zoom: Float) -private typealias MapItemId = String +typealias MapItemId = String // From https://developers.google.com/public-data/docs/canonical/countries_csv private val countries = listOf( CountryLocation("Hong Kong", LatLng(22.396428, 114.109497), 5f), - CountryLocation("Madison Square Garden (has indoor mode)", LatLng(40.7504656, -73.9937246), 19.33f), + CountryLocation( + "Madison Square Garden (has indoor mode)", + LatLng(40.7504656, -73.9937246), + 19.33f + ), CountryLocation("Bolivia", LatLng(-16.290154, -63.588653), 5f), CountryLocation("Ecuador", LatLng(-1.831239, -78.183406), 5f), CountryLocation("Sweden", LatLng(60.128161, 18.643501), 5f), @@ -74,7 +79,7 @@ private val countries = listOf( CountryLocation("Burundi", LatLng(-3.373056, 29.918886), 5f) ) -private data class MapListItem( +data class MapListItem( val title: String, val location: LatLng, val zoom: Float, @@ -98,7 +103,8 @@ class MapsInLazyColumnActivity : ComponentActivity() { } Column( - Modifier.fillMaxSize() + Modifier + .fillMaxSize() .systemBarsPadding(), ) { Row( @@ -125,7 +131,7 @@ class MapsInLazyColumnActivity : ComponentActivity() { } if (showLazyColumn) { Box(Modifier.border(1.dp, Color.LightGray.copy(0.5f))) { - MapsInLazyColumn(visibleItems) + MapsInLazyColumn(visibleItems, onMapLoaded = { }) } } } @@ -134,8 +140,15 @@ class MapsInLazyColumnActivity : ComponentActivity() { } @Composable -private fun MapsInLazyColumn(mapItems: List) { - val lazyListState = rememberLazyListState() +fun MapsInLazyColumn( + mapItems: List, + lazyListState: LazyListState = rememberLazyListState(), + onMapLoaded: () -> Unit +) { + + var isMapLoaded by remember { mutableStateOf(false) } + + val lazyListState = lazyListState val cameraPositionStates = mapItems.associate { item -> item.id to rememberCameraPositionState( @@ -168,7 +181,10 @@ private fun MapsInLazyColumn(mapItems: List) { .height(300.dp), contentAlignment = Alignment.Center ) { - MapCard(item, cameraPositionState) + MapCard(item, cameraPositionState, onMapLoaded = { + isMapLoaded = true + onMapLoaded() + }) } } } @@ -177,7 +193,11 @@ private fun MapsInLazyColumn(mapItems: List) { @OptIn(MapsComposeExperimentalApi::class) @Composable -private fun MapCard(item: MapListItem, cameraPositionState: CameraPositionState) { +private fun MapCard( + item: MapListItem, + cameraPositionState: CameraPositionState, + onMapLoaded: () -> Unit, +) { Card( Modifier.padding(16.dp), elevation = 4.dp @@ -192,11 +212,13 @@ private fun MapCard(item: MapListItem, cameraPositionState: CameraPositionState) var map: GoogleMap? by remember { mutableStateOf(null) } fun updateIndoorLevel() { - activatedIndoorLevel = map!!.focusedBuilding?.run { levels.getOrNull(activeLevelIndex)?.name } + activatedIndoorLevel = + map!!.focusedBuilding?.run { levels.getOrNull(activeLevelIndex)?.name } } Box { GoogleMap( + modifier = Modifier.testTag("Map"), onMapClick = { onMapClickCount++ }, @@ -207,7 +229,10 @@ private fun MapCard(item: MapListItem, cameraPositionState: CameraPositionState) ) }, cameraPositionState = cameraPositionState, - onMapLoaded = { mapLoaded = true }, + onMapLoaded = { + onMapLoaded.invoke() + mapLoaded = true + }, indoorStateChangeListener = object : IndoorStateChangeListener { override fun onIndoorBuildingFocused() { super.onIndoorBuildingFocused() @@ -242,7 +267,7 @@ private fun MapCard(item: MapListItem, cameraPositionState: CameraPositionState) @Composable fun TextWithBackground(text: String, fontWeight: FontWeight = FontWeight.Medium) { Text( - modifier = Modifier.background(Color.White.copy(0.7f)), + modifier = Modifier.background(Color.White.copy(0.7f)).testTag(text), text = text, fontWeight = fontWeight, fontSize = 10.sp