Skip to content

Commit cb5ff06

Browse files
committed
fix: make ui/logic changes to show user more consistent and understandable information
1 parent 57bee17 commit cb5ff06

3 files changed

Lines changed: 124 additions & 57 deletions

File tree

app/src/main/java/com/cornellappdev/transit/ui/components/home/EcosystemBottomSheetContent.kt

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import com.cornellappdev.transit.ui.theme.robotoFamily
4343
import com.cornellappdev.transit.ui.viewmodels.EcosystemFavoritesUiState
4444
import com.cornellappdev.transit.ui.viewmodels.FavoritesFilterSheetState
4545
import com.cornellappdev.transit.ui.viewmodels.FilterState
46+
import com.cornellappdev.transit.util.TimeUtils.getOpenStatus
4647
import com.cornellappdev.transit.util.TimeUtils.isOpenAnnotatedStringFromOperatingHours
4748
import com.cornellappdev.transit.util.ecosystem.capacityPercentAnnotatedString
4849
import com.cornellappdev.transit.ui.viewmodels.PrinterCardUiState
@@ -83,6 +84,8 @@ fun EcosystemBottomSheetContent(
8384
onRemoveAppliedFilter: (FavoritesFilterSheetState) -> Unit,
8485
operatingHoursToString: (List<DayOperatingHours>) -> AnnotatedString,
8586
distanceStringToPlace: (Double?, Double?) -> String,
87+
sanitizeLibraryAddress: (String) -> String,
88+
printerToCardUiState: (Printer) -> PrinterCardUiState,
8689
) {
8790
Column(modifier = modifier) {
8891
Row(
@@ -129,7 +132,9 @@ fun EcosystemBottomSheetContent(
129132
appliedFilters = appliedFilters,
130133
onRemoveAppliedFilter = onRemoveAppliedFilter,
131134
operatingHoursToString = operatingHoursToString,
132-
distanceStringToPlace = distanceStringToPlace
135+
distanceStringToPlace = distanceStringToPlace,
136+
sanitizeLibraryAddress = sanitizeLibraryAddress,
137+
printerToCardUiState = printerToCardUiState
133138
)
134139
}
135140

@@ -164,6 +169,8 @@ private fun BottomSheetFilteredContent(
164169
onRemoveAppliedFilter: (FavoritesFilterSheetState) -> Unit,
165170
operatingHoursToString: (List<DayOperatingHours>) -> AnnotatedString,
166171
distanceStringToPlace: (Double?, Double?) -> String,
172+
sanitizeLibraryAddress: (String) -> String,
173+
printerToCardUiState: (Printer) -> PrinterCardUiState,
167174
) {
168175
Column {
169176
if (currentFilter == FilterState.FAVORITES) {
@@ -209,7 +216,8 @@ private fun BottomSheetFilteredContent(
209216
onDetailsClick = onDetailsClick,
210217
operatingHoursToString = operatingHoursToString,
211218
capacityToString = ::capacityPercentAnnotatedString,
212-
distanceStringToPlace = distanceStringToPlace
219+
distanceStringToPlace = distanceStringToPlace,
220+
sanitizeLibraryAddress = sanitizeLibraryAddress
213221
)
214222
}
215223

@@ -219,7 +227,8 @@ private fun BottomSheetFilteredContent(
219227
navigateToPlace = navigateToPlace,
220228
favorites = favorites,
221229
onFavoriteStarClick = onFavoriteStarClick,
222-
distanceStringToPlace = distanceStringToPlace
230+
distanceStringToPlace = distanceStringToPlace,
231+
printerToCardUiState = printerToCardUiState
223232
)
224233
}
225234

@@ -253,6 +262,7 @@ private fun BottomSheetFilteredContent(
253262
favorites,
254263
onFavoriteStarClick,
255264
distanceStringToPlace,
265+
sanitizeLibraryAddress,
256266
)
257267
}
258268
}
@@ -277,7 +287,8 @@ private fun LazyListScope.favoriteList(
277287
onDetailsClick: (DetailedEcosystemPlace) -> Unit,
278288
operatingHoursToString: (List<DayOperatingHours>) -> AnnotatedString,
279289
capacityToString: (UpliftCapacity?) -> AnnotatedString,
280-
distanceStringToPlace: (Double?, Double?) -> String
290+
distanceStringToPlace: (Double?, Double?) -> String,
291+
sanitizeLibraryAddress: (String) -> String,
281292
) {
282293
item {
283294
Spacer(modifier = Modifier.height(8.dp))
@@ -295,10 +306,7 @@ private fun LazyListScope.favoriteList(
295306
RoundedImagePlaceCard(
296307
title = matchingEatery.name,
297308
subtitle = (matchingEatery.location
298-
?: "") + distanceStringToPlace(
299-
matchingEatery.latitude,
300-
matchingEatery.longitude
301-
),
309+
?: "") + distanceStringToPlace(matchingEatery.latitude, matchingEatery.longitude),
302310
isFavorite = true,
303311
onFavoriteClick = { onFavoriteStarClick(place) },
304312
leftAnnotatedString = operatingHoursToString(
@@ -322,10 +330,8 @@ private fun LazyListScope.favoriteList(
322330
if (matchingLibrary != null) {
323331
RoundedImagePlaceCard(
324332
title = matchingLibrary.location,
325-
subtitle = matchingLibrary.address + distanceStringToPlace(
326-
matchingLibrary.latitude,
327-
matchingLibrary.longitude
328-
),
333+
subtitle = sanitizeLibraryAddress(matchingLibrary.address) +
334+
distanceStringToPlace(matchingLibrary.latitude, matchingLibrary.longitude),
329335
isFavorite = true,
330336
onFavoriteClick = { onFavoriteStarClick(place) }
331337
) {
@@ -344,22 +350,23 @@ private fun LazyListScope.favoriteList(
344350
PlaceType.GYM -> {
345351
val matchingGym = gymByPlace[place]
346352
if (matchingGym != null) {
353+
val isGymOpen = getOpenStatus(matchingGym.operatingHours()).isOpen
347354
RoundedImagePlaceCard(
348355
title = matchingGym.name,
349-
subtitle = getGymLocationString(matchingGym.name) + distanceStringToPlace(
350-
matchingGym.latitude,
351-
matchingGym.longitude
352-
),
356+
subtitle = getGymLocationString(matchingGym.name) +
357+
distanceStringToPlace(matchingGym.latitude, matchingGym.longitude),
353358
isFavorite = true,
354359
onFavoriteClick = {
355360
onFavoriteStarClick(place)
356361
},
357362
leftAnnotatedString = operatingHoursToString(
358363
matchingGym.operatingHours()
359364
),
360-
rightAnnotatedString = capacityToString(
361-
matchingGym.upliftCapacity
362-
),
365+
rightAnnotatedString = if (isGymOpen) {
366+
capacityToString(matchingGym.upliftCapacity)
367+
} else {
368+
null
369+
},
363370
) {
364371
onDetailsClick(matchingGym)
365372
}
@@ -378,10 +385,8 @@ private fun LazyListScope.favoriteList(
378385
if (matchingPrinter != null) {
379386
PrinterCard(
380387
title = matchingPrinter.title,
381-
subtitle = matchingPrinter.subtitle + distanceStringToPlace(
382-
place.latitude,
383-
place.longitude
384-
),
388+
subtitle = matchingPrinter.subtitle +
389+
distanceStringToPlace(place.latitude, place.longitude),
385390
inColor = matchingPrinter.inColor,
386391
hasCopy = matchingPrinter.hasCopy,
387392
hasScan = matchingPrinter.hasScan,
@@ -445,23 +450,24 @@ private fun LazyListScope.gymList(
445450
}
446451

447452
items(gymsApiResponse.data) {
453+
val isGymOpen = getOpenStatus(it.operatingHours()).isOpen
448454
RoundedImagePlaceCard(
449455
imageUrl = it.imageUrl,
450456
title = it.name,
451-
subtitle = getGymLocationString(it.name) + distanceStringToPlace(
452-
it.latitude,
453-
it.longitude
454-
),
457+
subtitle = getGymLocationString(it.name) +
458+
distanceStringToPlace(it.latitude, it.longitude),
455459
isFavorite = it.toPlace() in favorites,
456460
onFavoriteClick = {
457461
onFavoriteStarClick(it.toPlace())
458462
},
459463
leftAnnotatedString = operatingHoursToString(
460464
it.operatingHours()
461465
),
462-
rightAnnotatedString = capacityToString(
463-
it.upliftCapacity
464-
),
466+
rightAnnotatedString = if (isGymOpen) {
467+
capacityToString(it.upliftCapacity)
468+
} else {
469+
null
470+
},
465471
placeholderRes = R.drawable.olin_library,
466472
) {
467473
onDetailsClick(it)
@@ -480,6 +486,7 @@ private fun LazyListScope.printerList(
480486
favorites: Set<Place>,
481487
onFavoriteStarClick: (Place) -> Unit,
482488
distanceStringToPlace: (Double?, Double?) -> String,
489+
printerToCardUiState: (Printer) -> PrinterCardUiState,
483490
) {
484491
when (staticPlaces.printers) {
485492
is ApiResponse.Error -> {
@@ -500,22 +507,15 @@ private fun LazyListScope.printerList(
500507

501508
items(staticPlaces.printers.data) {
502509
val place = it.toPlace()
503-
val alert = if (it.location.contains("*")) {
504-
it.location.substringAfter("*").trim('*').trim()
505-
} else {
506-
""
507-
}
510+
val printerUi = printerToCardUiState(it)
508511

509512
PrinterCard(
510-
title = it.location.substringBefore("*").trim(),
511-
subtitle = it.description.substringAfter("-").trim() + distanceStringToPlace(
512-
it.latitude,
513-
it.longitude
514-
),
515-
inColor = it.description.contains("Color", ignoreCase = true),
516-
hasCopy = it.description.contains("Copy", ignoreCase = true),
517-
hasScan = it.description.contains("Scan", ignoreCase = true),
518-
alertMessage = alert,
513+
title = printerUi.title,
514+
subtitle = printerUi.subtitle + distanceStringToPlace(it.latitude, it.longitude),
515+
inColor = printerUi.inColor,
516+
hasCopy = printerUi.hasCopy,
517+
hasScan = printerUi.hasScan,
518+
alertMessage = printerUi.alertMessage,
519519
isFavorite = place in favorites,
520520
onFavoriteClick = {
521521
onFavoriteStarClick(place)
@@ -589,6 +589,7 @@ private fun LazyListScope.libraryList(
589589
favorites: Set<Place>,
590590
onFavoriteStarClick: (Place) -> Unit,
591591
distanceStringToPlace: (Double?, Double?) -> String,
592+
sanitizeLibraryAddress: (String) -> String,
592593
) {
593594
when (staticPlaces.libraries) {
594595
is ApiResponse.Error -> {
@@ -611,7 +612,8 @@ private fun LazyListScope.libraryList(
611612
RoundedImagePlaceCard(
612613
placeholderRes = R.drawable.olin_library,
613614
title = it.location,
614-
subtitle = it.address + distanceStringToPlace(it.latitude, it.longitude),
615+
subtitle = sanitizeLibraryAddress(it.address) +
616+
distanceStringToPlace(it.latitude, it.longitude),
615617
isFavorite = it.toPlace() in favorites,
616618
onFavoriteClick = {
617619
onFavoriteStarClick(it.toPlace())
@@ -644,8 +646,7 @@ private fun StandardCard(
644646
navigateToPlace: (Place) -> Unit,
645647
distanceStringToPlace: (Double?, Double?) -> String,
646648
) {
647-
val distance = distanceStringToPlace(place.latitude, place.longitude)
648-
val subtitle = if (distance.isBlank()) place.subLabel else "${place.subLabel}$distance"
649+
val subtitle = place.subLabel + distanceStringToPlace(place.latitude, place.longitude)
649650

650651
BottomSheetLocationCard(
651652
title = place.name,
@@ -699,7 +700,18 @@ private fun PreviewEcosystemBottomSheet() {
699700
onFilterToggle = {},
700701
onRemoveAppliedFilter = {},
701702
operatingHoursToString = { _ -> AnnotatedString("") },
702-
distanceStringToPlace = { _, _ -> "" }
703+
distanceStringToPlace = { _, _ -> "" },
704+
sanitizeLibraryAddress = { it },
705+
printerToCardUiState = { _ ->
706+
PrinterCardUiState(
707+
title = "",
708+
subtitle = "",
709+
inColor = false,
710+
hasCopy = false,
711+
hasScan = false,
712+
alertMessage = ""
713+
)
714+
}
703715
)
704716
}
705717

@@ -860,9 +872,17 @@ private fun PreviewBottomSheetFilteredContentFavorites() {
860872
),
861873
onRemoveAppliedFilter = {},
862874
operatingHoursToString = { _ -> AnnotatedString("Open • 10am - 4pm") },
863-
distanceStringToPlace = { _, _ -> "distance" }
875+
distanceStringToPlace = { _, _ -> "distance" },
876+
sanitizeLibraryAddress = { it },
877+
printerToCardUiState = { _ ->
878+
PrinterCardUiState(
879+
title = "",
880+
subtitle = "",
881+
inColor = false,
882+
hasCopy = false,
883+
hasScan = false,
884+
alertMessage = ""
885+
)
886+
}
864887
)
865-
}
866-
867-
868-
888+
}

app/src/main/java/com/cornellappdev/transit/ui/screens/HomeScreen.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,9 @@ fun HomeScreen(
359359
onFilterToggle = homeViewModel::toggleFavoritesFilter,
360360
onRemoveAppliedFilter = homeViewModel::removeAppliedFilter,
361361
operatingHoursToString = ::isOpenAnnotatedStringFromOperatingHours,
362-
distanceStringToPlace = homeViewModel::distanceStringIfCurrentLocationExists
362+
distanceStringToPlace = homeViewModel::distanceTextOrPlaceholder,
363+
sanitizeLibraryAddress = homeViewModel::sanitizeLibraryAddress,
364+
printerToCardUiState = homeViewModel::printerToCardUiState
363365
)
364366
}
365367
}

app/src/main/java/com/cornellappdev/transit/ui/viewmodels/HomeViewModel.kt

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.onEach
3737
import kotlinx.coroutines.flow.stateIn
3838
import kotlinx.coroutines.launch
3939
import javax.inject.Inject
40+
import java.util.Locale
4041
import kotlin.text.toDouble
4142

4243
/**
@@ -433,6 +434,26 @@ class HomeViewModel @Inject constructor(
433434
}
434435
return ""
435436
}
437+
438+
/**
439+
* Returns distance text with a loading placeholder when current location is not ready.
440+
*/
441+
fun distanceTextOrPlaceholder(latitude: Double?, longitude: Double?): String {
442+
val distanceText = distanceStringIfCurrentLocationExists(latitude, longitude)
443+
return if (distanceText.isBlank()) " - Calculating Distance..." else distanceText
444+
}
445+
446+
/**
447+
* Keeps only the first segment of a library address (text before the first comma).
448+
*/
449+
fun sanitizeLibraryAddress(address: String): String {
450+
return address.substringBefore(",").trim()
451+
}
452+
453+
/**
454+
* Maps raw printer fields to UI-ready fields for card rendering.
455+
*/
456+
fun printerToCardUiState(printer: Printer): PrinterCardUiState = printer.toPrinterCardUiState()
436457
}
437458

438459
/**
@@ -459,17 +480,41 @@ data class PrinterCardUiState(
459480
)
460481

461482
private fun Printer.toPrinterCardUiState(): PrinterCardUiState {
462-
val alertMessage = location.substringAfter("*", "").trim('*').trim()
483+
//Hard-coded way to handle closed for construction message, change when backend is updated
484+
val constructionAlert = "CLOSED FOR CONSTRUCTION"
485+
val constructionRegex = Regex("""\bCLOSED\s+FOR\s+CONSTRUCTION\b""", RegexOption.IGNORE_CASE)
486+
val hasConstructionAlert = constructionRegex.containsMatchIn(location)
487+
488+
val rawTitle = location.substringBefore("*").trim()
489+
val title = rawTitle
490+
.replace(constructionRegex, "")
491+
.replace(Regex("""\s{2,}"""), " ")
492+
.trim(' ', '-', ',', ';', ':')
493+
494+
val starAlertMessage = location.substringAfter("*", "").trim('*').trim()
495+
val alertMessage = if (hasConstructionAlert) constructionAlert else starAlertMessage
496+
463497
return PrinterCardUiState(
464-
title = location.substringBefore("*").trim(),
465-
subtitle = description.substringAfter("-", description).trim(),
498+
title = title,
499+
subtitle = description.substringAfter("-", description).trim().toTitleCaseWords(),
466500
inColor = description.contains("Color", ignoreCase = true),
467501
hasCopy = description.contains("Copy", ignoreCase = true),
468502
hasScan = description.contains("Scan", ignoreCase = true),
469503
alertMessage = alertMessage
470504
)
471505
}
472506

507+
private fun String.toTitleCaseWords(): String {
508+
return split(Regex("""\s+"""))
509+
.filter { it.isNotBlank() }
510+
.joinToString(" ") { word ->
511+
word.lowercase(Locale.getDefault())
512+
.replaceFirstChar { char ->
513+
if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else char.toString()
514+
}
515+
}
516+
}
517+
473518
private fun Set<FavoritesFilterSheetState>.toAllowedPlaceTypes(): Set<PlaceType> = buildSet {
474519
if (FavoritesFilterSheetState.EATERIES in this@toAllowedPlaceTypes) add(PlaceType.EATERY)
475520
if (FavoritesFilterSheetState.LIBRARIES in this@toAllowedPlaceTypes) add(PlaceType.LIBRARY)

0 commit comments

Comments
 (0)