From c35f225f0ab25927d705529f0dce72a6574c15cd Mon Sep 17 00:00:00 2001 From: Ryan Cheung <110103899+RyanCheung555@users.noreply.github.com> Date: Fri, 27 Feb 2026 01:20:21 -0500 Subject: [PATCH 01/13] feat: implement new design for cards in favorite tab --- .../home/BottomSheetLocationCard.kt | 70 +++++++++++-------- .../home/EcosystemBottomSheetContent.kt | 20 ++++-- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt index ba663b6..69cf9a9 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt @@ -2,20 +2,22 @@ package com.cornellappdev.transit.ui.components.home import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.PlatformTextStyle -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.cornellappdev.transit.ui.theme.PrimaryText +import com.cornellappdev.transit.ui.theme.SecondaryText import com.cornellappdev.transit.ui.theme.Style /** @@ -26,40 +28,48 @@ fun BottomSheetLocationCard( title: String, subtitle1: String, subtitle2: String = "", + isFavorite: Boolean, + onFavoriteClick: () -> Unit, onClick: () -> Unit ) { Column( modifier = Modifier - .clickable { - onClick() - } + .fillMaxWidth() + .background(Color.White, shape = RoundedCornerShape(12.dp)) + .clickable(onClick = onClick) ) { - Column( + Box( modifier = Modifier .fillMaxWidth() - .height(90.dp) - .background(color = Color.White, shape = RoundedCornerShape(12.dp)) .padding(16.dp) ) { - Text( - text = title, - style = Style.cardH1, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = subtitle1, - style = TextStyle(platformStyle = PlatformTextStyle(includeFontPadding = false)), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = subtitle2, - style = TextStyle(platformStyle = PlatformTextStyle(includeFontPadding = false)), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + Column( + modifier = Modifier + .align(Alignment.CenterStart), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = title, + style = Style.cardH1, + color = PrimaryText, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(end = 32.dp) + ) + Text( + text = subtitle1, + style = Style.heading3, + color = SecondaryText, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(end = 32.dp) + + ) + } + + FavoritesStar(onFavoriteClick = onFavoriteClick, isFavorite = isFavorite) } + } } @@ -68,7 +78,9 @@ fun BottomSheetLocationCard( private fun PreviewBottomSheetLocationCard() { BottomSheetLocationCard( title = "Uris Hall", - subtitle1 = "Cornell University", - subtitle2 = "Open until 10:00 PM" + subtitle1 = "Bus Stop", + subtitle2 = "Is this subtitle necessary?", + isFavorite = true, + onFavoriteClick = {} ) { } } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 316db37..a96a980 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -117,7 +117,7 @@ private fun BottomSheetFilteredContent( ) { when (currentFilter) { FilterState.FAVORITES -> { - favoriteList(favorites, navigateToPlace, onAddFavoritesClick) + favoriteList(favorites, navigateToPlace, onAddFavoritesClick, onFavoriteStarClick) } FilterState.PRINTERS -> { @@ -157,7 +157,8 @@ private fun BottomSheetFilteredContent( private fun LazyListScope.favoriteList( favorites: Set, navigateToPlace: (Place) -> Unit, - onAddFavoritesClick: () -> Unit + onAddFavoritesClick: () -> Unit, + onFavoriteStarClick: (Place) -> Unit ) { item { AddFavoritesButton(onAddFavoritesClick = onAddFavoritesClick) @@ -166,8 +167,13 @@ private fun LazyListScope.favoriteList( items(favorites.toList()) { BottomSheetLocationCard( title = it.name, - subtitle1 = it.subLabel + subtitle1 = it.subLabel, + isFavorite = true, + onFavoriteClick = { + onFavoriteStarClick(it) + } ) { + navigateToPlace(it) //TODO: Eatery } Spacer(Modifier.height(10.dp)) @@ -192,7 +198,9 @@ private fun LazyListScope.gymList( items(staticPlaces.gyms.data) { BottomSheetLocationCard( title = it.name, - subtitle1 = it.id + subtitle1 = it.id, + isFavorite = true, + onFavoriteClick = {} ) { //TODO: Eatery } @@ -221,7 +229,9 @@ private fun LazyListScope.printerList( items(staticPlaces.printers.data) { BottomSheetLocationCard( title = it.location, - subtitle1 = it.description + subtitle1 = it.description, + isFavorite = true, + onFavoriteClick = {} ) { navigateToPlace( it.toPlace() From 544f531f04630942a8359eb7428683c5d321c435 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Wed, 4 Mar 2026 02:09:54 -0500 Subject: [PATCH 02/13] chore: add preview for menu item of APPLE_PLACE type --- .../cornellappdev/transit/ui/components/MenuItem.kt | 10 ++++++++-- .../ui/components/home/EcosystemBottomSheetContent.kt | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt index cd463f5..c602b03 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt @@ -69,8 +69,14 @@ fun MenuItem(type: PlaceType, label: String, sublabel: String, onClick: () -> Un } -@Preview +@Preview(showBackground = true) @Composable -fun PreviewMenuItem() { +fun PreviewMenuItemBusStop() { MenuItem(PlaceType.BUS_STOP, "Ithaca Commons", "Ithaca, NY", {}) +} + +@Preview(showBackground = true) +@Composable +fun PreviewMenuItemApplePlace() { + MenuItem(PlaceType.APPLE_PLACE, "Apple Place", "Ithaca, NY", {}) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index ebbfc8c..e5e866e 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -236,7 +236,7 @@ private fun LazyListScope.favoriteList( BottomSheetLocationCard( title = it.name, subtitle1 = it.subLabel, - isFavorite = true, + isFavorite = true, //hard-coded data may result in bugs in the future onFavoriteClick = { onFavoriteStarClick(it) } From 864efb15e2d074467bc5581d846c5b9833c80779 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Thu, 5 Mar 2026 23:46:34 -0500 Subject: [PATCH 03/13] feat: add preview for bottom sheet filtered content in the favorites tab --- .../home/EcosystemBottomSheetContent.kt | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index e5e866e..26effee 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.cornellappdev.transit.R import com.cornellappdev.transit.models.Place +import com.cornellappdev.transit.models.PlaceType import com.cornellappdev.transit.models.ecosystem.DayOperatingHours import com.cornellappdev.transit.models.ecosystem.DetailedEcosystemPlace import com.cornellappdev.transit.models.ecosystem.Eatery @@ -428,4 +429,60 @@ private fun PreviewEcosystemBottomSheet() { onRemoveAppliedFilter = {}, operatingHoursToString = { _ -> AnnotatedString("") } ) -} \ No newline at end of file +} + +@Preview(showBackground = true, name = "Favorites with Applied Filters") +@Composable +private fun PreviewBottomSheetFilteredContentFavorites() { + BottomSheetFilteredContent( + currentFilter = FilterState.FAVORITES, + staticPlaces = StaticPlaces( + ApiResponse.Success(emptyList()), + ApiResponse.Success(emptyList()), + ApiResponse.Success(emptyList()), + ApiResponse.Success(emptyList()) + ), + favorites = setOf( + Place( + latitude = 42.4534, + longitude = -76.4735, + name = "Toni Morrison Dining", + detail = "Toni Morrison Hall", + type = PlaceType.APPLE_PLACE + ), + Place( + latitude = 42.4534, + longitude = -76.4735, + name = "Olin Library", + detail = "Ho Plaza", + type = PlaceType.APPLE_PLACE + ), + Place( + latitude = 42.4480, + longitude = -76.4840, + name = "Noyes Community Recreation Center", + detail = "North Campus", + type = PlaceType.APPLE_PLACE + ), + Place( + latitude = 42.4440, + longitude = -76.4825, + name = "Seneca St & Fall Creek Dr", + detail = "Bus Stop", + type = PlaceType.BUS_STOP + ) + ), + navigateToPlace = {}, + onDetailsClick = {}, + onFavoriteStarClick = {}, + onAddFavoritesClick = {}, + onFilterButtonClick = {}, + appliedFilters = setOf( + FavoritesFilterSheetState.EATERIES, + FavoritesFilterSheetState.LIBRARIES + ), + onRemoveAppliedFilter = {}, + operatingHoursToString = { _ -> AnnotatedString("") } + ) +} + From a1321262b33b11f07fe4314078cdf0bf7ce544ba Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Sat, 7 Mar 2026 14:19:25 -0500 Subject: [PATCH 04/13] feat: add the other ecosystem places to show in favorites tab --- .../com/cornellappdev/transit/models/Place.kt | 15 +- .../home/DetailedPlaceSheetContent.kt | 19 +- .../home/EcosystemBottomSheetContent.kt | 368 ++++++++++++++---- .../ui/components/home/PlaceCardImage.kt | 16 +- .../components/home/RoundedImagePlaceCard.kt | 2 +- .../transit/util/ecosystem/PlaceUtils.kt | 24 +- 6 files changed, 343 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/models/Place.kt b/app/src/main/java/com/cornellappdev/transit/models/Place.kt index 58ebdb2..103bcc2 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/Place.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/Place.kt @@ -22,7 +22,19 @@ enum class PlaceType { BUS_STOP, @Json(name = "applePlace") - APPLE_PLACE + APPLE_PLACE, + + @Json(name = "eatery") + EATERY, + + @Json(name = "library") + LIBRARY, + + @Json(name = "gym") + GYM, + + @Json(name = "printer") + PRINTER } /** @@ -36,6 +48,7 @@ data class Place( @Json(name = "detail") val detail: String?, @Json(name = "type") var type: PlaceType ) { + //TODO: sublabel for bus stop should be the current distance away val subLabel get() = if (type == PlaceType.BUS_STOP) "Bus Stop" else detail.toString() } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceSheetContent.kt index 6fca4bc..17cc2f9 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceSheetContent.kt @@ -110,8 +110,21 @@ fun DetailedPlaceSheetContent( } is UpliftGym -> { - //TODO - Text(ecosystemPlace.name) + //Placeholder until gym implementation is merged in + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + DetailedPlaceHeaderSection( + title = ecosystemPlace.name, + subtitle = ecosystemPlace.id, + onFavoriteClick = { + onFavoriteStarClick(ecosystemPlace.toPlace()) + }, + isFavorite = ecosystemPlace.toPlace() in favorites + ) + } } } } @@ -148,7 +161,7 @@ fun DetailedPlaceSheetContent( } is UpliftGym -> { - //TODO + navigateToPlace(ecosystemPlace.toPlace()) } } } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 26effee..73c86cb 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -18,6 +18,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString @@ -31,7 +32,10 @@ import com.cornellappdev.transit.models.PlaceType import com.cornellappdev.transit.models.ecosystem.DayOperatingHours import com.cornellappdev.transit.models.ecosystem.DetailedEcosystemPlace import com.cornellappdev.transit.models.ecosystem.Eatery +import com.cornellappdev.transit.models.ecosystem.Library +import com.cornellappdev.transit.models.ecosystem.Printer import com.cornellappdev.transit.models.ecosystem.StaticPlaces +import com.cornellappdev.transit.models.ecosystem.UpliftGym import com.cornellappdev.transit.networking.ApiResponse import com.cornellappdev.transit.ui.theme.FavoritesDividerGray import com.cornellappdev.transit.ui.theme.robotoFamily @@ -168,52 +172,68 @@ private fun BottomSheetFilteredContent( } } val isFilterBarHidden = currentFilter == FilterState.FAVORITES && appliedFilters.isEmpty() - LazyColumn( - contentPadding = PaddingValues( - start = 12.dp, - end = 12.dp, - top = if (isFilterBarHidden) 0.dp else 8.dp, - bottom = 120.dp // Makes bottom content visible with padding at the end - ), - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(20.dp) - ) { - when (currentFilter) { - FilterState.FAVORITES -> { - favoriteList( - favorites, - navigateToPlace, - onAddFavoritesClick, - onFavoriteStarClick - ) - } + key(currentFilter, appliedFilters) { + LazyColumn( + contentPadding = PaddingValues( + start = 12.dp, + end = 12.dp, + top = if (isFilterBarHidden) 0.dp else 8.dp, + bottom = 120.dp // Makes bottom content visible with padding at the end + ), + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + when (currentFilter) { + FilterState.FAVORITES -> { + favoriteList( + favorites = favorites, + appliedFilters = appliedFilters, + navigateToPlace = navigateToPlace, + onAddFavoritesClick = onAddFavoritesClick, + onFavoriteStarClick = onFavoriteStarClick, + staticPlaces = staticPlaces, + onDetailsClick = onDetailsClick, + operatingHoursToString = operatingHoursToString + ) + } - FilterState.PRINTERS -> { - printerList(staticPlaces, navigateToPlace) - } + FilterState.PRINTERS -> { + printerList( + staticPlaces = staticPlaces, + navigateToPlace = navigateToPlace, + favorites = favorites, + onFavoriteStarClick = onFavoriteStarClick + ) + } - FilterState.GYMS -> { - gymList(staticPlaces, navigateToPlace) - } + FilterState.GYMS -> { + gymList( + staticPlaces = staticPlaces, + navigateToPlace = navigateToPlace, + favorites = favorites, + onFavoriteStarClick = onFavoriteStarClick + ) + } - FilterState.EATERIES -> { - eateryList( - eateriesApiResponse = staticPlaces.eateries, - onDetailsClick = onDetailsClick, - favorites = favorites, - onFavoriteStarClick = onFavoriteStarClick, - operatingHoursToString = operatingHoursToString - ) - } + FilterState.EATERIES -> { + eateryList( + eateriesApiResponse = staticPlaces.eateries, + onDetailsClick = onDetailsClick, + favorites = favorites, + onFavoriteStarClick = onFavoriteStarClick, + operatingHoursToString = operatingHoursToString + ) + } - FilterState.LIBRARIES -> { - libraryList( - staticPlaces, - navigateToPlace, - onDetailsClick, - favorites, - onFavoriteStarClick, - ) + FilterState.LIBRARIES -> { + libraryList( + staticPlaces, + navigateToPlace, + onDetailsClick, + favorites, + onFavoriteStarClick, + ) + } } } } @@ -225,25 +245,148 @@ private fun BottomSheetFilteredContent( */ private fun LazyListScope.favoriteList( favorites: Set, + appliedFilters: Set, navigateToPlace: (Place) -> Unit, onAddFavoritesClick: () -> Unit, - onFavoriteStarClick: (Place) -> Unit + onFavoriteStarClick: (Place) -> Unit, + staticPlaces: StaticPlaces, + onDetailsClick: (DetailedEcosystemPlace) -> Unit, + operatingHoursToString: (List) -> AnnotatedString ) { + val filteredFavorites = if (appliedFilters.isEmpty()) { + favorites.toList() + } else { + favorites.filter { place -> + appliedFilters.any { filter -> + when (filter) { + FavoritesFilterSheetState.EATERIES -> place.type == PlaceType.EATERY + FavoritesFilterSheetState.LIBRARIES -> place.type == PlaceType.LIBRARY + FavoritesFilterSheetState.GYMS -> place.type == PlaceType.GYM + FavoritesFilterSheetState.PRINTERS -> place.type == PlaceType.PRINTER + FavoritesFilterSheetState.OTHER -> + place.type == PlaceType.APPLE_PLACE || place.type == PlaceType.BUS_STOP + } + } + } + } + item { Spacer(modifier = Modifier.height(8.dp)) AddFavoritesButton(onAddFavoritesClick = onAddFavoritesClick) } - items(favorites.toList()) { - BottomSheetLocationCard( - title = it.name, - subtitle1 = it.subLabel, - isFavorite = true, //hard-coded data may result in bugs in the future - onFavoriteClick = { - onFavoriteStarClick(it) + + val eateries = (staticPlaces.eateries as? ApiResponse.Success)?.data ?: emptyList() + val libraries = (staticPlaces.libraries as? ApiResponse.Success)?.data ?: emptyList() + val gyms = (staticPlaces.gyms as? ApiResponse.Success)?.data ?: emptyList() + val printers = (staticPlaces.printers as? ApiResponse.Success)?.data ?: emptyList() + + items(filteredFavorites) { place -> + when (place.type) { + PlaceType.EATERY -> { + val matchingEatery = eateries.find { it.toPlace() == place } + if (matchingEatery != null) { + RoundedImagePlaceCard( + title = matchingEatery.name, + subtitle = matchingEatery.location ?: "", + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) }, + leftAnnotatedString = operatingHoursToString( + matchingEatery.formatOperatingHours() + ) + ) { + onDetailsClick(matchingEatery) + } + } else { + BottomSheetLocationCard( + title = place.name, + subtitle1 = place.subLabel, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + navigateToPlace(place) + } + } + } + + PlaceType.LIBRARY -> { + val matchingLibrary = libraries.find { it.toPlace() == place } + if (matchingLibrary != null) { + RoundedImagePlaceCard( + title = matchingLibrary.location, + subtitle = matchingLibrary.address, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + onDetailsClick(matchingLibrary) + } + } else { + BottomSheetLocationCard( + title = place.name, + subtitle1 = place.subLabel, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + navigateToPlace(place) + } + } + } + + PlaceType.GYM -> { + val matchingGym = gyms.find { it.toPlace() == place } + if (matchingGym != null) { + BottomSheetLocationCard( + title = matchingGym.name, + subtitle1 = matchingGym.id, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + onDetailsClick(matchingGym) + } + } else { + BottomSheetLocationCard( + title = place.name, + subtitle1 = place.subLabel, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + navigateToPlace(place) + } + } + } + + PlaceType.PRINTER -> { + val matchingPrinter = printers.find { it.toPlace() == place } + if (matchingPrinter != null) { + BottomSheetLocationCard( + title = matchingPrinter.location, + subtitle1 = matchingPrinter.description, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + navigateToPlace(matchingPrinter.toPlace()) + } + } else { + BottomSheetLocationCard( + title = place.name, + subtitle1 = place.subLabel, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + navigateToPlace(place) + } + } + } + + PlaceType.BUS_STOP, PlaceType.APPLE_PLACE -> { + BottomSheetLocationCard( + title = place.name, + subtitle1 = place.subLabel, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + navigateToPlace(place) + } } - ) { - navigateToPlace(it) - //TODO: Eatery } } } @@ -253,7 +396,9 @@ private fun LazyListScope.favoriteList( */ private fun LazyListScope.gymList( staticPlaces: StaticPlaces, - navigateToPlace: (Place) -> Unit + navigateToPlace: (Place) -> Unit, + favorites: Set, + onFavoriteStarClick: (Place) -> Unit ) { when (staticPlaces.gyms) { is ApiResponse.Error -> { @@ -263,17 +408,17 @@ private fun LazyListScope.gymList( } is ApiResponse.Success -> { - items(staticPlaces.gyms.data) { + items(staticPlaces.gyms.data) { gym -> + val place = gym.toPlace() BottomSheetLocationCard( - title = it.name, - subtitle1 = it.id, - isFavorite = true, - onFavoriteClick = {} + title = gym.name, + subtitle1 = gym.id, + isFavorite = place in favorites, + onFavoriteClick = { onFavoriteStarClick(place) } ) { - //TODO: Eatery + navigateToPlace(place) } } - } } } @@ -283,7 +428,9 @@ private fun LazyListScope.gymList( */ private fun LazyListScope.printerList( staticPlaces: StaticPlaces, - navigateToPlace: (Place) -> Unit + navigateToPlace: (Place) -> Unit, + favorites: Set, + onFavoriteStarClick: (Place) -> Unit ) { when (staticPlaces.printers) { is ApiResponse.Error -> { @@ -293,19 +440,17 @@ private fun LazyListScope.printerList( } is ApiResponse.Success -> { - items(staticPlaces.printers.data) { + items(staticPlaces.printers.data) { printer -> + val place = printer.toPlace() BottomSheetLocationCard( - title = it.location, - subtitle1 = it.description, - isFavorite = true, - onFavoriteClick = {} + title = printer.location, + subtitle1 = printer.description, + isFavorite = place in favorites, + onFavoriteClick = { onFavoriteStarClick(place) } ) { - navigateToPlace( - it.toPlace() - ) + navigateToPlace(place) } } - } } } @@ -434,35 +579,85 @@ private fun PreviewEcosystemBottomSheet() { @Preview(showBackground = true, name = "Favorites with Applied Filters") @Composable private fun PreviewBottomSheetFilteredContentFavorites() { + val mockEatery = Eatery( + id = 1, + name = "Trillium", + menuSummary = "Coffee, pastries, sandwiches", + imageUrl = null, + location = "Kennedy Hall", + campusArea = "Central Campus", + onlineOrderUrl = null, + latitude = 42.4488, + longitude = -76.4813, + paymentAcceptsMealSwipes = true, + paymentAcceptsBrbs = true, + paymentAcceptsCash = true, + events = null + ) + + val mockLibrary = Library( + id = 1, + location = "Olin Library", + address = "161 Ho Plaza", + latitude = 42.4534, + longitude = -76.4735 + ) + + val mockGym = UpliftGym( + name = "Noyes Community Recreation Center", + id = "noyes-rec", + facilityId = "1", + hours = listOf(null, null, null, null, null, null, null), + imageUrl = null, + upliftCapacity = null, + latitude = 42.4480, + longitude = -76.4840 + ) + + val mockPrinter = Printer( + id = 1, + location = "Mann Library", + description = "1st Floor, near entrance", + latitude = 42.4479, + longitude = -76.4764 + ) + BottomSheetFilteredContent( currentFilter = FilterState.FAVORITES, staticPlaces = StaticPlaces( - ApiResponse.Success(emptyList()), - ApiResponse.Success(emptyList()), - ApiResponse.Success(emptyList()), - ApiResponse.Success(emptyList()) + printers = ApiResponse.Success(listOf(mockPrinter)), + libraries = ApiResponse.Success(listOf(mockLibrary)), + eateries = ApiResponse.Success(listOf(mockEatery)), + gyms = ApiResponse.Success(listOf(mockGym)) ), favorites = setOf( Place( - latitude = 42.4534, - longitude = -76.4735, - name = "Toni Morrison Dining", - detail = "Toni Morrison Hall", - type = PlaceType.APPLE_PLACE + latitude = 42.4488, + longitude = -76.4813, + name = "Trillium", + detail = "Kennedy Hall", + type = PlaceType.EATERY ), Place( latitude = 42.4534, longitude = -76.4735, name = "Olin Library", - detail = "Ho Plaza", - type = PlaceType.APPLE_PLACE + detail = "161 Ho Plaza", + type = PlaceType.LIBRARY ), Place( latitude = 42.4480, longitude = -76.4840, name = "Noyes Community Recreation Center", detail = "North Campus", - type = PlaceType.APPLE_PLACE + type = PlaceType.GYM + ), + Place( + latitude = 42.4479, + longitude = -76.4764, + name = "Mann Library", + detail = "1st Floor, near entrance", + type = PlaceType.PRINTER ), Place( latitude = 42.4440, @@ -479,10 +674,13 @@ private fun PreviewBottomSheetFilteredContentFavorites() { onFilterButtonClick = {}, appliedFilters = setOf( FavoritesFilterSheetState.EATERIES, - FavoritesFilterSheetState.LIBRARIES + FavoritesFilterSheetState.LIBRARIES, + FavoritesFilterSheetState.GYMS, + FavoritesFilterSheetState.PRINTERS, + FavoritesFilterSheetState.OTHER ), onRemoveAppliedFilter = {}, - operatingHoursToString = { _ -> AnnotatedString("") } + operatingHoursToString = { _ -> AnnotatedString("Open • 10am - 4pm") } ) } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt index fc657a3..f4cddb6 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt @@ -19,24 +19,30 @@ import com.cornellappdev.transit.ui.theme.MetadataGray * Rounded image from a network request, fallback to a drawable */ @Composable -fun PlaceCardImage(imageUrl: String?, @DrawableRes placeholderRes: Int, shouldClipBottom: Boolean = false) { +fun PlaceCardImage( + imageUrl: String?, + @DrawableRes placeholderRes: Int? = null, + shouldClipBottom: Boolean = false +) { val imageModifier = Modifier .then( - if(shouldClipBottom) Modifier.clip(RoundedCornerShape(12.dp)) - else Modifier.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp))) + if (shouldClipBottom) Modifier.clip(RoundedCornerShape(12.dp)) + else Modifier.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) + ) .fillMaxWidth() .height(112.dp) .background(MetadataGray) - if (imageUrl.isNullOrBlank()) { + // Images with no placeholders will simply not show + if (imageUrl.isNullOrBlank() && placeholderRes != null) { Image( painter = painterResource(id = placeholderRes), contentDescription = null, contentScale = ContentScale.Crop, modifier = imageModifier ) - } else { + } else if (!imageUrl.isNullOrBlank() && placeholderRes != null) { AsyncImage( model = imageUrl, contentDescription = null, diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt index d659ba7..84e162a 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt @@ -38,7 +38,7 @@ fun RoundedImagePlaceCard( onFavoriteClick: () -> Unit, leftAnnotatedString: AnnotatedString? = null, rightAnnotatedString: AnnotatedString? = null, - @DrawableRes placeholderRes: Int, + @DrawableRes placeholderRes: Int? = null, onClick: () -> Unit, ) { Column( diff --git a/app/src/main/java/com/cornellappdev/transit/util/ecosystem/PlaceUtils.kt b/app/src/main/java/com/cornellappdev/transit/util/ecosystem/PlaceUtils.kt index 79a75e2..2697479 100644 --- a/app/src/main/java/com/cornellappdev/transit/util/ecosystem/PlaceUtils.kt +++ b/app/src/main/java/com/cornellappdev/transit/util/ecosystem/PlaceUtils.kt @@ -5,37 +5,49 @@ import com.cornellappdev.transit.models.PlaceType import com.cornellappdev.transit.models.ecosystem.Eatery import com.cornellappdev.transit.models.ecosystem.Library import com.cornellappdev.transit.models.ecosystem.Printer +import com.cornellappdev.transit.models.ecosystem.UpliftGym /** - * Predefined mapping from library to generic place + * Predefined mapping from library to place */ fun Library.toPlace(): Place = Place( latitude = this.latitude, longitude = this.longitude, name = this.location, detail = this.address, - type = PlaceType.APPLE_PLACE + type = PlaceType.LIBRARY ) /** - * Predefined mapping from printer to generic place + * Predefined mapping from printer to place */ fun Printer.toPlace(): Place = Place( latitude = this.latitude, longitude = this.longitude, name = this.location, detail = this.description, - type = PlaceType.APPLE_PLACE + type = PlaceType.PRINTER ) /** - * Predefined mapping from eatery to generic place. Nullable latitudes and longitudes default to 0 + * Predefined mapping from eatery to place. Nullable latitudes and longitudes default to 0 */ fun Eatery.toPlace(): Place = Place( latitude = this.latitude ?: 0.0, longitude = this.longitude ?: 0.0, name = this.name, detail = this.location, - type = PlaceType.APPLE_PLACE + type = PlaceType.EATERY +) + +/** + * Predefined mapping from gym to place. + */ +fun UpliftGym.toPlace(): Place = Place( + latitude = this.latitude, + longitude = this.longitude, + name = this.name, + detail = this.id, + type = PlaceType.GYM ) From 010373e60cf719c5bc08cb7f164854e5ad0ca65d Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Sat, 7 Mar 2026 19:20:38 -0500 Subject: [PATCH 05/13] chore: delete unused import and change preview to be private --- app/src/main/java/com/cornellappdev/transit/models/Place.kt | 1 - .../java/com/cornellappdev/transit/ui/components/MenuItem.kt | 5 +++-- .../transit/ui/components/home/RoundedImagePlaceCard.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/models/Place.kt b/app/src/main/java/com/cornellappdev/transit/models/Place.kt index 103bcc2..841f659 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/Place.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/Place.kt @@ -1,7 +1,6 @@ package com.cornellappdev.transit.models import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt index c602b03..ab5d493 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt @@ -36,6 +36,7 @@ fun MenuItem(type: PlaceType, label: String, sublabel: String, onClick: () -> Un .clickable(onClick = onClick), verticalAlignment = Alignment.CenterVertically ) { + //TODO: Add icons for each ecosystem type if (type == PlaceType.APPLE_PLACE) { Image( painterResource(R.drawable.location_pin_gray), @@ -71,12 +72,12 @@ fun MenuItem(type: PlaceType, label: String, sublabel: String, onClick: () -> Un @Preview(showBackground = true) @Composable -fun PreviewMenuItemBusStop() { +private fun PreviewMenuItemBusStop() { MenuItem(PlaceType.BUS_STOP, "Ithaca Commons", "Ithaca, NY", {}) } @Preview(showBackground = true) @Composable -fun PreviewMenuItemApplePlace() { +private fun PreviewMenuItemApplePlace() { MenuItem(PlaceType.APPLE_PLACE, "Apple Place", "Ithaca, NY", {}) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt index 84e162a..292bb22 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt @@ -108,7 +108,7 @@ fun RoundedImagePlaceCard( @Preview @Composable -fun RoundedImagePlaceCardPreview() { +private fun RoundedImagePlaceCardPreview() { RoundedImagePlaceCard( placeholderRes = R.drawable.olin_library, title = "Olin Library", From d2527da766890349546b02a42ea200ef35fbcf0a Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Wed, 11 Mar 2026 18:07:09 -0400 Subject: [PATCH 06/13] fix: apply suggestions from PR review --- .../java/com/cornellappdev/transit/models/Place.kt | 2 +- .../ui/components/home/BottomSheetLocationCard.kt | 2 -- .../components/home/EcosystemBottomSheetContent.kt | 13 +++++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/models/Place.kt b/app/src/main/java/com/cornellappdev/transit/models/Place.kt index 841f659..5d17271 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/Place.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/Place.kt @@ -49,5 +49,5 @@ data class Place( ) { //TODO: sublabel for bus stop should be the current distance away val subLabel - get() = if (type == PlaceType.BUS_STOP) "Bus Stop" else detail.toString() + get() = if (type == PlaceType.BUS_STOP) "Bus Stop" else detail.orEmpty() } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt index 69cf9a9..f5e2607 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt @@ -27,7 +27,6 @@ import com.cornellappdev.transit.ui.theme.Style fun BottomSheetLocationCard( title: String, subtitle1: String, - subtitle2: String = "", isFavorite: Boolean, onFavoriteClick: () -> Unit, onClick: () -> Unit @@ -79,7 +78,6 @@ private fun PreviewBottomSheetLocationCard() { BottomSheetLocationCard( title = "Uris Hall", subtitle1 = "Bus Stop", - subtitle2 = "Is this subtitle necessary?", isFavorite = true, onFavoriteClick = {} ) { } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 73c86cb..9ad8c3f 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -280,10 +280,15 @@ private fun LazyListScope.favoriteList( val gyms = (staticPlaces.gyms as? ApiResponse.Success)?.data ?: emptyList() val printers = (staticPlaces.printers as? ApiResponse.Success)?.data ?: emptyList() + val eateryByPlace: Map = eateries.associateBy { it.toPlace() } + val libraryByPlace: Map = libraries.associateBy { it.toPlace() } + val gymByPlace: Map = gyms.associateBy { it.toPlace() } + val printerByPlace: Map = printers.associateBy { it.toPlace() } + items(filteredFavorites) { place -> when (place.type) { PlaceType.EATERY -> { - val matchingEatery = eateries.find { it.toPlace() == place } + val matchingEatery = eateryByPlace[place] if (matchingEatery != null) { RoundedImagePlaceCard( title = matchingEatery.name, @@ -309,7 +314,7 @@ private fun LazyListScope.favoriteList( } PlaceType.LIBRARY -> { - val matchingLibrary = libraries.find { it.toPlace() == place } + val matchingLibrary = libraryByPlace[place] if (matchingLibrary != null) { RoundedImagePlaceCard( title = matchingLibrary.location, @@ -332,7 +337,7 @@ private fun LazyListScope.favoriteList( } PlaceType.GYM -> { - val matchingGym = gyms.find { it.toPlace() == place } + val matchingGym = gymByPlace[place] if (matchingGym != null) { BottomSheetLocationCard( title = matchingGym.name, @@ -355,7 +360,7 @@ private fun LazyListScope.favoriteList( } PlaceType.PRINTER -> { - val matchingPrinter = printers.find { it.toPlace() == place } + val matchingPrinter = printerByPlace[place] if (matchingPrinter != null) { BottomSheetLocationCard( title = matchingPrinter.location, From 86873240703718555f8ce87ed66f3e08c72ece51 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Thu, 12 Mar 2026 01:16:55 -0400 Subject: [PATCH 07/13] fix: addressed comments in BottomSheetLocationCard --- .../transit/ui/components/home/BottomSheetLocationCard.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt index f5e2607..ff5cccc 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt @@ -43,9 +43,7 @@ fun BottomSheetLocationCard( .padding(16.dp) ) { Column( - modifier = Modifier - .align(Alignment.CenterStart), - verticalArrangement = Arrangement.spacedBy(4.dp) + verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically) ) { Text( text = title, @@ -62,7 +60,6 @@ fun BottomSheetLocationCard( maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(end = 32.dp) - ) } From b1ce09f26d84c4e42eb5da93682ab04bdb8cac9a Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Fri, 13 Mar 2026 01:59:15 -0400 Subject: [PATCH 08/13] fix: improve how favorites are shown --- .../home/EcosystemBottomSheetContent.kt | 94 ++++++++++++------- .../transit/ui/screens/HomeScreen.kt | 3 + .../transit/ui/viewmodels/HomeViewModel.kt | 62 +++++++++++- 3 files changed, 124 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 1f1d8f7..5c6103b 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -39,6 +39,7 @@ import com.cornellappdev.transit.models.ecosystem.UpliftGym import com.cornellappdev.transit.networking.ApiResponse import com.cornellappdev.transit.ui.theme.FavoritesDividerGray import com.cornellappdev.transit.ui.theme.robotoFamily +import com.cornellappdev.transit.ui.viewmodels.EcosystemFavoritesUiState import com.cornellappdev.transit.ui.viewmodels.FavoritesFilterSheetState import com.cornellappdev.transit.ui.viewmodels.FilterState import com.cornellappdev.transit.util.ecosystem.toPlace @@ -61,6 +62,7 @@ fun EcosystemBottomSheetContent( onFilterClick: (FilterState) -> Unit, staticPlaces: StaticPlaces, favorites: Set, + favoritesUiState: EcosystemFavoritesUiState, modifier: Modifier = Modifier, navigateToPlace: (Place) -> Unit, onDetailsClick: (DetailedEcosystemPlace) -> Unit, @@ -113,6 +115,7 @@ fun EcosystemBottomSheetContent( currentFilter = activeFilter, staticPlaces = staticPlaces, favorites = favorites, + favoritesUiState = favoritesUiState, navigateToPlace = navigateToPlace, onDetailsClick = onDetailsClick, onFavoriteStarClick = onFavoriteStarClick, @@ -145,6 +148,7 @@ private fun BottomSheetFilteredContent( currentFilter: FilterState, staticPlaces: StaticPlaces, favorites: Set, + favoritesUiState: EcosystemFavoritesUiState, navigateToPlace: (Place) -> Unit, onDetailsClick: (DetailedEcosystemPlace) -> Unit, onFavoriteStarClick: (Place) -> Unit, @@ -187,11 +191,14 @@ private fun BottomSheetFilteredContent( FilterState.FAVORITES -> { favoriteList( favorites = favorites, - appliedFilters = appliedFilters, + filteredFavorites = favoritesUiState.filteredSortedFavorites, + eateryByPlace = favoritesUiState.eateryByPlace, + libraryByPlace = favoritesUiState.libraryByPlace, + gymByPlace = favoritesUiState.gymByPlace, + printerByPlace = favoritesUiState.printerByPlace, navigateToPlace = navigateToPlace, onAddFavoritesClick = onAddFavoritesClick, onFavoriteStarClick = onFavoriteStarClick, - staticPlaces = staticPlaces, onDetailsClick = onDetailsClick, operatingHoursToString = operatingHoursToString ) @@ -245,47 +252,26 @@ private fun BottomSheetFilteredContent( */ private fun LazyListScope.favoriteList( favorites: Set, - appliedFilters: Set, + filteredFavorites: List, + eateryByPlace: Map, + libraryByPlace: Map, + gymByPlace: Map, + printerByPlace: Map, navigateToPlace: (Place) -> Unit, onAddFavoritesClick: () -> Unit, onFavoriteStarClick: (Place) -> Unit, - staticPlaces: StaticPlaces, onDetailsClick: (DetailedEcosystemPlace) -> Unit, operatingHoursToString: (List) -> AnnotatedString ) { - val filteredFavorites = if (appliedFilters.isEmpty()) { - favorites.toList() - } else { - favorites.filter { place -> - appliedFilters.any { filter -> - when (filter) { - FavoritesFilterSheetState.EATERIES -> place.type == PlaceType.EATERY - FavoritesFilterSheetState.LIBRARIES -> place.type == PlaceType.LIBRARY - FavoritesFilterSheetState.GYMS -> place.type == PlaceType.GYM - FavoritesFilterSheetState.PRINTERS -> place.type == PlaceType.PRINTER - FavoritesFilterSheetState.OTHER -> - place.type == PlaceType.APPLE_PLACE || place.type == PlaceType.BUS_STOP - } - } - } - } - item { Spacer(modifier = Modifier.height(8.dp)) AddFavoritesButton(onAddFavoritesClick = onAddFavoritesClick) } - val eateries = (staticPlaces.eateries as? ApiResponse.Success)?.data ?: emptyList() - val libraries = (staticPlaces.libraries as? ApiResponse.Success)?.data ?: emptyList() - val gyms = (staticPlaces.gyms as? ApiResponse.Success)?.data ?: emptyList() - val printers = (staticPlaces.printers as? ApiResponse.Success)?.data ?: emptyList() - - val eateryByPlace: Map = eateries.associateBy { it.toPlace() } - val libraryByPlace: Map = libraries.associateBy { it.toPlace() } - val gymByPlace: Map = gyms.associateBy { it.toPlace() } - val printerByPlace: Map = printers.associateBy { it.toPlace() } - - items(filteredFavorites) { place -> + items( + items = filteredFavorites, + key = { place -> "${place.type}:${place.name}:${place.latitude}:${place.longitude}" } + ) { place -> when (place.type) { PlaceType.EATERY -> { val matchingEatery = eateryByPlace[place] @@ -584,6 +570,7 @@ private fun PreviewEcosystemBottomSheet() { ApiResponse.Pending ), favorites = emptySet(), + favoritesUiState = EcosystemFavoritesUiState(), modifier = Modifier, navigateToPlace = {}, onDetailsClick = {}, @@ -699,6 +686,49 @@ private fun PreviewBottomSheetFilteredContentFavorites() { type = PlaceType.BUS_STOP ) ), + favoritesUiState = EcosystemFavoritesUiState( + filteredSortedFavorites = listOf( + Place( + latitude = 42.4488, + longitude = -76.4813, + name = "Trillium", + detail = "Kennedy Hall", + type = PlaceType.EATERY + ), + Place( + latitude = 42.4534, + longitude = -76.4735, + name = "Olin Library", + detail = "161 Ho Plaza", + type = PlaceType.LIBRARY + ), + Place( + latitude = 42.4480, + longitude = -76.4840, + name = "Noyes Community Recreation Center", + detail = "North Campus", + type = PlaceType.GYM + ), + Place( + latitude = 42.4479, + longitude = -76.4764, + name = "Mann Library", + detail = "1st Floor, near entrance", + type = PlaceType.PRINTER + ), + Place( + latitude = 42.4440, + longitude = -76.4825, + name = "Seneca St & Fall Creek Dr", + detail = "Bus Stop", + type = PlaceType.BUS_STOP + ) + ), + eateryByPlace = listOf(mockEatery).associateBy { it.toPlace() }, + libraryByPlace = listOf(mockLibrary).associateBy { it.toPlace() }, + gymByPlace = listOf(mockGym).associateBy { it.toPlace() }, + printerByPlace = listOf(mockPrinter).associateBy { it.toPlace() } + ), navigateToPlace = {}, onDetailsClick = {}, onFavoriteStarClick = {}, diff --git a/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt b/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt index 903bcb2..b21560f 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt @@ -183,6 +183,8 @@ fun HomeScreen( val filterStateValue = homeViewModel.filterState.collectAsStateWithLifecycle().value val staticPlaces = homeViewModel.staticPlacesFlow.collectAsStateWithLifecycle().value + val ecosystemFavoritesUiState = + homeViewModel.ecosystemFavoritesUiState.collectAsStateWithLifecycle().value // Main search bar active/inactive var searchActive by remember { mutableStateOf(false) } @@ -333,6 +335,7 @@ fun HomeScreen( modifier = Modifier.onTapDisableSearch(), staticPlaces = staticPlaces, favorites = favorites, + favoritesUiState = ecosystemFavoritesUiState, navigateToPlace = { homeViewModel.beginRouteOptions(it) navController.navigate("route") diff --git a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt index 1276e78..388b021 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt @@ -4,24 +4,29 @@ import android.content.Context import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.withStyle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.cornellappdev.transit.models.LocationRepository import com.cornellappdev.transit.models.Place +import com.cornellappdev.transit.models.PlaceType import com.cornellappdev.transit.models.RouteRepository import com.cornellappdev.transit.models.SelectedRouteRepository import com.cornellappdev.transit.models.ecosystem.StaticPlaces import com.cornellappdev.transit.models.UserPreferenceRepository import com.cornellappdev.transit.models.ecosystem.DayOperatingHours +import com.cornellappdev.transit.models.ecosystem.Eatery import com.cornellappdev.transit.models.ecosystem.EateryRepository import com.cornellappdev.transit.models.ecosystem.GymRepository +import com.cornellappdev.transit.models.ecosystem.Library +import com.cornellappdev.transit.models.ecosystem.Printer +import com.cornellappdev.transit.models.ecosystem.UpliftGym import com.cornellappdev.transit.networking.ApiResponse import com.cornellappdev.transit.ui.theme.LateRed import com.cornellappdev.transit.ui.theme.LiveGreen import com.cornellappdev.transit.ui.theme.SecondaryText import com.cornellappdev.transit.util.TimeUtils.toPascalCaseString +import com.cornellappdev.transit.util.ecosystem.toPlace import com.google.android.gms.maps.model.LatLng import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -37,7 +42,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import java.time.DayOfWeek import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime @@ -146,6 +150,36 @@ class HomeViewModel @Inject constructor( val appliedFavoritesFilters: StateFlow> = _appliedFavoritesFilters.asStateFlow() + val ecosystemFavoritesUiState: StateFlow = combine( + userPreferenceRepository.favoritesFlow, + staticPlacesFlow, + appliedFavoritesFilters + ) { favorites, staticPlaces, appliedFilters -> + val allowedTypes = appliedFilters.toAllowedPlaceTypes() + + val filteredSortedFavorites = favorites.asSequence() + .filter { allowedTypes.isEmpty() || it.type in allowedTypes } + .sortedWith(compareBy({ it.type.ordinal }, { it.name })) + .toList() + + val eateries = (staticPlaces.eateries as? ApiResponse.Success)?.data.orEmpty() + val libraries = (staticPlaces.libraries as? ApiResponse.Success)?.data.orEmpty() + val gyms = (staticPlaces.gyms as? ApiResponse.Success)?.data.orEmpty() + val printers = (staticPlaces.printers as? ApiResponse.Success)?.data.orEmpty() + + EcosystemFavoritesUiState( + filteredSortedFavorites = filteredSortedFavorites, + eateryByPlace = eateries.associateBy { it.toPlace() }, + libraryByPlace = libraries.associateBy { it.toPlace() }, + gymByPlace = gyms.associateBy { it.toPlace() }, + printerByPlace = printers.associateBy { it.toPlace() } + ) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = EcosystemFavoritesUiState() + ) + fun toggleFavoritesFilter(filter: FavoritesFilterSheetState) { _selectedFavoritesFilters.value = if (filter in _selectedFavoritesFilters.value) { _selectedFavoritesFilters.value - filter @@ -507,4 +541,26 @@ class HomeViewModel @Inject constructor( ) } -} \ No newline at end of file +} + +/** + * Derived favorites content for the ecosystem tabs, computed in ViewModel. + */ +data class EcosystemFavoritesUiState( + val filteredSortedFavorites: List = emptyList(), + val eateryByPlace: Map = emptyMap(), + val libraryByPlace: Map = emptyMap(), + val gymByPlace: Map = emptyMap(), + val printerByPlace: Map = emptyMap() +) + +private fun Set.toAllowedPlaceTypes(): Set = buildSet { + if (FavoritesFilterSheetState.EATERIES in this@toAllowedPlaceTypes) add(PlaceType.EATERY) + if (FavoritesFilterSheetState.LIBRARIES in this@toAllowedPlaceTypes) add(PlaceType.LIBRARY) + if (FavoritesFilterSheetState.GYMS in this@toAllowedPlaceTypes) add(PlaceType.GYM) + if (FavoritesFilterSheetState.PRINTERS in this@toAllowedPlaceTypes) add(PlaceType.PRINTER) + if (FavoritesFilterSheetState.OTHER in this@toAllowedPlaceTypes) { + add(PlaceType.APPLE_PLACE) + add(PlaceType.BUS_STOP) + } +} From e78f7796105977d7d95936cb59c0f554607d2aed Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Fri, 13 Mar 2026 10:33:31 -0400 Subject: [PATCH 09/13] fix: create private helper to display standard card output --- .../home/EcosystemBottomSheetContent.kt | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 5c6103b..8f1a482 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -288,14 +288,11 @@ private fun LazyListScope.favoriteList( onDetailsClick(matchingEatery) } } else { - BottomSheetLocationCard( - title = place.name, - subtitle1 = place.subLabel, - isFavorite = true, - onFavoriteClick = { onFavoriteStarClick(place) } - ) { - navigateToPlace(place) - } + StandardCard( + place = place, + onFavoriteStarClick = onFavoriteStarClick, + navigateToPlace = navigateToPlace + ) } } @@ -311,14 +308,11 @@ private fun LazyListScope.favoriteList( onDetailsClick(matchingLibrary) } } else { - BottomSheetLocationCard( - title = place.name, - subtitle1 = place.subLabel, - isFavorite = true, - onFavoriteClick = { onFavoriteStarClick(place) } - ) { - navigateToPlace(place) - } + StandardCard( + place = place, + onFavoriteStarClick = onFavoriteStarClick, + navigateToPlace = navigateToPlace + ) } } @@ -334,14 +328,11 @@ private fun LazyListScope.favoriteList( onDetailsClick(matchingGym) } } else { - BottomSheetLocationCard( - title = place.name, - subtitle1 = place.subLabel, - isFavorite = true, - onFavoriteClick = { onFavoriteStarClick(place) } - ) { - navigateToPlace(place) - } + StandardCard( + place = place, + onFavoriteStarClick = onFavoriteStarClick, + navigateToPlace = navigateToPlace + ) } } @@ -370,26 +361,20 @@ private fun LazyListScope.favoriteList( ) } } else { - BottomSheetLocationCard( - title = place.name, - subtitle1 = place.subLabel, - isFavorite = true, - onFavoriteClick = { onFavoriteStarClick(place) } - ) { - navigateToPlace(place) - } + StandardCard( + place = place, + onFavoriteStarClick = onFavoriteStarClick, + navigateToPlace = navigateToPlace + ) } } PlaceType.BUS_STOP, PlaceType.APPLE_PLACE -> { - BottomSheetLocationCard( - title = place.name, - subtitle1 = place.subLabel, - isFavorite = true, - onFavoriteClick = { onFavoriteStarClick(place) } - ) { - navigateToPlace(place) - } + StandardCard( + place = place, + onFavoriteStarClick = onFavoriteStarClick, + navigateToPlace = navigateToPlace + ) } } } @@ -463,7 +448,7 @@ private fun LazyListScope.printerList( onFavoriteClick = { onFavoriteStarClick(place) } - ) { + ) { navigateToPlace( place ) @@ -550,6 +535,22 @@ private fun LazyListScope.libraryList( } } +@Composable +private fun StandardCard( + place: Place, + onFavoriteStarClick: (Place) -> Unit, + navigateToPlace: (Place) -> Unit +) { + BottomSheetLocationCard( + title = place.name, + subtitle1 = place.subLabel, + isFavorite = true, + onFavoriteClick = { onFavoriteStarClick(place) } + ) { + navigateToPlace(place) + } +} + @Preview(showBackground = true) @Composable private fun PreviewEcosystemBottomSheet() { From eaee09e45bf0058bb37014f5b6ec2b582fab67f3 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Fri, 13 Mar 2026 12:04:08 -0400 Subject: [PATCH 10/13] fix: move the handling of printer data logic to the homeviewmodel --- .../home/EcosystemBottomSheetContent.kt | 37 +++++++++++-------- .../transit/ui/viewmodels/HomeViewModel.kt | 31 +++++++++++++++- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 8f1a482..7fa1a95 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -42,6 +42,7 @@ import com.cornellappdev.transit.ui.theme.robotoFamily import com.cornellappdev.transit.ui.viewmodels.EcosystemFavoritesUiState import com.cornellappdev.transit.ui.viewmodels.FavoritesFilterSheetState import com.cornellappdev.transit.ui.viewmodels.FilterState +import com.cornellappdev.transit.ui.viewmodels.PrinterCardUiState import com.cornellappdev.transit.util.ecosystem.toPlace import kotlin.collections.isNotEmpty @@ -256,7 +257,7 @@ private fun LazyListScope.favoriteList( eateryByPlace: Map, libraryByPlace: Map, gymByPlace: Map, - printerByPlace: Map, + printerByPlace: Map, navigateToPlace: (Place) -> Unit, onAddFavoritesClick: () -> Unit, onFavoriteStarClick: (Place) -> Unit, @@ -338,27 +339,20 @@ private fun LazyListScope.favoriteList( PlaceType.PRINTER -> { val matchingPrinter = printerByPlace[place] - val alert = if (matchingPrinter?.location?.contains("*") == true) { - matchingPrinter.location.substringAfter("*").trim('*').trim() - } else { - "" - } if (matchingPrinter != null) { PrinterCard( - title = matchingPrinter.location.substringBefore("*").trim(), - subtitle = matchingPrinter.description.substringAfter("-").trim(), - inColor = matchingPrinter.description.contains("Color", ignoreCase = true), - hasCopy = matchingPrinter.description.contains("Copy", ignoreCase = true), - hasScan = matchingPrinter.description.contains("Scan", ignoreCase = true), - alertMessage = alert, + title = matchingPrinter.title, + subtitle = matchingPrinter.subtitle, + inColor = matchingPrinter.inColor, + hasCopy = matchingPrinter.hasCopy, + hasScan = matchingPrinter.hasScan, + alertMessage = matchingPrinter.alertMessage, isFavorite = place in favorites, onFavoriteClick = { onFavoriteStarClick(place) } ) { - navigateToPlace( - place - ) + navigateToPlace(place) } } else { StandardCard( @@ -728,7 +722,16 @@ private fun PreviewBottomSheetFilteredContentFavorites() { eateryByPlace = listOf(mockEatery).associateBy { it.toPlace() }, libraryByPlace = listOf(mockLibrary).associateBy { it.toPlace() }, gymByPlace = listOf(mockGym).associateBy { it.toPlace() }, - printerByPlace = listOf(mockPrinter).associateBy { it.toPlace() } + printerByPlace = mapOf( + mockPrinter.toPlace() to PrinterCardUiState( + title = "Mann Library", + subtitle = "near entrance", + inColor = false, + hasCopy = false, + hasScan = false, + alertMessage = "" + ) + ) ), navigateToPlace = {}, onDetailsClick = {}, @@ -747,3 +750,5 @@ private fun PreviewBottomSheetFilteredContentFavorites() { ) } + + diff --git a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt index 388b021..4fb0cb1 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt @@ -172,7 +172,10 @@ class HomeViewModel @Inject constructor( eateryByPlace = eateries.associateBy { it.toPlace() }, libraryByPlace = libraries.associateBy { it.toPlace() }, gymByPlace = gyms.associateBy { it.toPlace() }, - printerByPlace = printers.associateBy { it.toPlace() } + printerByPlace = printers.associate { printer -> + val place = printer.toPlace() + place to printer.toPrinterCardUiState() + } ) }.stateIn( scope = viewModelScope, @@ -551,9 +554,33 @@ data class EcosystemFavoritesUiState( val eateryByPlace: Map = emptyMap(), val libraryByPlace: Map = emptyMap(), val gymByPlace: Map = emptyMap(), - val printerByPlace: Map = emptyMap() + val printerByPlace: Map = emptyMap() +) + +/** + * UI-ready printer fields so composables don't parse backend strings. + */ +data class PrinterCardUiState( + val title: String, + val subtitle: String, + val inColor: Boolean, + val hasCopy: Boolean, + val hasScan: Boolean, + val alertMessage: String ) +private fun Printer.toPrinterCardUiState(): PrinterCardUiState { + val alertMessage = location.substringAfter("*", "").trim('*').trim() + return PrinterCardUiState( + title = location.substringBefore("*").trim(), + subtitle = description.substringAfter("-", description).trim(), + inColor = description.contains("Color", ignoreCase = true), + hasCopy = description.contains("Copy", ignoreCase = true), + hasScan = description.contains("Scan", ignoreCase = true), + alertMessage = alertMessage + ) +} + private fun Set.toAllowedPlaceTypes(): Set = buildSet { if (FavoritesFilterSheetState.EATERIES in this@toAllowedPlaceTypes) add(PlaceType.EATERY) if (FavoritesFilterSheetState.LIBRARIES in this@toAllowedPlaceTypes) add(PlaceType.LIBRARY) From 3470ec07da5d203710fbf91d071f89d359e584a1 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Sat, 14 Mar 2026 20:37:29 -0400 Subject: [PATCH 11/13] feat: add distance to ecosystem locations --- .../transit/models/ecosystem/Eatery.kt | 2 +- .../transit/models/ecosystem/StaticPlaces.kt | 4 +- .../transit/models/ecosystem/UpliftGym.kt | 2 +- .../home/EcosystemBottomSheetContent.kt | 102 ++++++++++++------ 4 files changed, 71 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/Eatery.kt b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/Eatery.kt index c83c4ae..691dd1b 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/Eatery.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/Eatery.kt @@ -77,7 +77,7 @@ data class Eatery( longitude = this.longitude ?: 0.0, name = this.name, detail = this.location, - type = PlaceType.APPLE_PLACE + type = PlaceType.EATERY ) } diff --git a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/StaticPlaces.kt b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/StaticPlaces.kt index 0600cd1..0a26fc2 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/StaticPlaces.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/StaticPlaces.kt @@ -30,7 +30,7 @@ data class Printer( longitude = this.longitude, name = this.location, detail = this.description, - type = PlaceType.APPLE_PLACE + type = PlaceType.PRINTER ) } @@ -55,6 +55,6 @@ data class Library( longitude = this.longitude, name = this.location, detail = this.address, - type = PlaceType.APPLE_PLACE + type = PlaceType.LIBRARY ) } diff --git a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/UpliftGym.kt b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/UpliftGym.kt index e45aa9c..ea75fae 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/UpliftGym.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/UpliftGym.kt @@ -70,7 +70,7 @@ data class UpliftGym( longitude = this.longitude, name = this.name, detail = getGymLocationString(this.name), - type = PlaceType.APPLE_PLACE + type = PlaceType.GYM ) } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index ccbdd2c..de7a359 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -207,7 +207,9 @@ private fun BottomSheetFilteredContent( onAddFavoritesClick = onAddFavoritesClick, onFavoriteStarClick = onFavoriteStarClick, onDetailsClick = onDetailsClick, - operatingHoursToString = operatingHoursToString + operatingHoursToString = operatingHoursToString, + capacityToString = ::capacityPercentAnnotatedString, + distanceStringToPlace = distanceStringToPlace ) } @@ -216,32 +218,33 @@ private fun BottomSheetFilteredContent( staticPlaces = staticPlaces, navigateToPlace = navigateToPlace, favorites = favorites, - onFavoriteStarClick = onFavoriteStarClick + onFavoriteStarClick = onFavoriteStarClick, + distanceStringToPlace = distanceStringToPlace ) } - FilterState.GYMS -> { - gymList( - gymsApiResponse = staticPlaces.gyms, - onDetailsClick = onDetailsClick, - favorites = favorites, - onFavoriteStarClick = onFavoriteStarClick, - operatingHoursToString = ::isOpenAnnotatedStringFromOperatingHours, - capacityToString = ::capacityPercentAnnotatedString, - distanceStringToPlace = distanceStringToPlace - ) - } + FilterState.GYMS -> { + gymList( + gymsApiResponse = staticPlaces.gyms, + onDetailsClick = onDetailsClick, + favorites = favorites, + onFavoriteStarClick = onFavoriteStarClick, + operatingHoursToString = ::isOpenAnnotatedStringFromOperatingHours, + capacityToString = ::capacityPercentAnnotatedString, + distanceStringToPlace = distanceStringToPlace + ) + } - FilterState.EATERIES -> { - eateryList( - eateriesApiResponse = staticPlaces.eateries, - onDetailsClick = onDetailsClick, - favorites = favorites, - onFavoriteStarClick = onFavoriteStarClick, - operatingHoursToString = operatingHoursToString, - distanceStringToPlace = distanceStringToPlace - ) - } + FilterState.EATERIES -> { + eateryList( + eateriesApiResponse = staticPlaces.eateries, + onDetailsClick = onDetailsClick, + favorites = favorites, + onFavoriteStarClick = onFavoriteStarClick, + operatingHoursToString = operatingHoursToString, + distanceStringToPlace = distanceStringToPlace + ) + } FilterState.LIBRARIES -> { libraryList( @@ -250,6 +253,7 @@ private fun BottomSheetFilteredContent( onDetailsClick, favorites, onFavoriteStarClick, + distanceStringToPlace, ) } } @@ -272,7 +276,9 @@ private fun LazyListScope.favoriteList( onAddFavoritesClick: () -> Unit, onFavoriteStarClick: (Place) -> Unit, onDetailsClick: (DetailedEcosystemPlace) -> Unit, - operatingHoursToString: (List) -> AnnotatedString + operatingHoursToString: (List) -> AnnotatedString, + capacityToString: (UpliftCapacity?) -> AnnotatedString, + distanceStringToPlace: (Double?, Double?) -> String ) { item { Spacer(modifier = Modifier.height(8.dp)) @@ -289,7 +295,11 @@ private fun LazyListScope.favoriteList( if (matchingEatery != null) { RoundedImagePlaceCard( title = matchingEatery.name, - subtitle = matchingEatery.location ?: "", + subtitle = (matchingEatery.location + ?: "") + distanceStringToPlace( + matchingEatery.latitude, + matchingEatery.longitude + ), isFavorite = true, onFavoriteClick = { onFavoriteStarClick(place) }, leftAnnotatedString = operatingHoursToString( @@ -312,7 +322,10 @@ private fun LazyListScope.favoriteList( if (matchingLibrary != null) { RoundedImagePlaceCard( title = matchingLibrary.location, - subtitle = matchingLibrary.address, + subtitle = matchingLibrary.address + distanceStringToPlace( + matchingLibrary.latitude, + matchingLibrary.longitude + ), isFavorite = true, onFavoriteClick = { onFavoriteStarClick(place) } ) { @@ -330,11 +343,22 @@ private fun LazyListScope.favoriteList( PlaceType.GYM -> { val matchingGym = gymByPlace[place] if (matchingGym != null) { - BottomSheetLocationCard( + RoundedImagePlaceCard( title = matchingGym.name, - subtitle1 = matchingGym.id, - isFavorite = true, - onFavoriteClick = { onFavoriteStarClick(place) } + subtitle = getGymLocationString(matchingGym.name) + distanceStringToPlace( + matchingGym.latitude, + matchingGym.longitude + ), + isFavorite = matchingGym.toPlace() in favorites, + onFavoriteClick = { + onFavoriteStarClick(matchingGym.toPlace()) + }, + leftAnnotatedString = operatingHoursToString( + matchingGym.operatingHours() + ), + rightAnnotatedString = capacityToString( + matchingGym.upliftCapacity + ), ) { onDetailsClick(matchingGym) } @@ -352,7 +376,10 @@ private fun LazyListScope.favoriteList( if (matchingPrinter != null) { PrinterCard( title = matchingPrinter.title, - subtitle = matchingPrinter.subtitle, + subtitle = matchingPrinter.subtitle + distanceStringToPlace( + place.latitude, + place.longitude + ), inColor = matchingPrinter.inColor, hasCopy = matchingPrinter.hasCopy, hasScan = matchingPrinter.hasScan, @@ -441,7 +468,8 @@ private fun LazyListScope.printerList( staticPlaces: StaticPlaces, navigateToPlace: (Place) -> Unit, favorites: Set, - onFavoriteStarClick: (Place) -> Unit + onFavoriteStarClick: (Place) -> Unit, + distanceStringToPlace: (Double?, Double?) -> String, ) { when (staticPlaces.printers) { is ApiResponse.Error -> { @@ -461,7 +489,10 @@ private fun LazyListScope.printerList( PrinterCard( title = it.location.substringBefore("*").trim(), - subtitle = it.description.substringAfter("-").trim(), + subtitle = it.description.substringAfter("-").trim() + distanceStringToPlace( + it.latitude, + it.longitude + ), inColor = it.description.contains("Color", ignoreCase = true), hasCopy = it.description.contains("Copy", ignoreCase = true), hasScan = it.description.contains("Scan", ignoreCase = true), @@ -532,7 +563,8 @@ private fun LazyListScope.libraryList( navigateToPlace: (Place) -> Unit, navigateToDetails: (DetailedEcosystemPlace) -> Unit, favorites: Set, - onFavoriteStarClick: (Place) -> Unit + onFavoriteStarClick: (Place) -> Unit, + distanceStringToPlace: (Double?, Double?) -> String, ) { when (staticPlaces.libraries) { is ApiResponse.Error -> { @@ -546,7 +578,7 @@ private fun LazyListScope.libraryList( RoundedImagePlaceCard( placeholderRes = R.drawable.olin_library, title = it.location, - subtitle = it.address, + subtitle = it.address + distanceStringToPlace(it.latitude, it.longitude), isFavorite = it.toPlace() in favorites, onFavoriteClick = { onFavoriteStarClick(it.toPlace()) From fe1846518c2d2aae377a71d05f41d3bea740d839 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Sat, 14 Mar 2026 20:41:16 -0400 Subject: [PATCH 12/13] feat: add distance to bus stops/apple place results --- .../home/EcosystemBottomSheetContent.kt | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index de7a359..8acc940 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -312,7 +312,8 @@ private fun LazyListScope.favoriteList( StandardCard( place = place, onFavoriteStarClick = onFavoriteStarClick, - navigateToPlace = navigateToPlace + navigateToPlace = navigateToPlace, + distanceStringToPlace = distanceStringToPlace ) } } @@ -335,7 +336,8 @@ private fun LazyListScope.favoriteList( StandardCard( place = place, onFavoriteStarClick = onFavoriteStarClick, - navigateToPlace = navigateToPlace + navigateToPlace = navigateToPlace, + distanceStringToPlace = distanceStringToPlace ) } } @@ -366,7 +368,8 @@ private fun LazyListScope.favoriteList( StandardCard( place = place, onFavoriteStarClick = onFavoriteStarClick, - navigateToPlace = navigateToPlace + navigateToPlace = navigateToPlace, + distanceStringToPlace = distanceStringToPlace ) } } @@ -395,7 +398,8 @@ private fun LazyListScope.favoriteList( StandardCard( place = place, onFavoriteStarClick = onFavoriteStarClick, - navigateToPlace = navigateToPlace + navigateToPlace = navigateToPlace, + distanceStringToPlace = distanceStringToPlace ) } } @@ -404,7 +408,8 @@ private fun LazyListScope.favoriteList( StandardCard( place = place, onFavoriteStarClick = onFavoriteStarClick, - navigateToPlace = navigateToPlace + navigateToPlace = navigateToPlace, + distanceStringToPlace = distanceStringToPlace ) } } @@ -595,11 +600,15 @@ private fun LazyListScope.libraryList( private fun StandardCard( place: Place, onFavoriteStarClick: (Place) -> Unit, - navigateToPlace: (Place) -> Unit + navigateToPlace: (Place) -> Unit, + distanceStringToPlace: (Double?, Double?) -> String, ) { + val distance = distanceStringToPlace(place.latitude, place.longitude) + val subtitle = if (distance.isBlank()) place.subLabel else "${place.subLabel}$distance" + BottomSheetLocationCard( title = place.name, - subtitle1 = place.subLabel, + subtitle1 = subtitle, isFavorite = true, onFavoriteClick = { onFavoriteStarClick(place) } ) { From 9a16e4683f1a1eef9fffdb7f4ab53f89792c0ac4 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Sat, 14 Mar 2026 20:58:43 -0400 Subject: [PATCH 13/13] fix: use matching logic for gyms as other ecosystem places --- .../transit/ui/components/home/EcosystemBottomSheetContent.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt index 8acc940..7c636d3 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt @@ -351,9 +351,9 @@ private fun LazyListScope.favoriteList( matchingGym.latitude, matchingGym.longitude ), - isFavorite = matchingGym.toPlace() in favorites, + isFavorite = true, onFavoriteClick = { - onFavoriteStarClick(matchingGym.toPlace()) + onFavoriteStarClick(place) }, leftAnnotatedString = operatingHoursToString( matchingGym.operatingHours()