featwip: pretty departures
This commit is contained in:
parent
ef630b6d58
commit
4e1e05495d
13 changed files with 264 additions and 37 deletions
|
|
@ -4,6 +4,7 @@ import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource
|
import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource
|
||||||
import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource
|
import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource
|
||||||
|
import moe.lava.banksia.core.model.Route
|
||||||
|
|
||||||
internal class ClientRouteRepository internal constructor(
|
internal class ClientRouteRepository internal constructor(
|
||||||
private val local: RouteLocalDataSource,
|
private val local: RouteLocalDataSource,
|
||||||
|
|
@ -21,5 +22,14 @@ internal class ClientRouteRepository internal constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val tripRouteMap = mutableMapOf<String, Route>()
|
||||||
|
|
||||||
override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) }
|
override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) }
|
||||||
|
override suspend fun getByTrip(tripId: String) = mutex.withLock {
|
||||||
|
tripRouteMap[tripId]
|
||||||
|
?: remote.getByTrip(tripId).also {
|
||||||
|
local.save(it)
|
||||||
|
tripRouteMap[tripId] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,6 @@ import moe.lava.banksia.core.room.entity.asEntity
|
||||||
internal class RouteLocalDataSource(private val dao: RouteDao) {
|
internal class RouteLocalDataSource(private val dao: RouteDao) {
|
||||||
suspend fun get(id: String) = dao.get(id)
|
suspend fun get(id: String) = dao.get(id)
|
||||||
suspend fun getAll() = dao.getAll()
|
suspend fun getAll() = dao.getAll()
|
||||||
|
suspend fun getByTrip(tripId: String) = dao.getByTrip(tripId)
|
||||||
suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray())
|
suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,6 @@ import moe.lava.banksia.core.model.Route
|
||||||
|
|
||||||
internal class RouteRemoteDataSource(val client: HttpClient) {
|
internal class RouteRemoteDataSource(val client: HttpClient) {
|
||||||
suspend fun get(id: String) = client.get("routes/${id}").body<Route>()
|
suspend fun get(id: String) = client.get("routes/${id}").body<Route>()
|
||||||
|
suspend fun getByTrip(tripId: String) = client.get("routes/by_trip/${tripId}").body<Route>()
|
||||||
suspend fun getAll() = client.get("routes").body<List<Route>>()
|
suspend fun getAll() = client.get("routes").body<List<Route>>()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package moe.lava.banksia.core.data.repositories
|
||||||
import moe.lava.banksia.core.model.Route
|
import moe.lava.banksia.core.model.Route
|
||||||
|
|
||||||
interface RouteRepository {
|
interface RouteRepository {
|
||||||
suspend fun get(id: String): Route
|
suspend fun get(id: String): Route?
|
||||||
|
suspend fun getByTrip(tripId: String): Route?
|
||||||
suspend fun getAll(): List<Route>
|
suspend fun getAll(): List<Route>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ interface RouteDao {
|
||||||
@Query("DELETE FROM Route")
|
@Query("DELETE FROM Route")
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Route INNER JOIN Trip on Route.id == Trip.routeId WHERE Trip.id == :tripId")
|
||||||
|
suspend fun getByTrip(tripId: String): RouteEntity?
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT Stop.* FROM Stop
|
SELECT Stop.* FROM Stop
|
||||||
INNER JOIN StopTime ON StopTime.stopId == Stop.id
|
INNER JOIN StopTime ON StopTime.stopId == Stop.id
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,16 @@ fun Application.module() {
|
||||||
else
|
else
|
||||||
call.respond(HttpStatusCode.NotFound)
|
call.respond(HttpStatusCode.NotFound)
|
||||||
}
|
}
|
||||||
|
get("/routes/by_trip/{trip_id}") {
|
||||||
|
val tripId = call.parameters["trip_id"]!!
|
||||||
|
val route = withContext(context = Dispatchers.IO) {
|
||||||
|
get<RouteDao>().getByTrip(tripId)
|
||||||
|
}
|
||||||
|
if (route != null)
|
||||||
|
call.respond(route.asModel())
|
||||||
|
else
|
||||||
|
call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
get("/stops") {
|
get("/stops") {
|
||||||
val routes = withContext(context = Dispatchers.IO) {
|
val routes = withContext(context = Dispatchers.IO) {
|
||||||
get<StopDao>().getAll()
|
get<StopDao>().getAll()
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,9 @@ kotlin {
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
androidMain.dependencies {
|
androidMain.dependencies {
|
||||||
|
implementation(libs.compose.ui.tooling.preview)
|
||||||
implementation(libs.play.services.location)
|
implementation(libs.play.services.location)
|
||||||
|
implementation(projects.ui.shared)
|
||||||
}
|
}
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
implementation(libs.compose.components.resources)
|
implementation(libs.compose.components.resources)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(JvmTarget.JVM_11)
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidResources {
|
||||||
|
enable = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
|
|
@ -47,4 +51,5 @@ dependencies {
|
||||||
compose.resources {
|
compose.resources {
|
||||||
publicResClass = true
|
publicResClass = true
|
||||||
packageOfResClass = "moe.lava.banksia.resources"
|
packageOfResClass = "moe.lava.banksia.resources"
|
||||||
|
generateResClass = always
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M480,600L280,400L680,400L480,600Z"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M280,560L480,360L680,560L280,560Z"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -45,6 +45,7 @@ sealed class InfoPanelState {
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun InfoPanel(
|
fun InfoPanel(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
state: InfoPanelState,
|
state: InfoPanelState,
|
||||||
onEvent: (InfoPanelEvent) -> Unit,
|
onEvent: (InfoPanelEvent) -> Unit,
|
||||||
onPeekHeightChange: (Dp) -> Unit,
|
onPeekHeightChange: (Dp) -> Unit,
|
||||||
|
|
@ -65,7 +66,7 @@ fun InfoPanel(
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = 24.dp)
|
||||||
.onSizeChanged {
|
.onSizeChanged {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,51 @@
|
||||||
package moe.lava.banksia.ui.layout.info
|
package moe.lava.banksia.ui.layout.info
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ListItemDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SegmentedListItem
|
||||||
|
import androidx.compose.material3.ShapeDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import moe.lava.banksia.resources.Res
|
||||||
|
import moe.lava.banksia.resources.arrow_drop_down
|
||||||
|
import moe.lava.banksia.resources.arrow_drop_up
|
||||||
|
import moe.lava.banksia.ui.extensions.BUS_ORANGE
|
||||||
|
import moe.lava.banksia.ui.extensions.TRAIN_BLUE
|
||||||
|
import moe.lava.banksia.ui.platform.BanksiaTheme
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
sealed class StopInfoPanelEvent : InfoPanelEvent()
|
sealed class StopInfoPanelEvent : InfoPanelEvent()
|
||||||
|
|
||||||
|
|
@ -23,19 +53,37 @@ data class StopInfoPanelState(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val subname: String? = null,
|
val subname: String? = null,
|
||||||
val departures: List<Departure>? = null,
|
val departures: List<DeparturePlatforms>? = null,
|
||||||
) : InfoPanelState() {
|
) : InfoPanelState() {
|
||||||
override val loading: Boolean
|
override val loading: Boolean
|
||||||
get() = departures == null
|
get() = departures == null
|
||||||
|
|
||||||
data class Departure(val directionName: String, val formattedTimes: String)
|
data class DeparturePlatforms(
|
||||||
|
val platform: String,
|
||||||
|
val departures: List<DepartureInfo>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DepartureInfo(
|
||||||
|
val routeName: String,
|
||||||
|
val routeColour: Color?,
|
||||||
|
val headsign: String,
|
||||||
|
val description: String?,
|
||||||
|
val time: Instant,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun StopInfoPanel(
|
internal fun StopInfoPanel(
|
||||||
state: StopInfoPanelState,
|
state: StopInfoPanelState,
|
||||||
onEvent: (StopInfoPanelEvent) -> Unit,
|
onEvent: (StopInfoPanelEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val colors = ListItemDefaults.colors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
selectedContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
Text(
|
Text(
|
||||||
state.name,
|
state.name,
|
||||||
|
|
@ -53,23 +101,122 @@ internal fun StopInfoPanel(
|
||||||
textAlign = TextAlign.Start
|
textAlign = TextAlign.Start
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
state.departures?.let {
|
state.departures?.let { departurePlatforms ->
|
||||||
Spacer(Modifier.height(5.dp))
|
Spacer(Modifier.height(5.dp))
|
||||||
it.forEach { (name, formatted) ->
|
Column(
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
Text(
|
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
||||||
name,
|
) {
|
||||||
style = MaterialTheme.typography.titleMedium,
|
departurePlatforms.forEach { (platform, departures) ->
|
||||||
fontWeight = FontWeight.SemiBold
|
var expanded by rememberSaveable { mutableStateOf(true) }
|
||||||
|
val base = ListItemDefaults.segmentedShapes(0, 2)
|
||||||
|
val large = MaterialTheme.shapes.large
|
||||||
|
|
||||||
|
if (departurePlatforms.size > 1) {
|
||||||
|
SegmentedListItem(
|
||||||
|
onClick = { expanded = !expanded },
|
||||||
|
colors = colors,
|
||||||
|
shapes = if (expanded) base else base.copy(shape = large),
|
||||||
|
trailingContent = {
|
||||||
|
Icon(
|
||||||
|
painterResource(if (expanded) Res.drawable.arrow_drop_up else Res.drawable.arrow_drop_down),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
if (expanded) MaterialTheme.colorScheme.surface else Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(100)
|
||||||
)
|
)
|
||||||
Text(
|
.padding(6.dp),
|
||||||
formatted,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier.padding(horizontal = 5.dp)
|
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = platform,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = expanded,
|
||||||
|
enter = expandVertically(MaterialTheme.motionScheme.fastSpatialSpec()),
|
||||||
|
exit = shrinkVertically(MaterialTheme.motionScheme.fastSpatialSpec()),
|
||||||
|
) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap)) {
|
||||||
|
departures.forEachIndexed { idx, dep ->
|
||||||
|
SegmentedListItem(
|
||||||
|
onClick = {},
|
||||||
|
// onClick = { onNavigate(ch) },
|
||||||
|
colors = colors,
|
||||||
|
shapes = ListItemDefaults.segmentedShapes(
|
||||||
|
idx + 1,
|
||||||
|
departures.size + 1
|
||||||
|
),
|
||||||
|
supportingContent = {
|
||||||
|
dep.description?.let { Text(dep.description) }
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Text(
|
||||||
|
text = (dep.time - Clock.System.now()).inWholeMinutes.toString(),
|
||||||
|
style = MaterialTheme.typography.headlineSmallEmphasized,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.clip(ShapeDefaults.ExtraSmall)
|
||||||
|
.background(dep.routeColour ?: MaterialTheme.colorScheme.surface)
|
||||||
|
.padding(vertical = 2.dp, horizontal = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = dep.routeName,
|
||||||
|
style = MaterialTheme.typography.labelSmallEmphasized,
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = dep.headsign,
|
||||||
|
style = MaterialTheme.typography.labelLargeEmphasized,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(10.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun StopInfoPanelPreview() {
|
||||||
|
fun dateIn(dur: Duration) = (Clock.System.now() + dur)
|
||||||
|
|
||||||
|
InfoPanel(
|
||||||
|
modifier = Modifier.background(BanksiaTheme.colors.background),
|
||||||
|
state = StopInfoPanelState(
|
||||||
|
id = "id",
|
||||||
|
name = "name",
|
||||||
|
subname = "sub",
|
||||||
|
departures = listOf(
|
||||||
|
StopInfoPanelState.DeparturePlatforms("Platform 1", listOf(
|
||||||
|
StopInfoPanelState.DepartureInfo("Sunbury", Color(TRAIN_BLUE), "Sunbury", "··· Malvern -> Anzac ··· Sunbury", dateIn(2.minutes)),
|
||||||
|
StopInfoPanelState.DepartureInfo("Sunbury", Color(TRAIN_BLUE), "West Footscray", "Express via Metro Tunnel", dateIn(8.minutes)),
|
||||||
|
)),
|
||||||
|
StopInfoPanelState.DeparturePlatforms("Platform 2", listOf(
|
||||||
|
StopInfoPanelState.DepartureInfo("237", Color(BUS_ORANGE), "Westall", null, dateIn(7.minutes)),
|
||||||
|
StopInfoPanelState.DepartureInfo("442", Color(BUS_ORANGE), "Dandenong", null, dateIn(8.minutes)),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onEvent = {},
|
||||||
|
onPeekHeightChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import moe.lava.banksia.core.util.LoopFlow.Companion.waitUntilSubscribed
|
||||||
import moe.lava.banksia.core.util.Point
|
import moe.lava.banksia.core.util.Point
|
||||||
import moe.lava.banksia.core.util.log
|
import moe.lava.banksia.core.util.log
|
||||||
import moe.lava.banksia.data.ptv.PtvService
|
import moe.lava.banksia.data.ptv.PtvService
|
||||||
|
import moe.lava.banksia.ui.extensions.getUIProperties
|
||||||
import moe.lava.banksia.ui.layout.info.InfoPanelEvent
|
import moe.lava.banksia.ui.layout.info.InfoPanelEvent
|
||||||
import moe.lava.banksia.ui.layout.info.InfoPanelState
|
import moe.lava.banksia.ui.layout.info.InfoPanelState
|
||||||
import moe.lava.banksia.ui.layout.info.RouteInfoPanelState
|
import moe.lava.banksia.ui.layout.info.RouteInfoPanelState
|
||||||
|
|
@ -36,8 +37,6 @@ import moe.lava.banksia.ui.map.util.CameraPositionBounds
|
||||||
import moe.lava.banksia.ui.map.util.Marker
|
import moe.lava.banksia.ui.map.util.Marker
|
||||||
import moe.lava.banksia.ui.state.MapState
|
import moe.lava.banksia.ui.state.MapState
|
||||||
import moe.lava.banksia.ui.state.SearchState
|
import moe.lava.banksia.ui.state.SearchState
|
||||||
import kotlin.time.Clock
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
|
|
||||||
sealed class MapScreenEvent {
|
sealed class MapScreenEvent {
|
||||||
data object DismissState : MapScreenEvent()
|
data object DismissState : MapScreenEvent()
|
||||||
|
|
@ -165,6 +164,7 @@ class MapScreenViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
val route = routeRepository.get(routeId)
|
val route = routeRepository.get(routeId)
|
||||||
|
?: return
|
||||||
// val gtfsRoute = ptvService.route(routeId)
|
// val gtfsRoute = ptvService.route(routeId)
|
||||||
iInfoState.update {
|
iInfoState.update {
|
||||||
RouteInfoPanelState(
|
RouteInfoPanelState(
|
||||||
|
|
@ -232,23 +232,51 @@ class MapScreenViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
val departures = stopTimeRepository.getForStop(id)
|
val departures = stopTimeRepository.getForStop(id)
|
||||||
.filter { !it.headsign.isNullOrBlank() }
|
.groupBy { it.stopId }
|
||||||
.groupBy { it.headsign!! }
|
.mapKeys { (id) ->
|
||||||
.map { (headsign, stopTimes) ->
|
val stop = stopRepository.get(id)
|
||||||
val now = Clock.System.now()
|
if (stop.platformCode.firstOrNull()?.isDigit() == true) {
|
||||||
val times = stopTimes
|
"Platform " + stop.platformCode
|
||||||
.map { it.arrivalTime.toInstant(TimeZone.currentSystemDefault()) }
|
|
||||||
.filter { it >= (now - 1.minutes) }
|
|
||||||
.joinToString(" | ") {
|
|
||||||
val diff = (it - now).inWholeMinutes.coerceAtLeast(0)
|
|
||||||
if (diff >= 65) {
|
|
||||||
"${((diff + 30.0) / 60.0).toInt()}hr"
|
|
||||||
} else {
|
} else {
|
||||||
"${diff}mn"
|
stop.platformCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StopInfoPanelState.Departure(headsign, times)
|
.entries
|
||||||
|
.sortedBy { (platform) -> platform }
|
||||||
|
.map { (platform, deps) ->
|
||||||
|
StopInfoPanelState.DeparturePlatforms(
|
||||||
|
platform = platform,
|
||||||
|
departures = deps.take(5).mapNotNull {
|
||||||
|
val route = routeRepository.getByTrip(it.tripId)
|
||||||
|
?: return@mapNotNull null
|
||||||
|
StopInfoPanelState.DepartureInfo(
|
||||||
|
routeName = route.number ?: route.name,
|
||||||
|
routeColour = route.type.getUIProperties().colour,
|
||||||
|
headsign = it.headsign ?: route.name,
|
||||||
|
description = null,
|
||||||
|
time = it.departureTime.toInstant(TimeZone.currentSystemDefault()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// val departures = stopTimeRepository.getForStop(id)
|
||||||
|
// .filter { !it.headsign.isNullOrBlank() }
|
||||||
|
// .groupBy { it.headsign!! }
|
||||||
|
// .map { (headsign, stopTimes) ->
|
||||||
|
// val now = Clock.System.now()
|
||||||
|
// val times = stopTimes
|
||||||
|
// .map { it.arrivalTime.toInstant(TimeZone.currentSystemDefault()) }
|
||||||
|
// .filter { it >= (now - 1.minutes) }
|
||||||
|
// .joinToString(" | ") {
|
||||||
|
// val diff = (it - now).inWholeMinutes.coerceAtLeast(0)
|
||||||
|
// if (diff >= 65) {
|
||||||
|
// "${((diff + 30.0) / 60.0).toInt()}hr"
|
||||||
|
// } else {
|
||||||
|
// "${diff}mn"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// StopInfoPanelState.DeparturePlatforms(headsign, times)
|
||||||
|
// }
|
||||||
iInfoState.update {
|
iInfoState.update {
|
||||||
if (it !is StopInfoPanelState)
|
if (it !is StopInfoPanelState)
|
||||||
it
|
it
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue