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 7c636d3..dcd6c8a 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 @@ -43,6 +43,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.LibraryCardUiState import com.cornellappdev.transit.util.TimeUtils.isOpenAnnotatedStringFromOperatingHours import com.cornellappdev.transit.util.ecosystem.capacityPercentAnnotatedString import com.cornellappdev.transit.ui.viewmodels.PrinterCardUiState @@ -65,6 +66,7 @@ fun EcosystemBottomSheetContent( activeFilter: FilterState, onFilterClick: (FilterState) -> Unit, staticPlaces: StaticPlaces, + libraryCardsApiResponse: ApiResponse>, favorites: Set, favoritesUiState: EcosystemFavoritesUiState, modifier: Modifier = Modifier, @@ -119,6 +121,7 @@ fun EcosystemBottomSheetContent( BottomSheetFilteredContent( currentFilter = activeFilter, staticPlaces = staticPlaces, + libraryCardsApiResponse = libraryCardsApiResponse, favorites = favorites, favoritesUiState = favoritesUiState, navigateToPlace = navigateToPlace, @@ -153,6 +156,7 @@ fun EcosystemBottomSheetContent( private fun BottomSheetFilteredContent( currentFilter: FilterState, staticPlaces: StaticPlaces, + libraryCardsApiResponse: ApiResponse>, favorites: Set, favoritesUiState: EcosystemFavoritesUiState, navigateToPlace: (Place) -> Unit, @@ -200,7 +204,7 @@ private fun BottomSheetFilteredContent( favorites = favorites, filteredFavorites = favoritesUiState.filteredSortedFavorites, eateryByPlace = favoritesUiState.eateryByPlace, - libraryByPlace = favoritesUiState.libraryByPlace, + libraryCardByPlace = favoritesUiState.libraryCardByPlace, gymByPlace = favoritesUiState.gymByPlace, printerByPlace = favoritesUiState.printerByPlace, navigateToPlace = navigateToPlace, @@ -248,7 +252,7 @@ private fun BottomSheetFilteredContent( FilterState.LIBRARIES -> { libraryList( - staticPlaces, + libraryCardsApiResponse, navigateToPlace, onDetailsClick, favorites, @@ -269,7 +273,7 @@ private fun LazyListScope.favoriteList( favorites: Set, filteredFavorites: List, eateryByPlace: Map, - libraryByPlace: Map, + libraryCardByPlace: Map, gymByPlace: Map, printerByPlace: Map, navigateToPlace: (Place) -> Unit, @@ -319,8 +323,9 @@ private fun LazyListScope.favoriteList( } PlaceType.LIBRARY -> { - val matchingLibrary = libraryByPlace[place] - if (matchingLibrary != null) { + val matchingLibraryCard = libraryCardByPlace[place] + if (matchingLibraryCard != null) { + val matchingLibrary = matchingLibraryCard.library RoundedImagePlaceCard( title = matchingLibrary.location, subtitle = matchingLibrary.address + distanceStringToPlace( @@ -328,9 +333,12 @@ private fun LazyListScope.favoriteList( matchingLibrary.longitude ), isFavorite = true, - onFavoriteClick = { onFavoriteStarClick(place) } + onFavoriteClick = { onFavoriteStarClick(place) }, + placeholderRes = matchingLibraryCard.placeholderRes ) { - onDetailsClick(matchingLibrary) + // Use detailed content sheet when backend is updated + // onDetailsClick(matchingLibrary) + navigateToPlace(matchingLibrary.toPlace()) } } else { StandardCard( @@ -564,14 +572,14 @@ private fun LazyListScope.eateryList( * LazyList scoped enumeration of libraries for bottom sheet */ private fun LazyListScope.libraryList( - staticPlaces: StaticPlaces, + libraryCardsApiResponse: ApiResponse>, navigateToPlace: (Place) -> Unit, navigateToDetails: (DetailedEcosystemPlace) -> Unit, favorites: Set, onFavoriteStarClick: (Place) -> Unit, distanceStringToPlace: (Double?, Double?) -> String, ) { - when (staticPlaces.libraries) { + when (libraryCardsApiResponse) { is ApiResponse.Error -> { } @@ -579,17 +587,20 @@ private fun LazyListScope.libraryList( } is ApiResponse.Success -> { - items(staticPlaces.libraries.data) { + items(libraryCardsApiResponse.data) { + val library = it.library RoundedImagePlaceCard( - placeholderRes = R.drawable.olin_library, - title = it.location, - subtitle = it.address + distanceStringToPlace(it.latitude, it.longitude), - isFavorite = it.toPlace() in favorites, + placeholderRes = it.placeholderRes, + title = library.location, + subtitle = library.address + distanceStringToPlace(library.latitude, library.longitude), + isFavorite = library.toPlace() in favorites, onFavoriteClick = { - onFavoriteStarClick(it.toPlace()) + onFavoriteStarClick(library.toPlace()) } ) { - navigateToDetails(it) + // Use detailed content sheet when backend is updated + // navigateToDetails(library) + navigateToPlace(library.toPlace()) } } } @@ -635,6 +646,7 @@ private fun PreviewEcosystemBottomSheet() { ApiResponse.Pending, ApiResponse.Pending ), + libraryCardsApiResponse = ApiResponse.Pending, favorites = emptySet(), favoritesUiState = EcosystemFavoritesUiState(), modifier = Modifier, @@ -716,6 +728,14 @@ private fun PreviewBottomSheetFilteredContentFavorites() { eateries = ApiResponse.Success(listOf(mockEatery)), gyms = ApiResponse.Success(listOf(mockGym)) ), + libraryCardsApiResponse = ApiResponse.Success( + listOf( + LibraryCardUiState( + library = mockLibrary, + placeholderRes = R.drawable.olin_library + ) + ) + ), favorites = setOf( Place( latitude = 42.4488, @@ -792,7 +812,12 @@ private fun PreviewBottomSheetFilteredContentFavorites() { ) ), eateryByPlace = listOf(mockEatery).associateBy { it.toPlace() }, - libraryByPlace = listOf(mockLibrary).associateBy { it.toPlace() }, + libraryCardByPlace = mapOf( + mockLibrary.toPlace() to LibraryCardUiState( + library = mockLibrary, + placeholderRes = R.drawable.olin_library + ) + ), gymByPlace = listOf(mockGym).associateBy { it.toPlace() }, printerByPlace = mapOf( mockPrinter.toPlace() to PrinterCardUiState( 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 f3aa71d..977fe7a 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 @@ -184,6 +184,7 @@ fun HomeScreen( val filterStateValue = homeViewModel.filterState.collectAsStateWithLifecycle().value val staticPlaces = homeViewModel.staticPlacesFlow.collectAsStateWithLifecycle().value + val libraryCardsApiResponse = homeViewModel.libraryCardsFlow.collectAsStateWithLifecycle().value val ecosystemFavoritesUiState = homeViewModel.ecosystemFavoritesUiState.collectAsStateWithLifecycle().value @@ -336,6 +337,7 @@ fun HomeScreen( onFilterClick = homeViewModel::setCategoryFilter, modifier = Modifier.onTapDisableSearch(), staticPlaces = staticPlaces, + libraryCardsApiResponse = libraryCardsApiResponse, favorites = favorites, favoritesUiState = ecosystemFavoritesUiState, navigateToPlace = { 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 00ba1f6..3f52338 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 @@ -1,8 +1,10 @@ package com.cornellappdev.transit.ui.viewmodels import android.content.Context +import androidx.annotation.DrawableRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.cornellappdev.transit.R import com.cornellappdev.transit.models.LocationRepository import com.cornellappdev.transit.models.Place import com.cornellappdev.transit.models.PlaceType @@ -49,6 +51,25 @@ class HomeViewModel @Inject constructor( private val selectedRouteRepository: SelectedRouteRepository ) : ViewModel() { + val libraryCardsFlow: StateFlow>> = + routeRepository.libraryFlow.map { response -> + when (response) { + is ApiResponse.Success -> { + ApiResponse.Success( + response.data + .filterNot { it.isExcludedLibrary() } + .map { it.toLibraryCardUiState() } + ) + } + is ApiResponse.Pending -> ApiResponse.Pending + is ApiResponse.Error -> ApiResponse.Error + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = ApiResponse.Pending + ) + /** * The current query in the add favorites search bar, as a StateFlow */ @@ -97,7 +118,7 @@ class HomeViewModel @Inject constructor( ) { printers, libraries, eateries, gyms -> StaticPlaces( printers, - libraries, + libraries.withExcludedLibrariesRemoved(), eateries, gyms ) @@ -146,6 +167,7 @@ class HomeViewModel @Inject constructor( val filteredSortedFavorites = favorites.asSequence() .filter { allowedTypes.isEmpty() || it.type in allowedTypes } + .filterNot { it.isExcludedLibraryPlace() } .sortedWith(compareBy({ it.type.ordinal }, { it.name })) .toList() @@ -157,7 +179,9 @@ class HomeViewModel @Inject constructor( EcosystemFavoritesUiState( filteredSortedFavorites = filteredSortedFavorites, eateryByPlace = eateries.associateBy { it.toPlace() }, - libraryByPlace = libraries.associateBy { it.toPlace() }, + libraryCardByPlace = libraries + .map { it.toLibraryCardUiState() } + .associateBy { it.library.toPlace() }, gymByPlace = gyms.associateBy { it.toPlace() }, printerByPlace = printers.associate { printer -> val place = printer.toPlace() @@ -402,11 +426,19 @@ class HomeViewModel @Inject constructor( data class EcosystemFavoritesUiState( val filteredSortedFavorites: List = emptyList(), val eateryByPlace: Map = emptyMap(), - val libraryByPlace: Map = emptyMap(), + val libraryCardByPlace: Map = emptyMap(), val gymByPlace: Map = emptyMap(), val printerByPlace: Map = emptyMap() ) +/** + * UI-ready library fields so composables receive configured fallback/override images. + */ +data class LibraryCardUiState( + val library: Library, + @DrawableRes val placeholderRes: Int +) + /** * UI-ready printer fields so composables don't parse backend strings. */ @@ -431,6 +463,57 @@ private fun Printer.toPrinterCardUiState(): PrinterCardUiState { ) } +private val libraryImageOverridesByLocation: Map = mapOf( + // Temporary placeholders: each location is explicitly configurable for future per-library assets. + "africana studies and research center" to R.drawable.library_img_africana_studies, + "carpenter hall" to R.drawable.library_img_carpenter_hall, + "clark hall" to R.drawable.library_img_clark_hall, + "comstock hall" to R.drawable.library_img_comstock_hall, + "imogene powers johnson center for birds and biodiversity" to R.drawable.olin_library, + "ives hall" to R.drawable.library_img_ives_hall, + "jordan hall" to R.drawable.olin_library, + "lincoln hall" to R.drawable.library_img_lincoln_hall, + "malott hall" to R.drawable.library_img_malott_hall, + "mann library" to R.drawable.library_img_mann_library, + "myron taylor hall" to R.drawable.library_img_myron_taylor_hall, + "myron taylor jane foster library addition" to R.drawable.olin_library, + "olin library" to R.drawable.olin_library, + "rand hall" to R.drawable.library_img_rand_hall, + "sage hall" to R.drawable.library_img_sage_hall, + "statler hall" to R.drawable.library_img_statler_hall, + "vet education center" to R.drawable.library_img_vet_center +) + +private val excludedLibraryLocations: Set = setOf( + "imogene powers johnson center for birds and biodiversity", + "myron taylor jane foster library addition", + "jordan hall" +) + +private fun ApiResponse>.withExcludedLibrariesRemoved(): ApiResponse> { + return when (this) { + is ApiResponse.Success -> ApiResponse.Success(data.filterNot { it.isExcludedLibrary() }) + is ApiResponse.Pending -> ApiResponse.Pending + is ApiResponse.Error -> ApiResponse.Error + } +} + +private fun Library.isExcludedLibrary(): Boolean { + return location.trim().lowercase() in excludedLibraryLocations +} + +private fun Place.isExcludedLibraryPlace(): Boolean { + return type == PlaceType.LIBRARY && name.trim().lowercase() in excludedLibraryLocations +} + +private fun Library.toLibraryCardUiState(): LibraryCardUiState { + val normalizedLocation = location.trim().lowercase() + return LibraryCardUiState( + library = this, + placeholderRes = libraryImageOverridesByLocation[normalizedLocation] ?: R.drawable.olin_library + ) +} + private fun Set.toAllowedPlaceTypes(): Set = buildSet { if (FavoritesFilterSheetState.EATERIES in this@toAllowedPlaceTypes) add(PlaceType.EATERY) if (FavoritesFilterSheetState.LIBRARIES in this@toAllowedPlaceTypes) add(PlaceType.LIBRARY) diff --git a/app/src/main/res/drawable/library_img_africana_studies.jpeg b/app/src/main/res/drawable/library_img_africana_studies.jpeg new file mode 100644 index 0000000..1215e55 Binary files /dev/null and b/app/src/main/res/drawable/library_img_africana_studies.jpeg differ diff --git a/app/src/main/res/drawable/library_img_carpenter_hall.jpg b/app/src/main/res/drawable/library_img_carpenter_hall.jpg new file mode 100644 index 0000000..712f82d Binary files /dev/null and b/app/src/main/res/drawable/library_img_carpenter_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_clark_hall.jpg b/app/src/main/res/drawable/library_img_clark_hall.jpg new file mode 100644 index 0000000..367dc57 Binary files /dev/null and b/app/src/main/res/drawable/library_img_clark_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_comstock_hall.jpg b/app/src/main/res/drawable/library_img_comstock_hall.jpg new file mode 100644 index 0000000..fc2a8cf Binary files /dev/null and b/app/src/main/res/drawable/library_img_comstock_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_ives_hall.jpg b/app/src/main/res/drawable/library_img_ives_hall.jpg new file mode 100644 index 0000000..038e4ab Binary files /dev/null and b/app/src/main/res/drawable/library_img_ives_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_lincoln_hall.jpg b/app/src/main/res/drawable/library_img_lincoln_hall.jpg new file mode 100644 index 0000000..ad7de6f Binary files /dev/null and b/app/src/main/res/drawable/library_img_lincoln_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_malott_hall.jpg b/app/src/main/res/drawable/library_img_malott_hall.jpg new file mode 100644 index 0000000..c755fd3 Binary files /dev/null and b/app/src/main/res/drawable/library_img_malott_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_mann_library.jpg b/app/src/main/res/drawable/library_img_mann_library.jpg new file mode 100644 index 0000000..e1bc202 Binary files /dev/null and b/app/src/main/res/drawable/library_img_mann_library.jpg differ diff --git a/app/src/main/res/drawable/library_img_myron_taylor_hall.jpg b/app/src/main/res/drawable/library_img_myron_taylor_hall.jpg new file mode 100644 index 0000000..aa10c3b Binary files /dev/null and b/app/src/main/res/drawable/library_img_myron_taylor_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_rand_hall.jpg b/app/src/main/res/drawable/library_img_rand_hall.jpg new file mode 100644 index 0000000..b3d6d0f Binary files /dev/null and b/app/src/main/res/drawable/library_img_rand_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_sage_hall.jpeg b/app/src/main/res/drawable/library_img_sage_hall.jpeg new file mode 100644 index 0000000..6f71df6 Binary files /dev/null and b/app/src/main/res/drawable/library_img_sage_hall.jpeg differ diff --git a/app/src/main/res/drawable/library_img_statler_hall.jpg b/app/src/main/res/drawable/library_img_statler_hall.jpg new file mode 100644 index 0000000..8a41478 Binary files /dev/null and b/app/src/main/res/drawable/library_img_statler_hall.jpg differ diff --git a/app/src/main/res/drawable/library_img_vet_center.jpg b/app/src/main/res/drawable/library_img_vet_center.jpg new file mode 100644 index 0000000..ae06241 Binary files /dev/null and b/app/src/main/res/drawable/library_img_vet_center.jpg differ