Skip to content

Show ecosystem type cards in favorites section and make filtering functional#122

Merged
RyanCheung555 merged 16 commits intomainfrom
ryan/favorites-card
Mar 15, 2026
Merged

Show ecosystem type cards in favorites section and make filtering functional#122
RyanCheung555 merged 16 commits intomainfrom
ryan/favorites-card

Conversation

@RyanCheung555
Copy link
Contributor

Overview

  • Eateries, Gyms, Libraries, and Printers now show in the favorites list when they are favorited from their respective categories, and they can be filtered with the pre-existing filter button

Changes Made

  • Implemented filter logic along with logic for displaying different types of ecosystem cards (No image)
  • Added the necessary place types to allow for custom implementations of RoundedImagePlaceCard in favorites list
  • Added previews to EcosystemBottomSheetContent file

Test Coverage

  • Medium Phone, ecosystem build
  • If pre-existing favorites exist, unfavorite all of them to see the newest UI
  • For Library/Printer UI testing, use testflight API instead of existing API

Next Steps (delete if not applicable)

  • Merge gym implementation to fix inconsistencies
  • Add ecosystem type places to the search results (currently only exists in their respective tabs)
  • Make smoother visuals when unfavoriting from favorite's list
  • Update specific card/information UI to more closely resemble designs

Screenshots (delete if not applicable)

Favoriting each type of place
favoriting_functionality.webm
Filtering functionality
filter_functionality.webm

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Home ecosystem bottom sheet favorites experience so that favorited ecosystem places (eateries, gyms, libraries, printers) can appear with their appropriate card types and be filtered using the existing favorites filter UI.

Changes:

  • Added new PlaceType values and updated ecosystem model → Place mappings so favorites can retain the originating ecosystem type.
  • Implemented favorites filtering + type-specific rendering in EcosystemBottomSheetContent (including gym/printer favorite toggling).
  • Updated shared UI components (place cards/images, bottom-sheet cards, search MenuItem) to support the new place types and card behaviors.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
app/src/main/java/com/cornellappdev/transit/util/ecosystem/PlaceUtils.kt Maps ecosystem entities to Place using the new ecosystem PlaceTypes (incl. gym).
app/src/main/java/com/cornellappdev/transit/models/Place.kt Extends PlaceType with EATERY/LIBRARY/GYM/PRINTER and impacts labeling.
app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt Implements favorites filtering and renders ecosystem-type cards in favorites; updates gym/printer lists and adds previews.
app/src/main/java/com/cornellappdev/transit/ui/components/home/RoundedImagePlaceCard.kt Makes the placeholder drawable optional to support cards without images.
app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt Makes placeholder optional and adjusts image rendering logic.
app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceSheetContent.kt Adds a temporary gym details header + directions navigation.
app/src/main/java/com/cornellappdev/transit/ui/components/home/BottomSheetLocationCard.kt Updates bottom-sheet card layout to include favorite star state/callback.
app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt Adds TODO for ecosystem icons and additional previews; impacts icons for new types.
Comments suppressed due to low confidence (2)

app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt:53

  • PlaceCardImage currently renders nothing when placeholderRes is null, even if imageUrl is non-blank, because the AsyncImage branch is gated on placeholderRes != null. This prevents remote images from showing in cases where you intentionally have no placeholder. Consider always rendering AsyncImage when imageUrl is present, and only using placeholderRes for error/placeholder handling (and/or render an empty Box with imageModifier when both are null to preserve layout if needed).
    // 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 if (!imageUrl.isNullOrBlank() && placeholderRes != null) {
        AsyncImage(
            model = imageUrl,
            contentDescription = null,
            error = painterResource(id = placeholderRes),
            contentScale = ContentScale.Crop,
            modifier = imageModifier
        )
    }

