From 45b59748d51289cdd4f7a96b3ca98a669991d253 Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Wed, 3 Dec 2025 14:47:39 -0500 Subject: [PATCH 1/9] Gyms cards without capacity or hours --- .../home/DetailedPlaceSheetContent.kt | 13 +- .../home/EcosystemBottomSheetContent.kt | 34 ++++-- .../ui/components/home/GymDetailsContent.kt | 114 ++++++++++++++++++ .../transit/util/ContentConstants.kt | 20 ++- .../transit/util/ecosystem/PlaceUtils.kt | 13 ++ 5 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt 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..2b7b58e 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,13 @@ fun DetailedPlaceSheetContent( } is UpliftGym -> { - //TODO - Text(ecosystemPlace.name) + GymDetailsContent( + gym = ecosystemPlace, + isFavorite = ecosystemPlace.toPlace() in favorites, + onFavoriteClick = { + onFavoriteStarClick(ecosystemPlace.toPlace()) + } + ) } } } @@ -148,7 +153,9 @@ 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 d1eae12..f6053f7 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 @@ -26,11 +26,13 @@ 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.StaticPlaces +import com.cornellappdev.transit.models.ecosystem.UpliftGym import com.cornellappdev.transit.networking.ApiResponse import com.cornellappdev.transit.ui.theme.robotoFamily import com.cornellappdev.transit.ui.viewmodels.FilterState import com.cornellappdev.transit.ui.viewmodels.HomeViewModel import com.cornellappdev.transit.util.ecosystem.toPlace +import com.cornellappdev.transit.util.getGymLocationString /** @@ -120,7 +122,12 @@ private fun BottomSheetFilteredContent( } FilterState.GYMS -> { - gymList(staticPlaces, navigateToPlace) + gymList( + gymsApiResponse = staticPlaces.gyms, + onDetailsClick = onDetailsClick, + favorites = favorites, + onFavoriteStarClick = onFavoriteStarClick, + ) } FilterState.EATERIES -> { @@ -167,23 +174,34 @@ private fun LazyListScope.favoriteList( * LazyList scoped enumeration of gyms for bottom sheet */ private fun LazyListScope.gymList( - staticPlaces: StaticPlaces, - navigateToPlace: (Place) -> Unit + gymsApiResponse: ApiResponse>, + onDetailsClick: (DetailedEcosystemPlace) -> Unit, + favorites: Set, + onFavoriteStarClick: (Place) -> Unit, ) { - when (staticPlaces.gyms) { + when (gymsApiResponse) { is ApiResponse.Error -> { } is ApiResponse.Pending -> { + item { + CenteredSpinningIndicator() + } } is ApiResponse.Success -> { - items(staticPlaces.gyms.data) { - BottomSheetLocationCard( + items(gymsApiResponse.data) { + RoundedImagePlaceCard( + imageUrl = it.imageUrl, title = it.name, - subtitle1 = it.id + subtitle = getGymLocationString(it.name), + isFavorite = it.toPlace() in favorites, + onFavoriteClick = { + onFavoriteStarClick(it.toPlace()) + }, + placeholderRes = R.drawable.olin_library, ) { - //TODO: Eatery + onDetailsClick(it) } } } diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt new file mode 100644 index 0000000..f328a64 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt @@ -0,0 +1,114 @@ +package com.cornellappdev.transit.ui.components.home + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.cornellappdev.transit.R +import com.cornellappdev.transit.models.ecosystem.UpliftGym +import com.cornellappdev.transit.ui.theme.DividerGray +import com.cornellappdev.transit.ui.theme.Gray05 +import com.cornellappdev.transit.ui.theme.PrimaryText +import com.cornellappdev.transit.ui.theme.SecondaryText +import com.cornellappdev.transit.ui.theme.Style +import com.cornellappdev.transit.ui.theme.TransitBlue +import com.cornellappdev.transit.ui.viewmodels.HomeViewModel +import com.cornellappdev.transit.util.getAboutContent +import com.cornellappdev.transit.util.getGymLocationString + +@Composable +fun GymDetailsContent( + homeViewModel: HomeViewModel = hiltViewModel(), + gym: UpliftGym, + isFavorite: Boolean, + onFavoriteClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + start = 20.dp, + end = 20.dp, + ) + ) { + PlaceCardImage( + imageUrl = gym.imageUrl, + placeholderRes = R.drawable.olin_library, + shouldClipBottom = true + ) + + DetailedPlaceHeaderSection( + gym.name, + getGymLocationString(gym.name), + onFavoriteClick = onFavoriteClick, + isFavorite = isFavorite + ) + + Spacer(modifier = Modifier.height(24.dp)) + + HorizontalDivider(thickness = 1.dp, color = DividerGray) + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = "About", + style = Style.detailSubtitle, + color = PrimaryText, + modifier = Modifier.padding(bottom = 12.dp) + ) + + Text( + text = getAboutContent(gym.name), + style = Style.detailBody, + color = SecondaryText, + modifier = Modifier.padding(bottom = 15.dp) + ) + + Text( + text = stringResource(R.string.view_menu), + style = Style.heading2, + color = TransitBlue + ) + + Spacer(modifier = Modifier.height(24.dp)) + + HorizontalDivider(thickness = 1.dp, color = DividerGray) + + Spacer(modifier = Modifier.height(24.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painterResource(R.drawable.location_pin_gray), + contentDescription = null, + modifier = Modifier + .size(20.dp), + tint = Gray05 + ) + Text( + text = getGymLocationString(gym.name) ?: "", + style = Style.detailBody, + color = SecondaryText, + modifier = Modifier.padding(start = 15.dp) + ) + + } + + Spacer(modifier = Modifier.height(24.dp)) + + HorizontalDivider(thickness = 1.dp, color = DividerGray) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt b/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt index d43f21d..9f5635a 100644 --- a/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt +++ b/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt @@ -1,11 +1,13 @@ package com.cornellappdev.transit.util +import android.adservices.adid.AdId + /** * Temporary mapping for about content */ fun getAboutContent(key: String): String { - val aboutContent = buildMap { + val aboutContent = buildMap { put("104West!", "Cornell's kosher and multicultural dining room is STAR-K and STAR-D certified.") put("Becker House Dining Room", "Dining room located in Carl Becker House on West Campus. Open only to residents from 6-7pm Wednesdays for House Dinners.") put("Cook House Dining Room", "Dining room located in Alice Cook House on West Campus. Open only to residents from 6-7pm Wednesdays for House Dinners.") @@ -19,4 +21,20 @@ fun getAboutContent(key: String): String { } return aboutContent.getOrDefault(key, "") +} + +/** + * Mapping gym names to location descriptions + */ +fun getGymLocationString(key: String): String { + val locationStrings = buildMap { + put("Helen Newman", "Helen Newman Hall") + put("Toni Morrison", "Toni Morrison Hall") + put("Noyes", "Noyes Community Recreation Center") + put("Teagle Up", "Teagle Hall") + put("Teagle Down", "Teagle Hall") + + } + + return locationStrings.getOrDefault(key, "") } \ No newline at end of file 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..206317d 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,6 +5,8 @@ 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 +import com.cornellappdev.transit.util.getGymLocationString /** @@ -39,3 +41,14 @@ fun Eatery.toPlace(): Place = Place( detail = this.location, type = PlaceType.APPLE_PLACE ) + +/** + * Predefined mapping from gym to generic place. + */ +fun UpliftGym.toPlace(): Place = Place( + latitude = this.latitude, + longitude = this.longitude, + name = this.name, + detail = getGymLocationString(this.name), + type = PlaceType.APPLE_PLACE +) From 8b4acfc7389c065df990513007e4ae67b165cd4e Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Fri, 5 Dec 2025 13:37:01 -0500 Subject: [PATCH 2/9] Unify operating hours for gyms --- .../ecosystem/DetailedEcosystemPlace.kt | 6 --- .../transit/models/ecosystem/Eatery.kt | 31 +++++------ .../models/ecosystem/EcosystemPlace.kt | 26 +++++++++ .../transit/models/ecosystem/StaticPlaces.kt | 28 +++++++++- .../transit/models/ecosystem/UpliftGym.kt | 36 ++++++++++++- .../home/DetailedPlaceSheetContent.kt | 1 - .../components/home/EateryDetailsContent.kt | 15 ++---- .../home/EcosystemBottomSheetContent.kt | 8 ++- .../ui/components/home/GymDetailsContent.kt | 10 +++- .../ui/components/home/OperatingHoursList.kt | 4 +- .../ui/viewmodels/FavoritesViewModel.kt | 1 - .../cornellappdev/transit/util/TimeUtils.kt | 27 ++++++++++ .../transit/util/ecosystem/PlaceUtils.kt | 54 ------------------- 13 files changed, 148 insertions(+), 99 deletions(-) delete mode 100644 app/src/main/java/com/cornellappdev/transit/models/ecosystem/DetailedEcosystemPlace.kt create mode 100644 app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt delete mode 100644 app/src/main/java/com/cornellappdev/transit/util/ecosystem/PlaceUtils.kt diff --git a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/DetailedEcosystemPlace.kt b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/DetailedEcosystemPlace.kt deleted file mode 100644 index 356ed07..0000000 --- a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/DetailedEcosystemPlace.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.cornellappdev.transit.models.ecosystem - -/** - * Interface for working with places in ecosystem with special details, i.e. hours or capacity - */ -sealed interface DetailedEcosystemPlace \ No newline at end of file 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 6367f8e..1164e32 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 @@ -1,5 +1,8 @@ package com.cornellappdev.transit.models.ecosystem +import com.cornellappdev.transit.models.Place +import com.cornellappdev.transit.models.PlaceType +import com.cornellappdev.transit.util.TimeUtils.dayOrder import com.cornellappdev.transit.util.TimeUtils.toPascalCaseString import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @@ -27,27 +30,13 @@ data class Eatery( @Json(name = "events") val events: List? ) : DetailedEcosystemPlace { - /** - * Value to represent the custom order of days in a week (with Sunday as - * the first day due to a particular design choice). Used for sorting purposes - */ - private val dayOrder = mapOf( - "Sunday" to 1, - "Monday" to 2, - "Tuesday" to 3, - "Wednesday" to 4, - "Thursday" to 5, - "Friday" to 6, - "Saturday" to 7 - ) - /** * @Return a list of pairs representing each day of the week * and the corresponding times that an eatery is open. The list is sorted * by day with the custom dayOrder (Sunday first). */ - fun formatOperatingHours(): List { - val dailyHours = operatingHours() + override fun operatingHours(): List { + val dailyHours = getOperatingHours() // Convert map to list and sort by custom day order return dailyHours.entries @@ -66,7 +55,7 @@ data class Eatery( /** * @Return a map of each day of the week to its list of operating hours */ - private fun operatingHours(): Map> { + private fun getOperatingHours(): Map> { val dailyHours = mutableMapOf>() events?.forEach { event -> @@ -87,6 +76,14 @@ data class Eatery( return dailyHours } + + override fun toPlace(): Place = Place( + latitude = this.latitude ?: 0.0, + longitude = this.longitude ?: 0.0, + name = this.name, + detail = this.location, + type = PlaceType.APPLE_PLACE + ) } diff --git a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt new file mode 100644 index 0000000..08a7c6b --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt @@ -0,0 +1,26 @@ +package com.cornellappdev.transit.models.ecosystem + +import com.cornellappdev.transit.models.Place + +/** + * Specific places such as eateries or gyms + */ +interface EcosystemPlace { + + /** + * Convert from a specific ecosystem place to the generic [Place] class + */ + fun toPlace(): Place +} + + +/** + * Interface for working with places in ecosystem with special details, i.e. hours or capacity + */ +sealed interface DetailedEcosystemPlace: EcosystemPlace { + + /** + * Return the operating hours starting from Sunday + */ + fun operatingHours(): List +} \ No newline at end of file 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 f5074a6..0600cd1 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 @@ -1,5 +1,7 @@ package com.cornellappdev.transit.models.ecosystem +import com.cornellappdev.transit.models.Place +import com.cornellappdev.transit.models.PlaceType import com.cornellappdev.transit.networking.ApiResponse import com.squareup.moshi.Json @@ -22,7 +24,15 @@ data class Printer( @Json(name = "description") var description: String, @Json(name = "latitude") var latitude: Double, @Json(name = "longitude") var longitude: Double -) +) : EcosystemPlace { + override fun toPlace(): Place = Place( + latitude = this.latitude, + longitude = this.longitude, + name = this.location, + detail = this.description, + type = PlaceType.APPLE_PLACE + ) +} /** * Class representing a Cornell library @@ -33,4 +43,18 @@ data class Library( @Json(name = "address") var address: String, @Json(name = "latitude") var latitude: Double, @Json(name = "longitude") var longitude: Double -) : DetailedEcosystemPlace +) : DetailedEcosystemPlace { + + override fun operatingHours(): List { + //TODO: Implement + return emptyList() + } + + override fun toPlace(): Place = Place( + latitude = this.latitude, + longitude = this.longitude, + name = this.location, + detail = this.address, + type = PlaceType.APPLE_PLACE + ) +} 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 4bc95e2..e45aa9c 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 @@ -1,6 +1,10 @@ package com.cornellappdev.transit.models.ecosystem import android.icu.util.Calendar +import com.cornellappdev.transit.models.Place +import com.cornellappdev.transit.models.PlaceType +import com.cornellappdev.transit.util.TimeUtils.dayString +import com.cornellappdev.transit.util.getGymLocationString import kotlin.math.roundToInt /** @@ -38,7 +42,37 @@ data class UpliftGym( val upliftCapacity: UpliftCapacity?, val latitude: Double, val longitude: Double, -) : DetailedEcosystemPlace +) : DetailedEcosystemPlace { + + override fun operatingHours(): List { + + // Sunday is enforced to be first indexed day + val indexedHours = hours.takeLast(1) + hours.dropLast(1) + + val dayOperatingHours = indexedHours.mapIndexed { index, dayHours -> + dayHours?.let { + DayOperatingHours( + dayOfWeek = dayString[index] ?: "", + hours = dayHours.map { timeInterval -> + timeInterval.toString() + + }) + } ?: DayOperatingHours(dayOfWeek = dayString[index] ?: "", hours = listOf("Closed")) + } + + + return dayOperatingHours + + } + + override fun toPlace(): Place = Place( + latitude = this.latitude, + longitude = this.longitude, + name = this.name, + detail = getGymLocationString(this.name), + type = PlaceType.APPLE_PLACE + ) +} /** * A gym's capacity. 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 2b7b58e..6aef567 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 @@ -38,7 +38,6 @@ import com.cornellappdev.transit.ui.theme.SecondaryText import com.cornellappdev.transit.ui.theme.Style import com.cornellappdev.transit.ui.theme.TransitBlue import com.cornellappdev.transit.util.BOTTOM_SHEET_MAX_HEIGHT_PERCENT -import com.cornellappdev.transit.util.ecosystem.toPlace @Composable fun DetailedPlaceSheetContent( diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EateryDetailsContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EateryDetailsContent.kt index 64da8a8..323d170 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/EateryDetailsContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/EateryDetailsContent.kt @@ -1,8 +1,5 @@ package com.cornellappdev.transit.ui.components.home -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -10,20 +7,14 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.transit.R @@ -62,7 +53,7 @@ fun EateryDetailsContent( eatery.name, eatery.campusArea, leftAnnotatedString = homeViewModel.isOpenAnnotatedStringFromOperatingHours( - eatery.formatOperatingHours() + eatery.operatingHours() ), onFavoriteClick = onFavoriteClick, isFavorite = isFavorite @@ -122,8 +113,8 @@ fun EateryDetailsContent( HorizontalDivider(thickness = 1.dp, color = DividerGray) ExpandableOperatingHoursList( - homeViewModel.isOpenAnnotatedStringFromOperatingHours(eatery.formatOperatingHours()), - homeViewModel.rotateOperatingHours(eatery.formatOperatingHours()) + homeViewModel.isOpenAnnotatedStringFromOperatingHours(eatery.operatingHours()), + homeViewModel.rotateOperatingHours(eatery.operatingHours()) ) } 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 f6053f7..50196d9 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 @@ -31,7 +31,6 @@ import com.cornellappdev.transit.networking.ApiResponse import com.cornellappdev.transit.ui.theme.robotoFamily import com.cornellappdev.transit.ui.viewmodels.FilterState import com.cornellappdev.transit.ui.viewmodels.HomeViewModel -import com.cornellappdev.transit.util.ecosystem.toPlace import com.cornellappdev.transit.util.getGymLocationString @@ -127,6 +126,7 @@ private fun BottomSheetFilteredContent( onDetailsClick = onDetailsClick, favorites = favorites, onFavoriteStarClick = onFavoriteStarClick, + operatingHoursToString = homeViewModel::isOpenAnnotatedStringFromOperatingHours ) } @@ -178,6 +178,7 @@ private fun LazyListScope.gymList( onDetailsClick: (DetailedEcosystemPlace) -> Unit, favorites: Set, onFavoriteStarClick: (Place) -> Unit, + operatingHoursToString: (List) -> AnnotatedString ) { when (gymsApiResponse) { is ApiResponse.Error -> { @@ -199,6 +200,9 @@ private fun LazyListScope.gymList( onFavoriteClick = { onFavoriteStarClick(it.toPlace()) }, + leftAnnotatedString = operatingHoursToString( + it.operatingHours() + ), placeholderRes = R.drawable.olin_library, ) { onDetailsClick(it) @@ -269,7 +273,7 @@ private fun LazyListScope.eateryList( }, placeholderRes = R.drawable.olin_library, leftAnnotatedString = operatingHoursToString( - it.formatOperatingHours() + it.operatingHours() ) ) { onDetailsClick(it) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt index f328a64..d5ac140 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt @@ -54,7 +54,10 @@ fun GymDetailsContent( gym.name, getGymLocationString(gym.name), onFavoriteClick = onFavoriteClick, - isFavorite = isFavorite + isFavorite = isFavorite, + leftAnnotatedString = homeViewModel.isOpenAnnotatedStringFromOperatingHours( + gym.operatingHours() + ) ) Spacer(modifier = Modifier.height(24.dp)) @@ -110,5 +113,10 @@ fun GymDetailsContent( HorizontalDivider(thickness = 1.dp, color = DividerGray) + ExpandableOperatingHoursList( + homeViewModel.isOpenAnnotatedStringFromOperatingHours(gym.operatingHours()), + homeViewModel.rotateOperatingHours(gym.operatingHours()) + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/OperatingHoursList.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/OperatingHoursList.kt index 4afd729..7813e4e 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/OperatingHoursList.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/OperatingHoursList.kt @@ -67,7 +67,7 @@ private fun OperatingHoursRow( ) { Row( horizontalArrangement = Arrangement.Start, - modifier = Modifier.weight(0.6f) + modifier = Modifier.weight(0.58f) ) { if (isHighlighted) { VerticalDivider( @@ -89,7 +89,7 @@ private fun OperatingHoursRow( Column( horizontalAlignment = Alignment.Start, - modifier = Modifier.weight(0.4f) + modifier = Modifier.weight(0.42f) ) { hours.forEach { timeRange -> Text( diff --git a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FavoritesViewModel.kt b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FavoritesViewModel.kt index 5d980e9..5503132 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FavoritesViewModel.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/viewmodels/FavoritesViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import com.cornellappdev.transit.models.RouteRepository import com.cornellappdev.transit.models.Place import com.cornellappdev.transit.models.UserPreferenceRepository -import com.cornellappdev.transit.util.ecosystem.toPlace import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/cornellappdev/transit/util/TimeUtils.kt b/app/src/main/java/com/cornellappdev/transit/util/TimeUtils.kt index 34642b0..aabe29f 100644 --- a/app/src/main/java/com/cornellappdev/transit/util/TimeUtils.kt +++ b/app/src/main/java/com/cornellappdev/transit/util/TimeUtils.kt @@ -14,6 +14,33 @@ import java.util.Locale */ object TimeUtils { + /** + * Value to represent the custom order of days in a week (with Sunday as + * the first day). Sunday first indexing courtesy of Eatery + */ + val dayOrder = mapOf( + "Sunday" to 0, + "Monday" to 1, + "Tuesday" to 2, + "Wednesday" to 3, + "Thursday" to 4, + "Friday" to 5, + "Saturday" to 6 + ) + + /** + * Mapping from order to day + */ + val dayString = mapOf( + 0 to "Sunday", + 1 to "Monday", + 2 to "Tuesday", + 3 to "Wednesday", + 4 to "Thursday", + 5 to "Friday", + 6 to "Saturday" + ) + /** * Formatter for date and time. Ex: Mar 17, 1998 4:42 AM */ 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 deleted file mode 100644 index 206317d..0000000 --- a/app/src/main/java/com/cornellappdev/transit/util/ecosystem/PlaceUtils.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.cornellappdev.transit.util.ecosystem - -import com.cornellappdev.transit.models.Place -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 -import com.cornellappdev.transit.util.getGymLocationString - - -/** - * Predefined mapping from library to generic place - */ -fun Library.toPlace(): Place = Place( - latitude = this.latitude, - longitude = this.longitude, - name = this.location, - detail = this.address, - type = PlaceType.APPLE_PLACE -) - -/** - * Predefined mapping from printer to generic place - */ -fun Printer.toPlace(): Place = Place( - latitude = this.latitude, - longitude = this.longitude, - name = this.location, - detail = this.description, - type = PlaceType.APPLE_PLACE -) - -/** - * Predefined mapping from eatery to generic 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 -) - -/** - * Predefined mapping from gym to generic place. - */ -fun UpliftGym.toPlace(): Place = Place( - latitude = this.latitude, - longitude = this.longitude, - name = this.name, - detail = getGymLocationString(this.name), - type = PlaceType.APPLE_PLACE -) From ac1edc7a118a4e63d31709f0a275087a8cb6b48f Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Fri, 6 Feb 2026 15:30:52 -0500 Subject: [PATCH 3/9] Clean up docs --- .../com/cornellappdev/transit/models/ecosystem/Eatery.kt | 8 +++----- .../transit/models/ecosystem/EcosystemPlace.kt | 4 +++- 2 files changed, 6 insertions(+), 6 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 a45dbf5..c83c4ae 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 @@ -1,5 +1,8 @@ package com.cornellappdev.transit.models.ecosystem +import com.cornellappdev.transit.models.Place +import com.cornellappdev.transit.models.PlaceType +import com.cornellappdev.transit.util.TimeUtils.dayOrder import com.cornellappdev.transit.util.TimeUtils.toPascalCaseString import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @@ -27,11 +30,6 @@ data class Eatery( @Json(name = "events") val events: List? ) : DetailedEcosystemPlace { - /** - * @Return a list of associated dayOfWeek and hours pairs in [DayOperatingHours] representing - * each day of the week and the corresponding times that an eatery is open. The list is sorted - * by day with the custom dayOrder (Sunday first). - */ override fun operatingHours(): List { val dailyHours = getOperatingHours() diff --git a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt index 08a7c6b..36e61e8 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/ecosystem/EcosystemPlace.kt @@ -20,7 +20,9 @@ interface EcosystemPlace { sealed interface DetailedEcosystemPlace: EcosystemPlace { /** - * Return the operating hours starting from Sunday + * @Return a list of associated dayOfWeek and hours pairs in [DayOperatingHours] representing + * each day of the week and the corresponding times that an eatery is open. The list is sorted + * by day with the custom dayOrder (Sunday first). */ fun operatingHours(): List } \ No newline at end of file From 0d897f4e39f578edb3bef80b2e1c3bd3f65c3b39 Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Tue, 10 Feb 2026 21:12:49 -0500 Subject: [PATCH 4/9] Add circular indicator from uplift --- .../transit/models/LocationRepository.kt | 2 +- .../components/home/GymCapacityIndicator.kt | 126 ++++++++++++++++++ .../ui/components/home/GymDetailsContent.kt | 10 +- .../cornellappdev/transit/ui/theme/Color.kt | 10 +- .../transit/ui/viewmodels/HomeViewModel.kt | 35 ++++- .../cornellappdev/transit/util/ColorUtils.kt | 37 +++++ .../transit/util/ContentConstants.kt | 5 + app/src/main/res/values/strings.xml | 1 + 8 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt create mode 100644 app/src/main/java/com/cornellappdev/transit/util/ColorUtils.kt diff --git a/app/src/main/java/com/cornellappdev/transit/models/LocationRepository.kt b/app/src/main/java/com/cornellappdev/transit/models/LocationRepository.kt index d73d474..89ce16b 100644 --- a/app/src/main/java/com/cornellappdev/transit/models/LocationRepository.kt +++ b/app/src/main/java/com/cornellappdev/transit/models/LocationRepository.kt @@ -24,7 +24,7 @@ import javax.inject.Singleton @Singleton class LocationRepository @Inject constructor(private val routesNetworkApi: RoutesNetworkApi) { - //Source: Uplift + // Source: Uplift Android private lateinit var fusedLocationClient: FusedLocationProviderClient private val _currentLocation: MutableStateFlow = MutableStateFlow(null) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt new file mode 100644 index 0000000..da1df88 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt @@ -0,0 +1,126 @@ +package com.cornellappdev.transit.ui.components.home + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.cornellappdev.transit.models.ecosystem.UpliftCapacity +import com.cornellappdev.transit.ui.theme.AccentClosed +import com.cornellappdev.transit.ui.theme.AccentOpen +import com.cornellappdev.transit.ui.theme.AccentOrange +import com.cornellappdev.transit.ui.theme.Gray02 +import com.cornellappdev.transit.ui.theme.Gray04 +import com.cornellappdev.transit.ui.theme.PrimaryText +import com.cornellappdev.transit.ui.theme.robotoFamily +import com.cornellappdev.transit.util.colorInterp + +// Source: Uplift Android + +/** + * A circular indicator for the capacity at a given gym. + * + * @param capacity A tuple whose first element is the current number of people at the gym + * and whose second element is the max capacity. + * @param label The name of the gym placed under this indicator. Can be null to indicate no + * label. + * @param closed If this gym is closed. + */ +@Composable +fun GymCapacityIndicator( + capacity: UpliftCapacity?, + label: String?, + closed: Boolean, + gymDetail: Boolean = false +) { + val grayedOut = closed || capacity == null + + val fraction = if (!grayedOut) capacity!!.percent.toFloat() else 0f + val animatedFraction = remember { Animatable(0f) } + + // When the composable launches, animate the fraction to the capacity fraction. + LaunchedEffect(animatedFraction) { + animatedFraction.animateTo( + fraction, + animationSpec = tween(durationMillis = 750) + ) + } + + val orangeCutoff = .65f + + // Choose a color. If between 0 & 0.5, tween between open and orange. If between 0.5 and 1, + // tween between orange and closed. + val color = + if (fraction > orangeCutoff) + colorInterp( + (fraction - orangeCutoff) / (1 - orangeCutoff), + AccentOrange, + AccentClosed + ) + else + colorInterp( + fraction / orangeCutoff, + AccentOpen, + AccentClosed + ) + + val size = if (gymDetail) 104.5.dp else 72.dp + val percentFontSize = + (if (gymDetail) 17.sp else 12.sp) * (if (closed || capacity != null) 1f else .9f) + val labelColor = if (gymDetail) Gray04 else PrimaryText + val labelFontWeight = if (gymDetail) FontWeight(300) else FontWeight(600) + val labelPadding = if (gymDetail) 9.dp else 12.dp + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box { + CircularProgressIndicator( + color = Gray02, + strokeWidth = size / 9, + modifier = Modifier.size(size), + progress = 1f + ) + CircularProgressIndicator( + color = color, + strokeWidth = size / 9, + modifier = Modifier.size(size), + progress = animatedFraction.value, + strokeCap = StrokeCap.Round + ) + Text( + text = if (closed) "CLOSED" + else capacity?.percentString() ?: "NO DATA", + fontFamily = robotoFamily, + fontSize = percentFontSize, + fontWeight = FontWeight(700), + color = if (grayedOut) Gray02 else PrimaryText, + modifier = Modifier.align(Alignment.Center), + textAlign = TextAlign.Center, + ) + } + if (label != null && !(gymDetail && grayedOut)) { + Spacer(modifier = Modifier.height(labelPadding)) + Text( + text = label, + fontFamily = robotoFamily, + fontSize = 14.sp, + fontWeight = labelFontWeight, + color = labelColor, + textAlign = TextAlign.Center, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt index d5ac140..0879c88 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.transit.R @@ -57,9 +58,14 @@ fun GymDetailsContent( isFavorite = isFavorite, leftAnnotatedString = homeViewModel.isOpenAnnotatedStringFromOperatingHours( gym.operatingHours() - ) + ), + rightAnnotatedString = buildAnnotatedString { + append(gym.upliftCapacity?.percent.toString()) + } ) + GymCapacityIndicator(capacity = gym.upliftCapacity, label = null, closed = false) + Spacer(modifier = Modifier.height(24.dp)) HorizontalDivider(thickness = 1.dp, color = DividerGray) @@ -81,7 +87,7 @@ fun GymDetailsContent( ) Text( - text = stringResource(R.string.view_menu), + text = stringResource(R.string.view_gym), style = Style.heading2, color = TransitBlue ) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt b/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt index 7230a12..7636a30 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/theme/Color.kt @@ -25,4 +25,12 @@ val DetailsDividerGray = Color(0xffc6c6c8) val FavoritesYellow = Color(0xFFFEC50E) -val Gray05 = Color(0xFF586069) \ No newline at end of file +val Gray02 = Color(0xFFA5A5A5) +val Gray04 = Color(0xFF707070) +val Gray05 = Color(0xFF586069) + +// Gyms + +val AccentOpen = Color(0xFF64C270) +val AccentClosed = Color(0xFFF07D7D) +val AccentOrange = Color(0xFFFE8F13) \ No newline at end of file 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 2c7c9e7..bb7cc24 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,7 +4,6 @@ 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 @@ -37,7 +36,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 @@ -443,4 +441,37 @@ class HomeViewModel @Inject constructor( ) } + /** + * Format percent string based on a gym's current capacity + */ + fun capacityPercentAnnotatedString(percent: Double): AnnotatedString { + return buildAnnotatedString { + if (percent <= 0.65) { + withStyle( + style = SpanStyle( + color = LiveGreen, + ) + ) { + append("Open") + } + } else { + withStyle( + style = SpanStyle( + color = LateRed + ) + ) { + append("Closed") + } + } + withStyle( + style = SpanStyle( + color = SecondaryText + ) + ) { + append(" - ") + //append(openStatus.nextChangeTime) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/util/ColorUtils.kt b/app/src/main/java/com/cornellappdev/transit/util/ColorUtils.kt new file mode 100644 index 0000000..d3c7e93 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/transit/util/ColorUtils.kt @@ -0,0 +1,37 @@ +package com.cornellappdev.transit.util + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb + +// Source: Uplift Android + +/** + * Interpolates a color between [color1] and [color2] by choosing a color a [fraction] in between. + * Uses HSV interpolation, which generally gives more aesthetically pleasing results than RGB. + * + * @param fraction Float in [0..1]. 0 = color1, 1 = color2. In between interpolates between. + */ +fun colorInterp(fraction: Float, color1: Color, color2: Color): Color { + val fractionToUse = fraction.coerceIn(0f, 1f) + val HSV1 = FloatArray(3) + val HSV2 = FloatArray(3) + android.graphics.Color.colorToHSV(color1.toArgb(), HSV1) + android.graphics.Color.colorToHSV(color2.toArgb(), HSV2) + + for (i in 0..2) { + HSV2[i] = interpolate(fractionToUse, HSV1[i], HSV2[i]) + } + return Color.hsv( + HSV2[0], + HSV2[1], + HSV2[2], + interpolate(fractionToUse, color1.alpha, color2.alpha) + ) +} + +/** + * Interpolates between two floats. + */ +private fun interpolate(fraction: Float, a: Float, b: Float): Float { + return a + (b - a) * fraction +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt b/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt index 9f5635a..1f47111 100644 --- a/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt +++ b/app/src/main/java/com/cornellappdev/transit/util/ContentConstants.kt @@ -18,6 +18,11 @@ fun getAboutContent(key: String): String { put("Okenshields", "Dining room located in Willard Straight Hall on Central Campus.") put("Risley Dining Room", "Risley is our gluten-free, tree nut free and peanut free dining room under the AllerCheckā„¢\uFE0F approved by MenuTrinfoĀ®\uFE0F program, in Risley Residential College on North Campus.") put("Rose House Dining Room", "Dining room located in Flora Rose House on West Campus. Open only to residents from 6-7pm Wednesdays for House Dinners.") + put("Helen Newman", "Helen Newman Hall features a Pool, two-full sized Courts for Basketball/Volleyball/Badminton, a classroom and a dance studio where many Group Fitness classes, Physical Education classes, and club practices are held, a 16-lane Bowling Center and a Fitness Center. Helen Newman is also the home to the majority of the Recreational Services Administration Offices.") + put("Toni Morrison", "The Toni Morrison Fitness Center is located in the basement of Toni Morrison Hall.") + put("Noyes", "The Noyes Fitness Center is located on the second floor of the Noyes Community Recreation Center, adjacent to Jansen's Market.") + put("Teagle Down", "The Teagle Downstairs Fitness Center is located on the ground floor of Teagle Hall. The entrance of this Fitness Center is adjacent from the entrance to Teagle Hall from the parking lot facing the Lynah Ice Rink.") + put("Teagle Up", "The Teagle Upstairs Fitness Center is located on the second floor of Teagle Hall. The staircase to the entrance of this Fitness Center is directly across from the entrance to Teagle Hall from the parking lot facing the Lynah Ice Rink.") } return aboutContent.getOrDefault(key, "") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6488bf..bc71cee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,4 +10,5 @@ Covers humanities and social sciences with circulation and reference services. Reserve a room View Menu on Eatery + Learn More on Uplift \ No newline at end of file From e2238c8f34656b10040783729301d2f2756d2050 Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Thu, 12 Feb 2026 13:32:38 -0500 Subject: [PATCH 5/9] capacity on main screen --- .../home/DetailedPlaceHeaderSection.kt | 65 +++++++++++++++++++ .../home/EcosystemBottomSheetContent.kt | 10 ++- .../components/home/GymCapacityIndicator.kt | 22 +++---- .../ui/components/home/GymDetailsContent.kt | 14 ++-- .../transit/ui/viewmodels/HomeViewModel.kt | 42 ++++++------ .../transit/util/TransitConstants.kt | 2 + 6 files changed, 113 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceHeaderSection.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceHeaderSection.kt index 2232832..bd2a5cd 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceHeaderSection.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/DetailedPlaceHeaderSection.kt @@ -1,14 +1,18 @@ package com.cornellappdev.transit.ui.components.home import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.BottomEnd +import androidx.compose.ui.Alignment.Companion.TopEnd import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow @@ -82,6 +86,67 @@ fun DetailedPlaceHeaderSection( } } +/** + * Text area of detailed place header with favorites star on the top right and widget on the bottom right + */ +@Composable +fun DetailedPlaceHeaderSectionWithWidget( + title: String, + subtitle: String?, + leftAnnotatedString: AnnotatedString? = null, + widget: @Composable BoxScope.() -> Unit, + onFavoriteClick: () -> Unit, + isFavorite: Boolean +) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp) + ) { + Column( + modifier = Modifier + .align(Alignment.CenterStart), + ) { + Text( + text = title, + style = Style.detailHeading, + color = PrimaryText, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(end = 32.dp, bottom = 12.dp) + ) + subtitle?.let { + Text( + text = subtitle, + style = Style.cardSubtitle, + color = SecondaryText, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(end = 32.dp, bottom = 8.dp) + + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + leftAnnotatedString?.let { + Text( + text = leftAnnotatedString, + style = Style.cardSubtitle + ) + } + Spacer(modifier = Modifier.weight(1f)) + } + } + + Box(Modifier.align(BottomEnd)) { + widget() + } + FavoritesStar(onFavoriteClick, isFavorite) + } +} + @Preview(showBackground = true) @Composable private fun DetailedPlaceHeaderSectionPreview() { 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 50196d9..b587798 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 @@ -26,6 +26,7 @@ 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.StaticPlaces +import com.cornellappdev.transit.models.ecosystem.UpliftCapacity import com.cornellappdev.transit.models.ecosystem.UpliftGym import com.cornellappdev.transit.networking.ApiResponse import com.cornellappdev.transit.ui.theme.robotoFamily @@ -126,7 +127,8 @@ private fun BottomSheetFilteredContent( onDetailsClick = onDetailsClick, favorites = favorites, onFavoriteStarClick = onFavoriteStarClick, - operatingHoursToString = homeViewModel::isOpenAnnotatedStringFromOperatingHours + operatingHoursToString = homeViewModel::isOpenAnnotatedStringFromOperatingHours, + capacityToString = homeViewModel::capacityPercentAnnotatedString ) } @@ -178,7 +180,8 @@ private fun LazyListScope.gymList( onDetailsClick: (DetailedEcosystemPlace) -> Unit, favorites: Set, onFavoriteStarClick: (Place) -> Unit, - operatingHoursToString: (List) -> AnnotatedString + operatingHoursToString: (List) -> AnnotatedString, + capacityToString: (UpliftCapacity?) -> AnnotatedString, ) { when (gymsApiResponse) { is ApiResponse.Error -> { @@ -203,6 +206,9 @@ private fun LazyListScope.gymList( leftAnnotatedString = operatingHoursToString( it.operatingHours() ), + rightAnnotatedString = capacityToString( + it.upliftCapacity + ), placeholderRes = R.drawable.olin_library, ) { onDetailsClick(it) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt index da1df88..5419718 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt @@ -27,6 +27,7 @@ import com.cornellappdev.transit.ui.theme.Gray02 import com.cornellappdev.transit.ui.theme.Gray04 import com.cornellappdev.transit.ui.theme.PrimaryText import com.cornellappdev.transit.ui.theme.robotoFamily +import com.cornellappdev.transit.util.LIMITED_CAPACITY_THRESHOLD import com.cornellappdev.transit.util.colorInterp // Source: Uplift Android @@ -45,7 +46,6 @@ fun GymCapacityIndicator( capacity: UpliftCapacity?, label: String?, closed: Boolean, - gymDetail: Boolean = false ) { val grayedOut = closed || capacity == null @@ -60,30 +60,28 @@ fun GymCapacityIndicator( ) } - val orangeCutoff = .65f - // Choose a color. If between 0 & 0.5, tween between open and orange. If between 0.5 and 1, // tween between orange and closed. val color = - if (fraction > orangeCutoff) + if (fraction > LIMITED_CAPACITY_THRESHOLD) colorInterp( - (fraction - orangeCutoff) / (1 - orangeCutoff), + (fraction - LIMITED_CAPACITY_THRESHOLD) / (1 - LIMITED_CAPACITY_THRESHOLD), AccentOrange, AccentClosed ) else colorInterp( - fraction / orangeCutoff, + fraction / LIMITED_CAPACITY_THRESHOLD, AccentOpen, AccentClosed ) - val size = if (gymDetail) 104.5.dp else 72.dp + val size = 54.dp val percentFontSize = - (if (gymDetail) 17.sp else 12.sp) * (if (closed || capacity != null) 1f else .9f) - val labelColor = if (gymDetail) Gray04 else PrimaryText - val labelFontWeight = if (gymDetail) FontWeight(300) else FontWeight(600) - val labelPadding = if (gymDetail) 9.dp else 12.dp + 12.sp * (if (closed || capacity != null) 1f else .9f) + val labelColor = PrimaryText + val labelFontWeight = FontWeight(600) + val labelPadding = 12.dp Column(horizontalAlignment = Alignment.CenterHorizontally) { Box { @@ -111,7 +109,7 @@ fun GymCapacityIndicator( textAlign = TextAlign.Center, ) } - if (label != null && !(gymDetail && grayedOut)) { + if (label != null) { Spacer(modifier = Modifier.height(labelPadding)) Text( text = label, diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt index 0879c88..2eef9fb 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt @@ -51,7 +51,7 @@ fun GymDetailsContent( shouldClipBottom = true ) - DetailedPlaceHeaderSection( + DetailedPlaceHeaderSectionWithWidget( gym.name, getGymLocationString(gym.name), onFavoriteClick = onFavoriteClick, @@ -59,13 +59,15 @@ fun GymDetailsContent( leftAnnotatedString = homeViewModel.isOpenAnnotatedStringFromOperatingHours( gym.operatingHours() ), - rightAnnotatedString = buildAnnotatedString { - append(gym.upliftCapacity?.percent.toString()) - } + widget = { + GymCapacityIndicator( + capacity = gym.upliftCapacity, + label = null, + closed = false + ) + }, ) - GymCapacityIndicator(capacity = gym.upliftCapacity, label = null, closed = false) - Spacer(modifier = Modifier.height(24.dp)) HorizontalDivider(thickness = 1.dp, color = DividerGray) 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 bb7cc24..57e395c 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 @@ -2,9 +2,12 @@ package com.cornellappdev.transit.ui.viewmodels import android.content.Context import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.PlatformTextStyle 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.compose.ui.unit.sp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.cornellappdev.transit.models.LocationRepository @@ -16,10 +19,14 @@ import com.cornellappdev.transit.models.UserPreferenceRepository import com.cornellappdev.transit.models.ecosystem.DayOperatingHours import com.cornellappdev.transit.models.ecosystem.EateryRepository import com.cornellappdev.transit.models.ecosystem.GymRepository +import com.cornellappdev.transit.models.ecosystem.UpliftCapacity import com.cornellappdev.transit.networking.ApiResponse +import com.cornellappdev.transit.ui.theme.AccentOrange 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.ui.theme.robotoFamily +import com.cornellappdev.transit.util.LIMITED_CAPACITY_THRESHOLD import com.cornellappdev.transit.util.TimeUtils.toPascalCaseString import com.google.android.gms.maps.model.LatLng import dagger.hilt.android.lifecycle.HiltViewModel @@ -444,32 +451,23 @@ class HomeViewModel @Inject constructor( /** * Format percent string based on a gym's current capacity */ - fun capacityPercentAnnotatedString(percent: Double): AnnotatedString { - return buildAnnotatedString { - if (percent <= 0.65) { - withStyle( - style = SpanStyle( - color = LiveGreen, - ) - ) { - append("Open") - } - } else { - withStyle( - style = SpanStyle( - color = LateRed - ) - ) { - append("Closed") - } - } + fun capacityPercentAnnotatedString(capacity: UpliftCapacity?): AnnotatedString { + + // Return empty string if no capacity data available + if (capacity == null) { + return AnnotatedString("") + } + + return buildAnnotatedString { withStyle( style = SpanStyle( - color = SecondaryText + fontSize = 14.sp, + fontFamily = robotoFamily, + fontWeight = FontWeight(600), + color = if (capacity.percent <= LIMITED_CAPACITY_THRESHOLD) LiveGreen else AccentOrange, ) ) { - append(" - ") - //append(openStatus.nextChangeTime) + append("${capacity.percentString()} full") } } } diff --git a/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt b/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt index 694b25b..a6c8404 100644 --- a/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt +++ b/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt @@ -6,4 +6,6 @@ const val ECOSYSTEM_FLAG = BuildConfig.ECOSYSTEM_FLAG const val BOTTOM_SHEET_MAX_HEIGHT_PERCENT = 90 +const val LIMITED_CAPACITY_THRESHOLD = 0.65f + const val NOTIFICATIONS_ENABLED = false \ No newline at end of file From 3277a8c676f57409588079aefdf5f61374938b1f Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Thu, 12 Feb 2026 20:57:19 -0500 Subject: [PATCH 6/9] Fix padding --- .../transit/ui/components/home/RoundedImagePlaceCard.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 9fa4ab0..07e8156 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 @@ -42,9 +42,7 @@ fun RoundedImagePlaceCard( onClick: () -> Unit, ) { Column( - modifier = Modifier - .padding(horizontal = 24.dp, vertical = 10.dp) - .clickable { onClick() } + modifier = Modifier.clickable { onClick() } ) { Column( modifier = Modifier From 8449b760fc756878251250437178fd5c5a08f705 Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Sun, 22 Feb 2026 21:29:17 -0500 Subject: [PATCH 7/9] Adjust capacity indicator --- .../components/home/GymCapacityIndicator.kt | 24 +++++++------------ .../ui/components/home/GymDetailsContent.kt | 17 ++++++++----- .../transit/ui/viewmodels/HomeViewModel.kt | 17 ++++++++++--- .../transit/util/TransitConstants.kt | 6 ++++- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt index 5419718..e0159e8 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt @@ -24,10 +24,9 @@ import com.cornellappdev.transit.ui.theme.AccentClosed import com.cornellappdev.transit.ui.theme.AccentOpen import com.cornellappdev.transit.ui.theme.AccentOrange import com.cornellappdev.transit.ui.theme.Gray02 -import com.cornellappdev.transit.ui.theme.Gray04 import com.cornellappdev.transit.ui.theme.PrimaryText import com.cornellappdev.transit.ui.theme.robotoFamily -import com.cornellappdev.transit.util.LIMITED_CAPACITY_THRESHOLD +import com.cornellappdev.transit.util.HIGH_CAPACITY_THRESHOLD import com.cornellappdev.transit.util.colorInterp // Source: Uplift Android @@ -39,17 +38,14 @@ import com.cornellappdev.transit.util.colorInterp * and whose second element is the max capacity. * @param label The name of the gym placed under this indicator. Can be null to indicate no * label. - * @param closed If this gym is closed. */ @Composable fun GymCapacityIndicator( - capacity: UpliftCapacity?, + capacity: UpliftCapacity, label: String?, - closed: Boolean, ) { - val grayedOut = closed || capacity == null - val fraction = if (!grayedOut) capacity!!.percent.toFloat() else 0f + val fraction = capacity.percent.toFloat() val animatedFraction = remember { Animatable(0f) } // When the composable launches, animate the fraction to the capacity fraction. @@ -63,22 +59,21 @@ fun GymCapacityIndicator( // Choose a color. If between 0 & 0.5, tween between open and orange. If between 0.5 and 1, // tween between orange and closed. val color = - if (fraction > LIMITED_CAPACITY_THRESHOLD) + if (fraction > HIGH_CAPACITY_THRESHOLD) colorInterp( - (fraction - LIMITED_CAPACITY_THRESHOLD) / (1 - LIMITED_CAPACITY_THRESHOLD), + (fraction - HIGH_CAPACITY_THRESHOLD) / (1 - HIGH_CAPACITY_THRESHOLD), AccentOrange, AccentClosed ) else colorInterp( - fraction / LIMITED_CAPACITY_THRESHOLD, + fraction / HIGH_CAPACITY_THRESHOLD, AccentOpen, AccentClosed ) val size = 54.dp - val percentFontSize = - 12.sp * (if (closed || capacity != null) 1f else .9f) + val percentFontSize = 12.sp val labelColor = PrimaryText val labelFontWeight = FontWeight(600) val labelPadding = 12.dp @@ -99,12 +94,11 @@ fun GymCapacityIndicator( strokeCap = StrokeCap.Round ) Text( - text = if (closed) "CLOSED" - else capacity?.percentString() ?: "NO DATA", + text = capacity.percentString(), fontFamily = robotoFamily, fontSize = percentFontSize, fontWeight = FontWeight(700), - color = if (grayedOut) Gray02 else PrimaryText, + color = PrimaryText, modifier = Modifier.align(Alignment.Center), textAlign = TextAlign.Center, ) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt index 2eef9fb..0d0fbc8 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymDetailsContent.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.transit.R @@ -30,6 +29,9 @@ import com.cornellappdev.transit.ui.viewmodels.HomeViewModel import com.cornellappdev.transit.util.getAboutContent import com.cornellappdev.transit.util.getGymLocationString +/** + * Displays the full detail view for an individual gym within the ecosystem bottom sheet. + */ @Composable fun GymDetailsContent( homeViewModel: HomeViewModel = hiltViewModel(), @@ -37,6 +39,8 @@ fun GymDetailsContent( isFavorite: Boolean, onFavoriteClick: () -> Unit, ) { + val isOpen = homeViewModel.getOpenStatus(gym.operatingHours()).isOpen + Column( modifier = Modifier .fillMaxWidth() @@ -60,11 +64,12 @@ fun GymDetailsContent( gym.operatingHours() ), widget = { - GymCapacityIndicator( - capacity = gym.upliftCapacity, - label = null, - closed = false - ) + if (gym.upliftCapacity != null && isOpen) { + GymCapacityIndicator( + capacity = gym.upliftCapacity, + label = null, + ) + } }, ) 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 dbd0f64..208918f 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 @@ -21,12 +21,15 @@ import com.cornellappdev.transit.models.ecosystem.EateryRepository import com.cornellappdev.transit.models.ecosystem.GymRepository import com.cornellappdev.transit.models.ecosystem.UpliftCapacity import com.cornellappdev.transit.networking.ApiResponse +import com.cornellappdev.transit.ui.theme.AccentClosed +import com.cornellappdev.transit.ui.theme.AccentOpen import com.cornellappdev.transit.ui.theme.AccentOrange 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.ui.theme.robotoFamily -import com.cornellappdev.transit.util.LIMITED_CAPACITY_THRESHOLD +import com.cornellappdev.transit.util.HIGH_CAPACITY_THRESHOLD +import com.cornellappdev.transit.util.MEDIUM_CAPACITY_THRESHOLD import com.cornellappdev.transit.util.TimeUtils.toPascalCaseString import com.google.android.gms.maps.model.LatLng import dagger.hilt.android.lifecycle.HiltViewModel @@ -358,7 +361,7 @@ class HomeViewModel @Inject constructor( * * @param operatingHours A list of pairs mapping the first value day string to second value list of hours open */ - private fun getOpenStatus( + fun getOpenStatus( operatingHours: List, currentDateTime: LocalDateTime = LocalDateTime.now() ): OpenStatus { @@ -466,13 +469,21 @@ class HomeViewModel @Inject constructor( return AnnotatedString("") } + val color = if (capacity.percent <= MEDIUM_CAPACITY_THRESHOLD) { + AccentOpen + } else if (capacity.percent >= HIGH_CAPACITY_THRESHOLD) { + AccentClosed + } else { + AccentOrange + } + return buildAnnotatedString { withStyle( style = SpanStyle( fontSize = 14.sp, fontFamily = robotoFamily, fontWeight = FontWeight(600), - color = if (capacity.percent <= LIMITED_CAPACITY_THRESHOLD) LiveGreen else AccentOrange, + color = color, ) ) { append("${capacity.percentString()} full") diff --git a/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt b/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt index a6c8404..19ba516 100644 --- a/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt +++ b/app/src/main/java/com/cornellappdev/transit/util/TransitConstants.kt @@ -6,6 +6,10 @@ const val ECOSYSTEM_FLAG = BuildConfig.ECOSYSTEM_FLAG const val BOTTOM_SHEET_MAX_HEIGHT_PERCENT = 90 -const val LIMITED_CAPACITY_THRESHOLD = 0.65f +/** When the capacity turns to orange */ +const val MEDIUM_CAPACITY_THRESHOLD = 0.35f + +/** When the capacity turns to red */ +const val HIGH_CAPACITY_THRESHOLD = 0.65f const val NOTIFICATIONS_ENABLED = false \ No newline at end of file From 0f9d903e0fca201e9559a0176f585ecc8dc8ebe4 Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Tue, 24 Feb 2026 09:57:00 -0500 Subject: [PATCH 8/9] Fix operating hours bug --- .../ui/components/home/GymCapacityIndicator.kt | 11 +++++++++++ .../transit/ui/viewmodels/HomeViewModel.kt | 10 +++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt index e0159e8..3fccd29 100644 --- a/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt +++ b/app/src/main/java/com/cornellappdev/transit/ui/components/home/GymCapacityIndicator.kt @@ -1,5 +1,6 @@ package com.cornellappdev.transit.ui.components.home +import android.icu.util.Calendar import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box @@ -17,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.cornellappdev.transit.models.ecosystem.UpliftCapacity @@ -115,4 +117,13 @@ fun GymCapacityIndicator( ) } } +} + +@Composable +@Preview(showBackground = true) +private fun GymCapacityIndicatorPreview() { + GymCapacityIndicator( + UpliftCapacity(0.40, Calendar.getInstance()), + label = null + ) } \ No newline at end of file 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 208918f..a12c536 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 @@ -2,7 +2,6 @@ package com.cornellappdev.transit.ui.viewmodels import android.content.Context import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.PlatformTextStyle import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight @@ -356,7 +355,7 @@ class HomeViewModel @Inject constructor( } /** - * Given operating hours rotated for today's date, return whether it is open and when it is open until + * Given operating hours, return whether it is open and when it is open until * or when it will next open * * @param operatingHours A list of pairs mapping the first value day string to second value list of hours open @@ -365,13 +364,14 @@ class HomeViewModel @Inject constructor( operatingHours: List, currentDateTime: LocalDateTime = LocalDateTime.now() ): OpenStatus { + val rotatedOperatingHours = rotateOperatingHours(operatingHours) val currentTime = currentDateTime.toLocalTime() - val todaySchedule = operatingHours[0].hours // First day should be today after rotation + val todaySchedule = rotatedOperatingHours[0].hours // First day should be today after rotation // Check if closed today if (todaySchedule.any { it.equals("Closed", ignoreCase = true) }) { - return findOpenNextDay(operatingHours) + return findOpenNextDay(rotatedOperatingHours) } val timeRanges = todaySchedule.mapNotNull { parseTimeRange(it) } @@ -391,7 +391,7 @@ class HomeViewModel @Inject constructor( } // Closed for today, find next open day - return findOpenNextDay(operatingHours) + return findOpenNextDay(rotatedOperatingHours) } /** From f216f08b3d2665965a517ded2f2f8c2c98f6c0b4 Mon Sep 17 00:00:00 2001 From: Jonathan Chen <04jono@gmail.com> Date: Wed, 25 Feb 2026 09:53:37 -0500 Subject: [PATCH 9/9] Remove redundant operating hours rotation --- .../cornellappdev/transit/ui/viewmodels/HomeViewModel.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 a12c536..3abf108 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 @@ -367,7 +367,8 @@ class HomeViewModel @Inject constructor( val rotatedOperatingHours = rotateOperatingHours(operatingHours) val currentTime = currentDateTime.toLocalTime() - val todaySchedule = rotatedOperatingHours[0].hours // First day should be today after rotation + val todaySchedule = + rotatedOperatingHours[0].hours // First day should be today after rotation // Check if closed today if (todaySchedule.any { it.equals("Closed", ignoreCase = true) }) { @@ -453,9 +454,7 @@ class HomeViewModel @Inject constructor( */ fun isOpenAnnotatedStringFromOperatingHours(operatingHours: List): AnnotatedString { return getOpenStatusAnnotatedString( - getOpenStatus( - rotateOperatingHours(operatingHours) - ) + getOpenStatus(operatingHours) ) }