From e01bfbf608bc697dbae88eeeb997f677341298ed Mon Sep 17 00:00:00 2001 From: Ellen Poe Date: Mon, 29 Sep 2025 15:13:21 -0700 Subject: [PATCH 1/2] feat: search keyboard action --- .../earth/maps/cardinal/data/BoundingBox.kt | 11 +- .../java/earth/maps/cardinal/data/Place.kt | 8 +- .../maps/cardinal/transit/TransitousModels.kt | 12 +- .../cardinal/transit/TransitousService.kt | 4 +- .../earth/maps/cardinal/ui/core/AppContent.kt | 47 +++++- .../earth/maps/cardinal/ui/core/MapView.kt | 5 +- .../maps/cardinal/ui/core/MapViewModel.kt | 42 +++++- .../TransitItineraryDetailScreen.kt | 8 +- .../earth/maps/cardinal/ui/home/HomeScreen.kt | 136 +++--------------- .../maps/cardinal/ui/home/HomeViewModel.kt | 4 +- .../cardinal/ui/home/OfflineAreasScreen.kt | 1 - 11 files changed, 134 insertions(+), 144 deletions(-) diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/data/BoundingBox.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/data/BoundingBox.kt index 33a0674..7288300 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/data/BoundingBox.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/data/BoundingBox.kt @@ -18,4 +18,13 @@ package earth.maps.cardinal.data data class BoundingBox( val north: Double, val south: Double, val east: Double, val west: Double -) \ No newline at end of file +) { + fun toGeoJsonBoundingBox(): io.github.dellisd.spatialk.geojson.BoundingBox { + return io.github.dellisd.spatialk.geojson.BoundingBox( + north = north, + south = south, + west = west, + east = east + ) + } +} \ No newline at end of file diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/data/Place.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/data/Place.kt index 73b61f9..9bc0c7d 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/data/Place.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/data/Place.kt @@ -16,6 +16,8 @@ package earth.maps.cardinal.data +import io.github.dellisd.spatialk.geojson.Position + data class Place( val id: String? = null, val name: String, @@ -26,5 +28,9 @@ data class Place( val isMyLocation: Boolean = false, val isTransitStop: Boolean = false, val transitStopId: String? = null, -) +) { + fun toPosition(): Position { + return Position(latitude = latLng.latitude, longitude = latLng.longitude) + } +} diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousModels.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousModels.kt index 33305c6..87535bc 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousModels.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousModels.kt @@ -239,7 +239,7 @@ data class FareTransfer( ) @Serializable -data class Place( +data class TransitPlace( val name: String, val stopId: String? = null, val lat: Double, @@ -266,8 +266,8 @@ data class Place( @Serializable data class Leg( val mode: Mode, - @SerialName("from") val fromPlace: Place, - @SerialName("to") val toPlace: Place, + @SerialName("from") val fromTransitPlace: TransitPlace, + @SerialName("to") val toTransitPlace: TransitPlace, val duration: Int, val startTime: String, val endTime: String, @@ -288,7 +288,7 @@ data class Leg( val routeShortName: String? = null, val cancelled: Boolean? = null, val source: String? = null, - val intermediateStops: List? = null, + val intermediateStops: List? = null, val legGeometry: EncodedPolyline? = null, val steps: List? = null, val rental: Rental? = null, @@ -309,8 +309,8 @@ data class Itinerary( @Serializable data class PlanResponse( - val from: Place, - val to: Place, + val from: TransitPlace, + val to: TransitPlace, val direct: List, val itineraries: List, val previousPageCursor: String, diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousService.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousService.kt index 104bdd3..e5ee015 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousService.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/transit/TransitousService.kt @@ -211,8 +211,8 @@ class TransitousService @Inject constructor(private val appPreferenceRepository: Log.e(TAG, "Error fetching plan", e) emit( PlanResponse( - Place("", null, 0.0, 0.0, 0.0), - Place("", null, 0.0, 0.0, 0.0), + TransitPlace("", null, 0.0, 0.0, 0.0), + TransitPlace("", null, 0.0, 0.0, 0.0), emptyList(), emptyList(), "", diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt index 35ba655..34799e1 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt @@ -101,6 +101,7 @@ import earth.maps.cardinal.bottomsheet.rememberBottomSheetScaffoldState import earth.maps.cardinal.bottomsheet.rememberBottomSheetState import earth.maps.cardinal.data.AppPreferenceRepository import earth.maps.cardinal.data.Place +import earth.maps.cardinal.data.PolylineUtils import earth.maps.cardinal.data.RoutingMode import earth.maps.cardinal.data.room.OfflineArea import earth.maps.cardinal.routing.RouteRepository @@ -130,6 +131,7 @@ import io.github.dellisd.spatialk.geojson.BoundingBox import io.github.dellisd.spatialk.geojson.Position import kotlinx.coroutines.launch import org.maplibre.compose.camera.CameraPosition +import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.rememberCameraState import uniffi.ferrostar.Route @@ -149,7 +151,7 @@ fun AppContent( routeRepository: RouteRepository, appPreferenceRepository: AppPreferenceRepository, ) { - val mapPins = remember { mutableStateListOf() } + val mapPins = remember { mutableStateListOf() } val cameraState = rememberCameraState() var fabHeight by remember { mutableStateOf(0.dp) } val coroutineScope = rememberCoroutineScope() @@ -261,6 +263,7 @@ fun AppContent( showToolbar = true HomeScreenComposable( viewModel = homeViewModel, + cameraState = cameraState, mapPins = mapPins, peekHeight = peekHeight, navController = navController, @@ -272,6 +275,9 @@ fun AppContent( }, topOfBackStack = topOfBackStack, backStackEntry = backStackEntry, + screenWidthDp = screenWidthDp, + screenHeightDp = screenHeightDp, + appPreferenceRepository = appPreferenceRepository ) } @@ -356,10 +362,9 @@ fun AppContent( viewModel.setPlace(place) LaunchedEffect(place) { mapPins.clear() - val position = Position(place.latLng.longitude, place.latLng.latitude) // Clear any existing pins and add the new one to ensure only one pin is shown at a time mapPins.clear() - mapPins.add(position) + mapPins.add(place) val previousBackStackEntry = navController.previousBackStackEntry val shouldFlyToPoi = @@ -371,7 +376,10 @@ fun AppContent( coroutineScope.launch { cameraState.animateTo( CameraPosition( - target = position, zoom = 15.0, padding = PaddingValues( + target = Position( + latitude = place.latLng.latitude, + longitude = place.latLng.longitude + ), zoom = 15.0, padding = PaddingValues( start = screenWidthDp / 8, top = screenHeightDp / 8, end = screenWidthDp / 8, @@ -919,13 +927,17 @@ private fun CardinalToolbar( @Composable private fun HomeScreenComposable( viewModel: HomeViewModel, - mapPins: SnapshotStateList, + cameraState: CameraState, + mapPins: SnapshotStateList, peekHeight: Dp, navController: NavHostController, onPeekHeightChange: (Dp) -> Unit, onFabHeightChange: (Dp) -> Unit, topOfBackStack: NavBackStackEntry?, backStackEntry: NavBackStackEntry, + screenWidthDp: Dp, + screenHeightDp: Dp, + appPreferenceRepository: AppPreferenceRepository, ) { val coroutineScope = rememberCoroutineScope() val searchExpanded: Boolean? by viewModel.searchExpanded.collectAsState(null) @@ -994,6 +1006,31 @@ private fun HomeScreenComposable( viewModel.expandSearch() } }, + onResultPinsChange = { + mapPins.clear() + mapPins.addAll(it) + }, + onSearchEvent = { + viewModel.collapseSearch() + coroutineScope.launch { + val boundingBox = + PolylineUtils.calculateBoundingBox(mapPins.map { it.toPosition() }) + ?: return@launch + cameraState.animateTo( + boundingBox = boundingBox.toGeoJsonBoundingBox(), + padding = PaddingValues( + start = screenWidthDp / 8, + top = screenHeightDp / 8, + end = screenWidthDp / 8, + bottom = min( + 3f * screenHeightDp / 4, + peekHeight + screenHeightDp / 8 + ) + ), + duration = appPreferenceRepository.animationSpeedDurationValue + ) + } + } ) }) } diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapView.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapView.kt index 13b210c..c5d43d7 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapView.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapView.kt @@ -60,7 +60,6 @@ import earth.maps.cardinal.ui.map.LocationPuck import io.github.dellisd.spatialk.geojson.Feature import io.github.dellisd.spatialk.geojson.FeatureCollection import io.github.dellisd.spatialk.geojson.LineString -import io.github.dellisd.spatialk.geojson.Point import io.github.dellisd.spatialk.geojson.Polygon import io.github.dellisd.spatialk.geojson.Position import kotlinx.coroutines.launch @@ -98,7 +97,7 @@ fun MapView( onDropPin: (LatLng) -> Unit, onRequestLocationPermission: () -> Unit, hasLocationPermission: Boolean, - mapPins: List, + mapPins: List, fabInsets: PaddingValues, cameraState: CameraState, appPreferences: AppPreferenceRepository, @@ -108,7 +107,7 @@ fun MapView( ) { val context = LocalContext.current val styleState = rememberStyleState() - val pinFeatures = mapPins.map { Feature(geometry = Point(it)) } + val pinFeatures = mapPins.map { mapViewModel.createFeatureFromPlace(it) } rememberCoroutineScope() val styleVariant = if (isSystemInDarkTheme()) "dark" else "light" diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapViewModel.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapViewModel.kt index 7ff7c7a..63964cb 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapViewModel.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/MapViewModel.kt @@ -96,17 +96,46 @@ class MapViewModel @Inject constructor( var screenWidth: Dp = 0.dp val savedPlacesFlow: Flow = placeDao.getAllPlacesAsFlow().map { placeList -> - FeatureCollection(placeList.map { createFeatureFromPlace(it) }) + FeatureCollection(placeList.map { createFeatureFromSavedPlace(it) }) } init { locationRepository.startContinuousLocationUpdates(context) } + /** + * Creates a Feature from a Place with proper JSON escaping. + */ + fun createFeatureFromPlace(place: Place): Feature { + val properties = mutableMapOf( + "name" to escapeJsonString(place.name), + "description" to escapeJsonString(place.description) + ) + + place.address?.houseNumber?.let { properties["addr:housenumber"] = escapeJsonString(it) } + place.address?.road?.let { properties["addr:street"] = escapeJsonString(it) } + place.address?.city?.let { properties["addr:city"] = escapeJsonString(it) } + place.address?.postcode?.let { properties["addr:postcode"] = escapeJsonString(it) } + place.address?.state?.let { properties["addr:state"] = escapeJsonString(it) } + place.address?.country?.let { properties["addr:country"] = escapeJsonString(it) } + place.address?.countryCode?.let { properties["country_code"] = escapeJsonString(it) } + place.transitStopId?.let { properties["transit_stop_id"] = escapeJsonString(it) } + + return Feature( + geometry = Point( + Position( + latitude = place.latLng.latitude, + longitude = place.latLng.longitude + ) + ), + properties = properties + ) + } + /** * Creates a Feature from a SavedPlace with proper JSON escaping. */ - private fun createFeatureFromPlace(place: SavedPlace): Feature { + private fun createFeatureFromSavedPlace(place: SavedPlace): Feature { val name = place.customName ?: place.name val description = place.customDescription ?: place.type @@ -179,7 +208,14 @@ class MapViewModel @Inject constructor( ) { val features = cameraState.projection?.queryRenderedFeatures( dpOffset, - layerIds = setOf("user_favorites", "poi_z14", "poi_z15", "poi_z16", "poi_transit") + layerIds = setOf( + "map_pins", + "user_favorites", + "poi_z14", + "poi_z15", + "poi_z16", + "poi_transit" + ) ) Log.d(TAG, "${features?.count()} features available at tap location") val filteredFeatures = features?.filter { diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/directions/TransitItineraryDetailScreen.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/directions/TransitItineraryDetailScreen.kt index d671c52..bc62acf 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/directions/TransitItineraryDetailScreen.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/directions/TransitItineraryDetailScreen.kt @@ -285,11 +285,11 @@ private fun DetailedLegCard( color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( - text = leg.fromPlace.name, + text = leg.fromTransitPlace.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium ) - leg.fromPlace.departure?.let { departure -> + leg.fromTransitPlace.departure?.let { departure -> Text( text = "Depart: ${departure.formatTime(use24HourFormat)}", style = MaterialTheme.typography.bodySmall, @@ -317,11 +317,11 @@ private fun DetailedLegCard( color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( - text = leg.toPlace.name, + text = leg.toTransitPlace.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium ) - leg.toPlace.arrival?.let { arrival -> + leg.toTransitPlace.arrival?.let { arrival -> Text( text = "Arrive: ${arrival.formatTime(use24HourFormat)}", style = MaterialTheme.typography.bodySmall, diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeScreen.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeScreen.kt index 0a5af4e..fb1e850 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeScreen.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeScreen.kt @@ -17,10 +17,7 @@ package earth.maps.cardinal.ui.home import android.annotation.SuppressLint -import android.util.Log import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row @@ -31,11 +28,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.DividerDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalIconButton @@ -61,7 +56,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -84,6 +79,8 @@ fun HomeScreen( onPlaceSelected: (Place) -> Unit, onPeekHeightChange: (dp: Dp) -> Unit, onSearchFocusChange: (Boolean) -> Unit, + onResultPinsChange: (List) -> Unit, + onSearchEvent: () -> Unit, ) { val searchQuery = viewModel.searchQuery @@ -95,7 +92,9 @@ fun HomeScreen( viewModel.updateSearchQuery(query) }, onSearchFocusChange = onSearchFocusChange, + onResultPinsChange = onResultPinsChange, onPeekHeightChange = onPeekHeightChange, + onSearchEvent = onSearchEvent, onPlaceSelected = onPlaceSelected, homeInSearchScreen = viewModel.searchQuery.text.isNotEmpty(), ) @@ -108,12 +107,19 @@ private fun SearchPanelContent( searchQuery: TextFieldValue, onSearchQueryChange: (TextFieldValue) -> Unit, onSearchFocusChange: (Boolean) -> Unit, + onResultPinsChange: (List) -> Unit, + onSearchEvent: () -> Unit, onPeekHeightChange: (dp: Dp) -> Unit, onPlaceSelected: (Place) -> Unit, homeInSearchScreen: Boolean, ) { val addressFormatter = remember { AddressFormatter() } val pinnedPlaces by viewModel.pinnedPlaces().collectAsState(emptyList()) + val geocodePlaces by viewModel.geocodePlaces.collectAsState(emptyList()) + + LaunchedEffect(geocodePlaces) { + onResultPinsChange(geocodePlaces) + } Column( modifier = Modifier @@ -135,6 +141,9 @@ private fun SearchPanelContent( TextField( value = searchQuery, onValueChange = onSearchQueryChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = { onSearchEvent() }), + singleLine = true, modifier = Modifier .focusRequester(textField) .fillMaxWidth() @@ -208,10 +217,10 @@ private fun SearchPanelContent( if (homeInSearchScreen) { LazyColumn { - items(viewModel.geocodeResults.value) { + items(geocodePlaces) { SearchResultItem( addressFormatter = addressFormatter, - viewModel.geocodeResultToPlace(it), + it, onPlaceSelected ) } @@ -224,113 +233,6 @@ private fun SearchPanelContent( } } -@Composable -private fun SavedPlacesSheetContent( - homeInSearchScreen: Boolean, - isSearching: Boolean, - geocodeResults: List, - viewModel: HomeViewModel, - onPlaceSelected: (Place) -> Unit, - savedPlaces: List -) { - val addressFormatter = remember { AddressFormatter() } - val scrollState = rememberScrollState() - // Content: either saved places or search results - Column( - modifier = Modifier - .verticalScroll(scrollState) - .padding(horizontal = dimensionResource(dimen.padding)) - .fillMaxWidth() - ) { - if (homeInSearchScreen) { - // Show search results - if (isSearching) { - Text( - text = stringResource(string.searching_in_progress), - modifier = Modifier - .fillMaxWidth() - .padding(dimensionResource(dimen.padding)) - ) - } else { - for (result in geocodeResults) { - SearchResultItem( - addressFormatter = addressFormatter, - place = viewModel.geocodeResultToPlace(result), - onPlaceSelected = onPlaceSelected - ) - } - } - } else { - // Show saved places - for (place in savedPlaces) { - PlaceItem(place = place, onClick = { - onPlaceSelected(place) - }) - } - } - } -} - -@Composable -private fun PlaceItem(place: Place, onClick: () -> Unit) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = dimensionResource(dimen.padding)) - .clickable( - true, onClick = onClick - ), onClick = { - Log.d("Place", "$place") - onClick() - }, elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(dimensionResource(dimen.padding)), - verticalAlignment = Alignment.CenterVertically - ) { - // Place icon (simplified) - Box( - modifier = Modifier - .size(40.dp) - .padding(dimensionResource(dimen.padding) / 2), - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource( - when (place.icon) { - "home" -> drawable.ic_home - "work" -> drawable.ic_work - else -> drawable.ic_location_on - } - ), - contentDescription = place.name, - modifier = Modifier.size(dimensionResource(dimen.icon_size)) - ) - } - - // Place details - Column( - modifier = Modifier - .weight(1f) - .padding(start = dimensionResource(dimen.padding)) - ) { - Text( - text = place.name, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold - ) - Text( - text = place.description, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - } - } -} - @Composable fun NavigationIcon( place: Place, diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeViewModel.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeViewModel.kt index a87f593..4f0909b 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeViewModel.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/HomeViewModel.kt @@ -67,7 +67,9 @@ class HomeViewModel @Inject constructor( TextFieldValue() ) - val geocodeResults = mutableStateOf>(emptyList()) + val geocodeResults = MutableStateFlow>(emptyList()) + + val geocodePlaces = geocodeResults.map { list -> list.map { geocodeResultToPlace(it) } } var isSearching by mutableStateOf(false) private set diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/OfflineAreasScreen.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/OfflineAreasScreen.kt index 398469c..a2375cf 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/OfflineAreasScreen.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/home/OfflineAreasScreen.kt @@ -433,4 +433,3 @@ fun calculateBoundingBoxFromViewport( west = viewport.farLeft.longitude, ) } - From 7541a9595b69d92ebdd70d7172d2c7ad9f60e241 Mon Sep 17 00:00:00 2001 From: Ellen Poe Date: Mon, 29 Sep 2025 15:18:44 -0700 Subject: [PATCH 2/2] fix: remove duplicate pin clear --- .../app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt index 34799e1..996a39f 100644 --- a/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt +++ b/cardinal-android/app/src/main/java/earth/maps/cardinal/ui/core/AppContent.kt @@ -361,7 +361,6 @@ fun AppContent( place?.let { place -> viewModel.setPlace(place) LaunchedEffect(place) { - mapPins.clear() // Clear any existing pins and add the new one to ensure only one pin is shown at a time mapPins.clear() mapPins.add(place)