diff --git a/.gitignore b/.gitignore index 975a370..83f099d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,6 @@ captures **/xcshareddata/WorkspaceSettings.xcsettings secrets.properties -/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt +shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt /data/ /data diff --git a/build.gradle.kts b/build.gradle.kts index 9434477..0687328 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,6 @@ plugins { alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.kotlinMultiplatform) apply false - alias(libs.plugins.sqldelight) apply false alias(libs.plugins.wire) apply false } diff --git a/core/sqld/build.gradle.kts b/client/build.gradle.kts similarity index 51% rename from core/sqld/build.gradle.kts rename to client/build.gradle.kts index 472a908..29dbc08 100644 --- a/core/sqld/build.gradle.kts +++ b/client/build.gradle.kts @@ -4,12 +4,11 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) - alias(libs.plugins.sqldelight) } kotlin { android { - namespace = "moe.lava.banksia.core.sqld" + namespace = "moe.lava.banksia.client" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { @@ -17,37 +16,28 @@ kotlin { } } + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } + iosArm64() iosSimulatorArm64() - jvm() - sourceSets { androidMain.dependencies { - implementation(libs.sqldelight.driver.android) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.androidx.activity.compose) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.play.services.location) } commonMain.dependencies { - implementation(libs.okio) implementation(libs.koin.core) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) - - implementation(projects.core) - } - nativeMain.dependencies { - implementation(libs.sqldelight.driver.native) - } - jvmMain.dependencies { - implementation(libs.sqldelight.driver.jvm) - } - } -} - -sqldelight { - databases { - register("BanksiaDatabase") { - packageName.set("moe.lava.banksia.core.sqld") - schemaOutputDirectory.set(file("src/commonMain/sqldelight/schema")) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(projects.shared) } } } diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt new file mode 100644 index 0000000..e89d4e9 --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt @@ -0,0 +1,11 @@ +package moe.lava.banksia.client.data.route + +import moe.lava.banksia.model.Route +import moe.lava.banksia.room.dao.RouteDao +import moe.lava.banksia.room.entity.asEntity + +class RouteLocalDataSource(private val dao: RouteDao) { + suspend fun get(id: String) = dao.get(id) + suspend fun getAll() = dao.getAll() + suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt new file mode 100644 index 0000000..cbe9804 --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt @@ -0,0 +1,11 @@ +package moe.lava.banksia.client.data.route + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import moe.lava.banksia.model.Route + +class RouteRemoteDataSource(val client: HttpClient) { + suspend fun get(id: String) = client.get("routes/${id}").body() + suspend fun getAll() = client.get("routes").body>() +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt new file mode 100644 index 0000000..486aae0 --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.client.data.stop + +import moe.lava.banksia.model.Stop +import moe.lava.banksia.room.dao.RouteDao +import moe.lava.banksia.room.dao.StopDao +import moe.lava.banksia.room.entity.asEntity + +class StopLocalDataSource(private val dao: StopDao, private val routeDao: RouteDao) { + suspend fun get(id: String) = dao.get(id) + suspend fun getByRoute(id: String) = routeDao.stops(id) + suspend fun save(vararg stops: Stop) = dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) +} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt similarity index 64% rename from core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt index f39afd3..47c2f80 100644 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.core.data.sources.stop +package moe.lava.banksia.client.data.stop import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get -import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.model.Stop -internal class StopRemoteDataSource(val client: HttpClient) { +class StopRemoteDataSource(val client: HttpClient) { suspend fun get(id: String) = client.get("stops/${id}").body() suspend fun getByRoute(id: String) = client.get("route_stops/${id}").body>() } diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt new file mode 100644 index 0000000..3640b1f --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt @@ -0,0 +1,28 @@ +package moe.lava.banksia.client.data.stoptime + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.model.StopTimeDated +import moe.lava.banksia.model.atDate +import moe.lava.banksia.room.dao.StopTimeDao +import moe.lava.banksia.util.serialise +import kotlin.time.Clock + +class StopTimeLocalDataSource( + private val stopTimeDao: StopTimeDao, +) { + suspend fun getAtStop( + stopId: String, + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), + ): List { + return stopTimeDao + .getForStopDated( + stopId, + listOf(date.dayOfWeek).serialise(), + date.toEpochDays().toInt(), + ) + .map { it.asModel().atDate(date) } + .sortedBy { it.departureTime } + } +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt new file mode 100644 index 0000000..baf26e7 --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt @@ -0,0 +1,36 @@ +package moe.lava.banksia.client.data.stoptime + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.parameter +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.model.StopTimeDated +import kotlin.time.Clock + +class StopTimeRemoteDataSource( + private val client: HttpClient, +) { + suspend fun getAtStop( + stopId: String, + date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), + ): List { + return client.get("stoptimes/by_stop/${stopId}") { + parameter("date", date) + }.body>() + } + + /*suspend fun get( + stop: String? = null, + trip: String? = null, + day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek, + ): List { + return client.get("stoptimes") { + stop?.let { parameter("stop", it) } + trip?.let { parameter("trip", it) } + day?.let { parameter("day", it) } + }.body>() + }*/ +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt new file mode 100644 index 0000000..8b46fbd --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt @@ -0,0 +1,18 @@ +package moe.lava.banksia.client.data.trip + +import io.ktor.client.HttpClient +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.model.Trip +import kotlin.time.Clock + +class TripRemoteDataSource( + private val client: HttpClient, +) { + suspend fun get( + day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek, + ): List { + return listOf() + } +} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt similarity index 57% rename from core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt index 104c6bc..f22c7db 100644 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.data +package moe.lava.banksia.client.di import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpSend @@ -7,22 +7,22 @@ import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.plugin import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json -import moe.lava.banksia.core.Constants -import moe.lava.banksia.core.data.repositories.ClientRouteRepository -import moe.lava.banksia.core.data.repositories.ClientStopRepository -import moe.lava.banksia.core.data.repositories.RouteRepository -import moe.lava.banksia.core.data.repositories.StopRepository -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.stop.StopLocalDataSource -import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource -import moe.lava.banksia.core.util.log +import moe.lava.banksia.Constants +import moe.lava.banksia.client.data.route.RouteLocalDataSource +import moe.lava.banksia.client.data.route.RouteRemoteDataSource +import moe.lava.banksia.client.data.stop.StopLocalDataSource +import moe.lava.banksia.client.data.stop.StopRemoteDataSource +import moe.lava.banksia.client.data.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.client.data.stoptime.StopTimeRemoteDataSource +import moe.lava.banksia.client.repository.RouteRepository +import moe.lava.banksia.client.repository.StopRepository +import moe.lava.banksia.client.repository.StopTimeRepository import moe.lava.banksia.data.ptv.PtvService +import moe.lava.banksia.util.log import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind import org.koin.dsl.module -actual val platformModule = module { +val ClientModule = module { // HTTP Clients singleOf(::PtvService) single { @@ -49,8 +49,11 @@ actual val platformModule = module { singleOf(::RouteRemoteDataSource) singleOf(::StopLocalDataSource) singleOf(::StopRemoteDataSource) + singleOf(::StopTimeLocalDataSource) + singleOf(::StopTimeRemoteDataSource) // Repositories - singleOf(::ClientRouteRepository) bind RouteRepository::class - singleOf(::ClientStopRepository) bind StopRepository::class + singleOf(::RouteRepository) + singleOf(::StopRepository) + singleOf(::StopTimeRepository) } diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt new file mode 100644 index 0000000..22a6bcc --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt @@ -0,0 +1,25 @@ +package moe.lava.banksia.client.repository + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.lava.banksia.client.data.route.RouteLocalDataSource +import moe.lava.banksia.client.data.route.RouteRemoteDataSource + +class RouteRepository( + private val local: RouteLocalDataSource, + private val remote: RouteRemoteDataSource, +) { + private val mutex = Mutex() + suspend fun getAll() = mutex.withLock { + local + .getAll() + .map { it.asModel() } + .ifEmpty { + remote + .getAll() + .also { local.save(*it.toTypedArray()) } + } + } + + suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt new file mode 100644 index 0000000..690616a --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.client.repository + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.lava.banksia.client.data.stop.StopLocalDataSource +import moe.lava.banksia.client.data.stop.StopRemoteDataSource + +class StopRepository( + private val local: StopLocalDataSource, + private val remote: StopRemoteDataSource, +) { + private val mutex = Mutex() + + suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } + suspend fun getByRoute(id: String) = mutex.withLock { + local + .getByRoute(id) + .map { it.asModel() } + .ifEmpty { null } + ?: remote.getByRoute(id) + } +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt new file mode 100644 index 0000000..4f54840 --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt @@ -0,0 +1,16 @@ +package moe.lava.banksia.client.repository + +import moe.lava.banksia.client.data.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.client.data.stoptime.StopTimeRemoteDataSource +import moe.lava.banksia.model.StopTimeDated + +class StopTimeRepository( + private val local: StopTimeLocalDataSource, + private val remote: StopTimeRemoteDataSource, +) { + suspend fun getForStop(id: String): List { + return local + .getAtStop(id) + .ifEmpty { remote.getAtStop(id) } + } +} diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts deleted file mode 100644 index ecdba19..0000000 --- a/core/data/build.gradle.kts +++ /dev/null @@ -1,64 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -plugins { - alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.androidMultiplatformLibrary) -} - -kotlin { - android { - namespace = "moe.lava.banksia.core.data" - 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 { - val clientMain by creating { - dependsOn(commonMain.get()) - } - - androidMain.get().dependsOn(clientMain) - iosArm64Main.get().dependsOn(clientMain) - iosSimulatorArm64Main.get().dependsOn(clientMain) - - commonMain.dependencies { - implementation(libs.koin.core) - implementation(projects.core) - 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) - } - } -} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt deleted file mode 100644 index f46caac..0000000 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt +++ /dev/null @@ -1,36 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource -import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.sqld.mappers.asModel - -internal class ClientRouteRepository internal constructor( - private val local: RouteLocalDataSource, - private val remote: RouteRemoteDataSource, -) : RouteRepository { - private val mutex = Mutex() - override suspend fun getAll() = mutex.withLock { - local - .getAll() - .map { it.asModel() } - .ifEmpty { - remote - .getAll() - .also { local.save(*it.toTypedArray()) } - } - } - - private val tripRouteMap = mutableMapOf() - - 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 - } - } -} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt deleted file mode 100644 index 0aee84e..0000000 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource -import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource -import moe.lava.banksia.core.sqld.mappers.asModel - -internal class ClientStopRepository internal constructor( - private val local: StopLocalDataSource, - private val remote: StopRemoteDataSource, -) : StopRepository { - private val mutex = Mutex() - - override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } - override suspend fun getByRoute(id: String) = mutex.withLock { - local - .getByRoute(id) - .map { it.asModel() } - .ifEmpty { null } - ?: remote.getByRoute(id) - } -} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt deleted file mode 100644 index 8286b1f..0000000 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.lava.banksia.core.data.sources.route - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.sqld.RouteQueries -import moe.lava.banksia.core.sqld.mappers.asDb - -internal class RouteLocalDataSource(private val queries: RouteQueries) { - suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() } - suspend fun getAll() = withContext(Dispatchers.IO) { queries.getAll().executeAsList() } -// suspend fun getByTrip(tripId: String) = dao.getByTrip(tripId) - suspend fun save(vararg routes: Route) { - withContext(Dispatchers.IO) { - queries.transaction { - routes.forEach { - queries.insert(it.asDb()) - } - } - } - } -} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt deleted file mode 100644 index 15088fb..0000000 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package moe.lava.banksia.core.data.sources.route - -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get -import moe.lava.banksia.core.model.Route - -internal class RouteRemoteDataSource(val client: HttpClient) { - suspend fun get(id: String) = client.get("routes/${id}").body() - suspend fun getByPattern(patternId: Long) = client.get("routes/by_pattern/${patternId}").body() - suspend fun getAll() = client.get("routes").body>() -} diff --git a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt deleted file mode 100644 index 524d123..0000000 --- a/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt +++ /dev/null @@ -1,22 +0,0 @@ -package moe.lava.banksia.core.data.sources.stop - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext -import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.sqld.StopQueries -import moe.lava.banksia.core.sqld.mappers.asDb - -internal class StopLocalDataSource(private val queries: StopQueries) { - suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() } - suspend fun getByRoute(id: String) = withContext(Dispatchers.IO) { queries.getByRoute(id).executeAsList() } - suspend fun save(vararg stops: Stop) { - withContext(Dispatchers.IO) { - queries.transaction { - stops.forEach { - queries.insert(it.asDb()) - } - } - } - } -} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt deleted file mode 100644 index eea6a0e..0000000 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt +++ /dev/null @@ -1,13 +0,0 @@ -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) -} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt deleted file mode 100644 index ef3d6f1..0000000 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import moe.lava.banksia.core.model.Route - -interface RouteRepository { - suspend fun get(id: String): Route? - suspend fun getByPattern(patternId: Long): Route? - suspend fun getAll(): List -} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt deleted file mode 100644 index c663f89..0000000 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import moe.lava.banksia.core.model.Stop - -interface StopRepository { - suspend fun get(id: String): Stop - suspend fun getByRoute(id: String): List -} diff --git a/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt b/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt deleted file mode 100644 index 78a44d1..0000000 --- a/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.lava.banksia.core.data - -import org.koin.dsl.module - -internal actual val platformModule = module { - -} diff --git a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt deleted file mode 100644 index c47613c..0000000 --- a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt +++ /dev/null @@ -1,14 +0,0 @@ -package moe.lava.banksia.core.sqld - -import android.content.Context -import app.cash.sqldelight.driver.android.AndroidSqliteDriver -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -actual class DatabaseManager : KoinComponent { - actual val database by lazy { - val ctx = get().applicationContext - val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "${DBNAME}.db") - BanksiaDatabase(driver) - } -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt deleted file mode 100644 index 983eb58..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.lava.banksia.core.sqld - -internal const val DBNAME = "timetable" - -expect class DatabaseManager() { - val database: BanksiaDatabase -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt deleted file mode 100644 index deee453..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.lava.banksia.core.sqld - -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -val sqldDiModule = module { - singleOf(::DatabaseManager) - factory { get().database } - factory { get().routeQueries } - factory { get().serviceQueries } - factory { get().serviceExceptionQueries } - factory { get().shapeQueries } - factory { get().stopQueries } - factory { get().stoppingPatternQueries } - factory { get().stopTimeQueries } - factory { get().tripQueries } -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt deleted file mode 100644 index f3a5521..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt +++ /dev/null @@ -1,14 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.sqld.Route as DbRoute - -fun DbRoute.asModel() = Route( - id = id, - type = RouteType.from(type.toInt()), - number = number, - name = name, -) - -fun Route.asDb() = DbRoute(id, type.value.toLong(), number, name) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt deleted file mode 100644 index dbda5ea..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt +++ /dev/null @@ -1,21 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.model.Service -import moe.lava.banksia.core.util.deserialiseDaysBitflag -import moe.lava.banksia.core.util.serialise -import moe.lava.banksia.core.sqld.Service as DbService - -fun DbService.asModel() = Service( - id = id, - days = days.toInt().deserialiseDaysBitflag(), - start = LocalDate.fromEpochDays(start), - end = LocalDate.fromEpochDays(end), -) - -fun Service.asDb() = DbService( - id = id, - days = days.serialise().toLong(), - start = start.toEpochDays(), - end = end.toEpochDays(), -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt deleted file mode 100644 index ef0d201..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.model.ServiceException -import moe.lava.banksia.core.sqld.ServiceException as DbServiceException - -fun DbServiceException.asModel() = ServiceException( - serviceId = serviceId, - date = LocalDate.fromEpochDays(date), - type = type.toInt(), -) - -fun ServiceException.asDb() = DbServiceException( - serviceId = serviceId, - type = date.toEpochDays(), - date = type.toLong(), -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt deleted file mode 100644 index 4a8d7db..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt +++ /dev/null @@ -1,52 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Shape -import moe.lava.banksia.core.model.ShapePath -import moe.lava.banksia.core.util.Point -import moe.lava.banksia.core.sqld.Shape as DbShape - -fun DbShape.asModel() = Shape( - id = id, - path = bytesToPath(path), -) - -fun Shape.asDb() = DbShape( - id = id, - path = bytesFromPath(path), -) - -private fun bytesToPath(value: ByteArray): ShapePath { - return value - .asSequence() - .asIterable() - .chunked(8) { - (it[0].toLong() and 0xFF) or - (it[1].toLong() and 0xFF shl 8) or - (it[2].toLong() and 0xFF shl 16) or - (it[3].toLong() and 0xFF shl 24) or - (it[4].toLong() and 0xFF shl 32) or - (it[5].toLong() and 0xFF shl 40) or - (it[6].toLong() and 0xFF shl 48) or - (it[7].toLong() and 0xFF shl 56) - } - .map { Double.fromBits(it) } - .chunked(2) - .map { (lat, lng) -> Point(lat, lng) } - .toList() -} - -private fun bytesFromPath(path: ShapePath): ByteArray { - return path - .flatMap { (lat, lng) -> listOf(lat.toBits(), lng.toBits()) } - .flatMap { i -> listOf( - i.toByte(), - (i shr 8).toByte(), - (i shr 16).toByte(), - (i shr 24).toByte(), - (i shr 32).toByte(), - (i shr 40).toByte(), - (i shr 48).toByte(), - (i shr 56).toByte(), - ) } - .toByteArray() -} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt deleted file mode 100644 index 3bf6b54..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.util.Point -import moe.lava.banksia.core.sqld.Stop as DbStop - -fun DbStop.asModel() = Stop( - id = id, - name = name, - pos = Point(lat, lng), - parent = parent, - hasWheelChairBoarding = hasWheelChairBoarding == 1L, - level = level, - platformCode = platformCode, -) - -fun Stop.asDb() = DbStop( - id = id, - name = name, - lat = pos.lat, - lng = pos.lng, - parent = parent, - hasWheelChairBoarding = if (hasWheelChairBoarding) 1L else 0L, - level = level, - platformCode = platformCode -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt deleted file mode 100644 index 26d5390..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.FutureTime -import moe.lava.banksia.core.model.FutureTime.Companion.asInt -import moe.lava.banksia.core.model.StopTime -import moe.lava.banksia.core.model.TimeType -import moe.lava.banksia.core.sqld.StopTime as DbStopTime - -fun DbStopTime.asModel() = StopTime( - patternId = patternId, - stopId = stopId, - time = TimeType.Undated( - arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()), - departure = FutureTime.fromInt(departureTime.toInt()), - ), - pickupType = pickupType.toInt(), - dropOffType = dropOffType.toInt(), -) - -fun StopTime.Undated.asDb() = DbStopTime( - patternId = patternId, - stopId = stopId, - arrivalDelta = (time.arrival.asInt() - time.departure.asInt()).toLong(), - departureTime = time.departure.asInt().toLong(), - pickupType = pickupType.toLong(), - dropOffType = dropOffType.toLong(), -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt deleted file mode 100644 index d1409a2..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.StopTime -import moe.lava.banksia.core.model.StoppingPattern -import moe.lava.banksia.core.model.TimeType -import moe.lava.banksia.core.sqld.StoppingPattern as DbStoppingPattern - -fun DbStoppingPattern.asModel(stoptimes: List>) = StoppingPattern( - id = id, - routeId = routeId, - shapeId = shapeId, - headsign = headsign, - wheelchairAccessible = wheelchairAccessible == 1L, - stoptimes = stoptimes, -) - -fun StoppingPattern<*>.asDb() = DbStoppingPattern( - id = id, - routeId = routeId, - shapeId = shapeId, - headsign = headsign, - wheelchairAccessible = if (wheelchairAccessible) 1L else 0L, -) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt deleted file mode 100644 index b3443fb..0000000 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.banksia.core.sqld.mappers - -import moe.lava.banksia.core.model.Service -import moe.lava.banksia.core.model.StoppingPattern -import moe.lava.banksia.core.model.Trip -import moe.lava.banksia.core.sqld.Trip as DbTrip - -fun DbTrip.asModel(pattern: StoppingPattern.Undated, service: Service): Trip.Undated { - if (serviceId != service.id) { - throw IllegalArgumentException("trip and service id mismatch (${serviceId} != ${service.id})") - } - return Trip( - id = gtfsId, - pattern = pattern, - service = service, - directionId = directionId.toInt(), - blockId = blockId.toString(), - ) -} - -fun Trip.Undated.asDb() = DbTrip( - gtfsId = id, - patternId = pattern.id, - serviceId = service.id, - directionId = directionId.toLong(), - blockId = blockId?.toLong(), -) diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq deleted file mode 100644 index e607975..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq +++ /dev/null @@ -1,20 +0,0 @@ -CREATE TABLE Route ( - id TEXT PRIMARY KEY NOT NULL, - type INTEGER NOT NULL, - number TEXT, - name TEXT NOT NULL -); - -getAll: -SELECT * FROM Route; - -get: -SELECT * FROM Route WHERE id == ?; - -getByPattern: -SELECT Route.* FROM Route -INNER JOIN StoppingPattern ON Route.id == StoppingPattern.routeId -WHERE StoppingPattern.id == :patternId; - -insert: -INSERT OR REPLACE INTO Route VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq deleted file mode 100644 index a1c5fad..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE Service ( - id TEXT PRIMARY KEY NOT NULL, - days INTEGER NOT NULL, - start INTEGER NOT NULL, - end INTEGER NOT NULL -); - -CREATE INDEX idx_Service_days ON Service (days); - -insert: -INSERT INTO Service VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq deleted file mode 100644 index 332f198..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE ServiceException ( - serviceId TEXT NOT NULL, - type INTEGER NOT NULL, - date INTEGER NOT NULL, - PRIMARY KEY (serviceId, type) -); - -insert: -INSERT INTO ServiceException VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq deleted file mode 100644 index 8734200..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE Shape ( - id TEXT PRIMARY KEY NOT NULL, - path BLOB NOT NULL -); - -insert: -INSERT INTO Shape VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq deleted file mode 100644 index 4af5c50..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq +++ /dev/null @@ -1,54 +0,0 @@ -CREATE TABLE Stop ( - id TEXT PRIMARY KEY NOT NULL, - name TEXT NOT NULL, - lat REAL NOT NULL, - lng REAL NOT NULL, - parent TEXT REFERENCES Stop(id), - hasWheelChairBoarding INTEGER NOT NULL, - level TEXT, - platformCode TEXT -); - -CREATE INDEX idx_Stop_parent ON Stop (parent); - -getAll: -SELECT * FROM Stop; - -getAllParentless: -SELECT * FROM Stop WHERE platformCode IS NOT NULL AND parent IS NULL; - -get: -SELECT * FROM Stop WHERE id == ?; - -getMany: -SELECT * FROM Stop WHERE id IN ?; - -insert: -INSERT INTO Stop VALUES ?; - -updateParents: -UPDATE Stop SET parent = ? WHERE id IN ?; - -getByRoute: -SELECT Stop.* FROM Stop -INNER JOIN StopTime ON StopTime.stopId == Stop.id -INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId -WHERE StoppingPattern.routeId == :id -GROUP BY Stop.id; - --- I vibecoded this, sorry -getParentsByRoute: -WITH RECURSIVE Tree AS ( - SELECT Stop.* FROM Stop - INNER JOIN StopTime ON StopTime.stopId == Stop.id - INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId - WHERE StoppingPattern.routeId == :id - GROUP BY Stop.id - - UNION ALL - - SELECT s.* - FROM Stop s - INNER JOIN Tree t ON s.id = t.parent -) -SELECT DISTINCT * FROM Tree WHERE parent IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq deleted file mode 100644 index 06bd76b..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq +++ /dev/null @@ -1,45 +0,0 @@ -CREATE TABLE StopTime ( - patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), - stopId TEXT NOT NULL REFERENCES Stop (id), - arrivalDelta INTEGER NOT NULL, - departureTime INTEGER NOT NULL, - pickupType INTEGER NOT NULL, - dropOffType INTEGER NOT NULL, - PRIMARY KEY (patternId, stopId) -) WITHOUT ROWID; - -CREATE INDEX idx_StopTime_stopId ON StopTime (stopId); - -insert: -INSERT OR REPLACE INTO StopTime VALUES ?; - -getForStopDated: -SELECT DISTINCT StopTime.* 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 -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; - -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; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq deleted file mode 100644 index 9a09e69..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE StoppingPattern ( - id INTEGER PRIMARY KEY NOT NULL, - routeId TEXT NOT NULL REFERENCES Route (id), - shapeId TEXT NOT NULL REFERENCES Shape (id), - headsign TEXT NOT NULL, - wheelchairAccessible INTEGER NOT NULL -); - -insert: -INSERT OR REPLACE INTO StoppingPattern VALUES ?; - -get: -SELECT * FROM StoppingPattern WHERE id == :id; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq deleted file mode 100644 index c53b62a..0000000 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE Trip ( - gtfsId TEXT PRIMARY KEY NOT NULL, - patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), - serviceId TEXT NOT NULL REFERENCES Service (id), - blockId INTEGER, - directionId INTEGER NOT NULL -); - -CREATE INDEX idx_Trip_patternId ON Trip (patternId); -CREATE INDEX idx_Trip_serviceId ON Trip (serviceId); - -insert: -INSERT OR REPLACE INTO Trip VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/schema/1.db b/core/sqld/src/commonMain/sqldelight/schema/1.db deleted file mode 100644 index feaacb3..0000000 Binary files a/core/sqld/src/commonMain/sqldelight/schema/1.db and /dev/null differ diff --git a/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt b/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt deleted file mode 100644 index 9ce0627..0000000 --- a/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.lava.banksia.core.sqld - -import app.cash.sqldelight.driver.native.NativeSqliteDriver -import org.koin.core.component.KoinComponent - -actual class DatabaseManager : KoinComponent { - actual val database by lazy { - val driver = NativeSqliteDriver(BanksiaDatabase.Schema, "${DBNAME}.db") - BanksiaDatabase(driver) - } -} diff --git a/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt b/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt deleted file mode 100644 index 61d9e95..0000000 --- a/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt +++ /dev/null @@ -1,56 +0,0 @@ -package moe.lava.banksia.core.sqld - -import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import moe.lava.banksia.core.util.error -import org.koin.core.component.KoinComponent -import java.io.File -import java.util.Properties -import kotlin.system.exitProcess - -actual class DatabaseManager : KoinComponent { - private var driver = connect() - actual val database get() = BanksiaDatabase(driver) - - private fun connect(path: String = "./data/${DBNAME}.db") = - JdbcSqliteDriver("jdbc:sqlite:${path}", Properties(), BanksiaDatabase.Schema) - .apply { execute(null, "PRAGMA journal_mode = OFF;", 0) } - - fun makeAlt() = run { - File("./data/${DBNAME}_alt.db").takeIf { it.exists() }?.delete() - val driver = connect("./data/${DBNAME}_alt.db") - BanksiaDatabase(driver) to { driver.close() } - } - - fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { - val live = File("./data/${DBNAME}.db") - val alt = File("./data/${DBNAME}_alt.db") - val old = File("./data/${DBNAME}_old.db") - - if (live.takeIf { it.exists() }?.renameTo(old) == false) { - error("DatabaseManager", "Failed to rename database from live to old (${live.absolutePath} -> ${old.absolutePath})") - return - } - if (alt.takeIf { it.exists() }?.renameTo(live) == false) { - error("DatabaseManager", "Failed to rename database from alt to live, trying to undo.. (${alt.absolutePath} -> ${live.absolutePath})") - if (!live.renameTo(old)) { - error("DatabaseManager", "Failed to undo, critical failure, exiting..") - exitProcess(1) - } - return - } - val oldDriver = driver - driver = connect() - - scope.launch { - delay(5000) - if (old.takeIf { it.exists() }?.delete() == false) { - error("DatabaseManager", "Failed to unlink old database, stray files! (${old.absolutePath})") - } - oldDriver.close() - } - } -} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt deleted file mode 100644 index 7e23b5d..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt +++ /dev/null @@ -1,3 +0,0 @@ -package moe.lava.banksia.core.endpoints - -object Endpoint diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/ServiceException.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/ServiceException.kt deleted file mode 100644 index ef2f918..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/ServiceException.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.lava.banksia.core.model - -import kotlinx.datetime.LocalDate -import kotlinx.serialization.Serializable - -@Serializable -data class ServiceException( - val serviceId: String, - val date: LocalDate, - val type: Int, -) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt deleted file mode 100644 index edd7c51..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.lava.banksia.core.model - -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.Serializable - -@Serializable -data class StopTime( - val patternId: Long, - val stopId: String, - val time: T, - val pickupType: Int, - val dropOffType: Int, -) { - typealias Dated = StopTime - typealias Undated = StopTime -} - -@Serializable -sealed class TimeType { - @Serializable - data class Undated( - val arrival: FutureTime, - val departure: FutureTime, - ) : TimeType() - - @Serializable - data class Dated( - val arrival: LocalDateTime, - val departure: LocalDateTime, - ) : TimeType() -} - -fun TimeType.Undated.atDate(date: LocalDate) = TimeType.Dated( - arrival = arrival.atDate(date), - departure = departure.atDate(date), -) - -fun StopTime.atDate(date: LocalDate) = StopTime( - patternId = patternId, - stopId = stopId, - time = time.atDate(date), - pickupType = pickupType, - dropOffType = dropOffType, -) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt deleted file mode 100644 index 1374cff..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt +++ /dev/null @@ -1,16 +0,0 @@ -package moe.lava.banksia.core.model - -import kotlinx.serialization.Serializable - -@Serializable -data class StoppingPattern( - val id: Long, - val routeId: String, - val shapeId: String, - val headsign: String, - val wheelchairAccessible: Boolean, - val stoptimes: List>, -) { - typealias Dated = StoppingPattern - typealias Undated = StoppingPattern -} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt deleted file mode 100644 index 752d6d2..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.lava.banksia.core.model - -import kotlinx.serialization.Serializable - -@Serializable -data class Trip( - val id: String, - val pattern: StoppingPattern, - val service: Service, - val directionId: Int, - val blockId: String?, -) { - typealias Dated = Trip - typealias Undated = Trip -} diff --git a/core/stoptime/build.gradle.kts b/core/stoptime/build.gradle.kts deleted file mode 100644 index 44cf072..0000000 --- a/core/stoptime/build.gradle.kts +++ /dev/null @@ -1,64 +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.stoptime" - compileSdk = libs.versions.android.compileSdk.get().toInt() - - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - freeCompilerArgs.add("-Xexpect-actual-classes") - } - - iosArm64() - iosSimulatorArm64() - - jvm() - - sourceSets { - val clientMain by creating { - dependsOn(commonMain.get()) - } - - androidMain.get().dependsOn(clientMain) - iosArm64Main.get().dependsOn(clientMain) - iosSimulatorArm64Main.get().dependsOn(clientMain) - - androidMain.dependencies { - 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) - } - jvmMain.dependencies { - implementation(libs.koin.ktor) - implementation(libs.ktor.server.core) - } - } -} diff --git a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt deleted file mode 100644 index 2f83304..0000000 --- a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.lava.banksia.core.data - -import moe.lava.banksia.core.data.repositories.StopTimeRepository -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal actual val platformModule = module { - singleOf(::StopTimeRepository) - singleOf(::StopTimeRemoteDataSource) -} diff --git a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt deleted file mode 100644 index ecaff8e..0000000 --- a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.flow.flow -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource - -actual class StopTimeRepository internal constructor( - private val local: StopTimeLocalDataSource, - private val remote: StopTimeRemoteDataSource, -) { - actual suspend fun getForStop(id: String, date: LocalDate) = flow { - emit(local.getAtStop(id, date)) - - remote.getAtStop(id, date) - .takeIf { it.isNotEmpty() } - ?.let { emit(it) } - } -} diff --git a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt deleted file mode 100644 index 0c38f64..0000000 --- a/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.lava.banksia.core.data.sources.stoptime - -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get -import io.ktor.client.request.parameter -import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -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 - -internal class StopTimeRemoteDataSource( - private val client: HttpClient, -) { - suspend fun getAtStop( - stopId: String, - date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): List { - return client.get(Endpoint.stopTimeByStop(stopId)) { - parameter("date", date) - }.body>() - } -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt deleted file mode 100644 index d46affa..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.lava.banksia.core.data - -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import org.koin.core.module.Module -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal expect val platformModule: Module; - -val stopTimeDataDiModule = module { - includes(platformModule) - singleOf(::StopTimeLocalDataSource) -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt deleted file mode 100644 index 38de29d..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt +++ /dev/null @@ -1,34 +0,0 @@ -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, -) diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt deleted file mode 100644 index 6a81c09..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.flow.Flow -import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.data.dto.ExtendedStopTime -import kotlin.time.Clock - -expect class StopTimeRepository { - suspend fun getForStop( - id: String, - date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): Flow> -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt deleted file mode 100644 index f22dc09..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt +++ /dev/null @@ -1,30 +0,0 @@ -package moe.lava.banksia.core.data.sources.stoptime - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.data.dto.ExtendedStopTime -import moe.lava.banksia.core.data.dto.asModel -import moe.lava.banksia.core.sqld.StopTimeQueries -import moe.lava.banksia.core.util.serialise -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -internal class StopTimeLocalDataSource : KoinComponent { - private val queries get() = get() - - suspend fun getAtStop(stopId: String, date: LocalDate): List { - return withContext(context = Dispatchers.IO) { - queries - .getExtendedForStop( - listOf(date.dayOfWeek).serialise().toLong(), - date.toEpochDays(), - stopId, - ) - .executeAsList() - .map { it.asModel(date) } - .sortedBy { it.time.departure } - } - } -} diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt deleted file mode 100644 index f689b2d..0000000 --- a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt +++ /dev/null @@ -1,3 +0,0 @@ -package moe.lava.banksia.core.endpoints - -fun Endpoint.stopTimeByStop(stopId: String) = "stoptimes/by_stop/${stopId}" diff --git a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt deleted file mode 100644 index 70ef406..0000000 --- a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.lava.banksia.core.data - -import moe.lava.banksia.core.data.repositories.StopTimeRepository -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -internal actual val platformModule = module { - singleOf(::StopTimeRepository) -} diff --git a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt deleted file mode 100644 index b4c37a6..0000000 --- a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import kotlinx.coroutines.flow.flow -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource - -actual class StopTimeRepository internal constructor( - private val local: StopTimeLocalDataSource, -) { - actual suspend fun getForStop(id: String, date: LocalDate) = flow { - emit(local.getAtStop(id, date)) - } -} diff --git a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt deleted file mode 100644 index 5791855..0000000 --- a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt +++ /dev/null @@ -1,27 +0,0 @@ -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() - - 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) - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 483c5d5..70676f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,36 +1,40 @@ [versions] agp = "9.1.0" -android-compileSdk = "37" +android-compileSdk = "36" android-minSdk = "24" -android-targetSdk = "37" -androidx-activity= "1.13.0" -androidx-lifecycle = "2.10.0" -compose-multiplatform = "1.12.0-alpha02" +android-targetSdk = "36" +androidx-activityCompose = "1.12.4" +androidx-appcompat = "1.7.0" +androidx-constraintlayout = "2.2.1" +androidx-core-ktx = "1.15.0" +androidx-espresso-core = "3.6.1" +androidx-lifecycle = "2.9.6" +androidx-material = "1.12.0" +androidx-test-junit = "1.2.1" +compose-multiplatform = "1.11.0-alpha02" composeunstyled = "1.49.6" coroutines = "1.10.2" geo = "0.8.0" -koin = "4.2.0" -kotlin = "2.3.20" +junit = "4.13.2" +koin = "4.1.1" +kotlin = "2.3.10" kotlinxDatetime = "0.7.1" kotlinxSerializationCsv = "0.2.18" kotlinxSerialization = "1.10.0" ksp = "2.3.4" -ktor = "3.4.1" +ktor = "3.4.0" logback = "1.5.32" maplibre = "0.12.1" material = "1.7.3" -material3 = "1.11.0-alpha07" -okio = "3.17.0" +material3 = "1.11.0-alpha02" +okio = "3.16.4" playServicesLocation = "21.3.0" +sqlite = "2.6.2" +room = "2.8.4" secretsGradlePlugin = "2.0.1" -sqldelight = "2.3.2" -wire = "6.1.0" +wire = "5.5.0" [libraries] -androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } -androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } -androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } -androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "compose-multiplatform" } compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "compose-multiplatform" } compose-material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "material" } @@ -40,11 +44,18 @@ compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "compose-mu compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "compose-multiplatform" } compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "compose-multiplatform" } composeunstyled = { module = "com.composables:composeunstyled", version.ref = "composeunstyled" } +moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" } +moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } +androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } @@ -62,14 +73,14 @@ ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "k ktor-server-tests = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre" } -moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" } -moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } +room-common = { group = "androidx.room", name = "room-common", version.ref = "room" } +room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } +room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" } secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } -sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } -sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } -sqldelight-driver-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" } ui-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "compose-multiplatform" } [plugins] @@ -82,6 +93,6 @@ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" } +room = { id = "androidx.room", version.ref = "room" } secretsGradle = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" } -sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } wire = { id = "com.squareup.wire", version.ref = "wire" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 9d2cb78..2f7d989 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -5,27 +5,15 @@ plugins { application } -group = "moe.lava.banksia.server" +group = "moe.lava.banksia" version = "1.0.0" application { mainClass.set("moe.lava.banksia.server.ApplicationKt") applicationDefaultJvmArgs = listOf("-Dio.ktor.development=${extra["io.ktor.development"] ?: "false"}") } -kotlin { - compilerOptions { - freeCompilerArgs.add("-Xexplicit-backing-fields") - } -} - dependencies { - implementation(projects.core) - implementation(projects.core.data) - implementation(projects.core.sqld) - implementation(projects.core.stoptime) - implementation(projects.server.gtfs) - implementation(projects.server.gtfsRt) - + implementation(projects.shared) implementation(libs.logback) implementation(libs.koin.core) implementation(libs.koin.ktor) @@ -38,6 +26,8 @@ dependencies { implementation(libs.ktor.server.contentnegotiation) implementation(libs.ktor.server.core) implementation(libs.ktor.server.netty) + implementation(libs.room.runtime) + implementation(libs.sqlite.bundled) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) } diff --git a/server/gtfs/build.gradle.kts b/server/gtfs/build.gradle.kts deleted file mode 100644 index 8f6d646..0000000 --- a/server/gtfs/build.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - alias(libs.plugins.kotlinJvm) - alias(libs.plugins.kotlinSerialization) -} - -kotlin { - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - freeCompilerArgs.add("-Xexplicit-backing-fields") - } -} - -dependencies { - implementation(projects.core) - implementation(libs.kotlinx.serialization.csv) - implementation(libs.kotlinx.datetime) - implementation(libs.ktor.client.contentnegotiation) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.okhttp) -} diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt deleted file mode 100644 index c844499..0000000 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ /dev/null @@ -1,388 +0,0 @@ -package moe.lava.banksia.server.gtfs - -import com.lightningkite.kotlinx.serialization.csv.CsvFormat -import com.lightningkite.kotlinx.serialization.csv.StringDeferringConfig -import io.ktor.client.HttpClient -import io.ktor.client.request.prepareRequest -import io.ktor.client.request.url -import io.ktor.client.statement.bodyAsChannel -import io.ktor.util.cio.writeChannel -import io.ktor.util.logging.Logger -import io.ktor.utils.io.copyAndClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion -import kotlinx.datetime.DayOfWeek -import kotlinx.datetime.LocalDate -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.serializer -import moe.lava.banksia.core.Constants -import moe.lava.banksia.core.model.FutureTime.Companion.asInt -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.model.Service -import moe.lava.banksia.core.model.ServiceException -import moe.lava.banksia.core.model.Shape -import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.model.StopTime -import moe.lava.banksia.core.model.StoppingPattern -import moe.lava.banksia.core.model.TimeType -import moe.lava.banksia.core.model.Trip -import moe.lava.banksia.core.util.Point -import moe.lava.banksia.server.gtfs.structures.GtfsRoute -import moe.lava.banksia.server.gtfs.structures.GtfsService -import moe.lava.banksia.server.gtfs.structures.GtfsServiceException -import moe.lava.banksia.server.gtfs.structures.GtfsShape -import moe.lava.banksia.server.gtfs.structures.GtfsStop -import moe.lava.banksia.server.gtfs.structures.GtfsStopTime -import moe.lava.banksia.server.gtfs.structures.GtfsTrip -import java.io.File -import java.nio.ByteBuffer -import java.security.MessageDigest -import java.util.zip.ZipFile -import kotlin.time.ExperimentalTime - -private typealias StopWithSource = Pair - -sealed class GtfsData { - data class RouteChunk(val routes: List) : GtfsData() - data class ServiceChunk(val services: List) : GtfsData() - data class ServiceExceptionChunk(val exceptions: List) : GtfsData() - data class ShapeChunk(val shapes: List) : GtfsData() - data class StopChunk(val stops: List) : GtfsData() - data class TripChunk(val trips: List) : GtfsData() -} - -class GtfsParser( - private val log: Logger, - private val client: HttpClient, -) { - private val csv = CsvFormat(StringDeferringConfig(EmptySerializersModule())) - private val datasetPath = File("/tmp/banksia", "dataset.zip") - - @OptIn(ExperimentalTime::class) - suspend fun update(datasetUrl: String): Flow { - val parentDir = datasetPath.parentFile - @Suppress("SimplifyBooleanWithConstants", "KotlinConstantConditions") - if (parentDir.exists() && !Constants.devMode) - parentDir.deleteRecursively() - - parentDir.mkdirs() - - log.info("fetching..") - client.prepareRequest { - url(datasetUrl) - }.execute { resp -> - if (!datasetPath.exists()) - resp.bodyAsChannel().copyAndClose(datasetPath.writeChannel()) - log.info("fetched!") - } - - log.info("extracting...") - @Suppress("KotlinConstantConditions") - val files = if (Constants.devMode) { - datasetPath.parentFile - .listFiles { it.isDirectory } - .flatMap { d -> d.listFiles { f -> f.extension == "txt" }.toList() } - .ifEmpty { extractAll(datasetPath) } -// .filter { it.parentFile.name == "2" } - } else { - extractAll(datasetPath) - } - - log.info("parsing...") - return parse(files) - .onCompletion { - @Suppress("KotlinConstantConditions") - if (!Constants.devMode) { - parentDir.deleteRecursively() - } - - log.info("done!") - } - } - - private fun parse(files: List) = flow { - files - .filter { it.name == "routes.txt" } - .forEach { emit(GtfsData.RouteChunk(parseRoutes(it))) } - - files - .filter { it.name == "stops.txt" } - .flatMap { parseStops(it) } - .let { emit(GtfsData.StopChunk(fixupDuplicateStops(it))) } - - files - .filter { it.name == "shapes.txt" } - .forEach { emit(GtfsData.ShapeChunk(parseShapes(it))) } - - val services = files - .filter { it.name == "calendar.txt" } - .flatMap { fd -> - parseServices(fd) - .also { emit(GtfsData.ServiceChunk(it)) } - } - .associateBy { it.id } - - files - .filter { it.name == "calendar_dates.txt" } - .forEach { emit(GtfsData.ServiceExceptionChunk(parseServiceExceptions(it))) } - - val trips = files - .filter { it.name == "trips.txt" } - .flatMap { fd -> - parseTrips(fd, services) - } - .associateBy { it.id } - - files - .filter { it.name == "stop_times.txt" } - .forEach { fd -> - log.info("parsing stop times for ${fd.parent}...") - parseStopTimes(fd) { seq -> - val times = ArrayList>(1000100) - seq.forEach { pair -> - val (_, stoptime) = pair - if (times.size > 1000000 && stoptime.patternId == 1L) { - emit(GtfsData.TripChunk(processStoptimes(trips, times))) - times.clear() - } - - times.add(pair) - } - emit(GtfsData.TripChunk(processStoptimes(trips, times))) - } - } - } - - private fun hashCalc(headsign: String, stops: List): Long { - val inst = MessageDigest.getInstance("SHA-256") - inst.update(headsign.toByteArray()) - stops.forEach { - inst.update(it.stopId.toByteArray()) - val dint = it.time.departure.asInt() - inst.update((dint).toByte()) - inst.update((dint shr 8).toByte()) - val aint = it.time.arrival.asInt() - inst.update((aint).toByte()) - inst.update((aint shr 8).toByte()) - } - - val buf = inst.digest().slice(0..7).toByteArray() - buf[0] = 0 - buf[1] = 0 - return ByteBuffer.wrap(buf).long - } - - private fun processStoptimes(trips: Map, times: ArrayList>) = - times.groupBy { it.first } - .map { (tripId, pairs) -> - val trip = trips[tripId]!! - val stoptimes = pairs.map { it.second } - val hash = hashCalc(trip.pattern.headsign, stoptimes) - trip.copy(pattern = trip.pattern.copy( - id = hash, - stoptimes = stoptimes.map { it.copy(patternId = hash) } - )) - } - - private fun parseRoutes(fd: File) = - fd.parseCsv() - .map { with(it) { - Route( - id = route_id, - type = RouteType.from(fd.parentFile.name.toInt()), - number = route_short_name, - name = route_long_name, - ) - } } - - private fun parseShapes(fd: File) = - fd.parseCsv() - .groupBy { it.shape_id } - .map { (id, group) -> - val points = group - .sortedBy { it.shape_pt_sequence } - .map { Point(it.shape_pt_lat, it.shape_pt_lon) } - - Shape(id, points) - } - - private fun parseStops(fd: File): List = - fd.parseCsv() - .map { with(it) { - fd.parentFile.name to Stop( - id = stop_id, - name = stop_name, - pos = Point(stop_lat, stop_lon), - parent = parent_station.ifEmpty { null }, - hasWheelChairBoarding = wheelchair_boarding == "1", - level = level_id.ifEmpty { null }, - platformCode = platform_code.ifEmpty { null }, - ) - } } - - private inline fun parseStopTimes(fd: File, block: (Sequence>) -> Unit) = - fd.parseCsvSequence { seq -> - seq - .map { with(it) { - it.trip_id to StopTime( - patternId = stop_sequence, - stopId = stop_id, - time = TimeType.Undated( - arrival = GtfsStopTime.parseGtfsTime(arrival_time), - departure = GtfsStopTime.parseGtfsTime(departure_time), - ), - pickupType = pickup_type, - dropOffType = drop_off_type, - ) - } } - .let { block(it) } - } - - private fun parseServices(fd: File) = - fd.parseCsv() - .map { with(it) { - val days = buildList { - if (monday == 1) add(DayOfWeek.MONDAY) - if (tuesday == 1) add(DayOfWeek.TUESDAY) - if (wednesday == 1) add(DayOfWeek.WEDNESDAY) - if (thursday == 1) add(DayOfWeek.THURSDAY) - if (friday == 1) add(DayOfWeek.FRIDAY) - if (saturday == 1) add(DayOfWeek.SATURDAY) - if (sunday == 1) add(DayOfWeek.SUNDAY) - } - Service( - id = "${fd.parentFile.name}_${service_id}", - days = days, - start = LocalDate.parse(start_date, LocalDate.Formats.ISO_BASIC), - end = LocalDate.parse(end_date, LocalDate.Formats.ISO_BASIC), - ) - } } - - private fun parseServiceExceptions(fd: File) = - fd.parseCsv() - .map { with(it) { - ServiceException( - serviceId = "${fd.parentFile.name}_${service_id}", - date = LocalDate.parse(date, LocalDate.Formats.ISO_BASIC), - type = exception_type, - ) - } } - - private fun parseTrips(fd: File, services: Map) = - fd.parseCsv() - .map { with(it) { - Trip.Undated( - id = trip_id, - pattern = StoppingPattern( - id = 0, - routeId = route_id, - shapeId = shape_id, - headsign = trip_headsign, - wheelchairAccessible = wheelchair_accessible == "1", - stoptimes = listOf() - ), - service = services["${fd.parentFile.name}_${service_id}"]!!, - directionId = direction_id.toInt(), - blockId = block_id.ifEmpty { null }, - ) - } } - - private fun extract(fd: File): List { - val outputs = mutableListOf() - ZipFile(fd).use { zip -> - for (entry in zip.entries()) { - zip.getInputStream(entry).use { input -> - val out = File(fd.parentFile, entry.name) - out.parentFile.mkdirs() - out.outputStream().use { output -> - input.copyTo(output) - } - outputs.add(out) - } - } - } - return outputs - } - - private fun extractAll(fd: File) = extract(fd).flatMap(::extract) - - private inline fun File.parseCsv(): List = this - .readText() - .replace("\uFEFF", "") // remove bom - .replace("\r\n", "\n") // crlf -> lf - .let { csv.decodeFromString(it) } - - private inline fun File.parseCsvSequence(block: (Sequence) -> Unit) = this - .bufferedReader() - .use { reader -> - val iter = object : CharIterator() { - var next: Char? = null - override fun nextChar(): Char { - if (!hasNext()) { - throw NoSuchElementException() - } - val ret = next!! - next = null - return ret - } - override fun hasNext(): Boolean { - if (next == null) { - do { - next = null - val new = reader.read() - if (new != -1) { - next = new.toChar() - } - } while (next == '\uFEFF' || next == '\r') - } - return next != null - } - } - block(csv.decodeToSequence(iter, csv.serializersModule.serializer())) - } - - // Type priority used to resolve duplicates, preferring the first one in the chain - private val typePriorityRanking = listOf( - RouteType.MetroTrain, - RouteType.RegionalTrain, - RouteType.MetroTram, - RouteType.MetroBus, - RouteType.RegionalBus, - RouteType.SkyBus, - ).map { it.value.toString() } - - @Suppress("LoggingStringTemplateAsArgument") // ? - private fun fixupDuplicateStops(stops: List): List { - return stops - .groupBy { (_, stops) -> stops.id } - .map { (id, stops) -> - // Just return it if no duplicate - if (stops.size == 1) return@map stops[0].second - - // Just return the first one if all the stops' data match - if (stops.withIndex().all { (idx, stop) -> idx == 0 || stop.second == stops[idx - 1].second }) - return@map stops[0].second - - // Find first stop ordered by the types - val res = typePriorityRanking - .firstNotNullOfOrNull { type -> - stops.find { it.first == type } - } - - val (_, stop) = if (res == null) { - log.warn("Cannot resolve duplicate stop ${id}, using first one") - stops.forEach { (type, stop) -> log.warn(" - ($type): $stop") } - stops[0] - } else { - log.debug("Resolving $id for type ${res.first}") - stops.forEach { (type, stop) -> log.debug("${if (res.first == type) "*" else " "} - ($type): $stop") } - res - } - - stop - } - } -} diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsServiceException.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsServiceException.kt deleted file mode 100644 index a31aff0..0000000 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsServiceException.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.lava.banksia.server.gtfs.structures - -import kotlinx.serialization.Serializable - -@Suppress("PropertyName") -@Serializable -internal data class GtfsServiceException( - val service_id: String, - val date: String, - val exception_type: Int, -) diff --git a/server/gtfs_rt/build.gradle.kts b/server/gtfs_rt/build.gradle.kts deleted file mode 100644 index 2887e0b..0000000 --- a/server/gtfs_rt/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - alias(libs.plugins.kotlinJvm) - alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.wire) -} - -kotlin { - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - freeCompilerArgs.add("-Xexplicit-backing-fields") - } -} - -dependencies { - implementation(projects.core) - 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) -} - -wire { - sourcePath { - srcDir("src/main/proto") - } - kotlin {} -} diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt deleted file mode 100644 index aaee0a9..0000000 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt +++ /dev/null @@ -1,116 +0,0 @@ -package moe.lava.banksia.server.gtfsrt - -import com.google.transit.realtime.FeedMessage -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime -import moe.lava.banksia.core.util.log -import java.io.File -import kotlin.time.Instant - -private const val BASE_DIR = "./data/gtfsr-archive/" - -internal class GtfsrtArchiver { - private var started = false - - suspend fun start(flow: SharedFlow>) { - if (started) { - log("GtfsrtArchiver", "Tried to start when already started") - return - } - started = true - coroutineScope { - launch { compressJob() } - - flow.collect { (type, rawData) -> - val data = try { - FeedMessage.ADAPTER.decode(rawData) - } catch (e: Throwable) { - log("gtfsr $type", "Failed to parse proto: $e") - return@collect - } - val timestamp = data.header_.timestamp - ?: return@collect log("gtfsr $type", "Failed to read proto timestamp") - - val time = Instant.fromEpochSeconds(timestamp).toLocalDateTime(TimeZone.currentSystemDefault()) - - val (prevWeek, prevDay) = (time.dayOfYear - 1) / 7 to (time.dayOfYear - 1) % 7 - val (nextWeek, nextDay) = time.dayOfYear / 7 to time.dayOfYear % 7 - - val base = File(BASE_DIR, type) - val previousParent = File(base, "${time.year}-${prevWeek.toString().padStart(2, '0')}/${prevDay}") - val currentParent = File(base, "${time.year}-${nextWeek.toString().padStart(2, '0')}/${nextDay}") - val target = File(currentParent, "${timestamp}.proto") - - if (previousParent.isDirectory) { - enqueueCompression(previousParent) - if (prevWeek != nextWeek) { - enqueueCompression(previousParent.parentFile) - } - } - - if (!target.exists()) { - try { - if (!target.parentFile.isDirectory) { - target.parentFile.mkdirs() - } - target.writeBytes(rawData) - } catch (e: Throwable) { - log("gtfsr $type", "Failed to write ${target}: $e") - } - } - } - } - } - - private val cqueue = mutableSetOf() - private val ignore = mutableSetOf() - private val cmut = Mutex() - private suspend fun enqueueCompression(fd: File) { - cmut.withLock { cqueue.add(fd) } - } - - private suspend fun compressJob() { - while(true) { - while(true) { - val next = cmut.withLock { cqueue.firstOrNull() } - ?: break - if (!next.isDirectory) { - cmut.withLock { cqueue.remove(next) } - continue - } - if (next in ignore) continue - - withContext(Dispatchers.IO) { - val proc = ProcessBuilder( - "tar", "-acf", - "${next.absolutePath}.tar.zst", - next.absolutePath - ).start() - val exitCode = proc.waitFor() - if (exitCode == 0) { - log("CompressJob", "Compressed ${next.absolutePath} to ${next.absolutePath}.tar.zst") - if (next.deleteRecursively()) { - cmut.withLock { cqueue.remove(next) } - } else { - log("CompressJob", "Failed to delete $next") - ignore.add(next) - } - } else { - val msg = proc.errorStream.readAllBytes().decodeToString() - log("CompressJob", "Failed to delete $next (exit code $exitCode") - log("CompressJob", msg) - } - } - } - delay(30000) - } - } -} diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt deleted file mode 100644 index 6f46ed7..0000000 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt +++ /dev/null @@ -1,87 +0,0 @@ -package moe.lava.banksia.server.gtfsrt - -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.url -import io.ktor.client.statement.bodyAsText -import io.ktor.client.statement.readRawBytes -import io.ktor.http.isSuccess -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import moe.lava.banksia.core.Constants -import moe.lava.banksia.core.util.LogScope -import moe.lava.banksia.core.util.log - -private val types = arrayOf( - "metro/trip-updates", - "metro/vehicle-positions", - "metro/service-alerts", - "tram/trip-updates", - "tram/vehicle-positions", - "tram/service-alerts", - "bus/trip-updates", - "bus/vehicle-positions", - "vline/trip-updates", - "vline/vehicle-positions", -) - -class GtfsrtService( - private val client: HttpClient, -) { - private val archiver = GtfsrtArchiver() - private var started = false - - internal val rawMessages: SharedFlow> - field = MutableSharedFlow>() - - fun start( - scope: CoroutineScope, - enableArchiving: Boolean = false, - ) { - if (started) { - log("GtfsrtService", "Tried to start when already started") - return - } - - if (enableArchiving) { - scope.launch { archiver.start(rawMessages) } - } - - scope.launch { fetch() } - } - - private suspend fun fetch() { - coroutineScope { - types.map { type -> - launch(context = Dispatchers.IO) { - val logger = LogScope("gtfsr $type") - try { - val res = client.get { - url("https://api.opendata.transport.vic.gov.au/opendata/public-transport/gtfs/realtime/v1/${type}") - header("KeyId", Constants.opendataKey) - } - if (!res.status.isSuccess()) { - logger.log("${res.status} | ${res.bodyAsText()}") - } else { - val bytes = res.readRawBytes() - rawMessages.emit(type to bytes) - } - } catch (e: Throwable) { - logger.log("$e") - logger.log(e.stackTraceToString()) - } - } - }.joinAll() - } - - delay(10000) - fetch() - } -} diff --git a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt index dedffe5..76ee8ba 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -15,59 +15,47 @@ import io.ktor.server.routing.routing import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import moe.lava.banksia.core.Constants -import moe.lava.banksia.core.sqld.RouteQueries -import moe.lava.banksia.core.sqld.StopQueries -import moe.lava.banksia.core.sqld.mappers.asModel +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.Constants +import moe.lava.banksia.di.CommonModules +import moe.lava.banksia.model.atDate +import moe.lava.banksia.room.dao.RouteDao +import moe.lava.banksia.room.dao.StopDao +import moe.lava.banksia.room.dao.StopTimeDao +import moe.lava.banksia.room.dao.VersionMetadataDao import moe.lava.banksia.server.di.ServerModules -import moe.lava.banksia.server.gtfsrt.GtfsrtService -import moe.lava.banksia.server.routes.stopTimeRoutes +import moe.lava.banksia.server.gtfs.GtfsHandler +import moe.lava.banksia.server.gtfsr.GtfsrService +import moe.lava.banksia.util.serialise import org.koin.dsl.module -import org.koin.ktor.ext.get +import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin +import kotlin.time.Clock fun main() { - if (System.getenv("BANKSIA_PRODUCTION") == "1") Constants.devMode = false - embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module) .start(wait = true) } fun Application.module() { - log.info("devMode: ${Constants.devMode}") install(ContentNegotiation) { json() } install(Koin) { modules(module { single { log } }) - modules(ServerModules) + modules(CommonModules, ServerModules) } @Suppress("KotlinConstantConditions") - launch { get().start(this, !Constants.devMode) } + if (!Constants.devMode) { + val gtfsr by inject() + launch { gtfsr.start() } + } routing { - stopTimeRoutes() - - if (Constants.devMode) { - get("/fixup") { - call.respondText("received") - get().addParentsToStops() - } - } - get("/manage/fixup") { - val key = call.parameters["key"] - if (key != Constants.updateKey) { - call.respond(HttpStatusCode.Forbidden) - return@get - } - - call.respondText("fixing") - launch(context = Dispatchers.IO) { - get().addParentsToStops() - } - } - get("/manage/update") { + get("/update") { val key = call.parameters["key"] if (key != Constants.updateKey) { call.respond(HttpStatusCode.Forbidden) @@ -79,14 +67,30 @@ fun Application.module() { ?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip" call.respondText("received") launch(context = Dispatchers.IO) { - get().import(datasetUrl) - get().addParentsToStops() + val handler by inject() + handler.update(datasetUrl) + } + } + + get("/metadata/{type?}") { + val dao by inject() + val type = call.parameters["type"] + if (type == null) { + call.respond(dao.getAll().map { it.asModel() }) + return@get + } + + val data = dao.get(type)?.asModel() + if (data == null) { + call.respond(HttpStatusCode.NotFound) + } else { + call.respond(data) } } get("/routes") { val routes = withContext(context = Dispatchers.IO) { - get().getAll().executeAsList() + inject().value.getAll() } val res = routes.map { it.asModel() } call.respond(res) @@ -94,17 +98,16 @@ fun Application.module() { get("/routes/{route_id}") { val routeId = call.parameters["route_id"]!! val route = withContext(context = Dispatchers.IO) { - get().get(routeId).executeAsOneOrNull() + inject().value.get(routeId) } - if (route != null) { + if (route != null) call.respond(route.asModel()) - } else { + else call.respond(HttpStatusCode.NotFound) - } } get("/stops") { val routes = withContext(context = Dispatchers.IO) { - get().getAll().executeAsList() + inject().value.getAll() } val res = routes.map { it.asModel() } call.respond(res) @@ -112,26 +115,56 @@ fun Application.module() { get("/stops/{stop_id}") { val stopId = call.parameters["stop_id"]!! val stop = withContext(context = Dispatchers.IO) { - get().get(stopId).executeAsOneOrNull() + inject().value.get(stopId) } - if (stop != null) { + if (stop != null) call.respond(stop.asModel()) - } else { + else call.respond(HttpStatusCode.NotFound) - } } get("/route_stops/{route_id}") { val routeId = call.parameters["route_id"]!! - val useParent = call.queryParameters["parent"] !in listOf("false", "0") + val useParent = call.queryParameters["parent"] in listOf("true", "1") val stops = withContext(Dispatchers.IO) { - val queries = get() - if (useParent) { - queries.getParentsByRoute(routeId).executeAsList() - } else { - queries.getByRoute(routeId).executeAsList() - } + val routeDao by inject() + if (useParent) + routeDao.stopsParent(routeId) + else + routeDao.stops(routeId) } call.respond(stops.map { it.asModel() }) +// val stops = withContext(Dispatchers.IO) { +// val stopDao by inject() +// val stopTimeDao by inject() +// val tripDao by inject() +// +// tripDao.getByRoute(routeId) +// .map { it.id } +// .let { stopTimeDao.get(it) } +// .flatMap { it.asModel().stopInfos } +// .map { it.stopId } +// .let { stopDao.get(it) } +// .map { it.asModel() } +// } +// call.respond(stops) + + } + 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) { + inject().value + .getForStopDated( + stopId, + listOf(date.dayOfWeek).serialise(), + date.toEpochDays().toInt(), + ) + .map { it.asModel().atDate(date) } + .sortedBy { it.departureTime } + } + call.respond(times) } } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt deleted file mode 100644 index 97892e0..0000000 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.lava.banksia.server - -import moe.lava.banksia.core.sqld.BanksiaDatabase -import moe.lava.banksia.core.util.log -import java.security.MessageDigest -import moe.lava.banksia.core.sqld.Stop as DbStop - -class GtfsDataFixer( - private val database: BanksiaDatabase, -) { - fun addParentsToStops() { - val queries = database.stopQueries - val stops = queries.getAllParentless().executeAsList() - stops - .groupBy { it.name.split("/")[0] } - .filter { (_, stops) -> stops.size > 1 } - .forEach { (name, stops) -> - val avgLat = stops.map { it.lat }.average() - val avgLng = stops.map { it.lng }.average() - val hash = name.sha256().substring(0, 7) - val parentId = "bsia:df1:$hash" - val parent = DbStop( - id = parentId, - name = name, - lat = avgLat, - lng = avgLng, - parent = null, - hasWheelChairBoarding = if (stops.all { it.hasWheelChairBoarding == 1L }) 1L else 0L, - level = "", - platformCode = "", - ) - log("datafixer", "inserting ${parentId} for ${stops.size} children") - queries.transaction { - queries.insert(parent) - queries.updateParents(parentId, stops.map { it.id }) - } - } - } -} - -private fun String.sha256() = - MessageDigest - .getInstance("SHA-256") - .digest(this.toByteArray()) - .joinToString("") { "%02x".format(it) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt deleted file mode 100644 index 84fae70..0000000 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ /dev/null @@ -1,115 +0,0 @@ -package moe.lava.banksia.server - -import io.ktor.util.logging.Logger -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.model.Service -import moe.lava.banksia.core.model.ServiceException -import moe.lava.banksia.core.model.Shape -import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.model.Trip -import moe.lava.banksia.core.sqld.DatabaseManager -import moe.lava.banksia.core.sqld.mappers.asDb -import moe.lava.banksia.server.gtfs.GtfsData -import moe.lava.banksia.server.gtfs.GtfsParser -import kotlin.time.Clock -import moe.lava.banksia.core.sqld.BanksiaDatabase as Database - -class GtfsImporter( - private val parser: GtfsParser, - private val dbm: DatabaseManager, - private val log: Logger, -) { - suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) { - val (database, close) = dbm.makeAlt() - - parser.update(url).collect { chunk -> - when (chunk) { - is GtfsData.RouteChunk -> database.addRoutes(chunk.routes) - is GtfsData.ServiceChunk -> database.addServices(chunk.services) - is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions) - is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes) - is GtfsData.StopChunk -> database.addStops(chunk.stops) - is GtfsData.TripChunk -> database.addTrips(chunk.trips) - } - } - - close() - dbm.swap() - } - - private fun Database.addRoutes(routes: List) { - log.info("inserting routes...") - routeQueries.transaction { - routes.forEach { - routeQueries.insert(it.asDb()) - } - } - log.info("done") - } - - private fun Database.addServices(services: List) { - log.info("inserting services...") - serviceQueries.transaction { - services.forEach { - serviceQueries.insert(it.asDb()) - } - } - log.info("done") - } - - private fun Database.addServiceExceptions(exceptions: List) { - log.info("inserting exceptions...") - serviceExceptionQueries.transaction { - exceptions.forEach { - serviceExceptionQueries.insert(it.asDb()) - } - } - log.info("done") - } - - private fun Database.addShapes(shapes: List) { - log.info("inserting shapes...") - shapeQueries.transaction { - shapes.forEach { - shapeQueries.insert(it.asDb()) - } - } - log.info("done") - } - - private fun Database.addStops(stops: List) { - log.info("inserting stops...") - stops - .groupBy { it.id } - .forEach { (id, gstops) -> - if (gstops.size > 1) { - if (gstops.withIndex().any { (i, stop) -> i != 0 && stop != gstops[i - 1] }) { - gstops.forEach { - log.warn("duplicate $id: $it") - } - } - } - } - - stopQueries.transaction { - stops.forEach { - stopQueries.insert(it.asDb()) - } - } - log.info("done") - } - - private fun Database.addTrips(trips: List) { - log.info("inserting ${trips.size} trips...") - transaction { - trips.forEach { trip -> - stoppingPatternQueries.insert(trip.pattern.asDb()) - trip.pattern.stoptimes.forEach { stoptime -> - stopTimeQueries.insert(stoptime.asDb()) - } - tripQueries.insert(trip.asDb()) - } - } - log.info("done") - } -} diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index b2593b3..c7b650c 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,22 +1,13 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient -import moe.lava.banksia.core.data.dataDiModule -import moe.lava.banksia.server.GtfsDataFixer -import moe.lava.banksia.server.GtfsImporter -import moe.lava.banksia.server.gtfs.GtfsParser -import moe.lava.banksia.server.gtfsrt.GtfsrtService -import org.koin.core.module.dsl.factoryOf +import moe.lava.banksia.server.gtfs.GtfsHandler +import moe.lava.banksia.server.gtfsr.GtfsrService import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val ServerModules = module { - includes(dataDiModule) - single { HttpClient() } - singleOf(::GtfsParser) - singleOf(::GtfsrtService) - - factoryOf(::GtfsDataFixer) - factoryOf(::GtfsImporter) + singleOf(::GtfsHandler) + singleOf(::GtfsrService) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt new file mode 100644 index 0000000..28d50af --- /dev/null +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt @@ -0,0 +1,336 @@ +package moe.lava.banksia.server.gtfs + +import com.lightningkite.kotlinx.serialization.csv.CsvFormat +import com.lightningkite.kotlinx.serialization.csv.StringDeferringConfig +import io.ktor.client.HttpClient +import io.ktor.client.request.prepareRequest +import io.ktor.client.request.url +import io.ktor.client.statement.bodyAsChannel +import io.ktor.util.cio.writeChannel +import io.ktor.util.logging.Logger +import io.ktor.utils.io.copyAndClose +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.serializer +import moe.lava.banksia.Constants +import moe.lava.banksia.model.Route +import moe.lava.banksia.model.Service +import moe.lava.banksia.model.Shape +import moe.lava.banksia.model.Stop +import moe.lava.banksia.model.StopTime +import moe.lava.banksia.model.Trip +import moe.lava.banksia.room.Database +import moe.lava.banksia.room.converter.RouteTypeConverter +import moe.lava.banksia.room.entity.asEntity +import moe.lava.banksia.server.gtfs.structures.GtfsRoute +import moe.lava.banksia.server.gtfs.structures.GtfsService +import moe.lava.banksia.server.gtfs.structures.GtfsShape +import moe.lava.banksia.server.gtfs.structures.GtfsStop +import moe.lava.banksia.server.gtfs.structures.GtfsStopTime +import moe.lava.banksia.server.gtfs.structures.GtfsTrip +import moe.lava.banksia.util.Point +import java.io.File +import java.util.zip.ZipFile +import kotlin.time.Clock +import kotlin.time.ExperimentalTime + +class GtfsHandler( + private val log: Logger, + private val client: HttpClient, + private val db: Database, +) { + private val csv = CsvFormat(StringDeferringConfig(EmptySerializersModule())) + private val datasetPath = File("/tmp/banksia", "dataset.zip") + + @OptIn(ExperimentalTime::class) + suspend fun update(datasetUrl: String, date: Long? = null) { + val parentDir = datasetPath.parentFile + @Suppress("SimplifyBooleanWithConstants", "KotlinConstantConditions") + if (parentDir.exists() && !Constants.devMode) + parentDir.deleteRecursively() + + parentDir.mkdirs() + + log.info("fetching..") + client.prepareRequest { + url(datasetUrl) + }.execute { resp -> + if (!datasetPath.exists()) + resp.bodyAsChannel().copyAndClose(datasetPath.writeChannel()) + log.info("fetched!") + } + + log.info("extracting...") + @Suppress("KotlinConstantConditions") + val files = if (Constants.devMode) { + datasetPath.parentFile + .listFiles { it.isDirectory } + .flatMap { d -> d.listFiles { f -> f.extension == "txt" }.toList() } + .ifEmpty { extractAll(datasetPath) } + .filter { it.parentFile.name == "2" } + } else { + extractAll(datasetPath) + } + + addRoutes(files) + addStops(files) + addShapes(files) + val services = addServices(files) + val trips = addTrips(files, services.associateBy { it.id }) + addStopTimes(files, trips.associateBy { it.id }) + + updateMetadata(date ?: Clock.System.now().epochSeconds) + + @Suppress("KotlinConstantConditions") + if (!Constants.devMode) { + parentDir.deleteRecursively() + } + + log.info("done!") + } + + private suspend fun updateMetadata(date: Long) { + val dao = db.versionMetadataDao + log.info("updating metadata...") + dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) + } + + private suspend fun addRoutes(files: List) { + val dao = db.routeDao + log.info("parsing routes...") + val routes = files + .filter { it.name == "routes.txt" } + .flatMap { fd -> parseRoutes(fd) } + + log.info("inserting routes...") + dao.deleteAll() + dao.insertAll(*routes.map { it.asEntity() }.toTypedArray()) + } + + private fun parseRoutes(fd: File) = + fd.parseCsv() + .map { with(it) { + Route( + id = route_id, + type = RouteTypeConverter.from(fd.parentFile.name.toInt()), + number = route_short_name, + name = route_long_name, + ) + } } + + private suspend fun addShapes(files: List) { + val dao = db.shapeDao + log.info("parsing shapes...") + val shapes = files + .filter { it.name == "shapes.txt" } + .flatMap { fd -> parseShapes(fd) } + + log.info("inserting shapes...") + dao.deleteAll() + dao.insertAll(*shapes.map { it.asEntity() }.toTypedArray()) + } + + private fun parseShapes(fd: File) = + fd.parseCsv() + .groupBy { it.shape_id } + .map { (id, group) -> + val points = group + .sortedBy { it.shape_pt_sequence } + .map { Point(it.shape_pt_lat, it.shape_pt_lon) } + + Shape(id, points) + } + + private suspend fun addStops(files: List) { + val dao = db.stopDao + log.info("parsing stops...") + val stops = files + .filter { it.name == "stops.txt" } + .flatMap { fd -> parseStops(fd) } + + log.info("inserting stops...") + dao.deleteAll() + stops + .groupBy { it.id } + .forEach { (id, gstops) -> + if (gstops.size > 1) { + if (gstops.withIndex().any { (i, stop) -> i != 0 && stop != gstops[i - 1] }) { + gstops.forEach { + log.info("duplicate $id: $it") + } + } + } + } + dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) + } + + private fun parseStops(fd: File) = + fd.parseCsv() + .map { with(it) { + Stop( + id = stop_id, + name = stop_name, + pos = Point(stop_lat, stop_lon), + parent = parent_station, + hasWheelChairBoarding = wheelchair_boarding == "1", + level = level_id, + platformCode = platform_code, + ) + } } + + private suspend fun addStopTimes(files: List, trips: Map) { + val dao = db.stopTimeDao + dao.deleteAll() + log.info("parsing stop times...") + files + .filter { it.name == "stop_times.txt" } + .forEach { fd -> + log.info("parsing stop times for ${fd.parent}...") + parseStopTimes(fd, trips) { seq -> + seq.chunked(1000000) + .forEach { queue -> + log.info("converting stop times (${queue.size}) for ${fd.parent}...") + val conv = queue.map { it.asEntity() }.toTypedArray() + log.info("inserting stop times (${conv.size}) for ${fd.parent}...") + dao.insertOrReplaceAll(*conv) + } + } + } + } + + private inline fun parseStopTimes(fd: File, trips: Map, block: (Sequence) -> Unit) = + fd.parseCsvSequence { seq -> + seq + .map { with(it) { + StopTime( + tripId = trip_id, + stopId = stop_id, + arrivalTime = GtfsStopTime.parseGtfsTime(arrival_time), + departureTime = GtfsStopTime.parseGtfsTime(departure_time), + headsign = stop_headsign.ifEmpty { trips[trip_id]!!.tripHeadsign }, + pickupType = pickup_type, + dropOffType = drop_off_type, + ) + } } + .let { block(it) } + } + + private suspend fun addServices(files: List): List { + val dao = db.serviceDao + log.info("parsing services...") + val services = files + .filter { it.name == "calendar.txt" } + .flatMap { fd -> parseServices(fd) } + + log.info("inserting services...") + dao.deleteAll() + dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) + + return services + } + + private fun parseServices(fd: File) = + fd.parseCsv() + .map { with(it) { + val days = buildList { + if (monday == 1) add(DayOfWeek.MONDAY) + if (tuesday == 1) add(DayOfWeek.TUESDAY) + if (wednesday == 1) add(DayOfWeek.WEDNESDAY) + if (thursday == 1) add(DayOfWeek.THURSDAY) + if (friday == 1) add(DayOfWeek.FRIDAY) + if (saturday == 1) add(DayOfWeek.SATURDAY) + if (sunday == 1) add(DayOfWeek.SUNDAY) + } + Service( + id = service_id, + days = days, + start = LocalDate.parse(start_date, LocalDate.Formats.ISO_BASIC), + end = LocalDate.parse(end_date, LocalDate.Formats.ISO_BASIC), + ) + } } + + private suspend fun addTrips(files: List, services: Map): List { + val dao = db.tripDao + log.info("parsing trips...") + val trips = files + .filter { it.name == "trips.txt" } + .flatMap { fd -> parseTrips(fd, services) } + + log.info("inserting trips...") + dao.deleteAll() + dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) + + return trips + } + + private fun parseTrips(fd: File, services: Map) = + fd.parseCsv() + .map { with(it) { + Trip( + id = trip_id, + routeId = route_id, + service = services[service_id]!!, + shapeId = shape_id.ifEmpty { null }, + tripHeadsign = trip_headsign, + directionId = direction_id, + blockId = block_id, + wheelchairAccessible = wheelchair_accessible, + ) + } } + + private fun extract(fd: File): List { + val outputs = mutableListOf() + ZipFile(fd).use { zip -> + for (entry in zip.entries()) { + zip.getInputStream(entry).use { input -> + val out = File(fd.parentFile, entry.name) + out.parentFile.mkdirs() + out.outputStream().use { output -> + input.copyTo(output) + } + outputs.add(out) + } + } + } + return outputs + } + + private fun extractAll(fd: File) = extract(fd).flatMap(::extract) + + private inline fun File.parseCsv(): List = this + .readText() + .replace("\uFEFF", "") // remove bom + .replace("\r\n", "\n") // crlf -> lf + .let { csv.decodeFromString(it) } + + private inline fun File.parseCsvSequence(block: (Sequence) -> Unit) = this + .bufferedReader() + .use { reader -> + val iter = object : CharIterator() { + var next: Char? = null + override fun nextChar(): Char { + if (!hasNext()) { + throw NoSuchElementException() + } + val ret = next!! + next = null + return ret + } + override fun hasNext(): Boolean { + if (next == null) { + do { + next = null + val new = reader.read() + if (new != -1) { + next = new.toChar() + } + } while (next == '\uFEFF' || next == '\r') + } + return next != null + } + } + block(csv.decodeToSequence(iter, csv.serializersModule.serializer())) + } +} diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt similarity index 91% rename from server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt rename to server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt index 4b1bad9..c4eabeb 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -internal data class GtfsRoute( +data class GtfsRoute( val route_id: String, val agency_id: String, val route_short_name: String, diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt similarity index 91% rename from server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt rename to server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt index 1bf9573..9347b5e 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -internal data class GtfsService( +data class GtfsService( val service_id: String, val monday: Int, val tuesday: Int, diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt similarity index 90% rename from server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt rename to server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt index 32231ab..19cdfb5 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -internal data class GtfsShape( +data class GtfsShape( val shape_id: String, val shape_pt_lat: Double, val shape_pt_lon: Double, diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt similarity index 92% rename from server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt rename to server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt index cb1a018..023a3e1 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -internal data class GtfsStop( +data class GtfsStop( val stop_id: String, val stop_name: String, val stop_lat: Double, diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt similarity index 84% rename from server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt rename to server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt index c0bbaf2..61e8a1c 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt @@ -1,16 +1,16 @@ package moe.lava.banksia.server.gtfs.structures import kotlinx.serialization.Serializable -import moe.lava.banksia.core.model.FutureTime +import moe.lava.banksia.model.FutureTime @Suppress("PropertyName") @Serializable -internal data class GtfsStopTime( +data class GtfsStopTime( val trip_id: String, val arrival_time: String, val departure_time: String, val stop_id: String, - val stop_sequence: Long, + val stop_sequence: Int, val stop_headsign: String, val pickup_type: Int, val drop_off_type: Int, diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt similarity index 92% rename from server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt rename to server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt index 0b0d865..fcfc864 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -internal data class GtfsTrip( +data class GtfsTrip( val route_id: String, val service_id: String, val trip_id: String, diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfsr/GtfsrService.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfsr/GtfsrService.kt new file mode 100644 index 0000000..5a0b1dc --- /dev/null +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfsr/GtfsrService.kt @@ -0,0 +1,164 @@ +package moe.lava.banksia.server.gtfsr + +import com.google.transit.realtime.FeedMessage +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.url +import io.ktor.client.statement.bodyAsText +import io.ktor.client.statement.readRawBytes +import io.ktor.http.isSuccess +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import moe.lava.banksia.Constants +import moe.lava.banksia.util.LogScope +import moe.lava.banksia.util.log +import java.io.File +import java.time.Instant +import java.time.ZoneId + +private const val BASE_DIR = "./data/gtfsr-archive/" + +class GtfsrService(private val client: HttpClient) { + private var started = false + private val latest = mutableMapOf() + + fun latestFor(type: String) = latest[type] + + private val iFlow = MutableSharedFlow>() + val flow = iFlow.asSharedFlow() + + companion object { + val types = arrayOf( + "metro/trip-updates", + "metro/vehicle-positions", + "metro/service-alerts", + "tram/trip-updates", + "tram/vehicle-positions", + "tram/service-alerts", + "bus/trip-updates", + "bus/vehicle-positions", + "vline/trip-updates", + "vline/vehicle-positions", + ) + } + + suspend fun start() { + if (started) { + log("GtfsrService", "Tried to start when already started") + return + } + started = true + coroutineScope { + launch { compressJob() } + + while (true) { + val results = mutableMapOf() + types.map { type -> + launch(context = Dispatchers.IO) { + val logger = LogScope("gtfsr $type") + try { + val res = client.get { + url("https://api.opendata.transport.vic.gov.au/opendata/public-transport/gtfs/realtime/v1/${type}") + header("KeyId", Constants.opendataKey) + } + if (!res.status.isSuccess()) { + logger.log("${res.status} | ${res.bodyAsText()}") + } else { + results[type] = res.readRawBytes() + } + } catch (e: Throwable) { + logger.log("$e") + logger.log(e.stackTraceToString()) + } + } + }.joinAll() + + results.forEach { (type, data) -> + val dec = try { + FeedMessage.ADAPTER.decode(data) + } catch (e: Throwable) { + log("gtfsr $type", "Failed to parse proto: $e") + return@forEach + } + val timestamp = dec.header_.timestamp + ?: return@forEach log("gtfsr $type", "Failed to read proto timestamp") + + val time = Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault()) + + val base = File(BASE_DIR, type) + val previousParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7).toString().padStart(2, '0')}") + val currentParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7 + 1).toString().padStart(2, '0')}") + val target = File(currentParent, "${timestamp}.proto") + + if (previousParent.isDirectory) { + enqueueCompression(previousParent) + } + + if (!target.exists()) { + try { + if (!target.parentFile.isDirectory) { + target.parentFile.mkdirs() + } + target.writeBytes(data) + } catch (e: Throwable) { + log("gtfsr $type", "Failed to write ${target}: $e") + } + } + } + delay(10000) + } + } + } + + private val cqueue = mutableSetOf() + private val ignore = mutableSetOf() + private val cmut = Mutex() + private suspend fun enqueueCompression(fd: File) { + cmut.withLock { cqueue.add(fd) } + } + + private suspend fun compressJob() { + while(true) { + while(true) { + val next = cmut.withLock { cqueue.firstOrNull() } + ?: break + if (!next.isDirectory) { + cmut.withLock { cqueue.remove(next) } + continue + } + if (next in ignore) continue + + withContext(Dispatchers.IO) { + val proc = ProcessBuilder( + "tar", "-acf", + "${next.absolutePath}.tar.zst", + next.absolutePath + ).start() + val exitCode = proc.waitFor() + if (exitCode == 0) { + if (next.deleteRecursively()) { + cmut.withLock { cqueue.remove(next) } + } else { + log("CompressJob", "Failed to delete $next") + ignore.add(next) + } + } else { + val msg = proc.errorStream.readAllBytes().decodeToString() + log("CompressJob", "Failed to delete $next (exit code $exitCode") + log("CompressJob", msg) + } + } + } + delay(30000) + } + } +} diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml index 6519371..de5d8bf 100644 --- a/server/src/main/resources/logback.xml +++ b/server/src/main/resources/logback.xml @@ -14,7 +14,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + diff --git a/settings.gradle.kts b/settings.gradle.kts index bdb499e..4688423 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,13 +32,9 @@ dependencyResolutionManagement { } include(":androidApp") +include(":client") include(":server") -include(":server:gtfs") -include(":server:gtfs_rt") -include(":core") -include(":core:data") -include(":core:stoptime") -include(":core:sqld") +include(":shared") include(":ui") include(":ui:maps") include(":ui:shared") diff --git a/core/build.gradle.kts b/shared/build.gradle.kts similarity index 70% rename from core/build.gradle.kts rename to shared/build.gradle.kts index 3dd2ee6..953d790 100644 --- a/core/build.gradle.kts +++ b/shared/build.gradle.kts @@ -4,11 +4,18 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.ksp) + alias(libs.plugins.room) + alias(libs.plugins.wire) +} + +room { + schemaDirectory("$projectDir/schemas") } kotlin { android { - namespace = "moe.lava.banksia.core" + namespace = "moe.lava.banksia.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { @@ -40,9 +47,25 @@ kotlin { implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.protobuf) + implementation(libs.room.runtime) + implementation(libs.sqlite.bundled) } iosMain.dependencies { implementation(libs.ktor.client.darwin) } } } + +dependencies { + add("kspAndroid", libs.room.compiler) + add("kspIosArm64", libs.room.compiler) + add("kspIosSimulatorArm64", libs.room.compiler) + add("kspJvm", libs.room.compiler) +} + +wire { + sourcePath { + srcDir("src/commonMain/proto") + } + kotlin {} +} diff --git a/shared/schemas/moe.lava.banksia.room.Database/1.json b/shared/schemas/moe.lava.banksia.room.Database/1.json new file mode 100644 index 0000000..037062e --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/1.json @@ -0,0 +1,72 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "e536f5a9b1408377bcc449195169648c", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e536f5a9b1408377bcc449195169648c')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/2.json b/shared/schemas/moe.lava.banksia.room.Database/2.json new file mode 100644 index 0000000..04a14e3 --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/2.json @@ -0,0 +1,315 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "83ece554400bb035c267dc2414c23293", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '83ece554400bb035c267dc2414c23293')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/3.json b/shared/schemas/moe.lava.banksia.room.Database/3.json new file mode 100644 index 0000000..e769926 --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/3.json @@ -0,0 +1,339 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "5a7252ab3bcae4d0d0950024b19ba002", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a7252ab3bcae4d0d0950024b19ba002')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/4.json b/shared/schemas/moe.lava.banksia.room.Database/4.json new file mode 100644 index 0000000..783b3ee --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/4.json @@ -0,0 +1,368 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "4426fd2ccc826d9d9d9021546b105850", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": true, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": true, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/5.json b/shared/schemas/moe.lava.banksia.room.Database/5.json new file mode 100644 index 0000000..c4a786d --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/5.json @@ -0,0 +1,368 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "4426fd2ccc826d9d9d9021546b105850", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": true, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": true, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/6.json b/shared/schemas/moe.lava.banksia.room.Database/6.json new file mode 100644 index 0000000..5ab26dc --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/6.json @@ -0,0 +1,368 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "5f52de4cc0ddbcf02a0d8be4cf4d4cfd", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5f52de4cc0ddbcf02a0d8be4cf4d4cfd')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/7.json b/shared/schemas/moe.lava.banksia.room.Database/7.json new file mode 100644 index 0000000..d4c62b2 --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/7.json @@ -0,0 +1,415 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "15c94df0a62438ff28d451c074c94c59", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '15c94df0a62438ff28d451c074c94c59')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/8.json b/shared/schemas/moe.lava.banksia.room.Database/8.json new file mode 100644 index 0000000..9240dd5 --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/8.json @@ -0,0 +1,426 @@ +{ + "formatVersion": 1, + "database": { + "version": 8, + "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')" + ] + } +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/9.json b/shared/schemas/moe.lava.banksia.room.Database/9.json new file mode 100644 index 0000000..2359dbd --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/9.json @@ -0,0 +1,426 @@ +{ + "formatVersion": 1, + "database": { + "version": 9, + "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')" + ] + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/moe/lava/banksia/di/PlatformModule.android.kt b/shared/src/androidMain/kotlin/moe/lava/banksia/di/PlatformModule.android.kt new file mode 100644 index 0000000..0447f4b --- /dev/null +++ b/shared/src/androidMain/kotlin/moe/lava/banksia/di/PlatformModule.android.kt @@ -0,0 +1,25 @@ +package moe.lava.banksia.di + +import android.content.Context +import androidx.room.Room +import androidx.room.RoomDatabase +import moe.lava.banksia.room.Database +import org.koin.core.parameter.ParametersHolder +import org.koin.core.scope.Scope +import org.koin.dsl.module + +class AndroidDatabaseBuilder(val ctx: Context) : PlatformDatabaseBuilder { + override fun getBuilder(): RoomDatabase.Builder { + val appContext = ctx.applicationContext + val dbFile = appContext.getDatabasePath("room.db") + return Room.databaseBuilder( + context = appContext, + name = dbFile.absolutePath + ) + } +} + +actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = + AndroidDatabaseBuilder(get()) + +internal actual val ExtPlatformModule = module { } diff --git a/core/src/androidMain/kotlin/moe/lava/banksia/core/util/Logging.android.kt b/shared/src/androidMain/kotlin/moe/lava/banksia/util/Logging.android.kt similarity index 87% rename from core/src/androidMain/kotlin/moe/lava/banksia/core/util/Logging.android.kt rename to shared/src/androidMain/kotlin/moe/lava/banksia/util/Logging.android.kt index e0b792e..31c3072 100644 --- a/core/src/androidMain/kotlin/moe/lava/banksia/core/util/Logging.android.kt +++ b/shared/src/androidMain/kotlin/moe/lava/banksia/util/Logging.android.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util import android.util.Log diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton b/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton similarity index 88% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton rename to shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton index 909f642..15b3c58 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton @@ -6,7 +6,7 @@ object Constants { const val opendataKey: String = "" const val serverUrl: String = "https://banksia.lava.moe/api/" // TODO - var devMode: Boolean = false + const val devMode: Boolean = false const val updateKey: String = "" const val protomapsKey: String = "" } diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsRealtime.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt similarity index 89% rename from server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsRealtime.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt index 128f141..172238f 100644 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsRealtime.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.server.gtfsrt +package moe.lava.banksia.data.gtfsr import com.google.transit.realtime.FeedMessage diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt similarity index 88% rename from server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt index 4466b91..979f1f5 100644 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt @@ -1,7 +1,7 @@ -package moe.lava.banksia.server.gtfsrt +package moe.lava.banksia.data.gtfsr import com.google.transit.realtime.FeedMessage -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.util.Point class RealtimeVehiclePositions(data: FeedMessage) : GtfsRealtime(data) { private val positions = mutableMapOf() diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt similarity index 97% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt index 54717a2..77ab12d 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt @@ -16,12 +16,7 @@ import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.delay import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import moe.lava.banksia.core.Constants -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.util.LoopFlow.Companion.initWith -import moe.lava.banksia.core.util.error -import moe.lava.banksia.core.util.log -import moe.lava.banksia.core.util.loopFlow +import moe.lava.banksia.Constants import moe.lava.banksia.data.ptv.structures.PtvDeparture import moe.lava.banksia.data.ptv.structures.PtvDirection import moe.lava.banksia.data.ptv.structures.PtvRoute @@ -29,6 +24,11 @@ import moe.lava.banksia.data.ptv.structures.PtvRouteType import moe.lava.banksia.data.ptv.structures.PtvRouteType.Companion.asPtvType import moe.lava.banksia.data.ptv.structures.PtvRun import moe.lava.banksia.data.ptv.structures.PtvStop +import moe.lava.banksia.model.RouteType +import moe.lava.banksia.util.LoopFlow.Companion.initWith +import moe.lava.banksia.util.error +import moe.lava.banksia.util.log +import moe.lava.banksia.util.loopFlow import okio.ByteString.Companion.encodeUtf8 import kotlin.random.Random diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt similarity index 100% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt similarity index 100% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt similarity index 100% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt similarity index 94% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt index 4aae762..3178328 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt @@ -2,7 +2,7 @@ package moe.lava.banksia.data.ptv.structures import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.model.RouteType @Serializable data class PtvRoute( diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt similarity index 97% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt index d8808f1..c9988bf 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.model.RouteType object PtvRouteTypeSerialiser : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt similarity index 100% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt similarity index 100% rename from core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt new file mode 100644 index 0000000..8658342 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt @@ -0,0 +1,17 @@ +package moe.lava.banksia.di + +import moe.lava.banksia.room.Database +import org.koin.dsl.module + +val CommonModules = module { + includes(PlatformModule) + + single { Database.build(get().getBuilder()) } + single { get().versionMetadataDao } + single { get().routeDao } + single { get().serviceDao } + single { get().shapeDao } + single { get().stopDao } + single { get().stopTimeDao } + single { get().tripDao } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/PlatformModule.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/di/PlatformModule.kt new file mode 100644 index 0000000..6f29f14 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/di/PlatformModule.kt @@ -0,0 +1,21 @@ +package moe.lava.banksia.di + +import androidx.room.RoomDatabase +import moe.lava.banksia.room.Database +import org.koin.core.module.Module +import org.koin.core.parameter.ParametersHolder +import org.koin.core.scope.Scope +import org.koin.dsl.module + +interface PlatformDatabaseBuilder { + fun getBuilder(): RoomDatabase.Builder +} + +expect fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder + +internal expect val ExtPlatformModule: Module + +internal val PlatformModule = module { + includes(ExtPlatformModule) + single { provideDatabaseBuilder(it) } +} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/FutureTime.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt similarity index 94% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/FutureTime.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt index 7c77309..91c5c77 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/FutureTime.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate @@ -12,7 +12,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import moe.lava.banksia.core.model.FutureTime.Companion.asInt +import moe.lava.banksia.model.FutureTime.Companion.asInt @Serializable(FutureTimeSerialiser::class) data class FutureTime( diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Route.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Route.kt similarity index 82% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/Route.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/Route.kt index b2741f4..9cfff0f 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Route.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Route.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model import kotlinx.serialization.Serializable diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/RouteType.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt similarity index 66% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/RouteType.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt index 86555a6..08a9c53 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/RouteType.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model import kotlinx.serialization.Serializable @@ -13,8 +13,4 @@ enum class RouteType(val value: Int) { SkyBus(11), Interstate(10), ; - - companion object { - fun from(value: Int) = entries.first { it.value == value } - } } diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Run.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Run.kt similarity index 52% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/Run.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/Run.kt index 69799bf..328a4b0 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Run.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Run.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model data class Run( val ref: String, diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Service.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Service.kt similarity index 87% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/Service.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/Service.kt index 8568397..a57fb82 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Service.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Service.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Shape.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Shape.kt similarity index 67% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/Shape.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/Shape.kt index 7b71427..6299ca0 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Shape.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Shape.kt @@ -1,7 +1,7 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model import kotlinx.serialization.Serializable -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.util.Point typealias ShapePath = List diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt similarity index 53% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt index bbe6fbf..df10a58 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt @@ -1,15 +1,15 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model import kotlinx.serialization.Serializable -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.util.Point @Serializable data class Stop( val id: String, val name: String, val pos: Point, - val parent: String?, + val parent: String, val hasWheelChairBoarding: Boolean, - val level: String?, - val platformCode: String?, + val level: String, + val platformCode: String, ) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTime.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTime.kt new file mode 100644 index 0000000..682839d --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTime.kt @@ -0,0 +1,14 @@ +package moe.lava.banksia.model + +import kotlinx.serialization.Serializable + +@Serializable +data class StopTime( + val tripId: String, + val stopId: String, + val arrivalTime: FutureTime, + val departureTime: FutureTime, + val headsign: String?, + val pickupType: Int, + val dropOffType: Int, +) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt new file mode 100644 index 0000000..55288fa --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt @@ -0,0 +1,26 @@ +package moe.lava.banksia.model + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.Serializable + +@Serializable +data class StopTimeDated( + val tripId: String, + val stopId: String, + val arrivalTime: LocalDateTime, + val departureTime: LocalDateTime, + val headsign: String?, + val pickupType: Int, + val dropOffType: Int, +) + +fun StopTime.atDate(date: LocalDate) = StopTimeDated( + tripId = tripId, + stopId = stopId, + arrivalTime = arrivalTime.atDate(date), + departureTime = departureTime.atDate(date), + headsign = headsign, + pickupType = pickupType, + dropOffType = dropOffType, +) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt new file mode 100644 index 0000000..81d3f8d --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt @@ -0,0 +1,15 @@ +package moe.lava.banksia.model + +import kotlinx.serialization.Serializable + +@Serializable +data class Trip( + val id: String, + val routeId: String, + val service: Service, + val shapeId: String?, + val tripHeadsign: String, + val directionId: String, + val blockId: String, + val wheelchairAccessible: String, +) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/VersionMetadata.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/VersionMetadata.kt similarity index 79% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/model/VersionMetadata.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/model/VersionMetadata.kt index 2ee4f28..1770b23 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/VersionMetadata.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/VersionMetadata.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.model +package moe.lava.banksia.model import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt new file mode 100644 index 0000000..89bc24a --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -0,0 +1,60 @@ +package moe.lava.banksia.room + +import androidx.room.AutoMigration +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import moe.lava.banksia.room.converter.RouteTypeConverter +import moe.lava.banksia.room.dao.RouteDao +import moe.lava.banksia.room.dao.ServiceDao +import moe.lava.banksia.room.dao.ShapeDao +import moe.lava.banksia.room.dao.StopDao +import moe.lava.banksia.room.dao.StopTimeDao +import moe.lava.banksia.room.dao.TripDao +import moe.lava.banksia.room.dao.VersionMetadataDao +import moe.lava.banksia.room.entity.RouteEntity +import moe.lava.banksia.room.entity.ServiceEntity +import moe.lava.banksia.room.entity.ShapeEntity +import moe.lava.banksia.room.entity.StopEntity +import moe.lava.banksia.room.entity.StopTimeEntity +import moe.lava.banksia.room.entity.TripEntity +import moe.lava.banksia.room.entity.VersionMetadataEntity +import androidx.room.Database as DatabaseAnnotation + +@DatabaseAnnotation( + version = 9, + entities = [ + RouteEntity::class, + ServiceEntity::class, + ShapeEntity::class, + StopEntity::class, + StopTimeEntity::class, + TripEntity::class, + VersionMetadataEntity::class, + ], + autoMigrations = [ + AutoMigration(from = 1, to = 2), + AutoMigration(from = 2, to = 3), + ] +) +@TypeConverters(RouteTypeConverter::class) +abstract class Database : RoomDatabase() { + abstract val versionMetadataDao: VersionMetadataDao + abstract val routeDao: RouteDao + abstract val serviceDao: ServiceDao + abstract val shapeDao: ShapeDao + abstract val stopDao: StopDao + abstract val stopTimeDao: StopTimeDao + abstract val tripDao: TripDao + + companion object { + fun build(base: Builder) = + base.fallbackToDestructiveMigration(true) + .setDriver(BundledSQLiteDriver()) + .setQueryCoroutineContext(Dispatchers.IO) +// .fallbackToDestructiveMigration(true) + .build() + } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt new file mode 100644 index 0000000..8927f14 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.room.converter + +import androidx.room.TypeConverter +import moe.lava.banksia.model.RouteType + +object RouteTypeConverter { + @TypeConverter + fun from(value: Int) = RouteType.entries.first { it.value == value } + + @TypeConverter + fun to(routeType: RouteType) = routeType.value +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/ShapePathConverter.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/ShapePathConverter.kt new file mode 100644 index 0000000..08a8064 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/ShapePathConverter.kt @@ -0,0 +1,43 @@ +package moe.lava.banksia.room.converter + +import androidx.room.TypeConverter +import moe.lava.banksia.model.ShapePath +import moe.lava.banksia.util.Point + +object ShapePathConverter { + @TypeConverter + fun from(value: ByteArray): ShapePath { + return value + .asIterable() + .chunked(8) { + (it[0].toLong() and 0xFF) or + (it[1].toLong() and 0xFF shl 8) or + (it[2].toLong() and 0xFF shl 16) or + (it[3].toLong() and 0xFF shl 24) or + (it[4].toLong() and 0xFF shl 32) or + (it[5].toLong() and 0xFF shl 40) or + (it[6].toLong() and 0xFF shl 48) or + (it[7].toLong() and 0xFF shl 56) + } + .map { Double.fromBits(it) } + .chunked(2) + .map { (lat, lng) -> Point(lat, lng) } + } + + @TypeConverter + fun to(path: ShapePath): ByteArray { + return path + .flatMap { (lat, lng) -> listOf(lat.toBits(), lng.toBits()) } + .flatMap { i -> listOf( + i.toByte(), + (i shr 8).toByte(), + (i shr 16).toByte(), + (i shr 24).toByte(), + (i shr 32).toByte(), + (i shr 40).toByte(), + (i shr 48).toByte(), + (i shr 56).toByte(), + ) } + .toByteArray() + } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt new file mode 100644 index 0000000..0174f0f --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt @@ -0,0 +1,49 @@ +package moe.lava.banksia.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.room.entity.RouteEntity +import moe.lava.banksia.room.entity.StopEntity + +@Dao +interface RouteDao { + @Query("SELECT * FROM Route") + suspend fun getAll(): List + + @Query("SELECT * FROM Route WHERE id == :id") + suspend fun get(id: String): RouteEntity? + + @Insert + suspend fun insertAll(vararg routes: RouteEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg routes: RouteEntity) + + @Delete + suspend fun delete(route: RouteEntity) + + @Query("DELETE FROM Route") + suspend fun deleteAll() + + @Query(""" + SELECT Stop.* FROM Stop + INNER JOIN StopTime ON StopTime.stopId == Stop.id + INNER JOIN Trip ON Trip.id == StopTime.tripId + WHERE Trip.routeId == :id + GROUP BY Stop.id + """) + suspend fun stops(id: String): List + + @Query(""" + SELECT Stop.* FROM Stop + INNER JOIN Stop Child ON Child.parent == Stop.id + INNER JOIN StopTime ON StopTime.stopId == Child.id + INNER JOIN Trip ON Trip.id == StopTime.tripId + WHERE Trip.routeId == :id + GROUP BY Stop.id + """) + suspend fun stopsParent(id: String): List +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt new file mode 100644 index 0000000..6fc2906 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt @@ -0,0 +1,29 @@ +package moe.lava.banksia.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.room.entity.ServiceEntity + +@Dao +interface ServiceDao { + @Query("SELECT * FROM Service") + suspend fun getAll(): List + + @Query("SELECT * FROM Service WHERE id == :id") + suspend fun get(id: String): ServiceEntity? + + @Insert + suspend fun insertAll(vararg services: ServiceEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg services: ServiceEntity) + + @Delete + suspend fun delete(service: ServiceEntity) + + @Query("DELETE FROM Service") + suspend fun deleteAll() +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt new file mode 100644 index 0000000..c48735a --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import moe.lava.banksia.room.entity.ShapeEntity + +@Dao +interface ShapeDao { + @Query("SELECT * FROM Shape WHERE id == :id") + suspend fun get(id: String): ShapeEntity? + + @Insert + suspend fun insertAll(vararg shapes: ShapeEntity) + + @Delete + suspend fun delete(shape: ShapeEntity) + + @Query("DELETE FROM Shape") + suspend fun deleteAll() +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt new file mode 100644 index 0000000..f6b2ef2 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt @@ -0,0 +1,32 @@ +package moe.lava.banksia.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.room.entity.StopEntity + +@Dao +interface StopDao { + @Query("SELECT * FROM Stop") + suspend fun getAll(): List + + @Query("SELECT * FROM Stop WHERE id == :id") + suspend fun get(id: String): StopEntity? + + @Query("SELECT * FROM Stop WHERE id IN (:ids)") + suspend fun get(ids: List): List + + @Insert + suspend fun insertAll(vararg stops: StopEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg stops: StopEntity) + + @Delete + suspend fun delete(stop: StopEntity) + + @Query("DELETE FROM Stop") + suspend fun deleteAll() +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt new file mode 100644 index 0000000..d5e1744 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt @@ -0,0 +1,44 @@ +package moe.lava.banksia.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.room.entity.StopTimeEntity + +@Dao +interface StopTimeDao { + @Query("SELECT * FROM StopTime") + suspend fun getAll(): List + + @Query("SELECT * FROM StopTime WHERE tripId == :tripId") + suspend fun getForTrip(tripId: String): StopTimeEntity? + + @Query("SELECT * FROM StopTime WHERE tripId IN (:tripIds)") + suspend fun getForTrips(tripIds: List): List + + @Query("SELECT * FROM StopTime WHERE stopId == :stopId") + suspend fun getForStop(stopId: String): List + + @Query(""" + SELECT * FROM StopTime + INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` + INNER JOIN Trip ON Trip.serviceId == Service.id + WHERE StopTime.tripId == Trip.id + AND StopTime.stopId == :stopId + """) + suspend fun getForStopDated(stopId: String, days: Int, date: Int): List + + @Insert + suspend fun insertAll(vararg stopTimes: StopTimeEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg stopTimes: StopTimeEntity) + + @Delete + suspend fun delete(stopTime: StopTimeEntity) + + @Query("DELETE FROM StopTime") + suspend fun deleteAll() +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/TripDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/TripDao.kt new file mode 100644 index 0000000..9778a1a --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/TripDao.kt @@ -0,0 +1,32 @@ +package moe.lava.banksia.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.room.entity.TripEntity + +@Dao +interface TripDao { + @Query("SELECT * FROM Trip") + suspend fun getAll(): List + + @Query("SELECT * FROM Trip WHERE id == :id") + suspend fun get(id: String): TripEntity? + + @Query("SELECT * FROM Trip WHERE routeId == :id") + suspend fun getByRoute(id: String): List + + @Insert + suspend fun insertAll(vararg trips: TripEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg trips: TripEntity) + + @Delete + suspend fun delete(trip: TripEntity) + + @Query("DELETE FROM Trip") + suspend fun deleteAll() +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/VersionMetadataDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/VersionMetadataDao.kt new file mode 100644 index 0000000..b96102e --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/VersionMetadataDao.kt @@ -0,0 +1,27 @@ +package moe.lava.banksia.room.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE +import androidx.room.Query +import moe.lava.banksia.room.entity.VersionMetadataEntity + +@Dao +interface VersionMetadataDao { + @Query("SELECT * FROM VersionMetadata WHERE type == :type") + suspend fun get(type: String): VersionMetadataEntity? + + @Query("SELECT * FROM VersionMetadata") + suspend fun getAll(): List + + @Insert(onConflict = REPLACE) + suspend fun update(vararg data: VersionMetadataEntity) + + suspend fun update(vararg data: Pair) { + update(*data.map { (type, lastUpdated) -> VersionMetadataEntity(type, lastUpdated) }.toTypedArray()) + } + + suspend fun update(lastUpdated: Long, types: Collection) { + update(*types.map { VersionMetadataEntity(it, lastUpdated) }.toTypedArray()) + } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/RouteEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/RouteEntity.kt new file mode 100644 index 0000000..cc690d6 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/RouteEntity.kt @@ -0,0 +1,18 @@ +package moe.lava.banksia.room.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import moe.lava.banksia.model.Route +import moe.lava.banksia.model.RouteType + +@Entity("Route") +data class RouteEntity( + @PrimaryKey val id: String, + val type: RouteType, + val number: String?, + val name: String, +) { + fun asModel() = Route(id, type, number, name) +} + +fun Route.asEntity() = RouteEntity(id, type, number, name) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt new file mode 100644 index 0000000..027aaa8 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt @@ -0,0 +1,31 @@ +package moe.lava.banksia.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.datetime.LocalDate +import moe.lava.banksia.model.Service +import moe.lava.banksia.util.deserialiseDaysBitflag +import moe.lava.banksia.util.serialise + +@Entity("Service") +data class ServiceEntity( + @PrimaryKey val id: String, + @ColumnInfo(index = true) val days: Int, + val start: Int, + val end: Int, +) { + fun asModel() = Service( + id, + days.deserialiseDaysBitflag(), + LocalDate.fromEpochDays(start), + LocalDate.fromEpochDays(end), + ) +} + +fun Service.asEntity() = ServiceEntity( + id, + days.serialise(), + start.toEpochDays().toInt(), + end.toEpochDays().toInt(), +) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ShapeEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ShapeEntity.kt new file mode 100644 index 0000000..87ca671 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ShapeEntity.kt @@ -0,0 +1,19 @@ +package moe.lava.banksia.room.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import moe.lava.banksia.model.Shape +import moe.lava.banksia.model.ShapePath +import moe.lava.banksia.room.converter.ShapePathConverter + +@Entity("Shape") +@TypeConverters(ShapePathConverter::class) +data class ShapeEntity( + @PrimaryKey val id: String, + val path: ShapePath, +) { + fun asModel() = Shape(id, path) +} + +fun Shape.asEntity() = ShapeEntity(id, path) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt new file mode 100644 index 0000000..9c6cf15 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt @@ -0,0 +1,23 @@ +package moe.lava.banksia.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import moe.lava.banksia.model.Stop +import moe.lava.banksia.util.Point + +@Entity("Stop") +data class StopEntity( + @PrimaryKey val id: String, + val name: String, + val lat: Double, + val lng: Double, + @ColumnInfo(index = true) val parent: String, + val hasWheelChairBoarding: Boolean, + val level: String, + val platformCode: String, +) { + fun asModel() = Stop(id, name, Point(lat, lng), parent, hasWheelChairBoarding, level, platformCode) +} + +fun Stop.asEntity() = StopEntity(id, name, pos.lat, pos.lng, parent, hasWheelChairBoarding, level, platformCode) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt new file mode 100644 index 0000000..bb20ff1 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt @@ -0,0 +1,53 @@ +package moe.lava.banksia.room.entity + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.Companion.CASCADE +import androidx.room.Index +import kotlinx.serialization.ExperimentalSerializationApi +import moe.lava.banksia.model.FutureTime +import moe.lava.banksia.model.FutureTime.Companion.asInt +import moe.lava.banksia.model.StopTime + +@Entity( + "StopTime", + primaryKeys = ["tripId", "stopId"], + indices = [ + Index("tripId", unique = false), + Index("stopId", unique = false), + ], + foreignKeys = [ + ForeignKey(TripEntity::class, parentColumns = ["id"], childColumns = ["tripId"], onDelete = CASCADE), + ForeignKey(StopEntity::class, parentColumns = ["id"], childColumns = ["stopId"], onDelete = CASCADE), + ] +) +data class StopTimeEntity( + val tripId: String, + val stopId: String, + val arrivalTime: Int, + val departureTime: Int, + val headsign: String?, + val pickupType: Int, + val dropOffType: Int, +) { + fun asModel() = StopTime( + tripId, + stopId, + FutureTime.fromInt(arrivalTime), + FutureTime.fromInt(departureTime), + headsign, + pickupType, + dropOffType, + ) +} + +@OptIn(ExperimentalSerializationApi::class) +fun StopTime.asEntity() = StopTimeEntity( + tripId, + stopId, + arrivalTime.asInt(), + departureTime.asInt(), + headsign, + pickupType, + dropOffType, +) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt new file mode 100644 index 0000000..3753d44 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt @@ -0,0 +1,49 @@ +package moe.lava.banksia.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.Companion.CASCADE +import androidx.room.Index +import androidx.room.PrimaryKey +import moe.lava.banksia.model.Trip + +@Entity( + "Trip", + foreignKeys = [ + ForeignKey(RouteEntity::class, parentColumns = ["id"], childColumns = ["routeId"], onDelete = CASCADE), + ForeignKey(ServiceEntity::class, parentColumns = ["id"], childColumns = ["serviceId"], onDelete = CASCADE), + ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE), + ], + indices = [Index("shapeId")], +) +data class TripEntity( + @PrimaryKey val id: String, + @ColumnInfo(index = true) val routeId: String, + val serviceId: String, + val shapeId: String?, + val tripHeadsign: String, + val directionId: String, + val blockId: String, + val wheelchairAccessible: String, +) + +fun Trip.Companion.from(tripEntity: TripEntity, serviceEntity: ServiceEntity): Trip { + if (tripEntity.serviceId != serviceEntity.id) { + throw IllegalArgumentException("trip and service id mismatch (${tripEntity.serviceId} != ${serviceEntity.id})") + } + return with(tripEntity) { + Trip( + id = id, + routeId = routeId, + service = serviceEntity.asModel(), + shapeId = shapeId, + tripHeadsign = tripHeadsign, + directionId = directionId, + blockId = blockId, + wheelchairAccessible = wheelchairAccessible + ) + } +} + +fun Trip.asEntity() = TripEntity(id, routeId, service.id, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/VersionMetadataEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/VersionMetadataEntity.kt new file mode 100644 index 0000000..fc00b44 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/VersionMetadataEntity.kt @@ -0,0 +1,19 @@ +package moe.lava.banksia.room.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey +import moe.lava.banksia.model.VersionMetadata + +@Entity( + "VersionMetadata", +) +data class VersionMetadataEntity( + /** Entity type this metadata applies to */ + @PrimaryKey val type: String, + /** Last updated */ + val lastUpdated: Long, +) { + fun asModel() = VersionMetadata(type, lastUpdated) +} + +fun VersionMetadata.asEntity() = VersionMetadataEntity(type, lastUpdated) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/BoxedValue.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt similarity index 87% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/util/BoxedValue.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt index f761518..3ff5702 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/BoxedValue.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util /** Wraps an arbitrary value, such that equality checks are forced to be done by reference */ class BoxedValue(val value: T) { diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/CacheMap.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/CacheMap.kt similarity index 97% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/util/CacheMap.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/util/CacheMap.kt index 22236c6..e41cef6 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/CacheMap.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/CacheMap.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/DayOfWeekExtension.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt similarity index 96% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/util/DayOfWeekExtension.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt index 7feca0d..87d3244 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/DayOfWeekExtension.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util import kotlinx.datetime.DayOfWeek diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Logging.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/Logging.kt similarity index 88% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/util/Logging.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/util/Logging.kt index 9d5f55a..7f26800 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Logging.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/Logging.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util fun error(tag: String, throwable: Throwable) = error(tag, "", throwable) expect fun log(tag: String, msg: String) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/LoopFlow.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/LoopFlow.kt similarity index 98% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/util/LoopFlow.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/util/LoopFlow.kt index ec21d62..ee3e826 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/LoopFlow.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/LoopFlow.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Point.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/Point.kt similarity index 75% rename from core/src/commonMain/kotlin/moe/lava/banksia/core/util/Point.kt rename to shared/src/commonMain/kotlin/moe/lava/banksia/util/Point.kt index 4db05e2..4aae7d4 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Point.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/Point.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util import kotlinx.serialization.Serializable diff --git a/server/gtfs_rt/src/main/proto/gtfs-realtime.proto b/shared/src/commonMain/proto/gtfs-realtime.proto similarity index 100% rename from server/gtfs_rt/src/main/proto/gtfs-realtime.proto rename to shared/src/commonMain/proto/gtfs-realtime.proto diff --git a/shared/src/iosMain/kotlin/moe/lava/banksia/di/PlatformModule.ios.kt b/shared/src/iosMain/kotlin/moe/lava/banksia/di/PlatformModule.ios.kt new file mode 100644 index 0000000..8597856 --- /dev/null +++ b/shared/src/iosMain/kotlin/moe/lava/banksia/di/PlatformModule.ios.kt @@ -0,0 +1,18 @@ +package moe.lava.banksia.di + +import androidx.room.RoomDatabase +import moe.lava.banksia.room.Database +import org.koin.core.parameter.ParametersHolder +import org.koin.core.scope.Scope +import org.koin.dsl.module + +class IosDatabaseBuilder() : PlatformDatabaseBuilder { + override fun getBuilder(): RoomDatabase.Builder { + TODO("Not yet implemented") + } +} + +actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = + IosDatabaseBuilder() + +internal actual val ExtPlatformModule = module { } diff --git a/core/src/iosMain/kotlin/moe/lava/banksia/core/util/Logging.ios.kt b/shared/src/iosMain/kotlin/moe/lava/banksia/util/Logging.ios.kt similarity index 83% rename from core/src/iosMain/kotlin/moe/lava/banksia/core/util/Logging.ios.kt rename to shared/src/iosMain/kotlin/moe/lava/banksia/util/Logging.ios.kt index 014c1d2..b58b89a 100644 --- a/core/src/iosMain/kotlin/moe/lava/banksia/core/util/Logging.ios.kt +++ b/shared/src/iosMain/kotlin/moe/lava/banksia/util/Logging.ios.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util actual fun log(tag: String, msg: String) { TODO("Not yet implemented") diff --git a/shared/src/jvmMain/kotlin/moe/lava/banksia/di/PlatformModule.jvm.kt b/shared/src/jvmMain/kotlin/moe/lava/banksia/di/PlatformModule.jvm.kt new file mode 100644 index 0000000..3e93241 --- /dev/null +++ b/shared/src/jvmMain/kotlin/moe/lava/banksia/di/PlatformModule.jvm.kt @@ -0,0 +1,23 @@ +package moe.lava.banksia.di + +import androidx.room.Room +import androidx.room.RoomDatabase +import moe.lava.banksia.room.Database +import org.koin.core.parameter.ParametersHolder +import org.koin.core.scope.Scope +import org.koin.dsl.module +import java.io.File + +class JvmDatabaseBuilder() : PlatformDatabaseBuilder { + override fun getBuilder(): RoomDatabase.Builder { + val dbFile = File("./data/room.db") + return Room.databaseBuilder( + name = dbFile.absolutePath, + ) + } +} + +actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = + JvmDatabaseBuilder() + +internal actual val ExtPlatformModule = module { } diff --git a/core/src/jvmMain/kotlin/moe/lava/banksia/core/util/Logging.jvm.kt b/shared/src/jvmMain/kotlin/moe/lava/banksia/util/Logging.jvm.kt similarity index 86% rename from core/src/jvmMain/kotlin/moe/lava/banksia/core/util/Logging.jvm.kt rename to shared/src/jvmMain/kotlin/moe/lava/banksia/util/Logging.jvm.kt index de7fdaa..0a1ea10 100644 --- a/core/src/jvmMain/kotlin/moe/lava/banksia/core/util/Logging.jvm.kt +++ b/shared/src/jvmMain/kotlin/moe/lava/banksia/util/Logging.jvm.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.core.util +package moe.lava.banksia.util actual fun log(tag: String, msg: String) { println("[$tag] $msg") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index b599bc6..201a10f 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -41,9 +41,7 @@ kotlin { sourceSets { androidMain.dependencies { - implementation(libs.compose.ui.tooling.preview) implementation(libs.play.services.location) - implementation(projects.ui.shared) } commonMain.dependencies { implementation(libs.compose.components.resources) @@ -69,9 +67,8 @@ kotlin { implementation(libs.moko.geo.compose) implementation(libs.ui.backhandler) - implementation(projects.core) - implementation(projects.core.data) - implementation(projects.core.stoptime) + implementation(projects.client) + implementation(projects.shared) implementation(projects.ui.maps) implementation(projects.ui.shared) } diff --git a/ui/maps/build.gradle.kts b/ui/maps/build.gradle.kts index 4e859d1..324b0b3 100644 --- a/ui/maps/build.gradle.kts +++ b/ui/maps/build.gradle.kts @@ -49,7 +49,7 @@ kotlin { implementation(libs.compose.material3) implementation(libs.compose.ui) - implementation(projects.core) + implementation(projects.shared) implementation(projects.ui.shared) } } diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt index d76c1f4..1df9cb1 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt @@ -6,15 +6,12 @@ import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.safeDrawing import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import kotlinx.coroutines.launch import kotlinx.serialization.json.JsonObject -import moe.lava.banksia.core.Constants +import moe.lava.banksia.Constants import moe.lava.banksia.ui.map.mappers.routeColorExpression -import moe.lava.banksia.ui.map.mappers.toMapPosition import moe.lava.banksia.ui.platform.BanksiaTheme import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.rememberCameraState @@ -29,7 +26,6 @@ import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.util.ClickResult import org.maplibre.spatialk.geojson.Feature import org.maplibre.spatialk.geojson.Geometry -import kotlin.time.Duration.Companion.seconds @Composable internal fun MapLibreMaps( @@ -38,7 +34,7 @@ internal fun MapLibreMaps( positionState: MapsPositionState, stops: GeoJsonData.Features?, // vehicles: GeoJsonData.Features?, - stopInnerColor: Color = BanksiaTheme.colors.surface, + stopInnerColor: Color, onStopClicked: (Feature) -> Unit, ) { val camPos = rememberCameraState( @@ -47,17 +43,6 @@ internal fun MapLibreMaps( target = MELBOURNE_POS ) ) - val scope = rememberCoroutineScope() - scope.launch { - positionState.updates.collect { - val (position, box) = it.toMapPosition() - if (box != null) { - camPos.animateTo(box, duration = 1.seconds) - } else { - camPos.animateTo(position, duration = 1.seconds) - } - } - } val variant = if (isSystemInDarkTheme()) "dark" else "light" @@ -78,7 +63,7 @@ internal fun MapLibreMaps( CircleLayer( id = "maps-stops0", source = stopsSource, - color = const(stopInnerColor), + color = const(BanksiaTheme.colors.surface), radius = const(3.dp), strokeWidth = const(2.dp), strokeColor = routeColorExpression, diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt index 92a9695..52b7250 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt @@ -4,11 +4,11 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import moe.lava.banksia.core.util.Point import moe.lava.banksia.ui.map.mappers.asFeatures import moe.lava.banksia.ui.map.mappers.toPosition import moe.lava.banksia.ui.map.util.Marker import moe.lava.banksia.ui.platform.BanksiaTheme +import moe.lava.banksia.util.Point internal val MELBOURNE = Point(-37.8136, 144.9631) internal val MELBOURNE_POS = MELBOURNE.toPosition() diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt index 94421a7..a9fe8b2 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt @@ -7,18 +7,16 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch -import moe.lava.banksia.ui.map.util.CameraPosition +import moe.lava.banksia.util.Point class MapsPositionState internal constructor( private val scope: CoroutineScope ) { - internal val updates: SharedFlow + internal val updates: SharedFlow field = MutableSharedFlow() - fun update(position: CameraPosition) { - scope.launch { - updates.emit(position) - } + fun update(position: Point) { + scope.launch { updates.emit(position) } } } diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/CameraPosition.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/CameraPosition.kt deleted file mode 100644 index b463c18..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/CameraPosition.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.lava.banksia.ui.map.mappers - -import moe.lava.banksia.ui.map.util.CameraPosition -import org.maplibre.spatialk.geojson.BoundingBox -import org.maplibre.compose.camera.CameraPosition as MLCameraPosition - -internal fun CameraPosition.toMapPosition() = Pair( - MLCameraPosition(target = this.centre.toPosition(), zoom = 16.0), - this.bounds?.let { - BoundingBox( - southwest = it.southwest.toPosition(), - northeast = it.northeast.toPosition(), - ) - } -) diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt index 3fe99c2..32a910c 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.ui.map.mappers import kotlinx.serialization.Serializable -import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.model.RouteType import moe.lava.banksia.ui.map.util.Marker import org.maplibre.compose.sources.GeoJsonData import org.maplibre.spatialk.geojson.FeatureCollection diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt index ed568c2..c137394 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.map.mappers -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.util.Point import org.maplibre.spatialk.geojson.Position internal fun Point.toPosition() = Position(lng, lat) diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt index 584c76f..523e438 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.ui.map.mappers import androidx.compose.runtime.Composable -import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.model.RouteType import moe.lava.banksia.ui.extensions.getUIProperties import moe.lava.banksia.ui.platform.BanksiaTheme import org.maplibre.compose.expressions.dsl.case diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt index aba2858..710cebb 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.map.util -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.util.Point data class CameraPosition( val centre: Point = Point(-37.8136, 144.9631), diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt index 9381262..4adf3b1 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt @@ -1,5 +1,5 @@ package moe.lava.banksia.ui.map.util -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.util.Point data class CameraPositionBounds(val northeast: Point, val southwest: Point) diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt index ac33868..9326b2a 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt @@ -1,8 +1,8 @@ package moe.lava.banksia.ui.map.util import kotlinx.serialization.Serializable -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.model.RouteType +import moe.lava.banksia.util.Point @Serializable sealed class Marker { diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt index 04b8dc6..146d74b 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.map.util import androidx.compose.ui.graphics.Color -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.util.Point data class Polyline(val points: List, val colour: Color) diff --git a/ui/shared/build.gradle.kts b/ui/shared/build.gradle.kts index 2a78572..c784fed 100644 --- a/ui/shared/build.gradle.kts +++ b/ui/shared/build.gradle.kts @@ -16,10 +16,6 @@ kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } - - androidResources { - enable = true - } } compilerOptions { @@ -39,7 +35,7 @@ kotlin { implementation(libs.compose.ui) implementation(libs.compose.ui.tooling.preview) - implementation(projects.core) + implementation(projects.shared) } } } @@ -51,5 +47,4 @@ dependencies { compose.resources { publicResClass = true packageOfResClass = "moe.lava.banksia.resources" - generateResClass = always } diff --git a/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_down.xml b/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_down.xml deleted file mode 100644 index ac49572..0000000 --- a/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_down.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_up.xml b/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_up.xml deleted file mode 100644 index 322fa56..0000000 --- a/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_up.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt index 90914ae..e84d765 100644 --- a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt +++ b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt @@ -11,10 +11,10 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.model.RouteType.MetroBus -import moe.lava.banksia.core.model.RouteType.MetroTrain -import moe.lava.banksia.core.model.RouteType.MetroTram +import moe.lava.banksia.model.RouteType +import moe.lava.banksia.model.RouteType.MetroBus +import moe.lava.banksia.model.RouteType.MetroTrain +import moe.lava.banksia.model.RouteType.MetroTram import moe.lava.banksia.ui.extensions.getUIProperties import org.jetbrains.compose.resources.painterResource diff --git a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt index 805f572..992b910 100644 --- a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt +++ b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt @@ -1,8 +1,8 @@ package moe.lava.banksia.ui.extensions import androidx.compose.ui.graphics.Color -import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.data.ptv.structures.PtvRouteType +import moe.lava.banksia.model.RouteType import moe.lava.banksia.resources.Res import moe.lava.banksia.resources.bus import moe.lava.banksia.resources.bus_background diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt index f74dc1a..453e1ee 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt @@ -3,6 +3,7 @@ package moe.lava.banksia.ui import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi +import moe.lava.banksia.di.CommonModules import moe.lava.banksia.ui.di.AppModule import moe.lava.banksia.ui.screens.map.MapScreen import org.koin.compose.KoinMultiplatformApplication @@ -13,7 +14,7 @@ import org.koin.dsl.koinConfiguration @Composable fun App() { KoinMultiplatformApplication(config = koinConfiguration { - modules(AppModule) + modules(CommonModules, AppModule) }) { MapScreen() } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt index cff36fb..4c93644 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt @@ -1,13 +1,12 @@ package moe.lava.banksia.ui.di -import moe.lava.banksia.core.data.dataDiModule +import moe.lava.banksia.client.di.ClientModule import moe.lava.banksia.ui.screens.map.MapScreenViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val AppModule = module { - includes(dataDiModule) - + includes(ClientModule) // ViewModel viewModelOf(::MapScreenViewModel) } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt index 9fb37d7..fa0354d 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeContent import androidx.compose.foundation.layout.size @@ -28,26 +27,18 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay +import moe.lava.banksia.ui.screens.map.MapScreenEvent +import moe.lava.banksia.ui.state.InfoPanelState import kotlin.time.Duration.Companion.milliseconds -sealed class InfoPanelEvent - -sealed class InfoPanelState { - abstract val loading: Boolean - - data object None : InfoPanelState() { - override val loading = false - } -} - @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun InfoPanel( - modifier: Modifier = Modifier, state: InfoPanelState, - onEvent: (InfoPanelEvent) -> Unit, + onEvent: (MapScreenEvent) -> Unit, onPeekHeightChange: (Dp) -> Unit, ) { if (state is InfoPanelState.None) @@ -66,20 +57,18 @@ fun InfoPanel( } Column( - modifier = modifier + Modifier .fillMaxWidth() .padding(horizontal = 24.dp) - .heightIn(min = 350.dp) .onSizeChanged { -// onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) }) - onPeekHeightChange(350.dp) + onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) }) } ) { Box { when (state) { - is RouteInfoPanelState -> RouteInfoPanel(state, onEvent) - is StopInfoPanelState -> StopInfoPanel(state, onEvent) - is TripInfoPanelState -> TripInfoPanel(state, onEvent) + is InfoPanelState.Route -> RouteInfoPanel(state, onEvent) + is InfoPanelState.Stop -> StopInfoPanel(state, onEvent) + is InfoPanelState.Trip -> TripInfoPanel(state, onEvent) is InfoPanelState.None -> throw UnsupportedOperationException() } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt index a1a97d3..b55b7c1 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt @@ -9,22 +9,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.ui.components.RouteIcon - -sealed class RouteInfoPanelEvent : InfoPanelEvent() - -data class RouteInfoPanelState( - val name: String, - val type: RouteType, -) : InfoPanelState() { - override val loading = false -} +import moe.lava.banksia.ui.screens.map.MapScreenEvent +import moe.lava.banksia.ui.state.InfoPanelState @Composable internal fun RouteInfoPanel( - state: RouteInfoPanelState, - onEvent: (RouteInfoPanelEvent) -> Unit, + state: InfoPanelState.Route, + onEvent: (MapScreenEvent) -> Unit, ) { Column(Modifier.fillMaxWidth()) { Row { diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt index 369721c..731ef88 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt @@ -1,358 +1,63 @@ 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.Row 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.height 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.SegmentedListItem -import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.style.TextOverflow 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 +import moe.lava.banksia.ui.screens.map.MapScreenEvent +import moe.lava.banksia.ui.state.InfoPanelState -sealed class StopInfoPanelEvent : InfoPanelEvent() { - data object ToggleGrouping : StopInfoPanelEvent() -} - -data class StopInfoPanelState( - val id: String, - val name: String, - val subname: String? = null, - val departures: List? = null, -) : InfoPanelState() { - override val loading: Boolean - get() = departures.isNullOrEmpty() - - data class DeparturePlatforms( - val platform: String, - val departures: List, - ) - - 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, -) { - 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 internal fun StopInfoPanel( - state: StopInfoPanelState, - onEvent: (StopInfoPanelEvent) -> Unit, + state: InfoPanelState.Stop, + onEvent: (MapScreenEvent) -> Unit, ) { - 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 { + Column(Modifier.fillMaxWidth()) { + Text( + state.name, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Start + ) + state.subname?.let { + Text( + "/ $it", + modifier = Modifier.padding(start = 5.dp), + style = MaterialTheme.typography.titleSmall, + color = Color.Gray, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Start + ) + } + state.departures?.let { + Spacer(Modifier.height(5.dp)) + it.forEach { (name, formatted) -> + Row(verticalAlignment = Alignment.CenterVertically) { Text( - state.name, - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Start + name, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold + ) + Text( + formatted, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 5.dp) ) - state.subname?.let { - Text( - "/ $it", - modifier = Modifier.padding(start = 5.dp), - style = MaterialTheme.typography.titleSmall, - color = Color.Gray, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Start - ) - } - } - IconButton( - onClick = { onEvent(StopInfoPanelEvent.ToggleGrouping) }, - ) { Icon(Icons.Default.Edit, null) } - } - Spacer(Modifier.height(10.dp)) - AnimatedContent( - targetState = state.departures, - transitionSpec = { spec }, - ) { departures -> - departures?.let { departurePlatforms -> - if (departurePlatforms.size > 1) { - ManyPlatforms(departurePlatforms) - } 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 = {}, - ) -} diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt index 29bdd37..2d221b2 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt @@ -9,23 +9,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.ui.components.RouteIcon - -sealed class TripInfoPanelEvent : InfoPanelEvent() - -data class TripInfoPanelState( - val direction: String, - val type: RouteType, - val routeName: String? = null, -) : InfoPanelState() { - override val loading = routeName == null -} +import moe.lava.banksia.ui.screens.map.MapScreenEvent +import moe.lava.banksia.ui.state.InfoPanelState @Composable internal fun TripInfoPanel( - state: TripInfoPanelState, - onEvent: (TripInfoPanelEvent) -> Unit, + state: InfoPanelState.Trip, + onEvent: (MapScreenEvent) -> Unit, ) { Column(Modifier.fillMaxWidth()) { Row { diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt index 1303bf5..70b8ed4 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt @@ -38,10 +38,9 @@ import moe.lava.banksia.ui.layout.AppBottomSheet import moe.lava.banksia.ui.layout.Searcher import moe.lava.banksia.ui.layout.SheetStateWrapper import moe.lava.banksia.ui.layout.info.InfoPanel -import moe.lava.banksia.ui.layout.info.InfoPanelState import moe.lava.banksia.ui.map.Maps -import moe.lava.banksia.ui.map.rememberMapsPositionState import moe.lava.banksia.ui.platform.BanksiaTheme +import moe.lava.banksia.ui.state.InfoPanelState import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel @@ -65,13 +64,6 @@ fun MapScreen( val sheetState = SheetStateWrapper.create() var searchExpandedState by rememberSaveable { mutableStateOf(false) } - val mapsPositionState = rememberMapsPositionState() - scope.launch { - viewModel.cameraChangeEmitter.collect { - mapsPositionState.update(it.value) - } - } - LaunchedEffect(infoState) { if (infoState !is InfoPanelState.None) { sheetState.peek() @@ -88,7 +80,6 @@ fun MapScreen( SearchBarDefaults.InputFieldHeight.roundToPx() }, bottom = sheetState.bottomInset), stops = mapState.stops, - positionState = mapsPositionState, // vehicles = mapState.vehicles, onStopClicked = { stop -> viewModel.handleEvent(MapScreenEvent.SelectStop(stop)) diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt index de06381..a3435af 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt @@ -15,30 +15,25 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.datetime.TimeZone 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.StopRepository -import moe.lava.banksia.core.data.repositories.StopTimeRepository -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.model.RouteType -import moe.lava.banksia.core.util.BoxedValue -import moe.lava.banksia.core.util.BoxedValue.Companion.box -import moe.lava.banksia.core.util.LoopFlow.Companion.waitUntilSubscribed -import moe.lava.banksia.core.util.Point -import moe.lava.banksia.core.util.log +import moe.lava.banksia.client.repository.RouteRepository +import moe.lava.banksia.client.repository.StopRepository +import moe.lava.banksia.client.repository.StopTimeRepository 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.InfoPanelState -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.TripInfoPanelState +import moe.lava.banksia.model.Route +import moe.lava.banksia.model.RouteType import moe.lava.banksia.ui.map.util.CameraPosition import moe.lava.banksia.ui.map.util.CameraPositionBounds import moe.lava.banksia.ui.map.util.Marker +import moe.lava.banksia.ui.state.InfoPanelState import moe.lava.banksia.ui.state.MapState import moe.lava.banksia.ui.state.SearchState +import moe.lava.banksia.util.BoxedValue +import moe.lava.banksia.util.BoxedValue.Companion.box +import moe.lava.banksia.util.LoopFlow.Companion.waitUntilSubscribed +import moe.lava.banksia.util.Point +import moe.lava.banksia.util.log +import kotlin.time.Clock +import kotlin.time.Duration.Companion.minutes sealed class MapScreenEvent { data object DismissState : MapScreenEvent() @@ -50,13 +45,10 @@ sealed class MapScreenEvent { data class SearchUpdate(val text: String) : MapScreenEvent() } -private data class InternalState( +data class InternalState( val route: String? = null, val stop: String? = null, val run: String? = null, - - val lastStopDepartures: List? = null, - val stopsGrouped: Boolean = true, ) class MapScreenViewModel( @@ -73,10 +65,6 @@ class MapScreenViewModel( viewModelScope.launch { switchRoute(value.route) } if (value.stop != last.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) switchRun(value.run) } @@ -111,14 +99,6 @@ class MapScreenViewModel( } } - fun handleEvent(event: InfoPanelEvent) { - viewModelScope.launch { - when (event) { - StopInfoPanelEvent.ToggleGrouping -> state = state.copy(stopsGrouped = !state.stopsGrouped) - } - } - } - fun bindTracker(locationTracker: LocationTracker) { locationTrackerJob = locationTracker.getLocationsFlow() .onEach { lastKnownLocation = Point(it.latitude, it.longitude) } @@ -126,6 +106,11 @@ class MapScreenViewModel( } fun centreCameraToLocation() { + viewModelScope.launch { + log("msvm", "getting..") + val routes = routeRepository.getAll() + log("msvm", routes.joinToString("\n")) + } lastKnownLocation?.let { location -> viewModelScope.launch { log("bvm", "emitting $location") @@ -175,9 +160,9 @@ class MapScreenViewModel( } val route = routeRepository.get(routeId) - ?: return +// val gtfsRoute = ptvService.route(routeId) iInfoState.update { - RouteInfoPanelState( + InfoPanelState.Route( name = route.name, type = route.type, ) @@ -202,7 +187,7 @@ class MapScreenViewModel( .onEach { run -> if (routeName == null) { iInfoState.update { - TripInfoPanelState( + InfoPanelState.Trip( direction = run.destinationName, type = RouteType.MetroTrain, // XXX HACK TODO FIXME ) @@ -211,7 +196,7 @@ class MapScreenViewModel( } iInfoState.update { - TripInfoPanelState( + InfoPanelState.Trip( direction = run.destinationName, type = RouteType.MetroTrain, // FIXME HACK XXX TODO routeName = routeName, @@ -225,77 +210,45 @@ class MapScreenViewModel( private suspend fun switchStop(id: String?) { if (id == null) { iInfoState.update { InfoPanelState.None } - state = state.copy(lastStopDepartures = null) return } val stop = stopRepository.get(id) +// val stop = ptvService.stop(routeType, stopId) val split = stop.name.split("/") val name = split[0] val subname = split.getOrNull(1) iInfoState.update { - StopInfoPanelState( + InfoPanelState.Stop( id = stop.id, name = name, subname = subname, ) } - stopTimeRepository.getForStop(id) - .onEach { departures -> - state = state.copy( - lastStopDepartures = departures - ) - } - .launchIn(viewModelScope) - } - - 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()), - ) + 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" } - ) - } - } 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 { - if (it !is StopInfoPanelState) - it - else - it.copy(departures = departures) + } + InfoPanelState.Stop.Departure(headsign, times) } + iInfoState.update { + if (it !is InfoPanelState.Stop) + it + else + it.copy(departures = departures) } } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt new file mode 100644 index 0000000..5b73914 --- /dev/null +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt @@ -0,0 +1,38 @@ +package moe.lava.banksia.ui.state + +import moe.lava.banksia.model.RouteType + +sealed class InfoPanelState { + abstract val loading: Boolean + + data object None : InfoPanelState() { + override val loading = false + } + + data class Route( + val name: String, + val type: RouteType, + ) : InfoPanelState() { + override val loading = false + } + + data class Stop( + val id: String, + val name: String, + val subname: String? = null, + val departures: List? = null, + ) : InfoPanelState() { + override val loading: Boolean + get() = departures == null + + data class Departure(val directionName: String, val formattedTimes: String) + } + + data class Trip( + val direction: String, + val type: RouteType, + val routeName: String? = null, + ) : InfoPanelState() { + override val loading = routeName == null + } +} diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt index 9f60514..05429cb 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.state -import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.model.RouteType data class SearchState( val entries: List = listOf(),