app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt:52

  • MenuItem currently treats every PlaceType other than APPLE_PLACE as a bus stop for icon purposes. Now that PlaceType includes EATERY/LIBRARY/GYM/PRINTER, those items (e.g., favorites/recents shown via MenuItem) will incorrectly render the bus stop icon. Consider switching to a when(type) that explicitly maps each PlaceType to the correct icon (or at least a generic place icon for non-bus-stop types).
        //TODO: Add icons for each ecosystem type
        if (type == PlaceType.APPLE_PLACE) {
            Image(
                painterResource(R.drawable.location_pin_gray),
                contentDescription = "Place",
                modifier = Modifier.padding(end = 20.dp),
            )
        } else {
            Image(
                painterResource(R.drawable.bus_stop_pin),
                contentDescription = "Stop",
                modifier = Modifier.padding(end = 20.dp),
            )
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt:52

  • MenuItem now treats every non-APPLE_PLACE type as a bus stop (bus icon + contentDescription "Stop"). With the newly added PlaceType values (EATERY/LIBRARY/GYM/PRINTER), favorites/recents shown in search suggestions and routing will display the wrong icon and accessibility label. Update this conditional to distinguish BUS_STOP from other place types (and provide a sensible default icon/contentDescription for ecosystem places until specific icons are added).
        //TODO: Add icons for each ecosystem type
        if (type == PlaceType.APPLE_PLACE) {
            Image(
                painterResource(R.drawable.location_pin_gray),
                contentDescription = "Place",
                modifier = Modifier.padding(end = 20.dp),
            )
        } else {
            Image(
                painterResource(R.drawable.bus_stop_pin),
                contentDescription = "Stop",
                modifier = Modifier.padding(end = 20.dp),
            )
        }

app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt:53

  • PlaceCardImage currently only renders an AsyncImage when placeholderRes != null. If imageUrl is non-blank but the caller intentionally omits a placeholder (now allowed by the nullable/default param), nothing is rendered at all. Consider rendering AsyncImage whenever imageUrl is present, and only passing the error/fallback painter when a placeholder is provided (and optionally render just the gray background when both are null).
    // 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 if (!imageUrl.isNullOrBlank() && placeholderRes != null) {
        AsyncImage(
            model = imageUrl,
            contentDescription = null,
            error = painterResource(id = placeholderRes),
            contentScale = ContentScale.Crop,
            modifier = imageModifier
        )
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

onAddFavoritesClick
)
}
key(currentFilter, appliedFilters) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this key or will compose just handle it based on those states being used inside?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly sure what your question is, but the key is used so that the scrollstate is set to be at the very top when switching between tabs. If there is an unintended sideeffect or an easier way to do this, I can implement that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be fine

Copy link
Member

@AndrewCheung360 AndrewCheung360 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

app/src/main/java/com/cornellappdev/transit/ui/components/home/PlaceCardImage.kt:49

  • AsyncImage is currently gated by placeholderRes != null, so providing a valid imageUrl with no placeholder results in no image being rendered at all. Consider always rendering AsyncImage when imageUrl is non-blank, and only using placeholderRes for placeholder/error drawables (or render a fixed-size gray box when both are null to keep layout stable).
    } else if (!imageUrl.isNullOrBlank() && placeholderRes != null) {
        AsyncImage(
            model = imageUrl,
            contentDescription = null,
            error = painterResource(id = placeholderRes),

app/src/main/java/com/cornellappdev/transit/ui/components/MenuItem.kt:43

  • With the new PlaceTypes (EATERY/LIBRARY/GYM/PRINTER), this logic will show the bus-stop icon for every non-APPLE_PLACE item (including ecosystem favorites/recents), which is misleading. Update the icon selection to distinguish at least BUS_STOP vs “place-like” types, and optionally add distinct icons per ecosystem type.
        //TODO: Add icons for each ecosystem type
        if (type == PlaceType.APPLE_PLACE) {
            Image(
                painterResource(R.drawable.location_pin_gray),
                contentDescription = "Place",

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +149 to +150
.sortedWith(compareBy<Place>({ it.type.ordinal }, { it.name }))
.toList()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future implementation will be to sort by distance (and by open/closed if possible)

Comment on lines +147 to +156
val filteredSortedFavorites = favorites.asSequence()
.filter { allowedTypes.isEmpty() || it.type in allowedTypes }
.sortedWith(compareBy<Place>({ 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()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old favorites are not a huge concern right now

@RyanCheung555 RyanCheung555 reopened this Mar 15, 2026
@RyanCheung555 RyanCheung555 merged commit 5e4023c into main Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants