diff --git a/.gitignore b/.gitignore index 83f099d..426800f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,3 @@ captures secrets.properties shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt /data/ -/data diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts deleted file mode 100644 index b8b100b..0000000 --- a/androidApp/build.gradle.kts +++ /dev/null @@ -1,57 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -plugins { - alias(libs.plugins.androidApplication) - alias(libs.plugins.composeMultiplatform) - alias(libs.plugins.composeCompiler) -} - -kotlin { - target { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - compilerOptions { - freeCompilerArgs.add("-Xexplicit-backing-fields") - } - - dependencies { - implementation(projects.ui) - implementation(libs.androidx.activity.compose) - implementation(libs.compose.ui.tooling.preview) - } -} - -dependencies { - debugImplementation(libs.compose.ui.tooling) -} - -android { - namespace = "moe.lava.banksia" - compileSdk = libs.versions.android.compileSdk.get().toInt() - - defaultConfig { - applicationId = "moe.lava.banksia" - minSdk = libs.versions.android.minSdk.get().toInt() - targetSdk = libs.versions.android.targetSdk.get().toInt() - versionCode = 1 - versionName = "1.0" - } - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - buildTypes { - getByName("release") { - isMinifyEnabled = false - signingConfig = signingConfigs.getByName("debug") - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} diff --git a/build.gradle.kts b/build.gradle.kts index 0687328..53d3bbb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { // this is necessary to avoid the plugins to be loaded multiple times // in each subproject's classloader alias(libs.plugins.androidApplication) apply false - alias(libs.plugins.androidMultiplatformLibrary) apply false + alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinJvm) apply false diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 29dbc08..f02ec51 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -1,16 +1,15 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.androidLibrary) } kotlin { - android { - namespace = "moe.lava.banksia.client" - compileSdk = libs.versions.android.compileSdk.get().toInt() - + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } @@ -20,6 +19,7 @@ kotlin { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") } + iosX64() iosArm64() iosSimulatorArm64() @@ -41,3 +41,15 @@ kotlin { } } } + +android { + namespace = "moe.lava.banksia.client" + compileSdk = libs.versions.android.compileSdk.get().toInt() + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + } +} 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 deleted file mode 100644 index 3640b1f..0000000 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index baf26e7..0000000 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt +++ /dev/null @@ -1,36 +0,0 @@ -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 deleted file mode 100644 index 8b46fbd..0000000 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -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/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt index f22c7db..a39a3ae 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt @@ -12,11 +12,8 @@ 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 @@ -49,11 +46,8 @@ val ClientModule = module { singleOf(::RouteRemoteDataSource) singleOf(::StopLocalDataSource) singleOf(::StopRemoteDataSource) - singleOf(::StopTimeLocalDataSource) - singleOf(::StopTimeRemoteDataSource) // Repositories singleOf(::RouteRepository) singleOf(::StopRepository) - singleOf(::StopTimeRepository) } 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 deleted file mode 100644 index 4f54840..0000000 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -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/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties deleted file mode 100644 index 9b7b12a..0000000 --- a/gradle/gradle-daemon-jvm.properties +++ /dev/null @@ -1,13 +0,0 @@ -#This file is generated by updateDaemonJvm -toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect -toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect -toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect -toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect -toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect -toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect -toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect -toolchainVendor=JETBRAINS -toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e171b55..638a098 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,37 +1,40 @@ [versions] -agp = "9.1.0" +agp = "8.13.1" android-compileSdk = "36" android-minSdk = "24" android-targetSdk = "36" -androidx-activity= "1.13.0" -androidx-lifecycle = "2.10.0" -compose-multiplatform = "1.11.0-alpha04" +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-alpha04" -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" -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" } @@ -41,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" } @@ -63,11 +73,11 @@ 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" } @@ -75,7 +85,7 @@ ui-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.r [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } -androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } +androidLibrary = { id = "com.android.library", version.ref = "agp" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f78a6..37f853b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 39f75f3..2f7d989 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -12,17 +12,8 @@ application { applicationDefaultJvmArgs = listOf("-Dio.ktor.development=${extra["io.ktor.development"] ?: "false"}") } -kotlin { - compilerOptions { - freeCompilerArgs.add("-Xexplicit-backing-fields") - } -} - dependencies { implementation(projects.shared) - implementation(projects.server.gtfs) - implementation(projects.server.gtfsRt) - implementation(libs.logback) implementation(libs.koin.core) implementation(libs.koin.ktor) diff --git a/server/gtfs/build.gradle.kts b/server/gtfs/build.gradle.kts deleted file mode 100644 index e83e745..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.shared) - 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/structures/GtfsService.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt deleted file mode 100644 index 1bf9573..0000000 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.lava.banksia.server.gtfs.structures - -import kotlinx.serialization.Serializable - -@Suppress("PropertyName") -@Serializable -internal data class GtfsService( - val service_id: String, - val monday: Int, - val tuesday: Int, - val wednesday: Int, - val thursday: Int, - val friday: Int, - val saturday: Int, - val sunday: Int, - val start_date: String, - val end_date: String, -) 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 934d8bc..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.shared) - 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 30a9fd3..0000000 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt +++ /dev/null @@ -1,109 +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.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 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(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) { - 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 8b30b2f..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.Constants -import moe.lava.banksia.util.LogScope -import moe.lava.banksia.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 2d33793..4ae3398 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -15,23 +15,17 @@ import io.ktor.server.routing.routing import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -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.util.serialise +import moe.lava.banksia.server.gtfs.GtfsHandler +import moe.lava.banksia.server.gtfsr.GtfsrService import org.koin.dsl.module import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin -import kotlin.time.Clock fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module) @@ -47,20 +41,10 @@ fun Application.module() { modules(CommonModules, ServerModules) } - @Suppress("KotlinConstantConditions") - if (!Constants.devMode) { - val gtfsr by inject() - launch { gtfsr.start(this, true) } - } + val gtfsr by inject() + launch { gtfsr.start() } routing { - if (Constants.devMode) { - get("/fixup") { - call.respondText("received") - val fixer by inject() - fixer.addParentsToStops() - } - } get("/update") { val key = call.parameters["key"] if (key != Constants.updateKey) { @@ -73,11 +57,8 @@ 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) { - val fixer by inject() - val importer by inject() - importer.import(datasetUrl) - - fixer.addParentsToStops() + val handler by inject() + handler.update(datasetUrl) } } @@ -133,7 +114,7 @@ fun Application.module() { } 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 routeDao by inject() if (useParent) @@ -142,23 +123,20 @@ fun Application.module() { routeDao.stops(routeId) } call.respond(stops.map { it.asModel() }) - } - get("/stoptimes/by_stop/{stop_id}") { - val stopId = call.parameters["stop_id"]!! - val date = call.queryParameters["date"] - ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) } - ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) - val times = withContext(context = Dispatchers.IO) { - inject().value - .getForStopDated( - stopId, - listOf(date.dayOfWeek).serialise(), - date.toEpochDays().toInt(), - ) - .map { it.asModel().atDate(date) } - .sortedBy { it.departureTime } - } - call.respond(times) +// 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) } } } 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 d3d307e..0000000 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ /dev/null @@ -1,43 +0,0 @@ -package moe.lava.banksia.server - -import moe.lava.banksia.room.Database -import moe.lava.banksia.room.entity.StopEntity -import moe.lava.banksia.util.log -import java.security.MessageDigest - -class GtfsDataFixer( - private val database: Database, -) { - suspend fun addParentsToStops() { - val dao = database.stopDao - val stops = dao.getAllParentless() - 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 = StopEntity( - id = parentId, - name = name, - lat = avgLat, - lng = avgLng, - parent = "", - hasWheelChairBoarding = stops.all { it.hasWheelChairBoarding }, - level = "", - platformCode = "", - ) - log("datafixer", "inserting ${parentId} for ${stops.size} children") - dao.insertAll(parent) - database.stopDao.updateParents(stops.map { it.id }, parentId) - } - } -} - -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 04ea373..0000000 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ /dev/null @@ -1,118 +0,0 @@ -package moe.lava.banksia.server - -import androidx.room.immediateTransaction -import androidx.room.useWriterConnection -import io.ktor.util.logging.Logger -import moe.lava.banksia.model.Route -import moe.lava.banksia.model.Service -import moe.lava.banksia.model.ServiceException -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.entity.asEntity -import moe.lava.banksia.server.gtfs.GtfsData -import moe.lava.banksia.server.gtfs.GtfsParser -import kotlin.time.Clock - -class GtfsImporter( - private val parser: GtfsParser, - private val database: Database, - private val log: Logger, -) { - suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) { - database.useWriterConnection { transactor -> - transactor.immediateTransaction { - database.routeDao.deleteAll() - database.serviceDao.deleteAll() - database.serviceExceptionDao.deleteAll() - database.shapeDao.deleteAll() - database.stopDao.deleteAll() - database.stopTimeDao.deleteAll() - database.tripDao.deleteAll() - - parser.update(url).collect { chunk -> - when (chunk) { - is GtfsData.RouteChunk -> addRoutes(chunk.routes) - is GtfsData.ServiceChunk -> addServices(chunk.services) - is GtfsData.ServiceExceptionChunk -> addServiceExceptions(chunk.exceptions) - is GtfsData.ShapeChunk -> addShapes(chunk.shapes) - is GtfsData.StopChunk -> addStops(chunk.stops) - is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) - is GtfsData.TripChunk -> addTrips(chunk.trips) - } - } - - updateMetadata(date) - } - } - } - - private suspend fun updateMetadata(date: Long) { - val dao = database.versionMetadataDao - log.info("updating metadata...") - dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) - log.info("done") - } - - private suspend fun addRoutes(routes: List) { - val dao = database.routeDao - log.info("inserting routes...") - dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) - log.info("done") - } - - private suspend fun addServices(services: List) { - val dao = database.serviceDao - log.info("inserting services...") - dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) - log.info("done") - } - - private suspend fun addServiceExceptions(exceptions: List) { - val dao = database.serviceExceptionDao - log.info("inserting exceptions...") - dao.insertOrReplaceAll(*exceptions.map { it.asEntity() }.toTypedArray()) - log.info("done") - } - - private suspend fun addShapes(shapes: List) { - val dao = database.shapeDao - log.info("inserting shapes...") - dao.insertOrReplaceAll(*shapes.map { it.asEntity() }.toTypedArray()) - log.info("done") - } - - private suspend fun addStops(stops: List) { - val dao = database.stopDao - 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") - } - } - } - } - dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) - log.info("done") - } - - private suspend fun addStopTimes(stopTimes: List) { - val dao = database.stopTimeDao - log.info("inserting ${stopTimes.size} stoptimes...") - dao.insertOrReplaceAll(*stopTimes.map { it.asEntity() }.toTypedArray()) - log.info("done") - } - - private suspend fun addTrips(trips: List) { - val dao = database.tripDao - log.info("inserting ${trips.size} trips...") - dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) - 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 6ee4365..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,18 +1,13 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient -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 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 { single { HttpClient() } - singleOf(::GtfsParser) - singleOf(::GtfsrtService) - - singleOf(::GtfsDataFixer) - singleOf(::GtfsImporter) + singleOf(::GtfsHandler) + singleOf(::GtfsrService) } diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt similarity index 63% rename from server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt rename to server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt index 21e239c..d85d5df 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt @@ -9,26 +9,19 @@ 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.Constants import moe.lava.banksia.model.Route -import moe.lava.banksia.model.RouteType -import moe.lava.banksia.model.Service -import moe.lava.banksia.model.ServiceException 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.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 @@ -36,27 +29,19 @@ 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 -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 StopTimeChunk(val stopTimes: List) : GtfsData() - data class TripChunk(val trips: List) : GtfsData() -} - -class GtfsParser( +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): Flow { + suspend fun update(datasetUrl: String, date: Long? = null) { val parentDir = datasetPath.parentFile @Suppress("SimplifyBooleanWithConstants", "KotlinConstantConditions") if (parentDir.exists() && !Constants.devMode) @@ -80,65 +65,42 @@ class GtfsParser( .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() - } + addRoutes(files) + addStops(files) + addShapes(files) + addTrips(files) + addStopTimes(files) - log.info("done!") - } + updateMetadata(date ?: Clock.System.now().epochSeconds) + + @Suppress("KotlinConstantConditions") + if (!Constants.devMode) { + parentDir.deleteRecursively() + } + + log.info("done!") } - private fun parse(files: List) = flow { - files + 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" } - .forEach { emit(GtfsData.RouteChunk(parseRoutes(it))) } + .flatMap { fd -> parseRoutes(fd) } - files - .filter { it.name == "stops.txt" } - .forEach { emit(GtfsData.StopChunk(parseStops(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) - .also { emit(GtfsData.TripChunk(it)) } - } - .associateBy { it.id } - - files - .filter { it.name == "stop_times.txt" } - .forEach { fd -> - log.info("parsing stop times for ${fd.parent}...") - parseStopTimes(fd, trips) { seq -> - seq.chunked(10000) - .forEach { emit(GtfsData.StopTimeChunk(it)) } - } - } + log.info("inserting routes...") + dao.deleteAll() + dao.insertAll(*routes.map { it.asEntity() }.toTypedArray()) } private fun parseRoutes(fd: File) = @@ -146,12 +108,24 @@ class GtfsParser( .map { with(it) { Route( id = route_id, - type = RouteType.from(fd.parentFile.name.toInt()), + 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 } @@ -163,6 +137,29 @@ class GtfsParser( 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) { @@ -177,7 +174,27 @@ class GtfsParser( ) } } - private inline fun parseStopTimes(fd: File, trips: Map, block: (Sequence) -> Unit) = + private suspend fun addStopTimes(files: List) { + 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) { 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, block: (Sequence) -> Unit) = fd.parseCsvSequence { seq -> seq .map { with(it) { @@ -186,7 +203,7 @@ class GtfsParser( stopId = stop_id, arrivalTime = GtfsStopTime.parseGtfsTime(arrival_time), departureTime = GtfsStopTime.parseGtfsTime(departure_time), - headsign = stop_headsign.ifEmpty { trips[trip_id]!!.tripHeadsign }, + headsign = stop_headsign, pickupType = pickup_type, dropOffType = drop_off_type, ) @@ -194,43 +211,25 @@ class GtfsParser( .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 = 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) { + val dao = db.tripDao + log.info("parsing trips...") + val trips = files + .filter { it.name == "trips.txt" } + .flatMap { fd -> parseTrips(fd) } - private fun parseServiceExceptions(fd: File) = - fd.parseCsv() - .map { with(it) { - ServiceException( - serviceId = service_id, - date = LocalDate.parse(date, LocalDate.Formats.ISO_BASIC), - type = exception_type, - ) - } } + log.info("inserting trips...") + dao.deleteAll() + dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) + } - private fun parseTrips(fd: File, services: Map) = + private fun parseTrips(fd: File) = fd.parseCsv() .map { with(it) { Trip( id = trip_id, routeId = route_id, - service = services[service_id]!!, + serviceId = service_id, shapeId = shape_id.ifEmpty { null }, tripHeadsign = trip_headsign, directionId = direction_id, 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/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 95% 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 76de3cd..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 @@ -5,7 +5,7 @@ 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, 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/settings.gradle.kts b/settings.gradle.kts index 72f0696..a33c5ec 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,9 +14,6 @@ pluginManagement { gradlePluginPortal() } } -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" -} dependencyResolutionManagement { repositories { @@ -31,12 +28,7 @@ dependencyResolutionManagement { } } -include(":androidApp") include(":client") include(":server") -include(":server:gtfs") -include(":server:gtfs_rt") include(":shared") include(":ui") -include(":ui:maps") -include(":ui:shared") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index b4ed8ad..1f26a53 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -1,11 +1,13 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.androidLibrary) alias(libs.plugins.ksp) alias(libs.plugins.room) + alias(libs.plugins.wire) } room { @@ -13,10 +15,8 @@ room { } kotlin { - android { - namespace = "moe.lava.banksia.shared" - compileSdk = libs.versions.android.compileSdk.get().toInt() - + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } @@ -26,6 +26,7 @@ kotlin { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") } + iosX64() iosArm64() iosSimulatorArm64() @@ -57,7 +58,27 @@ kotlin { dependencies { add("kspAndroid", libs.room.compiler) + add("kspIosX64", libs.room.compiler) add("kspIosArm64", libs.room.compiler) add("kspIosSimulatorArm64", libs.room.compiler) add("kspJvm", libs.room.compiler) } + +android { + namespace = "moe.lava.banksia.shared" + compileSdk = libs.versions.android.compileSdk.get().toInt() + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + } +} + +wire { + sourcePath { + srcDir("src/commonMain/proto") + } + kotlin {} +} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/10.json b/shared/schemas/moe.lava.banksia.room.Database/10.json deleted file mode 100644 index 751e946..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/10.json +++ /dev/null @@ -1,477 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 10, - "identityHash": "5b90bc800bfae6d22124ea0a6a906ca7", - "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": "ServiceException", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", - "fields": [ - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "date", - "columnName": "date", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "serviceId", - "date" - ] - }, - "indices": [ - { - "name": "index_ServiceException_serviceId", - "unique": false, - "columnNames": [ - "serviceId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" - }, - { - "name": "index_ServiceException_type", - "unique": false, - "columnNames": [ - "type" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" - } - ] - }, - { - "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, '5b90bc800bfae6d22124ea0a6a906ca7')" - ] - } -} \ No newline at end of file diff --git a/shared/schemas/moe.lava.banksia.room.Database/11.json b/shared/schemas/moe.lava.banksia.room.Database/11.json deleted file mode 100644 index 6fc2976..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/11.json +++ /dev/null @@ -1,498 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 11, - "identityHash": "c4be3d0c2a25f8c5c33132646a070d0e", - "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": "ServiceException", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", - "fields": [ - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "date", - "columnName": "date", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "serviceId", - "date" - ] - }, - "indices": [ - { - "name": "index_ServiceException_serviceId", - "unique": false, - "columnNames": [ - "serviceId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" - }, - { - "name": "index_ServiceException_type", - "unique": false, - "columnNames": [ - "type" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" - } - ] - }, - { - "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, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)", - "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" - }, - { - "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`)" - } - ], - "foreignKeys": [ - { - "table": "Stop", - "onDelete": "SET NULL", - "onUpdate": "NO ACTION", - "columns": [ - "parent" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "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_serviceId", - "unique": false, - "columnNames": [ - "serviceId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `${TABLE_NAME}` (`serviceId`)" - }, - { - "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, 'c4be3d0c2a25f8c5c33132646a070d0e')" - ] - } -} \ 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 deleted file mode 100644 index 783b3ee..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/4.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "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 deleted file mode 100644 index c4a786d..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/5.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "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 deleted file mode 100644 index 5ab26dc..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/6.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "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 deleted file mode 100644 index d4c62b2..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/7.json +++ /dev/null @@ -1,415 +0,0 @@ -{ - "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 deleted file mode 100644 index 9240dd5..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/8.json +++ /dev/null @@ -1,426 +0,0 @@ -{ - "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 deleted file mode 100644 index 2359dbd..0000000 --- a/shared/schemas/moe.lava.banksia.room.Database/9.json +++ /dev/null @@ -1,426 +0,0 @@ -{ - "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/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton b/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton index 15b3c58..7329ae3 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton @@ -8,5 +8,4 @@ object Constants { // TODO 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 94% 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 abebe76..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,4 +1,4 @@ -package moe.lava.banksia.server.gtfsrt +package moe.lava.banksia.data.gtfsr import com.google.transit.realtime.FeedMessage import moe.lava.banksia.util.Point diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt index c9988bf..0726665 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import moe.lava.banksia.model.RouteType -object PtvRouteTypeSerialiser : KSerializer { +private object PtvRouteTypeSerialiser : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( PtvRouteType::class.qualifiedName!!, PrimitiveKind.INT) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt index 1a39cfb..823174b 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt @@ -9,8 +9,6 @@ val CommonModules = module { single { Database.build(get().getBuilder()) } single { get().versionMetadataDao } single { get().routeDao } - single { get().serviceDao } - single { get().serviceExceptionDao } single { get().shapeDao } single { get().stopDao } single { get().stopTimeDao } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt index 91c5c77..c1853a9 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt @@ -1,10 +1,6 @@ package moe.lava.banksia.model -import kotlinx.datetime.DateTimeUnit -import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime -import kotlinx.datetime.atTime -import kotlinx.datetime.plus import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -43,10 +39,6 @@ data class FutureTime( val minute = time.minute val second = time.second val trueHour = time.hour + (if (dayOffset) 24 else 0) - - fun atDate(date: LocalDate) = date - .let { if (dayOffset) date.plus(1, DateTimeUnit.DAY) else date } - .atTime(time) } object FutureTimeSerialiser: KSerializer { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt index a51f132..08a9c53 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt @@ -13,8 +13,4 @@ enum class RouteType(val value: Int) { SkyBus(11), Interstate(10), ; - - companion object { - fun from(value: Int) = RouteType.entries.first { it.value == value } - } } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt deleted file mode 100644 index 305ede4..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.lava.banksia.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/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt index e1060bb..df10a58 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt @@ -8,7 +8,7 @@ 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, diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt deleted file mode 100644 index 55288fa..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt +++ /dev/null @@ -1,26 +0,0 @@ -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 index 81d3f8d..ef95eea 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable data class Trip( val id: String, val routeId: String, - val service: Service, + val serviceId: String, val shapeId: String?, val tripHeadsign: String, val directionId: String, diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt index 7c39ebf..163461a 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -3,25 +3,17 @@ package moe.lava.banksia.room import androidx.room.AutoMigration import androidx.room.RoomDatabase import androidx.room.TypeConverters -import androidx.room.migration.Migration -import androidx.room.util.foreignKeyCheck -import androidx.sqlite.SQLiteConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.sqlite.execSQL import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import moe.lava.banksia.room.converter.RouteTypeConverter +import moe.lava.banksia.room.dao.VersionMetadataDao import moe.lava.banksia.room.dao.RouteDao -import moe.lava.banksia.room.dao.ServiceDao -import moe.lava.banksia.room.dao.ServiceExceptionDao 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.ServiceExceptionEntity import moe.lava.banksia.room.entity.ShapeEntity import moe.lava.banksia.room.entity.StopEntity import moe.lava.banksia.room.entity.StopTimeEntity @@ -30,11 +22,9 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( - version = 11, + version = 3, entities = [ RouteEntity::class, - ServiceEntity::class, - ServiceExceptionEntity::class, ShapeEntity::class, StopEntity::class, StopTimeEntity::class, @@ -44,15 +34,12 @@ import androidx.room.Database as DatabaseAnnotation autoMigrations = [ AutoMigration(from = 1, to = 2), AutoMigration(from = 2, to = 3), - AutoMigration(from = 9, to = 10), ] ) @TypeConverters(RouteTypeConverter::class) abstract class Database : RoomDatabase() { abstract val versionMetadataDao: VersionMetadataDao abstract val routeDao: RouteDao - abstract val serviceDao: ServiceDao - abstract val serviceExceptionDao: ServiceExceptionDao abstract val shapeDao: ShapeDao abstract val stopDao: StopDao abstract val stopTimeDao: StopTimeDao @@ -63,21 +50,7 @@ abstract class Database : RoomDatabase() { base.fallbackToDestructiveMigration(true) .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.IO) - .addMigrations(MIGRATION_10_11) // .fallbackToDestructiveMigration(true) .build() } } - -val MIGRATION_10_11 = object : Migration(10, 11) { - override fun migrate(connection: SQLiteConnection) { - connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_Stop` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)") - connection.execSQL("INSERT INTO `_new_Stop` (`id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode`) SELECT `id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode` FROM `Stop`") - connection.execSQL("UPDATE `_new_Stop` SET `parent` = NULL WHERE `parent` == \"\"") - connection.execSQL("DROP TABLE `Stop`") - connection.execSQL("ALTER TABLE `_new_Stop` RENAME TO `Stop`") - connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `Stop` (`parent`)") - connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `Trip` (`serviceId`)") - foreignKeyCheck(connection, "Stop") - } -} 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 index 9ceb612..8927f14 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt @@ -5,7 +5,7 @@ import moe.lava.banksia.model.RouteType object RouteTypeConverter { @TypeConverter - fun from(value: Int) = RouteType.from(value) + 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/dao/RouteDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt index 94ce892..0174f0f 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt @@ -37,22 +37,13 @@ interface RouteDao { """) suspend fun stops(id: String): List - // I vibecoded this, sorry @Query(""" - WITH Tree AS ( - 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 - - UNION ALL - - SELECT s.* - FROM Stop s - INNER JOIN Tree t ON s.id = t.parent - ) - SELECT DISTINCT * FROM Tree WHERE parent IS NULL; + 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 deleted file mode 100644 index 6fc2906..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt +++ /dev/null @@ -1,29 +0,0 @@ -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/ServiceExceptionDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt deleted file mode 100644 index 123b0c6..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt +++ /dev/null @@ -1,29 +0,0 @@ -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.ServiceExceptionEntity - -@Dao -interface ServiceExceptionDao { - @Query("SELECT * FROM ServiceException") - suspend fun getAll(): List - - @Query("SELECT * FROM ServiceException WHERE serviceId == :id") - suspend fun get(id: String): List - - @Insert - suspend fun insertAll(vararg exceptions: ServiceExceptionEntity) - - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg exceptions: ServiceExceptionEntity) - - @Delete - suspend fun delete(service: ServiceExceptionEntity) - - @Query("DELETE FROM ServiceException") - 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 index ae4d53a..c48735a 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt @@ -3,7 +3,6 @@ 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.ShapeEntity @@ -15,9 +14,6 @@ interface ShapeDao { @Insert suspend fun insertAll(vararg shapes: ShapeEntity) - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg shapes: ShapeEntity) - @Delete suspend fun delete(shape: ShapeEntity) 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 index 869ae29..f6b2ef2 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt @@ -12,13 +12,6 @@ interface StopDao { @Query("SELECT * FROM Stop") suspend fun getAll(): List - @Query(""" - SELECT * FROM Stop - WHERE platformCode <> "" - AND parent == "" - """) - suspend fun getAllParentless(): List - @Query("SELECT * FROM Stop WHERE id == :id") suspend fun get(id: String): StopEntity? @@ -36,7 +29,4 @@ interface StopDao { @Query("DELETE FROM Stop") suspend fun deleteAll() - - @Query("UPDATE Stop SET parent = :parent WHERE id IN (:ids)") - suspend fun updateParents(ids: List, parent: String) } 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 index 82e0e4b..88485f4 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt @@ -13,24 +13,10 @@ interface StopTimeDao { suspend fun getAll(): List @Query("SELECT * FROM StopTime WHERE tripId == :tripId") - suspend fun getForTrip(tripId: String): StopTimeEntity? + suspend fun get(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 DISTINCT StopTime.* 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 - LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date - WHERE StopTime.tripId == Trip.id - AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) - AND ServiceException.type IS NULL - """) - suspend fun getForStopDated(stopId: String, days: Int, date: Int): List + suspend fun get(tripIds: List): List @Insert suspend fun insertAll(vararg stopTimes: StopTimeEntity) 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 index 027aaa8..4b14a95 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt @@ -1,23 +1,50 @@ package moe.lava.banksia.room.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import kotlinx.datetime.DayOfWeek 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 id: String, + val days: Int, val start: Int, val end: Int, ) { + object Parser { + private fun Int.check(other: Int) = (this and other) != 0 + + fun deserialiseDays(days: Int): List = buildList { + if (days.check(1)) + add(DayOfWeek.MONDAY) + if (days.check(1 shl 1)) + add(DayOfWeek.TUESDAY) + if (days.check(1 shl 2)) + add(DayOfWeek.WEDNESDAY) + if (days.check(1 shl 3)) + add(DayOfWeek.THURSDAY) + if (days.check(1 shl 4)) + add(DayOfWeek.FRIDAY) + if (days.check(1 shl 5)) + add(DayOfWeek.SATURDAY) + if (days.check(1 shl 6)) + add(DayOfWeek.SUNDAY) + } + fun serialiseDays(days: List): Int = + days.fold(0) { vl, n -> + vl + when (n) { + DayOfWeek.MONDAY -> 1 + DayOfWeek.TUESDAY -> 1 shl 1 + DayOfWeek.WEDNESDAY -> 1 shl 2 + DayOfWeek.THURSDAY -> 1 shl 3 + DayOfWeek.FRIDAY -> 1 shl 4 + DayOfWeek.SATURDAY -> 1 shl 5 + DayOfWeek.SUNDAY -> 1 shl 6 + } + } + } fun asModel() = Service( id, - days.deserialiseDaysBitflag(), + Parser.deserialiseDays(days), LocalDate.fromEpochDays(start), LocalDate.fromEpochDays(end), ) @@ -25,7 +52,7 @@ data class ServiceEntity( fun Service.asEntity() = ServiceEntity( id, - days.serialise(), + ServiceEntity.Parser.serialiseDays(days), start.toEpochDays().toInt(), end.toEpochDays().toInt(), ) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt deleted file mode 100644 index 313246d..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.lava.banksia.room.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import kotlinx.datetime.LocalDate -import moe.lava.banksia.model.ServiceException - -@Entity( - "ServiceException", - primaryKeys = ["serviceId", "date"] -) -data class ServiceExceptionEntity( - @ColumnInfo(index = true) val serviceId: String, - val date: Int, - @ColumnInfo(index = true) val type: Int, -) { - fun asModel() = ServiceException( - serviceId, - LocalDate.fromEpochDays(date), - type, - ) -} - -fun ServiceException.asEntity() = ServiceExceptionEntity( - serviceId, - date.toEpochDays().toInt(), - type, -) 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 index 9ce7bfb..9c6cf15 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt @@ -2,30 +2,17 @@ package moe.lava.banksia.room.entity import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.ForeignKey.Companion.SET_NULL import androidx.room.PrimaryKey import moe.lava.banksia.model.Stop import moe.lava.banksia.util.Point -@Entity( - "Stop", - foreignKeys = [ - ForeignKey( - StopEntity::class, - parentColumns = ["id"], - childColumns = ["parent"], - onDelete = SET_NULL, - deferred = true, - ), - ] -) +@Entity("Stop") data class StopEntity( @PrimaryKey val id: String, val name: String, val lat: Double, val lng: Double, - @ColumnInfo(index = true) val parent: String?, + @ColumnInfo(index = true) val parent: String, val hasWheelChairBoarding: Boolean, val level: String, val platformCode: String, 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 index bb20ff1..9b0aac8 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt @@ -3,7 +3,6 @@ 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 @@ -12,10 +11,6 @@ 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), 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 index 12bda02..ca7e9a7 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt @@ -4,7 +4,6 @@ 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 @@ -12,10 +11,8 @@ import moe.lava.banksia.model.Trip "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"), Index("serviceId")], ) data class TripEntity( @PrimaryKey val id: String, @@ -26,24 +23,8 @@ data class TripEntity( 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 asModel() = Trip(id, routeId, serviceId, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible) } -fun Trip.asEntity() = TripEntity(id, routeId, service.id, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible) +fun Trip.asEntity() = TripEntity(id, routeId, serviceId, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt index 3ff5702..0d6896d 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt @@ -1,6 +1,5 @@ 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) { operator fun component1() = value diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt deleted file mode 100644 index 87d3244..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt +++ /dev/null @@ -1,36 +0,0 @@ -package moe.lava.banksia.util - -import kotlinx.datetime.DayOfWeek - -private fun Int.check(other: Int) = (this and other) != 0 - -fun Int.deserialiseDaysBitflag(): List = buildList { - val days = this@deserialiseDaysBitflag - if (days.check(1)) - add(DayOfWeek.MONDAY) - if (days.check(1 shl 1)) - add(DayOfWeek.TUESDAY) - if (days.check(1 shl 2)) - add(DayOfWeek.WEDNESDAY) - if (days.check(1 shl 3)) - add(DayOfWeek.THURSDAY) - if (days.check(1 shl 4)) - add(DayOfWeek.FRIDAY) - if (days.check(1 shl 5)) - add(DayOfWeek.SATURDAY) - if (days.check(1 shl 6)) - add(DayOfWeek.SUNDAY) -} - -fun List.serialise(): Int = - this.fold(0) { vl, n -> - vl + when (n) { - DayOfWeek.MONDAY -> 1 - DayOfWeek.TUESDAY -> 1 shl 1 - DayOfWeek.WEDNESDAY -> 1 shl 2 - DayOfWeek.THURSDAY -> 1 shl 3 - DayOfWeek.FRIDAY -> 1 shl 4 - DayOfWeek.SATURDAY -> 1 shl 5 - DayOfWeek.SUNDAY -> 1 shl 6 - } - } 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/ui/build.gradle.kts b/ui/build.gradle.kts index 201a10f..100228c 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -1,31 +1,25 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) alias(libs.plugins.secretsGradle) } kotlin { - android { - namespace = "moe.lava.banksia.ui" - compileSdk = libs.versions.android.compileSdk.get().toInt() - + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } - - androidResources { - enable = true - } } compilerOptions { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - freeCompilerArgs.add("-Xexplicit-backing-fields") } listOf( @@ -41,6 +35,9 @@ kotlin { sourceSets { androidMain.dependencies { + implementation(libs.compose.ui.tooling.preview) + implementation(libs.androidx.activity.compose) + implementation(libs.kotlinx.coroutines.android) implementation(libs.play.services.location) } commonMain.dependencies { @@ -69,16 +66,47 @@ kotlin { implementation(projects.client) implementation(projects.shared) - implementation(projects.ui.maps) - implementation(projects.ui.shared) } } } +android { + namespace = "moe.lava.banksia" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + applicationId = "moe.lava.banksia" + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + signingConfig = signingConfigs.getByName("debug") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + dependencies { - androidRuntimeClasspath(libs.compose.ui.tooling) + debugImplementation(compose.uiTooling) } secrets { propertiesFileName = "secrets.properties" } + +compose.resources { + publicResClass = true + packageOfResClass = "moe.lava.banksia.resources" +} diff --git a/ui/maps/build.gradle.kts b/ui/maps/build.gradle.kts deleted file mode 100644 index 324b0b3..0000000 --- a/ui/maps/build.gradle.kts +++ /dev/null @@ -1,56 +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.composeMultiplatform) - alias(libs.plugins.composeCompiler) -} - -kotlin { - android { - namespace = "moe.lava.banksia.ui.map" - compileSdk = libs.versions.android.compileSdk.get().toInt() - - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - freeCompilerArgs.add("-Xexplicit-backing-fields") - } - - iosArm64() - iosSimulatorArm64() - - sourceSets { - androidMain.dependencies { - 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.koin.core) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.datetime) - implementation(libs.ktor.serialization.kotlinx.json) - - implementation(libs.maplibre.compose) - implementation(libs.moko.geo) - implementation(libs.moko.geo.compose) - - implementation(libs.compose.components.resources) - implementation(libs.compose.runtime) - implementation(libs.compose.foundation) - implementation(libs.compose.material3) - implementation(libs.compose.ui) - - 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 deleted file mode 100644 index 1df9cb1..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt +++ /dev/null @@ -1,85 +0,0 @@ -package moe.lava.banksia.ui.map - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.WindowInsets -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.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import kotlinx.serialization.json.JsonObject -import moe.lava.banksia.Constants -import moe.lava.banksia.ui.map.mappers.routeColorExpression -import moe.lava.banksia.ui.platform.BanksiaTheme -import org.maplibre.compose.camera.CameraPosition -import org.maplibre.compose.camera.rememberCameraState -import org.maplibre.compose.expressions.dsl.const -import org.maplibre.compose.layers.CircleLayer -import org.maplibre.compose.map.MapOptions -import org.maplibre.compose.map.MaplibreMap -import org.maplibre.compose.map.OrnamentOptions -import org.maplibre.compose.sources.GeoJsonData -import org.maplibre.compose.sources.rememberGeoJsonSource -import org.maplibre.compose.style.BaseStyle -import org.maplibre.compose.util.ClickResult -import org.maplibre.spatialk.geojson.Feature -import org.maplibre.spatialk.geojson.Geometry - -@Composable -internal fun MapLibreMaps( - modifier: Modifier, - insets: WindowInsets, - positionState: MapsPositionState, - stops: GeoJsonData.Features?, -// vehicles: GeoJsonData.Features?, - stopInnerColor: Color, - onStopClicked: (Feature) -> Unit, -) { - val camPos = rememberCameraState( - CameraPosition( - zoom = 16.0, - target = MELBOURNE_POS - ) - ) - - val variant = if (isSystemInDarkTheme()) "dark" else "light" - - MaplibreMap( - modifier = modifier, - baseStyle = BaseStyle.Uri("https://api.protomaps.com/styles/v5/$variant/en.json?key=${Constants.protomapsKey}"), - cameraState = camPos, - options = MapOptions( - ornamentOptions = OrnamentOptions( - padding = WindowInsets.safeDrawing.add(insets).asPaddingValues(), - isScaleBarEnabled = false, - isAttributionEnabled = false, - ) - ) - ) { - if (stops != null) { - val stopsSource = rememberGeoJsonSource(stops) - CircleLayer( - id = "maps-stops0", - source = stopsSource, - color = const(BanksiaTheme.colors.surface), - radius = const(3.dp), - strokeWidth = const(2.dp), - strokeColor = routeColorExpression, - ) - CircleLayer( - id = "maps-stops0-clickhandler", - source = stopsSource, - color = const(Color.Transparent), - radius = const(12.dp), - onClick = { features -> -// onEvent(MapScreenEvent.SelectStop(marker.type to feature.id!!.content)) -// val marker = Json.decodeFromJsonElement(feature.properties!!) - onStopClicked(features[0]) - ClickResult.Consume - } - ) - } - } -} 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 deleted file mode 100644 index 52b7250..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt +++ /dev/null @@ -1,37 +0,0 @@ -package moe.lava.banksia.ui.map - -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.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() - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun Maps( - modifier: Modifier = Modifier, - insets: WindowInsets = WindowInsets(), - stops: List = listOf(), -// vehicles: List = listOf(), - positionState: MapsPositionState = rememberMapsPositionState(), - onStopClicked: (id: String) -> Unit = {}, -// onVehicleClicked: (id: String) -> Unit = {}, -) { - MapLibreMaps( - modifier = modifier, - insets = insets, - positionState = positionState, - stops = stops.takeIf { it.isNotEmpty() }?.asFeatures(), -// vehicles = vehicles.takeIf { it.isNotEmpty() }?.asFeatures(), - stopInnerColor = BanksiaTheme.colors.surface, - onStopClicked = { feature -> onStopClicked(feature.id!!.content) }, -// onVehicleClicked = {}, - ) -} 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 deleted file mode 100644 index a9fe8b2..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.banksia.ui.map - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.launch -import moe.lava.banksia.util.Point - -class MapsPositionState internal constructor( - private val scope: CoroutineScope -) { - internal val updates: SharedFlow - field = MutableSharedFlow() - - fun update(position: Point) { - scope.launch { updates.emit(position) } - } -} - -@Composable -fun rememberMapsPositionState(): MapsPositionState { - val scope = rememberCoroutineScope() - return remember { MapsPositionState(scope) } -} 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 deleted file mode 100644 index 32a910c..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt +++ /dev/null @@ -1,40 +0,0 @@ -package moe.lava.banksia.ui.map.mappers - -import kotlinx.serialization.Serializable -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 -import org.maplibre.spatialk.geojson.dsl.addFeature -import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection -import org.maplibre.spatialk.geojson.Point as MLPoint - -@Serializable -data class MarkerProps( - val type: RouteType, -) - -@Suppress("NOTHING_TO_INLINE") -internal inline fun Iterable.asFeatures() = GeoJsonData.Features(asFeatureCollection()) - -internal fun Iterable.asFeatureCollection(): FeatureCollection { - val markers = this - return buildFeatureCollection { - markers.forEach { marker -> - val type = when (marker) { - is Marker.Stop -> marker.type - is Marker.Vehicle -> marker.type - } - val id = when (marker) { - is Marker.Stop -> marker.id - is Marker.Vehicle -> marker.ref - } - addFeature( - geometry = MLPoint(marker.point.toPosition()), - properties = MarkerProps(type), - ) { - setId(id) - } - } - } -} 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 deleted file mode 100644 index c137394..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt +++ /dev/null @@ -1,6 +0,0 @@ -package moe.lava.banksia.ui.map.mappers - -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 deleted file mode 100644 index 523e438..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.lava.banksia.ui.map.mappers - -import androidx.compose.runtime.Composable -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 -import org.maplibre.compose.expressions.dsl.const -import org.maplibre.compose.expressions.dsl.convertToString -import org.maplibre.compose.expressions.dsl.feature -import org.maplibre.compose.expressions.dsl.switch - -internal val routeColorExpression @Composable get() = switch( - input = feature["type"].convertToString(), - cases = RouteType.entries.map { - case(label = it.name, output = const(it.getUIProperties().colour)) - }.toTypedArray(), - fallback = const(BanksiaTheme.colors.surface), -) 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 deleted file mode 100644 index 9326b2a..0000000 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.lava.banksia.ui.map.util - -import kotlinx.serialization.Serializable -import moe.lava.banksia.model.RouteType -import moe.lava.banksia.util.Point - -@Serializable -sealed class Marker { - abstract val point: Point - - sealed class Typed : Marker() { - abstract val type: RouteType - } - - @Serializable - data class Stop( - override val point: Point, - override val type: RouteType, - val id: String, - ) : Typed() - - @Serializable - data class Vehicle( - override val point: Point, - override val type: RouteType, - val ref: String, - ) : Typed() -} diff --git a/ui/shared/build.gradle.kts b/ui/shared/build.gradle.kts deleted file mode 100644 index c784fed..0000000 --- a/ui/shared/build.gradle.kts +++ /dev/null @@ -1,50 +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.composeMultiplatform) - alias(libs.plugins.composeCompiler) -} - -kotlin { - android { - namespace = "moe.lava.banksia.ui.shared" - compileSdk = libs.versions.android.compileSdk.get().toInt() - - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - freeCompilerArgs.add("-Xexplicit-backing-fields") - } - - iosArm64() - iosSimulatorArm64() - - sourceSets { - commonMain.dependencies { - implementation(libs.compose.components.resources) - implementation(libs.compose.runtime) - implementation(libs.compose.foundation) - implementation(libs.compose.material3) - implementation(libs.compose.ui) - implementation(libs.compose.ui.tooling.preview) - - implementation(projects.shared) - } - } -} - -dependencies { - androidRuntimeClasspath(libs.compose.ui.tooling) -} - -compose.resources { - publicResClass = true - packageOfResClass = "moe.lava.banksia.resources" -} diff --git a/ui/shared/src/commonMain/composeResources/drawable/my_location_24.xml b/ui/shared/src/commonMain/composeResources/drawable/my_location_24.xml deleted file mode 100644 index e69de29..0000000 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 deleted file mode 100644 index e84d765..0000000 --- a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt +++ /dev/null @@ -1,52 +0,0 @@ -package moe.lava.banksia.ui.components - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -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.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 - -@Composable -fun RouteIcon( - modifier: Modifier = Modifier.Companion, - size: Dp = 40.dp, - routeType: RouteType, -) { - val properties = routeType.getUIProperties() - Image( - painter = painterResource(properties.icon), - contentDescription = null, - modifier = modifier - .size(size) - .aspectRatio(1f) - .padding(size * ICON_PADDING / 2) - .drawBehind { - drawCircle(properties.colour, radius = size.toPx() / 2f) - } - ) -} - -const val ICON_PADDING = 0.25f - -@Preview -@Composable -internal fun RouteIconPreview() { - Row { - RouteIcon(routeType = MetroTrain) - RouteIcon(routeType = MetroTram) - RouteIcon(routeType = MetroBus) - } -} - diff --git a/androidApp/src/main/AndroidManifest.xml b/ui/src/androidMain/AndroidManifest.xml similarity index 91% rename from androidApp/src/main/AndroidManifest.xml rename to ui/src/androidMain/AndroidManifest.xml index 16435e6..928349e 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/ui/src/androidMain/AndroidManifest.xml @@ -13,6 +13,9 @@ android:enableOnBackInvokedCallback="true" android:usesCleartextTraffic="true" android:theme="@android:style/Theme.Material.Light.NoActionBar"> + + + + + diff --git a/ui/shared/src/commonMain/composeResources/drawable/train.xml b/ui/src/commonMain/composeResources/drawable/train.xml similarity index 100% rename from ui/shared/src/commonMain/composeResources/drawable/train.xml rename to ui/src/commonMain/composeResources/drawable/train.xml diff --git a/ui/shared/src/commonMain/composeResources/drawable/train_background.xml b/ui/src/commonMain/composeResources/drawable/train_background.xml similarity index 100% rename from ui/shared/src/commonMain/composeResources/drawable/train_background.xml rename to ui/src/commonMain/composeResources/drawable/train_background.xml diff --git a/ui/shared/src/commonMain/composeResources/drawable/train_icon.xml b/ui/src/commonMain/composeResources/drawable/train_icon.xml similarity index 100% rename from ui/shared/src/commonMain/composeResources/drawable/train_icon.xml rename to ui/src/commonMain/composeResources/drawable/train_icon.xml diff --git a/ui/shared/src/commonMain/composeResources/drawable/tram.xml b/ui/src/commonMain/composeResources/drawable/tram.xml similarity index 100% rename from ui/shared/src/commonMain/composeResources/drawable/tram.xml rename to ui/src/commonMain/composeResources/drawable/tram.xml diff --git a/ui/shared/src/commonMain/composeResources/drawable/tram_background.xml b/ui/src/commonMain/composeResources/drawable/tram_background.xml similarity index 100% rename from ui/shared/src/commonMain/composeResources/drawable/tram_background.xml rename to ui/src/commonMain/composeResources/drawable/tram_background.xml diff --git a/ui/shared/src/commonMain/composeResources/drawable/tram_icon.xml b/ui/src/commonMain/composeResources/drawable/tram_icon.xml similarity index 100% rename from ui/shared/src/commonMain/composeResources/drawable/tram_icon.xml rename to ui/src/commonMain/composeResources/drawable/tram_icon.xml diff --git a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt similarity index 51% rename from ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt index 992b910..c06fd1e 100644 --- a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt @@ -1,8 +1,27 @@ -package moe.lava.banksia.ui.extensions +package moe.lava.banksia.ui.components +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import moe.lava.banksia.data.ptv.structures.PtvRouteType import moe.lava.banksia.model.RouteType +import moe.lava.banksia.model.RouteType.Interstate +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.model.RouteType.RegionalBus +import moe.lava.banksia.model.RouteType.RegionalCoach +import moe.lava.banksia.model.RouteType.RegionalTrain +import moe.lava.banksia.model.RouteType.SkyBus import moe.lava.banksia.resources.Res import moe.lava.banksia.resources.bus import moe.lava.banksia.resources.bus_background @@ -14,6 +33,7 @@ import moe.lava.banksia.resources.tram import moe.lava.banksia.resources.tram_background import moe.lava.banksia.resources.tram_icon import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.painterResource data class RouteTypeProperties( val colour: Color, @@ -29,31 +49,31 @@ const val VLINE_PURPLE = 0xFF8F1A95 fun RouteType.getUIProperties(): RouteTypeProperties { val colour = when (this) { - RouteType.MetroTrain -> TRAIN_BLUE - RouteType.MetroTram -> TRAM_GREEN - RouteType.MetroBus -> BUS_ORANGE - RouteType.RegionalTrain -> VLINE_PURPLE - RouteType.RegionalCoach -> VLINE_PURPLE - RouteType.RegionalBus -> VLINE_PURPLE - RouteType.SkyBus -> BUS_ORANGE - RouteType.Interstate -> BUS_ORANGE + MetroTrain -> TRAIN_BLUE + MetroTram -> TRAM_GREEN + MetroBus -> BUS_ORANGE + RegionalTrain -> VLINE_PURPLE + RegionalCoach -> VLINE_PURPLE + RegionalBus -> VLINE_PURPLE + SkyBus -> BUS_ORANGE + Interstate -> BUS_ORANGE } val (drawable, background, icon) = when (this) { - RouteType.MetroTrain, - RouteType.RegionalTrain, - RouteType.Interstate -> Triple( + MetroTrain, + RegionalTrain, + Interstate -> Triple( Res.drawable.train, Res.drawable.train_background, Res.drawable.train_icon ) - RouteType.MetroTram -> Triple( + MetroTram -> Triple( Res.drawable.tram, Res.drawable.tram_background, Res.drawable.tram_icon ) - RouteType.MetroBus, - RouteType.RegionalCoach, - RouteType.RegionalBus, - RouteType.SkyBus -> Triple( + MetroBus, + RegionalCoach, + RegionalBus, + SkyBus -> Triple( Res.drawable.bus, Res.drawable.bus_background, Res.drawable.bus_icon ) } @@ -82,3 +102,35 @@ fun PtvRouteType.getUIProperties(): RouteTypeProperties { return RouteTypeProperties(colour, drawable, background, icon) } +@Composable +fun RouteIcon( + modifier: Modifier = Modifier.Companion, + size: Dp = 40.dp, + routeType: RouteType, +) { + val properties = routeType.getUIProperties() + Image( + painter = painterResource(properties.icon), + contentDescription = null, + modifier = modifier + .size(size) + .aspectRatio(1f) + .padding(size * ICON_PADDING / 2) + .drawBehind { + drawCircle(properties.colour, radius = size.toPx() / 2f) + } + ) +} + +const val ICON_PADDING = 0.25f + +@Preview +@Composable +private fun RouteIconPreview() { + Row { + RouteIcon(routeType = MetroTrain) + RouteIcon(routeType = MetroTram) + RouteIcon(routeType = MetroBus) + } +} + diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt new file mode 100644 index 0000000..8d525f3 --- /dev/null +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt @@ -0,0 +1,177 @@ +package moe.lava.banksia.ui.layout + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +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.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContent +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.LoadingIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +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.components.RouteIcon +import moe.lava.banksia.ui.screens.map.MapScreenEvent +import moe.lava.banksia.ui.state.InfoPanelState +import kotlin.time.Duration.Companion.milliseconds + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun InfoPanel( + state: InfoPanelState, + onEvent: (MapScreenEvent) -> Unit, + onPeekHeightChange: (Dp) -> Unit, +) { + if (state is InfoPanelState.None) + return + + val localDensity = LocalDensity.current + var delayedLoad by remember { mutableStateOf(false) } + + LaunchedEffect(state.loading) { + if (state.loading) { + delay(200.milliseconds) + delayedLoad = true + } else { + delayedLoad = false + } + } + + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .onSizeChanged { + onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) }) + } + ) { + Box { + when (state) { + is InfoPanelState.Route -> RouteInfoPanel(state, onEvent) + is InfoPanelState.Stop -> StopInfoPanel(state, onEvent) + is InfoPanelState.Run -> RunInfoPanel(state, onEvent) + is InfoPanelState.None -> throw UnsupportedOperationException() + } + + this@Column.AnimatedVisibility( + modifier = Modifier.align(Alignment.TopEnd), + visible = delayedLoad, + label = "sheet-loading", + enter = fadeIn() + scaleIn(), + exit = fadeOut() + scaleOut(), + ) { + LoadingIndicator( + modifier = Modifier.size(48.dp) + ) + } + } + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeContent)) + } +} + +@Composable +private inline fun RouteInfoPanel( + state: InfoPanelState.Route, + onEvent: (MapScreenEvent) -> Unit, +) { + Column(Modifier.fillMaxWidth()) { + Row { + RouteIcon(routeType = state.type) + Text( + state.name, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Start + ) + } + } +} + +@Composable +private inline fun RunInfoPanel( + state: InfoPanelState.Run, + onEvent: (MapScreenEvent) -> Unit, +) { + Column(Modifier.fillMaxWidth()) { + Row { + RouteIcon(routeType = state.type) + Text( + "${state.direction} via ${state.routeName ?: "..."}", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Start + ) + } + } +} + +@Composable +private inline fun StopInfoPanel( + state: InfoPanelState.Stop, + onEvent: (MapScreenEvent) -> Unit, +) { + 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( + name, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold + ) + Text( + formatted, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(horizontal = 5.dp) + ) + } + } + } + } +} 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 deleted file mode 100644 index 55eac69..0000000 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt +++ /dev/null @@ -1,97 +0,0 @@ -package moe.lava.banksia.ui.layout.info - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.foundation.layout.Box -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.padding -import androidx.compose.foundation.layout.safeContent -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsBottomHeight -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.LoadingIndicator -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -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 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( - state: InfoPanelState, - onEvent: (InfoPanelEvent) -> Unit, - onPeekHeightChange: (Dp) -> Unit, -) { - if (state is InfoPanelState.None) - return - - val localDensity = LocalDensity.current - var delayedLoad by remember { mutableStateOf(false) } - - LaunchedEffect(state.loading) { - if (state.loading) { - delay(200.milliseconds) - delayedLoad = true - } else { - delayedLoad = false - } - } - - Column( - Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp) - .onSizeChanged { - 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.None -> throw UnsupportedOperationException() - } - - this@Column.AnimatedVisibility( - modifier = Modifier.align(Alignment.TopEnd), - visible = delayedLoad, - label = "sheet-loading", - enter = fadeIn() + scaleIn(), - exit = fadeOut() + scaleOut(), - ) { - LoadingIndicator( - modifier = Modifier.size(48.dp) - ) - } - } - Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeContent)) - } -} 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 deleted file mode 100644 index 655caca..0000000 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt +++ /dev/null @@ -1,40 +0,0 @@ -package moe.lava.banksia.ui.layout.info - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -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.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 -} - -@Composable -internal fun RouteInfoPanel( - state: RouteInfoPanelState, - onEvent: (RouteInfoPanelEvent) -> Unit, -) { - Column(Modifier.fillMaxWidth()) { - Row { - RouteIcon(routeType = state.type) - Text( - state.name, - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Start - ) - } - } -} 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 deleted file mode 100644 index dbe3b29..0000000 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt +++ /dev/null @@ -1,75 +0,0 @@ -package moe.lava.banksia.ui.layout.info - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp - -sealed class StopInfoPanelEvent : InfoPanelEvent() - -data class StopInfoPanelState( - 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) -} - -@Composable -internal fun StopInfoPanel( - state: StopInfoPanelState, - onEvent: (StopInfoPanelEvent) -> Unit, -) { - 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( - name, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.SemiBold - ) - Text( - formatted, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(horizontal = 5.dp) - ) - } - } - } - } -} 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 deleted file mode 100644 index 7b7dcf9..0000000 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt +++ /dev/null @@ -1,41 +0,0 @@ -package moe.lava.banksia.ui.layout.info - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -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.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 -} - -@Composable -internal fun TripInfoPanel( - state: TripInfoPanelState, - onEvent: (TripInfoPanelEvent) -> Unit, -) { - Column(Modifier.fillMaxWidth()) { - Row { - RouteIcon(routeType = state.type) - Text( - "${state.direction} via ${state.routeName ?: "..."}", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold, - textAlign = TextAlign.Start - ) - } - } -} diff --git a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt similarity index 100% rename from ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt 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 f4319be..15388be 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 @@ -35,15 +35,17 @@ import kotlinx.coroutines.launch import moe.lava.banksia.resources.Res import moe.lava.banksia.resources.my_location_24 import moe.lava.banksia.ui.layout.AppBottomSheet +import moe.lava.banksia.ui.layout.InfoPanel 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.platform.BanksiaTheme +import moe.lava.banksia.ui.state.InfoPanelState +import moe.lava.banksia.util.Point import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel +val MELBOURNE = Point(-37.8136, 144.9631) + @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable fun MapScreen( @@ -76,20 +78,14 @@ fun MapScreen( Scaffold { Maps( modifier = Modifier.fillMaxSize(), - insets = WindowInsets(top = with(LocalDensity.current) { + state = mapState, + onEvent = viewModel::handleEvent, + cameraPositionFlow = viewModel.cameraChangeEmitter, + extInsets = WindowInsets(top = with(LocalDensity.current) { SearchBarDefaults.InputFieldHeight.roundToPx() }, bottom = sheetState.bottomInset), - stops = mapState.stops, -// vehicles = mapState.vehicles, - onStopClicked = { stop -> - viewModel.handleEvent(MapScreenEvent.SelectStop(stop)) - }, -// onEvent = viewModel::handleEvent, -// cameraPositionFlow = viewModel.cameraChangeEmitter, -// setLastKnownLocation = viewModel::setLastKnownLocation, + setLastKnownLocation = viewModel::setLastKnownLocation, ) - -// onEvent() Searcher( state = searchState, onEvent = viewModel::handleEvent, 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 2e19c68..99ac1fa 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 @@ -13,45 +13,41 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toInstant 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.data.ptv.structures.PtvRoute import moe.lava.banksia.model.Route import moe.lava.banksia.model.RouteType -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.StopInfoPanelState -import moe.lava.banksia.ui.layout.info.TripInfoPanelState -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.components.getUIProperties +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.ui.utils.map.CameraPosition +import moe.lava.banksia.ui.utils.map.CameraPositionBounds +import moe.lava.banksia.ui.utils.map.Marker +import moe.lava.banksia.ui.utils.map.Polyline 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 +import kotlin.time.Instant sealed class MapScreenEvent { data object DismissState : MapScreenEvent() data class SelectRoute(val id: String?) : MapScreenEvent() data class SelectRun(val ref: String?) : MapScreenEvent() - data class SelectStop(val id: String?) : MapScreenEvent() + data class SelectStop(val typeIdPair: Pair?) : MapScreenEvent() data class SearchUpdate(val text: String) : MapScreenEvent() } -private data class InternalState( +data class InternalState( val route: String? = null, - val stop: String? = null, + val stop: Pair? = null, val run: String? = null, ) @@ -59,7 +55,6 @@ class MapScreenViewModel( private val ptvService: PtvService, private val routeRepository: RouteRepository, private val stopRepository: StopRepository, - private val stopTimeRepository: StopTimeRepository, ) : ViewModel() { private var state = InternalState() set(value) { @@ -97,18 +92,12 @@ class MapScreenViewModel( is MapScreenEvent.DismissState -> dismissState() is MapScreenEvent.SelectRoute -> state = InternalState(route = event.id) is MapScreenEvent.SelectRun -> state = state.copy(run = event.ref, stop = null) - is MapScreenEvent.SelectStop -> state = state.copy(stop = event.id, run = null) + is MapScreenEvent.SelectStop -> state = state.copy(stop = event.typeIdPair, run = null) is MapScreenEvent.SearchUpdate -> searchUpdate(event.text) } } } - fun handleEvent(event: InfoPanelEvent) { - viewModelScope.launch { -// when (event) { } - } - } - fun bindTracker(locationTracker: LocationTracker) { locationTrackerJob = locationTracker.getLocationsFlow() .onEach { lastKnownLocation = Point(it.latitude, it.longitude) } @@ -172,7 +161,7 @@ class MapScreenViewModel( val route = routeRepository.get(routeId) // val gtfsRoute = ptvService.route(routeId) iInfoState.update { - RouteInfoPanelState( + InfoPanelState.Route( name = route.name, type = route.type, ) @@ -197,7 +186,7 @@ class MapScreenViewModel( .onEach { run -> if (routeName == null) { iInfoState.update { - TripInfoPanelState( + InfoPanelState.Run( direction = run.destinationName, type = RouteType.MetroTrain, // XXX HACK TODO FIXME ) @@ -206,7 +195,7 @@ class MapScreenViewModel( } iInfoState.update { - TripInfoPanelState( + InfoPanelState.Run( direction = run.destinationName, type = RouteType.MetroTrain, // FIXME HACK XXX TODO routeName = routeName, @@ -217,11 +206,12 @@ class MapScreenViewModel( } // [TODO]: Cleanup - private suspend fun switchStop(id: String?) { - if (id == null) { + private suspend fun switchStop(pair: Pair?) { + if (pair == null) { iInfoState.update { InfoPanelState.None } return } + val (type, id) = pair val stop = stopRepository.get(id) // val stop = ptvService.stop(routeType, stopId) @@ -229,40 +219,52 @@ class MapScreenViewModel( val name = split[0] val subname = split.getOrNull(1) iInfoState.update { - StopInfoPanelState( + InfoPanelState.Stop( id = stop.id, name = name, subname = subname, ) } - val departures = stopTimeRepository.getForStop(id) - .filter { !it.headsign.isNullOrBlank() } - .groupBy { it.headsign!! } - .map { (headsign, stopTimes) -> - val now = Clock.System.now() - val times = stopTimes - .map { it.arrivalTime.toInstant(TimeZone.currentSystemDefault()) } - .filter { it >= (now - 1.minutes) } - .joinToString(" | ") { - val diff = (it - now).inWholeMinutes.coerceAtLeast(0) - if (diff >= 65) { - "${((diff + 30.0) / 60.0).toInt()}hr" - } else { - "${diff}mn" - } - } - StopInfoPanelState.Departure(headsign, times) - } + val res = ptvService.departures(type, stop.id) + // Map< + // Pair, + // Pair> + // > + val timetable = HashMap, Pair>>() + res.departures.forEach { dep -> + val key = Pair(dep.directionId, dep.routeId) + val direction = ptvService.direction(dep.directionId, dep.routeId) + val route = res.routes[dep.routeId.toString()] + val prefix = route?.let { if (it.routeNumber == "") "" else "${it.routeNumber} - " } ?: "" + val element = timetable.getOrPut(key) { Pair(prefix + direction.directionName, mutableListOf()) }.second + if (element.size >= 5) + return@forEach + + val date = Instant.parse(dep.estimatedDepartureUtc ?: dep.scheduledDepartureUtc) + val min = (date - Clock.System.now()).inWholeMinutes + if (min <= -5) + return@forEach + if (min >= 65) + element.add("${((min + 30.0) / 60.0).toInt()}hr") + else + element.add("${min}mn") + } + val departures = timetable.values.sortedBy { it.first }.map { (name, list) -> + if (list.isEmpty()) + InfoPanelState.Stop.Departure(name, "No departures") + else + InfoPanelState.Stop.Departure(name, list.joinToString(" | ")) + } iInfoState.update { - if (it !is StopInfoPanelState) + if (it !is InfoPanelState.Stop) it else it.copy(departures = departures) } } - /*private suspend fun buildPolylines(route: PtvRoute) { + private suspend fun buildPolylines(route: PtvRoute) { val routeWithGeo = if (route.geopath.isEmpty()) ptvService.route(route.routeId, true) else @@ -292,9 +294,9 @@ class MapScreenViewModel( iMapState.update { it.copy(polylines = polylines) } newCameraPosition?.let { iCameraChangeEmitter.emit(it.box()) } - }*/ + } - /*private fun buildRuns(route: PtvRoute) { + private fun buildRuns(route: PtvRoute) { ptvService .runsFlow(route.routeId) .waitUntilSubscribed(iInfoState) @@ -315,16 +317,19 @@ class MapScreenViewModel( iMapState.update { it.copy(vehicles = markers) } } .launchIn(viewModelScope) - }*/ + + } private suspend fun buildStops(route: Route) { val stops = stopRepository.getByRoute(route.id) + val colour = route.type.getUIProperties().colour val markers = stops .map { stop -> Marker.Stop( point = stop.pos, id = stop.id, + colour = colour, type = route.type, ) } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt new file mode 100644 index 0000000..fe20f9f --- /dev/null +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt @@ -0,0 +1,210 @@ +@file:Suppress("COMPOSE_APPLIER_CALL_MISMATCH") + +package moe.lava.banksia.ui.screens.map + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromJsonElement +import moe.lava.banksia.model.RouteType +import moe.lava.banksia.ui.components.getUIProperties +import moe.lava.banksia.ui.platform.BanksiaTheme +import moe.lava.banksia.ui.state.MapState +import moe.lava.banksia.ui.utils.map.CameraPosition +import moe.lava.banksia.ui.utils.map.Marker +import moe.lava.banksia.util.BoxedValue +import moe.lava.banksia.util.Point +import moe.lava.banksia.util.log +import org.maplibre.compose.camera.rememberCameraState +import org.maplibre.compose.expressions.dsl.case +import org.maplibre.compose.expressions.dsl.const +import org.maplibre.compose.expressions.dsl.convertToString +import org.maplibre.compose.expressions.dsl.feature +import org.maplibre.compose.expressions.dsl.switch +import org.maplibre.compose.layers.CircleLayer +import org.maplibre.compose.map.MapOptions +import org.maplibre.compose.map.MaplibreMap +import org.maplibre.compose.map.OrnamentOptions +import org.maplibre.compose.sources.GeoJsonData +import org.maplibre.compose.sources.rememberGeoJsonSource +import org.maplibre.compose.style.BaseStyle +import org.maplibre.compose.util.ClickResult +import org.maplibre.spatialk.geojson.BoundingBox +import org.maplibre.spatialk.geojson.FeatureCollection +import org.maplibre.spatialk.geojson.Position +import org.maplibre.spatialk.geojson.dsl.addFeature +import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection +import org.maplibre.compose.camera.CameraPosition as MLCameraPosition +import org.maplibre.spatialk.geojson.Point as MLPoint + +fun Point.toPos(): Position = Position(this.lng, this.lat) + +@Serializable +data class MarkerProps( + val type: RouteType, +) + +private fun buildMarkers(markers: List): FeatureCollection { + return buildFeatureCollection { + markers.forEach { marker -> + val type = when (marker) { + is Marker.Stop -> marker.type + is Marker.Vehicle -> marker.type + } + val id = when (marker) { + is Marker.Stop -> marker.id + is Marker.Vehicle -> marker.ref + } + addFeature( + geometry = MLPoint(marker.point.toPos()), + properties = MarkerProps(type), + ) { + setId(id) + } + } + } +} + +private val colorTypeExpression @Composable get() = switch( + input = feature["type"].convertToString(), + cases = RouteType.entries.map { + case(label = it.name, output = const(it.getUIProperties().colour)) + }.toTypedArray(), + fallback = const(BanksiaTheme.colors.surface), +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Maps( + modifier: Modifier, + state: MapState, + onEvent: (MapScreenEvent) -> Unit, + cameraPositionFlow: Flow>, + setLastKnownLocation: (Point) -> Unit, + extInsets: WindowInsets, +) { + val camPos = rememberCameraState( + MLCameraPosition( + zoom = 16.0, + target = MELBOURNE.toPos() + ) + ) + val newCameraPos by cameraPositionFlow.collectAsStateWithLifecycle(null) + LaunchedEffect(newCameraPos) { + log("maps", "newPos ${newCameraPos?.value}") + val pos = newCameraPos?.value ?: return@LaunchedEffect + if (pos.bounds != null) { + val (northeast, southwest) = pos.bounds + camPos.animateTo( + boundingBox = BoundingBox( + southwest.toPos(), + northeast.toPos() + ) + ) + } else { + camPos.animateTo(MLCameraPosition( + target = pos.centre.toPos(), + zoom = 16.0, + )) + } + } +// +// val ctx = LocalContext.current +// val fusedLocation = remember { LocationServices.getFusedLocationProviderClient(ctx) } +// LaunchedEffect(Unit) { +// @SuppressLint("MissingPermission") +// fusedLocation.lastLocation.addOnSuccessListener { +// if (it != null) { +// camPos.position = MLCameraPosition( +// zoom = 16.0, +// target = Position(it.longitude, it.latitude) +// ) +// setLastKnownLocation(Point(it.latitude, it.longitude)) +// } +// } +// } + + MaplibreMap( + modifier = modifier, + baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/positron"), + cameraState = camPos, + options = MapOptions( + ornamentOptions = OrnamentOptions( + padding = WindowInsets.safeDrawing.add(extInsets).asPaddingValues(), + isScaleBarEnabled = false, + isAttributionEnabled = false, + ) + ) + ) { + if (state.stops.isNotEmpty()) { + val stopsSource = rememberGeoJsonSource( + GeoJsonData.Features(buildMarkers(state.stops)) + ) + CircleLayer( + id = "maps-stops0", + source = stopsSource, + color = const(BanksiaTheme.colors.surface), + radius = const(3.dp), + strokeWidth = const(2.dp), + strokeColor = colorTypeExpression, + ) + CircleLayer( + id = "maps-stops0-clickhandler", + source = stopsSource, + color = const(Color.Transparent), + radius = const(12.dp), + onClick = { features -> + val feature = features[0] + val marker = Json.decodeFromJsonElement(feature.properties!!) + onEvent(MapScreenEvent.SelectStop(marker.type to feature.id!!.content)) + ClickResult.Consume + } + ) + } + + // TODO +// if (state.vehicles.isNotEmpty()) { +// val stopsSource = rememberGeoJsonSource( +// GeoJsonData.Features(buildMarkers(state.vehicles)) +// ) +// SymbolLayer +// CircleLayer( +// id = "maps-vehicles0", +// source = stopsSource, +// color = const(BanksiaTheme.colors.surface), +// radius = const(3.dp), +// strokeWidth = const(2.dp), +// strokeColor = colorTypeExpression, +// onClick = { features -> +// val feature = features[0] +// val marker = Json.decodeFromJsonElement(feature.properties!!) +// onEvent(MapScreenEvent.SelectStop(marker.type to feature.id!!.content)) +// ClickResult.Consume +// } +// ) +// } +// +// if (state.polylines.isNotEmpty()) { +// val polySource = rememberGeoJsonSource( +// +// ) +// LineLayer( +// id = "maps-routeline", +// source = polySource, +// color = colorTypeExpression, +// ) +// } + } +} 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..b0acbec --- /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 Run( + val direction: String, + val type: RouteType, + val routeName: String? = null, + ) : InfoPanelState() { + override val loading = routeName == null + } + + 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) + } +} diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt index 82ba204..ff71bf4 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.ui.state -import moe.lava.banksia.ui.map.util.Marker -import moe.lava.banksia.ui.map.util.Polyline +import moe.lava.banksia.ui.utils.map.Marker +import moe.lava.banksia.ui.utils.map.Polyline data class MapState( val stops: List = listOf(), diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt similarity index 81% rename from ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt index 710cebb..2bc80af 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.ui.map.util +package moe.lava.banksia.ui.utils.map import moe.lava.banksia.util.Point diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt similarity index 74% rename from ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt index 4adf3b1..335f668 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.ui.map.util +package moe.lava.banksia.ui.utils.map import moe.lava.banksia.util.Point diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt new file mode 100644 index 0000000..2efe33d --- /dev/null +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.ui.utils.map + +import androidx.compose.ui.graphics.Color +import moe.lava.banksia.model.RouteType +import moe.lava.banksia.util.Point + +sealed class Marker { + abstract val point: Point + + data class Stop( + override val point: Point, + val id: String, + val type: RouteType, + val colour: Color, + ) : Marker() + + data class Vehicle( + override val point: Point, + val ref: String, + val type: RouteType, + ) : Marker() +} diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt similarity index 79% rename from ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt index 146d74b..d9529e4 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.ui.map.util +package moe.lava.banksia.ui.utils.map import androidx.compose.ui.graphics.Color import moe.lava.banksia.util.Point diff --git a/ui/shared/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt b/ui/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt similarity index 100% rename from ui/shared/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt rename to ui/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt