Compare commits
1 commit
feat/depar
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b3016004b |
44 changed files with 627 additions and 211 deletions
|
|
@ -25,9 +25,40 @@ kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
val clientMain by creating {
|
||||||
|
dependsOn(commonMain.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
androidMain.get().dependsOn(clientMain)
|
||||||
|
iosArm64Main.get().dependsOn(clientMain)
|
||||||
|
iosSimulatorArm64Main.get().dependsOn(clientMain)
|
||||||
|
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
|
implementation(libs.koin.core)
|
||||||
implementation(projects.core)
|
implementation(projects.core)
|
||||||
api(projects.core.data.stoptime)
|
api(projects.core.stoptime)
|
||||||
|
}
|
||||||
|
|
||||||
|
androidMain.dependencies {
|
||||||
|
implementation(libs.koin.compose)
|
||||||
|
implementation(libs.ktor.client.okhttp)
|
||||||
|
}
|
||||||
|
commonMain.dependencies {
|
||||||
|
implementation(libs.okio)
|
||||||
|
implementation(libs.koin.core)
|
||||||
|
implementation(libs.ktor.client.core)
|
||||||
|
implementation(libs.ktor.client.contentnegotiation)
|
||||||
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(libs.kotlinx.datetime)
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
implementation(libs.kotlinx.serialization.protobuf)
|
||||||
|
|
||||||
|
implementation(projects.core)
|
||||||
|
implementation(projects.core.sqld)
|
||||||
|
}
|
||||||
|
iosMain.dependencies {
|
||||||
|
implementation(libs.ktor.client.darwin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
|
||||||
alias(libs.plugins.kotlinSerialization)
|
|
||||||
alias(libs.plugins.androidMultiplatformLibrary)
|
|
||||||
alias(libs.plugins.ksp)
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
android {
|
|
||||||
namespace = "moe.lava.banksia.core.data.client"
|
|
||||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
|
||||||
|
|
||||||
compilerOptions {
|
|
||||||
jvmTarget.set(JvmTarget.JVM_11)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compilerOptions {
|
|
||||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
|
||||||
}
|
|
||||||
|
|
||||||
iosArm64()
|
|
||||||
iosSimulatorArm64()
|
|
||||||
|
|
||||||
jvm()
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
androidMain.dependencies {
|
|
||||||
implementation(libs.koin.compose)
|
|
||||||
implementation(libs.ktor.client.okhttp)
|
|
||||||
}
|
|
||||||
commonMain.dependencies {
|
|
||||||
api(projects.core.data)
|
|
||||||
|
|
||||||
implementation(libs.okio)
|
|
||||||
implementation(libs.koin.core)
|
|
||||||
implementation(libs.ktor.client.core)
|
|
||||||
implementation(libs.ktor.client.contentnegotiation)
|
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.kotlinx.datetime)
|
|
||||||
implementation(libs.kotlinx.serialization.json)
|
|
||||||
implementation(libs.kotlinx.serialization.protobuf)
|
|
||||||
|
|
||||||
implementation(projects.core)
|
|
||||||
implementation(projects.core.sqld)
|
|
||||||
}
|
|
||||||
iosMain.dependencies {
|
|
||||||
implementation(libs.ktor.client.darwin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
plugins {
|
|
||||||
alias(libs.plugins.kotlinJvm)
|
|
||||||
alias(libs.plugins.kotlinSerialization)
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
compilerOptions {
|
|
||||||
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.okio)
|
|
||||||
implementation(libs.koin.core)
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.kotlinx.datetime)
|
|
||||||
|
|
||||||
api(projects.core.data)
|
|
||||||
implementation(projects.core)
|
|
||||||
}
|
|
||||||
|
|
@ -16,17 +16,13 @@ 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.data.sources.stop.StopLocalDataSource
|
import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource
|
||||||
import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource
|
import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource
|
||||||
import moe.lava.banksia.core.sqld.sqldDiModule
|
|
||||||
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 org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val clientDataDiModule = module {
|
actual val platformModule = module {
|
||||||
includes(sqldDiModule)
|
|
||||||
includes(stopTimeDataDiModule)
|
|
||||||
|
|
||||||
// HTTP Clients
|
// HTTP Clients
|
||||||
singleOf(::PtvService)
|
singleOf(::PtvService)
|
||||||
single {
|
single {
|
||||||
|
|
@ -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
|
||||||
import moe.lava.banksia.core.sqld.mappers.asModel
|
import moe.lava.banksia.core.sqld.mappers.asModel
|
||||||
|
|
||||||
internal class ClientRouteRepository internal constructor(
|
internal class ClientRouteRepository internal constructor(
|
||||||
|
|
@ -22,5 +23,14 @@ internal class ClientRouteRepository internal constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val tripRouteMap = mutableMapOf<Long, 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 getByPattern(patternId: Long) = mutex.withLock {
|
||||||
|
tripRouteMap[patternId]
|
||||||
|
?: remote.getByPattern(patternId).also {
|
||||||
|
local.save(it)
|
||||||
|
tripRouteMap[patternId] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import moe.lava.banksia.core.sqld.mappers.asDb
|
||||||
internal class RouteLocalDataSource(private val queries: RouteQueries) {
|
internal class RouteLocalDataSource(private val queries: RouteQueries) {
|
||||||
suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() }
|
suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() }
|
||||||
suspend fun getAll() = withContext(Dispatchers.IO) { queries.getAll().executeAsList() }
|
suspend fun getAll() = withContext(Dispatchers.IO) { queries.getAll().executeAsList() }
|
||||||
|
// suspend fun getByTrip(tripId: String) = dao.getByTrip(tripId)
|
||||||
suspend fun save(vararg routes: Route) {
|
suspend fun save(vararg routes: Route) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
queries.transaction {
|
queries.transaction {
|
||||||
|
|
@ -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 getByPattern(patternId: Long) = client.get("routes/by_pattern/${patternId}").body<Route>()
|
||||||
suspend fun getAll() = client.get("routes").body<List<Route>>()
|
suspend fun getAll() = client.get("routes").body<List<Route>>()
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package moe.lava.banksia.core.data
|
||||||
|
|
||||||
|
import moe.lava.banksia.core.sqld.sqldDiModule
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
internal expect val platformModule: Module
|
||||||
|
|
||||||
|
val dataDiModule = module {
|
||||||
|
includes(platformModule)
|
||||||
|
includes(sqldDiModule)
|
||||||
|
includes(stopTimeDataDiModule)
|
||||||
|
}
|
||||||
|
|
@ -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 getByPattern(patternId: Long): Route?
|
||||||
suspend fun getAll(): List<Route>
|
suspend fun getAll(): List<Route>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package moe.lava.banksia.core.data
|
||||||
|
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
internal actual val platformModule = module {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,10 @@ package moe.lava.banksia.core.sqld.mappers
|
||||||
|
|
||||||
import moe.lava.banksia.core.model.StopTime
|
import moe.lava.banksia.core.model.StopTime
|
||||||
import moe.lava.banksia.core.model.StoppingPattern
|
import moe.lava.banksia.core.model.StoppingPattern
|
||||||
|
import moe.lava.banksia.core.model.TimeType
|
||||||
import moe.lava.banksia.core.sqld.StoppingPattern as DbStoppingPattern
|
import moe.lava.banksia.core.sqld.StoppingPattern as DbStoppingPattern
|
||||||
|
|
||||||
fun DbStoppingPattern.asModel(stoptimes: List<StopTime.Undated>) = StoppingPattern.Undated(
|
fun <T: TimeType> DbStoppingPattern.asModel(stoptimes: List<StopTime<T>>) = StoppingPattern(
|
||||||
id = id,
|
id = id,
|
||||||
routeId = routeId,
|
routeId = routeId,
|
||||||
shapeId = shapeId,
|
shapeId = shapeId,
|
||||||
|
|
@ -13,7 +14,7 @@ fun DbStoppingPattern.asModel(stoptimes: List<StopTime.Undated>) = StoppingPatte
|
||||||
stoptimes = stoptimes,
|
stoptimes = stoptimes,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun StoppingPattern.Undated.asDb() = DbStoppingPattern(
|
fun StoppingPattern<*>.asDb() = DbStoppingPattern(
|
||||||
id = id,
|
id = id,
|
||||||
routeId = routeId,
|
routeId = routeId,
|
||||||
shapeId = shapeId,
|
shapeId = shapeId,
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,10 @@ SELECT * FROM Route;
|
||||||
get:
|
get:
|
||||||
SELECT * FROM Route WHERE id == ?;
|
SELECT * FROM Route WHERE id == ?;
|
||||||
|
|
||||||
|
getByPattern:
|
||||||
|
SELECT Route.* FROM Route
|
||||||
|
INNER JOIN StoppingPattern ON Route.id == StoppingPattern.routeId
|
||||||
|
WHERE StoppingPattern.id == :patternId;
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
INSERT INTO Route VALUES ?;
|
INSERT OR REPLACE INTO Route VALUES ?;
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,24 @@ INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId
|
||||||
WHERE StopTime.patternId == StoppingPattern.id
|
WHERE StopTime.patternId == StoppingPattern.id
|
||||||
AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId)
|
AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId)
|
||||||
AND ServiceException.type IS NULL;
|
AND ServiceException.type IS NULL;
|
||||||
|
|
||||||
|
getExtendedForStop:
|
||||||
|
SELECT DISTINCT
|
||||||
|
StopTime.patternId,
|
||||||
|
StopTime.arrivalDelta,
|
||||||
|
StopTime.departureTime,
|
||||||
|
StoppingPattern.headsign,
|
||||||
|
Route.type AS routeType,
|
||||||
|
Route.number AS routeNumber,
|
||||||
|
Route.name AS routeName,
|
||||||
|
Stop.platformCode AS stopPlatformCode
|
||||||
|
FROM StopTime
|
||||||
|
INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end`
|
||||||
|
LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date
|
||||||
|
INNER JOIN Trip ON Trip.serviceId == Service.id
|
||||||
|
INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId
|
||||||
|
INNER JOIN Route ON Route.id == StoppingPattern.routeId
|
||||||
|
INNER JOIN Stop ON Stop.id == StopTime.stopId
|
||||||
|
WHERE StopTime.patternId == StoppingPattern.id
|
||||||
|
AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId)
|
||||||
|
AND ServiceException.type IS NULL;
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,6 @@ CREATE TABLE StoppingPattern (
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
INSERT OR REPLACE INTO StoppingPattern VALUES ?;
|
INSERT OR REPLACE INTO StoppingPattern VALUES ?;
|
||||||
|
|
||||||
|
get:
|
||||||
|
SELECT * FROM StoppingPattern WHERE id == :id;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package moe.lava.banksia.core.endpoints
|
||||||
|
|
||||||
|
object Endpoint
|
||||||
|
|
@ -31,13 +31,15 @@ sealed class TimeType {
|
||||||
) : TimeType()
|
) : TimeType()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun TimeType.Undated.atDate(date: LocalDate) = TimeType.Dated(
|
||||||
|
arrival = arrival.atDate(date),
|
||||||
|
departure = departure.atDate(date),
|
||||||
|
)
|
||||||
|
|
||||||
fun StopTime<TimeType.Undated>.atDate(date: LocalDate) = StopTime(
|
fun StopTime<TimeType.Undated>.atDate(date: LocalDate) = StopTime(
|
||||||
patternId = patternId,
|
patternId = patternId,
|
||||||
stopId = stopId,
|
stopId = stopId,
|
||||||
time = TimeType.Dated(
|
time = time.atDate(date),
|
||||||
arrival = time.arrival.atDate(date),
|
|
||||||
departure = time.departure.atDate(date),
|
|
||||||
),
|
|
||||||
pickupType = pickupType,
|
pickupType = pickupType,
|
||||||
dropOffType = dropOffType,
|
dropOffType = dropOffType,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ plugins {
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
android {
|
android {
|
||||||
namespace = "moe.lava.banksia.core.data.stoptime"
|
namespace = "moe.lava.banksia.core.stoptime"
|
||||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||||
|
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
|
|
@ -56,5 +56,9 @@ kotlin {
|
||||||
iosMain.dependencies {
|
iosMain.dependencies {
|
||||||
implementation(libs.ktor.client.darwin)
|
implementation(libs.ktor.client.darwin)
|
||||||
}
|
}
|
||||||
|
jvmMain.dependencies {
|
||||||
|
implementation(libs.koin.ktor)
|
||||||
|
implementation(libs.ktor.server.core)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,9 @@ import io.ktor.client.request.parameter
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.todayIn
|
import kotlinx.datetime.todayIn
|
||||||
import moe.lava.banksia.core.model.StopTime
|
import moe.lava.banksia.core.data.dto.ExtendedStopTime
|
||||||
|
import moe.lava.banksia.core.endpoints.Endpoint
|
||||||
|
import moe.lava.banksia.core.endpoints.stopTimeByStop
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
||||||
internal class StopTimeRemoteDataSource(
|
internal class StopTimeRemoteDataSource(
|
||||||
|
|
@ -16,9 +18,9 @@ internal class StopTimeRemoteDataSource(
|
||||||
suspend fun getAtStop(
|
suspend fun getAtStop(
|
||||||
stopId: String,
|
stopId: String,
|
||||||
date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
||||||
): List<StopTime.Dated> {
|
): List<ExtendedStopTime> {
|
||||||
return client.get("stoptimes/by_stop/${stopId}") {
|
return client.get(Endpoint.stopTimeByStop(stopId)) {
|
||||||
parameter("date", date)
|
parameter("date", date)
|
||||||
}.body<List<StopTime.Dated>>()
|
}.body<List<ExtendedStopTime>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package moe.lava.banksia.core.data.dto
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import moe.lava.banksia.core.model.FutureTime
|
||||||
|
import moe.lava.banksia.core.model.RouteType
|
||||||
|
import moe.lava.banksia.core.model.TimeType
|
||||||
|
import moe.lava.banksia.core.model.atDate
|
||||||
|
import moe.lava.banksia.core.sqld.GetExtendedForStop
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ExtendedStopTime(
|
||||||
|
val patternId: Long,
|
||||||
|
val stopPlatformCode: String?,
|
||||||
|
val time: TimeType.Dated,
|
||||||
|
val headsign: String?,
|
||||||
|
val routeType: RouteType,
|
||||||
|
val routeNumber: String?,
|
||||||
|
val routeName: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: This probably doesn't belong here
|
||||||
|
fun GetExtendedForStop.asModel(date: LocalDate) = ExtendedStopTime(
|
||||||
|
patternId = patternId,
|
||||||
|
stopPlatformCode = stopPlatformCode,
|
||||||
|
time = TimeType.Undated(
|
||||||
|
arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()),
|
||||||
|
departure = FutureTime.fromInt(departureTime.toInt()),
|
||||||
|
).atDate(date),
|
||||||
|
headsign = headsign,
|
||||||
|
routeType = RouteType.from(routeType.toInt()),
|
||||||
|
routeNumber = routeNumber,
|
||||||
|
routeName = routeName,
|
||||||
|
)
|
||||||
|
|
@ -4,12 +4,12 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.todayIn
|
import kotlinx.datetime.todayIn
|
||||||
import moe.lava.banksia.core.model.StopTime
|
import moe.lava.banksia.core.data.dto.ExtendedStopTime
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
||||||
expect class StopTimeRepository {
|
expect class StopTimeRepository {
|
||||||
suspend fun getForStop(
|
suspend fun getForStop(
|
||||||
id: String,
|
id: String,
|
||||||
date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()),
|
||||||
): Flow<List<StopTime.Dated>>
|
): Flow<List<ExtendedStopTime>>
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,9 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.IO
|
import kotlinx.coroutines.IO
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import moe.lava.banksia.core.model.StopTime
|
import moe.lava.banksia.core.data.dto.ExtendedStopTime
|
||||||
import moe.lava.banksia.core.model.atDate
|
import moe.lava.banksia.core.data.dto.asModel
|
||||||
import moe.lava.banksia.core.sqld.StopTimeQueries
|
import moe.lava.banksia.core.sqld.StopTimeQueries
|
||||||
import moe.lava.banksia.core.sqld.mappers.asModel
|
|
||||||
import moe.lava.banksia.core.util.serialise
|
import moe.lava.banksia.core.util.serialise
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.get
|
import org.koin.core.component.get
|
||||||
|
|
@ -15,16 +14,16 @@ import org.koin.core.component.get
|
||||||
internal class StopTimeLocalDataSource : KoinComponent {
|
internal class StopTimeLocalDataSource : KoinComponent {
|
||||||
private val queries get() = get<StopTimeQueries>()
|
private val queries get() = get<StopTimeQueries>()
|
||||||
|
|
||||||
suspend fun getAtStop(stopId: String, date: LocalDate): List<StopTime.Dated> {
|
suspend fun getAtStop(stopId: String, date: LocalDate): List<ExtendedStopTime> {
|
||||||
return withContext(context = Dispatchers.IO) {
|
return withContext(context = Dispatchers.IO) {
|
||||||
queries
|
queries
|
||||||
.getForStopDated(
|
.getExtendedForStop(
|
||||||
listOf(date.dayOfWeek).serialise().toLong(),
|
listOf(date.dayOfWeek).serialise().toLong(),
|
||||||
date.toEpochDays(),
|
date.toEpochDays(),
|
||||||
stopId,
|
stopId,
|
||||||
)
|
)
|
||||||
.executeAsList()
|
.executeAsList()
|
||||||
.map { it.asModel().atDate(date) }
|
.map { it.asModel(date) }
|
||||||
.sortedBy { it.time.departure }
|
.sortedBy { it.time.departure }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package moe.lava.banksia.core.endpoints
|
||||||
|
|
||||||
|
fun Endpoint.stopTimeByStop(stopId: String) = "stoptimes/by_stop/${stopId}"
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package moe.lava.banksia.server.routes
|
||||||
|
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.todayIn
|
||||||
|
import moe.lava.banksia.core.data.repositories.StopTimeRepository
|
||||||
|
import moe.lava.banksia.core.endpoints.Endpoint
|
||||||
|
import moe.lava.banksia.core.endpoints.stopTimeByStop
|
||||||
|
import org.koin.ktor.ext.inject
|
||||||
|
import kotlin.time.Clock
|
||||||
|
|
||||||
|
fun Route.stopTimeRoutes() {
|
||||||
|
val repo by inject<StopTimeRepository>()
|
||||||
|
|
||||||
|
get(Endpoint.stopTimeByStop("{stop_id}")) {
|
||||||
|
val stopId = call.parameters["stop_id"]!!
|
||||||
|
val date = call.queryParameters["date"]
|
||||||
|
?.let { LocalDate.parse(it, LocalDate.Formats.ISO) }
|
||||||
|
?: Clock.System.todayIn(TimeZone.currentSystemDefault())
|
||||||
|
val data = repo.getForStop(stopId, date).first()
|
||||||
|
call.respond(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "9.1.0"
|
agp = "9.1.0"
|
||||||
android-compileSdk = "36"
|
android-compileSdk = "37"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
android-targetSdk = "36"
|
android-targetSdk = "37"
|
||||||
androidx-activity= "1.13.0"
|
androidx-activity= "1.13.0"
|
||||||
androidx-lifecycle = "2.10.0"
|
androidx-lifecycle = "2.10.0"
|
||||||
compose-multiplatform = "1.11.0-alpha04"
|
compose-multiplatform = "1.12.0-alpha02"
|
||||||
composeunstyled = "1.49.6"
|
composeunstyled = "1.49.6"
|
||||||
coroutines = "1.10.2"
|
coroutines = "1.10.2"
|
||||||
geo = "0.8.0"
|
geo = "0.8.0"
|
||||||
|
|
@ -19,7 +19,7 @@ ktor = "3.4.1"
|
||||||
logback = "1.5.32"
|
logback = "1.5.32"
|
||||||
maplibre = "0.12.1"
|
maplibre = "0.12.1"
|
||||||
material = "1.7.3"
|
material = "1.7.3"
|
||||||
material3 = "1.11.0-alpha04"
|
material3 = "1.11.0-alpha07"
|
||||||
okio = "3.17.0"
|
okio = "3.17.0"
|
||||||
playServicesLocation = "21.3.0"
|
playServicesLocation = "21.3.0"
|
||||||
secretsGradlePlugin = "2.0.1"
|
secretsGradlePlugin = "2.0.1"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ kotlin {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.core)
|
implementation(projects.core)
|
||||||
|
implementation(projects.core.data)
|
||||||
implementation(projects.core.sqld)
|
implementation(projects.core.sqld)
|
||||||
|
implementation(projects.core.stoptime)
|
||||||
implementation(projects.server.gtfs)
|
implementation(projects.server.gtfs)
|
||||||
implementation(projects.server.gtfsRt)
|
implementation(projects.server.gtfsRt)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,22 +15,16 @@ import io.ktor.server.routing.routing
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
import kotlinx.datetime.TimeZone
|
|
||||||
import kotlinx.datetime.todayIn
|
|
||||||
import moe.lava.banksia.core.Constants
|
import moe.lava.banksia.core.Constants
|
||||||
import moe.lava.banksia.core.model.atDate
|
|
||||||
import moe.lava.banksia.core.sqld.RouteQueries
|
import moe.lava.banksia.core.sqld.RouteQueries
|
||||||
import moe.lava.banksia.core.sqld.StopQueries
|
import moe.lava.banksia.core.sqld.StopQueries
|
||||||
import moe.lava.banksia.core.sqld.StopTimeQueries
|
|
||||||
import moe.lava.banksia.core.sqld.mappers.asModel
|
import moe.lava.banksia.core.sqld.mappers.asModel
|
||||||
import moe.lava.banksia.core.util.serialise
|
|
||||||
import moe.lava.banksia.server.di.ServerModules
|
import moe.lava.banksia.server.di.ServerModules
|
||||||
import moe.lava.banksia.server.gtfsrt.GtfsrtService
|
import moe.lava.banksia.server.gtfsrt.GtfsrtService
|
||||||
|
import moe.lava.banksia.server.routes.stopTimeRoutes
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.koin.ktor.ext.get
|
import org.koin.ktor.ext.get
|
||||||
import org.koin.ktor.plugin.Koin
|
import org.koin.ktor.plugin.Koin
|
||||||
import kotlin.time.Clock
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
if (System.getenv("BANKSIA_PRODUCTION") == "1") Constants.devMode = false
|
if (System.getenv("BANKSIA_PRODUCTION") == "1") Constants.devMode = false
|
||||||
|
|
@ -53,6 +47,8 @@ fun Application.module() {
|
||||||
launch { get<GtfsrtService>().start(this, !Constants.devMode) }
|
launch { get<GtfsrtService>().start(this, !Constants.devMode) }
|
||||||
|
|
||||||
routing {
|
routing {
|
||||||
|
stopTimeRoutes()
|
||||||
|
|
||||||
if (Constants.devMode) {
|
if (Constants.devMode) {
|
||||||
get("/fixup") {
|
get("/fixup") {
|
||||||
call.respondText("received")
|
call.respondText("received")
|
||||||
|
|
@ -137,23 +133,5 @@ fun Application.module() {
|
||||||
}
|
}
|
||||||
call.respond(stops.map { it.asModel() })
|
call.respond(stops.map { it.asModel() })
|
||||||
}
|
}
|
||||||
get("/stoptimes/by_stop/{stop_id}") {
|
|
||||||
val stopId = call.parameters["stop_id"]!!
|
|
||||||
val date = call.queryParameters["date"]
|
|
||||||
?.let { LocalDate.parse(it, LocalDate.Formats.ISO) }
|
|
||||||
?: Clock.System.todayIn(TimeZone.currentSystemDefault())
|
|
||||||
val times = withContext(context = Dispatchers.IO) {
|
|
||||||
get<StopTimeQueries>()
|
|
||||||
.getForStopDated(
|
|
||||||
listOf(date.dayOfWeek).serialise().toLong(),
|
|
||||||
date.toEpochDays(),
|
|
||||||
stopId,
|
|
||||||
)
|
|
||||||
.executeAsList()
|
|
||||||
.map { it.asModel().atDate(date) }
|
|
||||||
.sortedBy { it.time.departure }
|
|
||||||
}
|
|
||||||
call.respond(times)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package moe.lava.banksia.server.di
|
package moe.lava.banksia.server.di
|
||||||
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import moe.lava.banksia.core.sqld.sqldDiModule
|
import moe.lava.banksia.core.data.dataDiModule
|
||||||
import moe.lava.banksia.server.GtfsDataFixer
|
import moe.lava.banksia.server.GtfsDataFixer
|
||||||
import moe.lava.banksia.server.GtfsImporter
|
import moe.lava.banksia.server.GtfsImporter
|
||||||
import moe.lava.banksia.server.gtfs.GtfsParser
|
import moe.lava.banksia.server.gtfs.GtfsParser
|
||||||
|
|
@ -11,7 +11,7 @@ import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val ServerModules = module {
|
val ServerModules = module {
|
||||||
includes(sqldDiModule)
|
includes(dataDiModule)
|
||||||
|
|
||||||
single { HttpClient() }
|
single { HttpClient() }
|
||||||
singleOf(::GtfsParser)
|
singleOf(::GtfsParser)
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,7 @@ include(":server:gtfs")
|
||||||
include(":server:gtfs_rt")
|
include(":server:gtfs_rt")
|
||||||
include(":core")
|
include(":core")
|
||||||
include(":core:data")
|
include(":core:data")
|
||||||
include(":core:data:client")
|
include(":core:stoptime")
|
||||||
include(":core:data:server")
|
|
||||||
include(":core:data:stoptime")
|
|
||||||
include(":core:sqld")
|
include(":core:sqld")
|
||||||
include(":ui")
|
include(":ui")
|
||||||
include(":ui:maps")
|
include(":ui:maps")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -68,7 +70,8 @@ kotlin {
|
||||||
implementation(libs.ui.backhandler)
|
implementation(libs.ui.backhandler)
|
||||||
|
|
||||||
implementation(projects.core)
|
implementation(projects.core)
|
||||||
implementation(projects.core.data.client)
|
implementation(projects.core.data)
|
||||||
|
implementation(projects.core.stoptime)
|
||||||
implementation(projects.ui.maps)
|
implementation(projects.ui.maps)
|
||||||
implementation(projects.ui.shared)
|
implementation(projects.ui.shared)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package moe.lava.banksia.ui.di
|
package moe.lava.banksia.ui.di
|
||||||
|
|
||||||
import moe.lava.banksia.core.data.clientDataDiModule
|
import moe.lava.banksia.core.data.dataDiModule
|
||||||
import moe.lava.banksia.ui.screens.map.MapScreenViewModel
|
import moe.lava.banksia.ui.screens.map.MapScreenViewModel
|
||||||
import org.koin.core.module.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val AppModule = module {
|
val AppModule = module {
|
||||||
includes(clientDataDiModule)
|
includes(dataDiModule)
|
||||||
|
|
||||||
// ViewModel
|
// ViewModel
|
||||||
viewModelOf(::MapScreenViewModel)
|
viewModelOf(::MapScreenViewModel)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeContent
|
import androidx.compose.foundation.layout.safeContent
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
|
@ -27,7 +28,6 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.coerceAtMost
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
@ -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,11 +66,13 @@ fun InfoPanel(
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = 24.dp)
|
||||||
|
.heightIn(min = 350.dp)
|
||||||
.onSizeChanged {
|
.onSizeChanged {
|
||||||
onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) })
|
// onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) })
|
||||||
|
onPeekHeightChange(350.dp)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,297 @@
|
||||||
package moe.lava.banksia.ui.layout.info
|
package moe.lava.banksia.ui.layout.info
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
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.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
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.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
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.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
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() {
|
||||||
|
data object ToggleGrouping : StopInfoPanelEvent()
|
||||||
|
}
|
||||||
|
|
||||||
data class StopInfoPanelState(
|
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.isNullOrEmpty()
|
||||||
|
|
||||||
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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun listColors() = ListItemDefaults.colors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
selectedContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
private fun MonoPlatform(
|
||||||
|
state: StopInfoPanelState.DeparturePlatforms
|
||||||
|
) {
|
||||||
|
val departures = state.departures
|
||||||
|
val lazyState = LazyListState(firstVisibleItemIndex =
|
||||||
|
departures.indexOfFirst {
|
||||||
|
it.time > Clock.System.now()
|
||||||
|
}.coerceAtLeast(0)
|
||||||
|
)
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
||||||
|
state = lazyState,
|
||||||
|
) {
|
||||||
|
itemsIndexed(departures) { idx, dep ->
|
||||||
|
SegmentedListItem(
|
||||||
|
onClick = {},
|
||||||
|
colors = listColors(),
|
||||||
|
shapes = ListItemDefaults.segmentedShapes(
|
||||||
|
idx,
|
||||||
|
departures.size,
|
||||||
|
),
|
||||||
|
supportingContent = {
|
||||||
|
dep.description?.let { Text(dep.description) }
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy((-4).dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = (dep.time - Clock.System.now()).inWholeMinutes.toString(),
|
||||||
|
style = MaterialTheme.typography.headlineSmallEmphasized,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "mn",
|
||||||
|
style = MaterialTheme.typography.labelSmallEmphasized,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
private fun ManyPlatforms(
|
||||||
|
state: List<StopInfoPanelState.DeparturePlatforms>,
|
||||||
|
) {
|
||||||
|
val expandedList = remember { mutableStateListOf(*Array(state.size) { true }) }
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
state.forEachIndexed { idx, depInfo ->
|
||||||
|
val (platform, departures) = depInfo
|
||||||
|
val expanded = expandedList[idx]
|
||||||
|
stickyHeader(key = "header_${depInfo.hashCode()}") {
|
||||||
|
val base = ListItemDefaults.segmentedShapes(0, 2)
|
||||||
|
val large = MaterialTheme.shapes.large
|
||||||
|
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.animateItem()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
|
.padding(bottom = ListItemDefaults.SegmentedGap)
|
||||||
|
) {
|
||||||
|
SegmentedListItem(
|
||||||
|
onClick = { expandedList[idx] = !expandedList[idx] },
|
||||||
|
colors = listColors(),
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
.padding(6.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = platform,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expanded) {
|
||||||
|
item(key = "items_${depInfo.hashCode()}") {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
||||||
|
) {
|
||||||
|
departures.filter { it.time > Clock.System.now() }.take(5)
|
||||||
|
.forEachIndexed { idx, dep ->
|
||||||
|
SegmentedListItem(
|
||||||
|
onClick = {},
|
||||||
|
colors = listColors(),
|
||||||
|
shapes = ListItemDefaults.segmentedShapes(
|
||||||
|
idx + 1,
|
||||||
|
(departures.size + 1).coerceAtMost(6),
|
||||||
|
),
|
||||||
|
supportingContent = {
|
||||||
|
dep.description?.let { Text(dep.description) }
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy((-4).dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = (dep.time - Clock.System.now()).inWholeMinutes.toString(),
|
||||||
|
style = MaterialTheme.typography.headlineSmallEmphasized,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "mn",
|
||||||
|
style = MaterialTheme.typography.labelSmallEmphasized,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item(key = "spacer_${depInfo.hashCode()}") {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.animateItem().height(10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun StopInfoPanel(
|
internal fun StopInfoPanel(
|
||||||
state: StopInfoPanelState,
|
state: StopInfoPanelState,
|
||||||
onEvent: (StopInfoPanelEvent) -> Unit,
|
onEvent: (StopInfoPanelEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxWidth()) {
|
val spec = fadeIn(tween(300, 300)) togetherWith fadeOut(tween(300))
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = state,
|
||||||
|
contentKey = { it.id },
|
||||||
|
transitionSpec = { spec },
|
||||||
|
) { state ->
|
||||||
|
Column(Modifier.fillMaxWidth().fillMaxHeight()) {
|
||||||
|
Row {
|
||||||
|
Column {
|
||||||
Text(
|
Text(
|
||||||
state.name,
|
state.name,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
|
@ -53,23 +308,51 @@ internal fun StopInfoPanel(
|
||||||
textAlign = TextAlign.Start
|
textAlign = TextAlign.Start
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
state.departures?.let {
|
}
|
||||||
Spacer(Modifier.height(5.dp))
|
IconButton(
|
||||||
it.forEach { (name, formatted) ->
|
onClick = { onEvent(StopInfoPanelEvent.ToggleGrouping) },
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
) { Icon(Icons.Default.Edit, null) }
|
||||||
Text(
|
}
|
||||||
name,
|
Spacer(Modifier.height(10.dp))
|
||||||
style = MaterialTheme.typography.titleMedium,
|
AnimatedContent(
|
||||||
fontWeight = FontWeight.SemiBold
|
targetState = state.departures,
|
||||||
)
|
transitionSpec = { spec },
|
||||||
Text(
|
) { departures ->
|
||||||
formatted,
|
departures?.let { departurePlatforms ->
|
||||||
maxLines = 1,
|
if (departurePlatforms.size > 1) {
|
||||||
overflow = TextOverflow.Ellipsis,
|
ManyPlatforms(departurePlatforms)
|
||||||
modifier = Modifier.padding(horizontal = 5.dp)
|
} else if (departurePlatforms.size == 1) {
|
||||||
|
MonoPlatform(departurePlatforms[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.toInstant
|
import kotlinx.datetime.toInstant
|
||||||
|
import moe.lava.banksia.core.data.dto.ExtendedStopTime
|
||||||
import moe.lava.banksia.core.data.repositories.RouteRepository
|
import moe.lava.banksia.core.data.repositories.RouteRepository
|
||||||
import moe.lava.banksia.core.data.repositories.StopRepository
|
import moe.lava.banksia.core.data.repositories.StopRepository
|
||||||
import moe.lava.banksia.core.data.repositories.StopTimeRepository
|
import moe.lava.banksia.core.data.repositories.StopTimeRepository
|
||||||
|
|
@ -26,9 +27,11 @@ 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
|
||||||
|
import moe.lava.banksia.ui.layout.info.StopInfoPanelEvent
|
||||||
import moe.lava.banksia.ui.layout.info.StopInfoPanelState
|
import moe.lava.banksia.ui.layout.info.StopInfoPanelState
|
||||||
import moe.lava.banksia.ui.layout.info.TripInfoPanelState
|
import moe.lava.banksia.ui.layout.info.TripInfoPanelState
|
||||||
import moe.lava.banksia.ui.map.util.CameraPosition
|
import moe.lava.banksia.ui.map.util.CameraPosition
|
||||||
|
|
@ -36,8 +39,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()
|
||||||
|
|
@ -53,6 +54,9 @@ private data class InternalState(
|
||||||
val route: String? = null,
|
val route: String? = null,
|
||||||
val stop: String? = null,
|
val stop: String? = null,
|
||||||
val run: String? = null,
|
val run: String? = null,
|
||||||
|
|
||||||
|
val lastStopDepartures: List<ExtendedStopTime>? = null,
|
||||||
|
val stopsGrouped: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
class MapScreenViewModel(
|
class MapScreenViewModel(
|
||||||
|
|
@ -69,6 +73,10 @@ class MapScreenViewModel(
|
||||||
viewModelScope.launch { switchRoute(value.route) }
|
viewModelScope.launch { switchRoute(value.route) }
|
||||||
if (value.stop != last.stop)
|
if (value.stop != last.stop)
|
||||||
viewModelScope.launch { switchStop(value.stop) }
|
viewModelScope.launch { switchStop(value.stop) }
|
||||||
|
if (value.lastStopDepartures != last.lastStopDepartures)
|
||||||
|
viewModelScope.launch { buildDepartures() }
|
||||||
|
if (value.stopsGrouped != last.stopsGrouped)
|
||||||
|
viewModelScope.launch { buildDepartures() }
|
||||||
if (value.run != last.run)
|
if (value.run != last.run)
|
||||||
switchRun(value.run)
|
switchRun(value.run)
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +113,9 @@ class MapScreenViewModel(
|
||||||
|
|
||||||
fun handleEvent(event: InfoPanelEvent) {
|
fun handleEvent(event: InfoPanelEvent) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// when (event) { }
|
when (event) {
|
||||||
|
StopInfoPanelEvent.ToggleGrouping -> state = state.copy(stopsGrouped = !state.stopsGrouped)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +175,7 @@ class MapScreenViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
val route = routeRepository.get(routeId)
|
val route = routeRepository.get(routeId)
|
||||||
// val gtfsRoute = ptvService.route(routeId)
|
?: return
|
||||||
iInfoState.update {
|
iInfoState.update {
|
||||||
RouteInfoPanelState(
|
RouteInfoPanelState(
|
||||||
name = route.name,
|
name = route.name,
|
||||||
|
|
@ -215,11 +225,11 @@ class MapScreenViewModel(
|
||||||
private suspend fun switchStop(id: String?) {
|
private suspend fun switchStop(id: String?) {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
iInfoState.update { InfoPanelState.None }
|
iInfoState.update { InfoPanelState.None }
|
||||||
|
state = state.copy(lastStopDepartures = null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val stop = stopRepository.get(id)
|
val stop = stopRepository.get(id)
|
||||||
// val stop = ptvService.stop(routeType, stopId)
|
|
||||||
val split = stop.name.split("/")
|
val split = stop.name.split("/")
|
||||||
val name = split[0]
|
val name = split[0]
|
||||||
val subname = split.getOrNull(1)
|
val subname = split.getOrNull(1)
|
||||||
|
|
@ -232,27 +242,54 @@ class MapScreenViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTimeRepository.getForStop(id)
|
stopTimeRepository.getForStop(id)
|
||||||
.onEach { stoptimes ->
|
.onEach { departures ->
|
||||||
val departures = stoptimes
|
state = state.copy(
|
||||||
// .filter { !it.headsign.isNullOrBlank() }
|
lastStopDepartures = departures
|
||||||
// .groupBy { it.headsign!! }
|
)
|
||||||
.groupBy { it.stopId } // TODO: Placeholder
|
|
||||||
.map { (headsign, stopTimes) ->
|
|
||||||
val now = Clock.System.now()
|
|
||||||
val times = stopTimes
|
|
||||||
.map { it.time.arrival.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"
|
|
||||||
}
|
}
|
||||||
}
|
.launchIn(viewModelScope)
|
||||||
StopInfoPanelState.Departure(headsign, times)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun friendlyPlatform(platform: String) =
|
||||||
|
platform.takeUnless { it.firstOrNull()?.isDigit() == true }
|
||||||
|
?: "Platform $platform"
|
||||||
|
private fun buildDepartures() {
|
||||||
|
val rawDepartures = state.lastStopDepartures ?: return
|
||||||
|
val departures = if (state.stopsGrouped) {
|
||||||
|
rawDepartures
|
||||||
|
.groupBy { it.stopPlatformCode }
|
||||||
|
.mapKeys { (platform) -> platform?.let { friendlyPlatform(it) } }
|
||||||
|
.entries
|
||||||
|
.sortedBy { (platform) -> platform }
|
||||||
|
.map { (platform, deps) ->
|
||||||
|
StopInfoPanelState.DeparturePlatforms(
|
||||||
|
platform = platform ?: "",
|
||||||
|
departures = deps.map {
|
||||||
|
StopInfoPanelState.DepartureInfo(
|
||||||
|
routeName = it.routeNumber ?: it.routeName,
|
||||||
|
routeColour = it.routeType.getUIProperties().colour,
|
||||||
|
headsign = it.headsign ?: it.routeName,
|
||||||
|
description = null,
|
||||||
|
time = it.time.departure.toInstant(TimeZone.currentSystemDefault()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (rawDepartures.isEmpty()) {
|
||||||
|
listOf()
|
||||||
|
} else {
|
||||||
|
listOf(StopInfoPanelState.DeparturePlatforms(platform = "", departures = rawDepartures.map { dep ->
|
||||||
|
StopInfoPanelState.DepartureInfo(
|
||||||
|
routeName = dep.routeNumber ?: dep.routeName,
|
||||||
|
routeColour = dep.routeType.getUIProperties().colour,
|
||||||
|
headsign = dep.headsign ?: dep.routeName,
|
||||||
|
description = dep.stopPlatformCode?.let { friendlyPlatform(it) },
|
||||||
|
time = dep.time.departure.toInstant(TimeZone.currentSystemDefault()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
departures.let { departures ->
|
||||||
iInfoState.update {
|
iInfoState.update {
|
||||||
if (it !is StopInfoPanelState)
|
if (it !is StopInfoPanelState)
|
||||||
it
|
it
|
||||||
|
|
@ -260,7 +297,6 @@ class MapScreenViewModel(
|
||||||
it.copy(departures = departures)
|
it.copy(departures = departures)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private suspend fun buildPolylines(route: PtvRoute) {
|
/*private suspend fun buildPolylines(route: PtvRoute) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue