Skip to content

Commit

Permalink
Show the open state of the location
Browse files Browse the repository at this point in the history
  • Loading branch information
cristan committed Sep 1, 2024
1 parent daaedd4 commit 75b1989
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 26 deletions.
6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ dependencies {

testImplementation(libs.junit)
testImplementation(libs.koin.test.junit4)
// No need for kluent-android: the only difference is stuff about functions with spaces in them, but Android has support for that for ages now
// https://github.com/MarkusAmshove/Kluent/pull/58
testImplementation(libs.kluent)
// To get JUnit errors from kotlin.test, to e.g. enable diff windows in failure messages
testImplementation(libs.kotlin.test.junit)


androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import nl.ovfietsbeschikbaarheid.model.LocationOverviewModel
import nl.ovfietsbeschikbaarheid.model.OpeningHoursModel
import nl.ovfietsbeschikbaarheid.model.ServiceType
import timber.log.Timber
import java.time.LocalDateTime
import java.util.Locale
import java.util.TimeZone
import kotlin.math.max

object DetailsMapper {
Expand Down Expand Up @@ -67,7 +69,7 @@ object DetailsMapper {
val maxCapacity = foundCapacity ?: rentalBikesAvailable ?: 0


val serviceType = when(payload.extra.serviceType) {
val serviceType = when (payload.extra.serviceType) {
"Bemenst" -> ServiceType.Bemenst
"Kluizen" -> ServiceType.Kluizen
"Sleutelautomaat" -> ServiceType.Sleutelautomaat
Expand All @@ -90,11 +92,16 @@ object DetailsMapper {
location = location,
coordinates = LatLng(payload.lat, payload.lng),
stationName = allStations[payload.stationCode],
alternatives = alternatives
alternatives = alternatives,
openState = payload.openingHours?.let {
OpenStateMapper.getOpenState(
it, LocalDateTime.now(TimeZone.getTimeZone("Europe/Amsterdam").toZoneId())
)
},
)
}

private fun getDayName(dayOfWeek: Int): String {
fun getDayName(dayOfWeek: Int): String {
return when (dayOfWeek) {
1 -> "Maandag"
2 -> "Dinsdag"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package nl.ovfietsbeschikbaarheid.mapper

import nl.ovfietsbeschikbaarheid.dto.OpeningHours
import nl.ovfietsbeschikbaarheid.model.OpenState
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.temporal.ChronoUnit
import java.util.Locale

object OpenStateMapper {
fun getOpenState(openingHours: List<OpeningHours>, dateTime: LocalDateTime): OpenState {
if (
openingHours.all { it.startTime == "00:00" && it.endTime == "24:00" } ||
openingHours.all { it.startTime == "00:00" && it.endTime == "00:01" } ||
openingHours.all { it.startTime == "00:00" && it.endTime == "23:59" }
) {
return OpenState.Open247
}
// Monday is 1, Sunday is 7, same as what is returned from the API
val today = dateTime.dayOfWeek.value

val yesterdayOpeningHours = openingHours.find {
val dayYesterday = if (today == 1) 7 else today - 1
if (it.dayOfWeek == dayYesterday && it.closesNextDay) {
dateTime.toLocalTime() < LocalTime.parse(it.endTime)
} else {
false
}
}
if (yesterdayOpeningHours != null) {
val minutesUntilClosing = dateTime.toLocalTime().until(LocalTime.parse(yesterdayOpeningHours.endTime), ChronoUnit.MINUTES)
return if (minutesUntilClosing < 60) {
OpenState.Closing(yesterdayOpeningHours.endTime)
} else {
OpenState.Open(yesterdayOpeningHours.endTime)
}
}

val todayOpeningHours = openingHours.find {
if (it.dayOfWeek == today) {
val afterStartTime = it.startTime == "00:00" || dateTime.toLocalTime() >= LocalTime.parse(it.startTime)
val beforeEndTime = it.endTime == "24:00" || it.closesNextDay || dateTime.toLocalTime() <= LocalTime.parse(it.endTime)
afterStartTime && beforeEndTime
} else {
false
}
}
if (todayOpeningHours != null) {
val endTime = if(todayOpeningHours.endTime == "24:00") LocalTime.MAX else LocalTime.parse(todayOpeningHours.endTime)
val minutesUntilClosing = if (todayOpeningHours.closesNextDay) {
// Until the end of the day + the hours still open after 24:00 hours
dateTime.toLocalTime().until(LocalTime.MAX, ChronoUnit.MINUTES) + LocalTime.MIN.until(endTime, ChronoUnit.MINUTES)
} else {
dateTime.toLocalTime().until(endTime, ChronoUnit.MINUTES)
}
return if (minutesUntilClosing < 60) {
OpenState.Closing(todayOpeningHours.endTime)
} else {
OpenState.Open(todayOpeningHours.endTime)
}
}

val todayOpen = openingHours.find {
it.dayOfWeek == today && dateTime.toLocalTime() < LocalTime.parse(it.startTime)
}
if (todayOpen != null) {
return OpenState.Closed(openDay = null, todayOpen.startTime)
}
val nextDayInWeek = (today..7).firstNotNullOfOrNull { day ->
openingHours.find { it.dayOfWeek == day }
}
val monday = openingHours.find { it.dayOfWeek == 1 }
val nextDayOpen = nextDayInWeek ?: monday !!
val opensTodayOrTomorrow = (today == 7 && nextDayOpen.dayOfWeek == 1) || (nextDayOpen.dayOfWeek == today) || (nextDayOpen.dayOfWeek - today == 1)
return if (opensTodayOrTomorrow) {
OpenState.Closed(openDay = "morgen", nextDayOpen.startTime)
} else {
OpenState.Closed(openDay = DetailsMapper.getDayName(nextDayOpen.dayOfWeek).lowercase(Locale.UK), nextDayOpen.startTime)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import nl.ovfietsbeschikbaarheid.R

data class DetailsModel(
val description: String,
val openState: OpenState?,
val openingHours: List<OpeningHoursModel>,
val rentalBikesAvailable: Int?,
val capacity: Int,
Expand All @@ -31,6 +32,13 @@ data class OpeningHoursModel(
val endTime: String,
)

sealed class OpenState {
data object Open247 : OpenState()
data class Open(val closingTime: String) : OpenState()
data class Closing(val closingTime: String) : OpenState()
data class Closed(val openDay: String?, val openTime: String) : OpenState()
}

enum class ServiceType(val text: String, @DrawableRes val icon: Int) {
Bemenst("Bemenst", R.drawable.baseline_person_24),
// See: https://www.ns.nl/fietsenstallingen/abonnementen/fietskluizen.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
Expand Down Expand Up @@ -58,11 +59,15 @@ import nl.ovfietsbeschikbaarheid.ext.withStyledLink
import nl.ovfietsbeschikbaarheid.model.DetailsModel
import nl.ovfietsbeschikbaarheid.model.LocationModel
import nl.ovfietsbeschikbaarheid.model.LocationOverviewModel
import nl.ovfietsbeschikbaarheid.model.OpenState
import nl.ovfietsbeschikbaarheid.model.OpeningHoursModel
import nl.ovfietsbeschikbaarheid.model.ServiceType
import nl.ovfietsbeschikbaarheid.state.ScreenState
import nl.ovfietsbeschikbaarheid.ui.components.OvCard
import nl.ovfietsbeschikbaarheid.ui.theme.Green50
import nl.ovfietsbeschikbaarheid.ui.theme.OVFietsBeschikbaarheidTheme
import nl.ovfietsbeschikbaarheid.ui.theme.Orange50
import nl.ovfietsbeschikbaarheid.ui.theme.Red50
import nl.ovfietsbeschikbaarheid.ui.theme.Yellow50
import nl.ovfietsbeschikbaarheid.ui.view.FullPageError
import nl.ovfietsbeschikbaarheid.ui.view.FullPageLoader
Expand Down Expand Up @@ -174,29 +179,7 @@ private fun ActualDetails(
Modifier.verticalScroll(rememberScrollState())
) {
Column(Modifier.padding(start = 20.dp, end = 20.dp, bottom = 20.dp, top = 4.dp)) {
OvCard {
Row {
Text("OV-fietsen beschikbaar")
}
val amount = details.rentalBikesAvailable?.toString() ?: "Onbekend"
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
contentAlignment = Alignment.Center
) {
val progress =
if (details.rentalBikesAvailable == null) 0f else details.rentalBikesAvailable.toFloat() / details.capacity
CircularProgressIndicator(progress = { progress }, modifier = Modifier.size(220.dp),
strokeWidth = 36.dp,
strokeCap = StrokeCap.Butt,
gapSize = 0.dp)
Text(
text = amount,
fontSize = if (details.rentalBikesAvailable != null) 60.sp else 24.sp
)
}
}
MainInfo(details)

Location(details, onLocationClicked)

Expand All @@ -213,6 +196,65 @@ private fun ActualDetails(
}
}

@Composable
private fun MainInfo(details: DetailsModel) {
OvCard {
Text("OV-fietsen beschikbaar")
val rentalBikesAvailable = details.rentalBikesAvailable
val amount = rentalBikesAvailable?.toString() ?: "Onbekend"
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
contentAlignment = Alignment.Center
) {
val progress = if (rentalBikesAvailable == null) 0f else rentalBikesAvailable.toFloat() / details.capacity
val color =
when (details.openState) {
is OpenState.Closed -> Red50
is OpenState.Closing -> Orange50
else -> ProgressIndicatorDefaults.circularColor
}
CircularProgressIndicator(
progress = { progress }, modifier = Modifier.size(220.dp),
color = color,
strokeWidth = 36.dp,
strokeCap = StrokeCap.Butt,
gapSize = 0.dp
)
Text(
text = amount,
fontSize = if (rentalBikesAvailable != null) 60.sp else 24.sp
)
}
details.openState?.let { openState ->
Row(Modifier.align(Alignment.End)) {
when (openState) {
is OpenState.Closed -> {
Text(text = "Gesloten", color = Red50)
if (openState.openDay != null) {
Text(text = " • Gaat ${openState.openDay} open om ${openState.openTime}")
} else {
Text(text = " • Gaat open om ${openState.openTime}")
}
}

is OpenState.Closing -> {
Text(text = "Sluit snel", color = Orange50)
Text(" • Sluit om ${openState.closingTime}")
}

is OpenState.Open -> {
Text("Open tot ${openState.closingTime}")
}

OpenState.Open247 -> Text(text = "24/7 open")
}
}
}
}
}

@Composable
private fun Location(details: DetailsModel, onNavigateClicked: (String) -> Unit) {
OvCard(
Expand Down Expand Up @@ -400,6 +442,7 @@ fun DetailsPreview() {
)
val details = DetailsModel(
"Hilversum",
OpenState.Open247,
openingHours,
144,
200,
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/nl/ovfietsbeschikbaarheid/ui/theme/Color.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import androidx.compose.ui.graphics.Color
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Indigo05 = Color(0xFFE8EAF6)
val Green50 = Color(0xFF4CAF50)
val Red50 = Color(0xFFF44336)
val Orange50 = Color(0xFFFF9800)
val Gray80 = Color(0xFF424242)
val Blue70 = Color(0xFF1976D2)
val Blue90 = Color(0xFF0D47A1)
Expand Down
Loading

0 comments on commit 75b1989

Please sign in to comment.