diff --git a/.gitignore b/.gitignore
index 426800f..83f099d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ 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
new file mode 100644
index 0000000..b8b100b
--- /dev/null
+++ b/androidApp/build.gradle.kts
@@ -0,0 +1,57 @@
+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/ui/src/androidMain/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml
similarity index 91%
rename from ui/src/androidMain/AndroidManifest.xml
rename to androidApp/src/main/AndroidManifest.xml
index 928349e..16435e6 100644
--- a/ui/src/androidMain/AndroidManifest.xml
+++ b/androidApp/src/main/AndroidManifest.xml
@@ -13,9 +13,6 @@
android:enableOnBackInvokedCallback="true"
android:usesCleartextTraffic="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
-
{
+ return stopTimeDao
+ .getForStopDated(
+ stopId,
+ listOf(date.dayOfWeek).serialise(),
+ date.toEpochDays().toInt(),
+ )
+ .map { it.asModel().atDate(date) }
+ .sortedBy { it.departureTime }
+ }
+}
diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt
new file mode 100644
index 0000000..baf26e7
--- /dev/null
+++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt
@@ -0,0 +1,36 @@
+package moe.lava.banksia.client.data.stoptime
+
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.request.get
+import io.ktor.client.request.parameter
+import kotlinx.datetime.LocalDate
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.todayIn
+import moe.lava.banksia.model.StopTimeDated
+import kotlin.time.Clock
+
+class StopTimeRemoteDataSource(
+ private val client: HttpClient,
+) {
+ suspend fun getAtStop(
+ stopId: String,
+ date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()),
+ ): List {
+ return client.get("stoptimes/by_stop/${stopId}") {
+ parameter("date", date)
+ }.body>()
+ }
+
+ /*suspend fun get(
+ stop: String? = null,
+ trip: String? = null,
+ day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek,
+ ): List {
+ return client.get("stoptimes") {
+ stop?.let { parameter("stop", it) }
+ trip?.let { parameter("trip", it) }
+ day?.let { parameter("day", it) }
+ }.body>()
+ }*/
+}
diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt
new file mode 100644
index 0000000..8b46fbd
--- /dev/null
+++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt
@@ -0,0 +1,18 @@
+package moe.lava.banksia.client.data.trip
+
+import io.ktor.client.HttpClient
+import kotlinx.datetime.DayOfWeek
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.todayIn
+import moe.lava.banksia.model.Trip
+import kotlin.time.Clock
+
+class TripRemoteDataSource(
+ private val client: HttpClient,
+) {
+ suspend fun get(
+ day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek,
+ ): List {
+ return listOf()
+ }
+}
diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt
index a39a3ae..f22c7db 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,8 +12,11 @@ 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
@@ -46,8 +49,11 @@ 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
new file mode 100644
index 0000000..4f54840
--- /dev/null
+++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt
@@ -0,0 +1,16 @@
+package moe.lava.banksia.client.repository
+
+import moe.lava.banksia.client.data.stoptime.StopTimeLocalDataSource
+import moe.lava.banksia.client.data.stoptime.StopTimeRemoteDataSource
+import moe.lava.banksia.model.StopTimeDated
+
+class StopTimeRepository(
+ private val local: StopTimeLocalDataSource,
+ private val remote: StopTimeRemoteDataSource,
+) {
+ suspend fun getForStop(id: String): List {
+ return local
+ .getAtStop(id)
+ .ifEmpty { remote.getAtStop(id) }
+ }
+}
diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties
new file mode 100644
index 0000000..9b7b12a
--- /dev/null
+++ b/gradle/gradle-daemon-jvm.properties
@@ -0,0 +1,13 @@
+#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 638a098..70676f5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.13.1"
+agp = "9.1.0"
android-compileSdk = "36"
android-minSdk = "24"
android-targetSdk = "36"
@@ -85,7 +85,7 @@ ui-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.r
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
-androidLibrary = { id = "com.android.library", version.ref = "agp" }
+androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.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 37f853b..37f78a6 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-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
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 4ae3398..76ee8ba 100644
--- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt
+++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt
@@ -15,17 +15,24 @@ 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.gtfs.GtfsHandler
import moe.lava.banksia.server.gtfsr.GtfsrService
+import moe.lava.banksia.util.serialise
import org.koin.dsl.module
import org.koin.ktor.ext.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)
@@ -41,8 +48,11 @@ fun Application.module() {
modules(CommonModules, ServerModules)
}
- val gtfsr by inject()
- launch { gtfsr.start() }
+ @Suppress("KotlinConstantConditions")
+ if (!Constants.devMode) {
+ val gtfsr by inject()
+ launch { gtfsr.start() }
+ }
routing {
get("/update") {
@@ -137,6 +147,24 @@ fun Application.module() {
// .map { it.asModel() }
// }
// call.respond(stops)
+
+ }
+ get("/stoptimes/by_stop/{stop_id}") {
+ val stopId = call.parameters["stop_id"]!!
+ val date = call.queryParameters["date"]
+ ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) }
+ ?: Clock.System.todayIn(TimeZone.currentSystemDefault())
+ val times = withContext(context = Dispatchers.IO) {
+ inject().value
+ .getForStopDated(
+ stopId,
+ listOf(date.dayOfWeek).serialise(),
+ date.toEpochDays().toInt(),
+ )
+ .map { it.asModel().atDate(date) }
+ .sortedBy { it.departureTime }
+ }
+ call.respond(times)
}
}
}
diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt
index d85d5df..28d50af 100644
--- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt
+++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt
@@ -9,11 +9,14 @@ import io.ktor.client.statement.bodyAsChannel
import io.ktor.util.cio.writeChannel
import io.ktor.util.logging.Logger
import io.ktor.utils.io.copyAndClose
+import kotlinx.datetime.DayOfWeek
+import kotlinx.datetime.LocalDate
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.serializer
import moe.lava.banksia.Constants
import moe.lava.banksia.model.Route
+import moe.lava.banksia.model.Service
import moe.lava.banksia.model.Shape
import moe.lava.banksia.model.Stop
import moe.lava.banksia.model.StopTime
@@ -22,6 +25,7 @@ import moe.lava.banksia.room.Database
import moe.lava.banksia.room.converter.RouteTypeConverter
import moe.lava.banksia.room.entity.asEntity
import moe.lava.banksia.server.gtfs.structures.GtfsRoute
+import moe.lava.banksia.server.gtfs.structures.GtfsService
import moe.lava.banksia.server.gtfs.structures.GtfsShape
import moe.lava.banksia.server.gtfs.structures.GtfsStop
import moe.lava.banksia.server.gtfs.structures.GtfsStopTime
@@ -65,6 +69,7 @@ class GtfsHandler(
.listFiles { it.isDirectory }
.flatMap { d -> d.listFiles { f -> f.extension == "txt" }.toList() }
.ifEmpty { extractAll(datasetPath) }
+ .filter { it.parentFile.name == "2" }
} else {
extractAll(datasetPath)
}
@@ -72,8 +77,9 @@ class GtfsHandler(
addRoutes(files)
addStops(files)
addShapes(files)
- addTrips(files)
- addStopTimes(files)
+ val services = addServices(files)
+ val trips = addTrips(files, services.associateBy { it.id })
+ addStopTimes(files, trips.associateBy { it.id })
updateMetadata(date ?: Clock.System.now().epochSeconds)
@@ -174,7 +180,7 @@ class GtfsHandler(
)
} }
- private suspend fun addStopTimes(files: List) {
+ private suspend fun addStopTimes(files: List, trips: Map) {
val dao = db.stopTimeDao
dao.deleteAll()
log.info("parsing stop times...")
@@ -182,7 +188,7 @@ class GtfsHandler(
.filter { it.name == "stop_times.txt" }
.forEach { fd ->
log.info("parsing stop times for ${fd.parent}...")
- parseStopTimes(fd) { seq ->
+ parseStopTimes(fd, trips) { seq ->
seq.chunked(1000000)
.forEach { queue ->
log.info("converting stop times (${queue.size}) for ${fd.parent}...")
@@ -194,7 +200,7 @@ class GtfsHandler(
}
}
- private inline fun parseStopTimes(fd: File, block: (Sequence) -> Unit) =
+ private inline fun parseStopTimes(fd: File, trips: Map, block: (Sequence) -> Unit) =
fd.parseCsvSequence { seq ->
seq
.map { with(it) {
@@ -203,7 +209,7 @@ class GtfsHandler(
stopId = stop_id,
arrivalTime = GtfsStopTime.parseGtfsTime(arrival_time),
departureTime = GtfsStopTime.parseGtfsTime(departure_time),
- headsign = stop_headsign,
+ headsign = stop_headsign.ifEmpty { trips[trip_id]!!.tripHeadsign },
pickupType = pickup_type,
dropOffType = drop_off_type,
)
@@ -211,25 +217,61 @@ class GtfsHandler(
.let { block(it) }
}
- private suspend fun addTrips(files: List) {
+ private suspend fun addServices(files: List): List {
+ val dao = db.serviceDao
+ log.info("parsing services...")
+ val services = files
+ .filter { it.name == "calendar.txt" }
+ .flatMap { fd -> parseServices(fd) }
+
+ log.info("inserting services...")
+ dao.deleteAll()
+ dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray())
+
+ return services
+ }
+
+ private fun parseServices(fd: File) =
+ fd.parseCsv()
+ .map { with(it) {
+ val days = buildList {
+ if (monday == 1) add(DayOfWeek.MONDAY)
+ if (tuesday == 1) add(DayOfWeek.TUESDAY)
+ if (wednesday == 1) add(DayOfWeek.WEDNESDAY)
+ if (thursday == 1) add(DayOfWeek.THURSDAY)
+ if (friday == 1) add(DayOfWeek.FRIDAY)
+ if (saturday == 1) add(DayOfWeek.SATURDAY)
+ if (sunday == 1) add(DayOfWeek.SUNDAY)
+ }
+ Service(
+ id = service_id,
+ days = days,
+ start = LocalDate.parse(start_date, LocalDate.Formats.ISO_BASIC),
+ end = LocalDate.parse(end_date, LocalDate.Formats.ISO_BASIC),
+ )
+ } }
+
+ private suspend fun addTrips(files: List, services: Map): List {
val dao = db.tripDao
log.info("parsing trips...")
val trips = files
.filter { it.name == "trips.txt" }
- .flatMap { fd -> parseTrips(fd) }
+ .flatMap { fd -> parseTrips(fd, services) }
log.info("inserting trips...")
dao.deleteAll()
dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray())
+
+ return trips
}
- private fun parseTrips(fd: File) =
+ private fun parseTrips(fd: File, services: Map) =
fd.parseCsv()
.map { with(it) {
Trip(
id = trip_id,
routeId = route_id,
- serviceId = service_id,
+ service = services[service_id]!!,
shapeId = shape_id.ifEmpty { null },
tripHeadsign = trip_headsign,
directionId = direction_id,
diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt
new file mode 100644
index 0000000..9347b5e
--- /dev/null
+++ b/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt
@@ -0,0 +1,18 @@
+package moe.lava.banksia.server.gtfs.structures
+
+import kotlinx.serialization.Serializable
+
+@Suppress("PropertyName")
+@Serializable
+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/settings.gradle.kts b/settings.gradle.kts
index a33c5ec..4688423 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,6 +14,9 @@ pluginManagement {
gradlePluginPortal()
}
}
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
+}
dependencyResolutionManagement {
repositories {
@@ -28,7 +31,10 @@ dependencyResolutionManagement {
}
}
+include(":androidApp")
include(":client")
include(":server")
include(":shared")
include(":ui")
+include(":ui:maps")
+include(":ui:shared")
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 1f26a53..953d790 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -1,10 +1,9 @@
-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.androidLibrary)
+ alias(libs.plugins.androidMultiplatformLibrary)
alias(libs.plugins.ksp)
alias(libs.plugins.room)
alias(libs.plugins.wire)
@@ -15,8 +14,10 @@ room {
}
kotlin {
- androidTarget {
- @OptIn(ExperimentalKotlinGradlePluginApi::class)
+ android {
+ namespace = "moe.lava.banksia.shared"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
@@ -26,7 +27,6 @@ kotlin {
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
}
- iosX64()
iosArm64()
iosSimulatorArm64()
@@ -58,27 +58,14 @@ 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/4.json b/shared/schemas/moe.lava.banksia.room.Database/4.json
new file mode 100644
index 0000000..783b3ee
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/4.json
@@ -0,0 +1,368 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 4,
+ "identityHash": "4426fd2ccc826d9d9d9021546b105850",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": true,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": true,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/schemas/moe.lava.banksia.room.Database/5.json b/shared/schemas/moe.lava.banksia.room.Database/5.json
new file mode 100644
index 0000000..c4a786d
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/5.json
@@ -0,0 +1,368 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 5,
+ "identityHash": "4426fd2ccc826d9d9d9021546b105850",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": true,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": true,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/schemas/moe.lava.banksia.room.Database/6.json b/shared/schemas/moe.lava.banksia.room.Database/6.json
new file mode 100644
index 0000000..5ab26dc
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/6.json
@@ -0,0 +1,368 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 6,
+ "identityHash": "5f52de4cc0ddbcf02a0d8be4cf4d4cfd",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": false,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": false,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5f52de4cc0ddbcf02a0d8be4cf4d4cfd')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/schemas/moe.lava.banksia.room.Database/7.json b/shared/schemas/moe.lava.banksia.room.Database/7.json
new file mode 100644
index 0000000..d4c62b2
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/7.json
@@ -0,0 +1,415 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 7,
+ "identityHash": "15c94df0a62438ff28d451c074c94c59",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Service",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "days",
+ "columnName": "days",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Service_days",
+ "unique": false,
+ "columnNames": [
+ "days"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)"
+ }
+ ]
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": false,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": false,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '15c94df0a62438ff28d451c074c94c59')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/schemas/moe.lava.banksia.room.Database/8.json b/shared/schemas/moe.lava.banksia.room.Database/8.json
new file mode 100644
index 0000000..9240dd5
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/8.json
@@ -0,0 +1,426 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 8,
+ "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Service",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "days",
+ "columnName": "days",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Service_days",
+ "unique": false,
+ "columnNames": [
+ "days"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)"
+ }
+ ]
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": false,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": false,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Service",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "serviceId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/schemas/moe.lava.banksia.room.Database/9.json b/shared/schemas/moe.lava.banksia.room.Database/9.json
new file mode 100644
index 0000000..2359dbd
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/9.json
@@ -0,0 +1,426 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 9,
+ "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Service",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "days",
+ "columnName": "days",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "start",
+ "columnName": "start",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "end",
+ "columnName": "end",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Service_days",
+ "unique": false,
+ "columnNames": [
+ "days"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)"
+ }
+ ]
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": false,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": false,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Service",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "serviceId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton b/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton
index 7329ae3..15b3c58 100644
--- a/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton
@@ -8,4 +8,5 @@ object Constants {
// TODO
const val devMode: Boolean = false
const val updateKey: String = ""
+ const val protomapsKey: String = ""
}
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 0726665..c9988bf 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
-private object PtvRouteTypeSerialiser : KSerializer {
+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 823174b..8658342 100644
--- a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt
@@ -9,6 +9,7 @@ val CommonModules = module {
single { Database.build(get().getBuilder()) }
single { get().versionMetadataDao }
single { get().routeDao }
+ single { get().serviceDao }
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 c1853a9..91c5c77 100644
--- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt
@@ -1,6 +1,10 @@
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
@@ -39,6 +43,10 @@ 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/StopTimeDated.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt
new file mode 100644
index 0000000..55288fa
--- /dev/null
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt
@@ -0,0 +1,26 @@
+package moe.lava.banksia.model
+
+import kotlinx.datetime.LocalDate
+import kotlinx.datetime.LocalDateTime
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class StopTimeDated(
+ val tripId: String,
+ val stopId: String,
+ val arrivalTime: LocalDateTime,
+ val departureTime: LocalDateTime,
+ val headsign: String?,
+ val pickupType: Int,
+ val dropOffType: Int,
+)
+
+fun StopTime.atDate(date: LocalDate) = StopTimeDated(
+ tripId = tripId,
+ stopId = stopId,
+ arrivalTime = arrivalTime.atDate(date),
+ departureTime = departureTime.atDate(date),
+ headsign = headsign,
+ pickupType = pickupType,
+ dropOffType = dropOffType,
+)
diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt
index ef95eea..81d3f8d 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 serviceId: String,
+ val service: Service,
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 163461a..89bc24a 100644
--- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt
@@ -7,13 +7,15 @@ import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import moe.lava.banksia.room.converter.RouteTypeConverter
-import moe.lava.banksia.room.dao.VersionMetadataDao
import moe.lava.banksia.room.dao.RouteDao
+import moe.lava.banksia.room.dao.ServiceDao
import moe.lava.banksia.room.dao.ShapeDao
import moe.lava.banksia.room.dao.StopDao
import moe.lava.banksia.room.dao.StopTimeDao
import moe.lava.banksia.room.dao.TripDao
+import moe.lava.banksia.room.dao.VersionMetadataDao
import moe.lava.banksia.room.entity.RouteEntity
+import moe.lava.banksia.room.entity.ServiceEntity
import moe.lava.banksia.room.entity.ShapeEntity
import moe.lava.banksia.room.entity.StopEntity
import moe.lava.banksia.room.entity.StopTimeEntity
@@ -22,9 +24,10 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity
import androidx.room.Database as DatabaseAnnotation
@DatabaseAnnotation(
- version = 3,
+ version = 9,
entities = [
RouteEntity::class,
+ ServiceEntity::class,
ShapeEntity::class,
StopEntity::class,
StopTimeEntity::class,
@@ -40,6 +43,7 @@ import androidx.room.Database as DatabaseAnnotation
abstract class Database : RoomDatabase() {
abstract val versionMetadataDao: VersionMetadataDao
abstract val routeDao: RouteDao
+ abstract val serviceDao: ServiceDao
abstract val shapeDao: ShapeDao
abstract val stopDao: StopDao
abstract val stopTimeDao: StopTimeDao
diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt
new file mode 100644
index 0000000..6fc2906
--- /dev/null
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt
@@ -0,0 +1,29 @@
+package moe.lava.banksia.room.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy.Companion.REPLACE
+import androidx.room.Query
+import moe.lava.banksia.room.entity.ServiceEntity
+
+@Dao
+interface ServiceDao {
+ @Query("SELECT * FROM Service")
+ suspend fun getAll(): List
+
+ @Query("SELECT * FROM Service WHERE id == :id")
+ suspend fun get(id: String): ServiceEntity?
+
+ @Insert
+ suspend fun insertAll(vararg services: ServiceEntity)
+
+ @Insert(onConflict = REPLACE)
+ suspend fun insertOrReplaceAll(vararg services: ServiceEntity)
+
+ @Delete
+ suspend fun delete(service: ServiceEntity)
+
+ @Query("DELETE FROM Service")
+ suspend fun deleteAll()
+}
diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt
index 88485f4..d5e1744 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,10 +13,22 @@ interface StopTimeDao {
suspend fun getAll(): List
@Query("SELECT * FROM StopTime WHERE tripId == :tripId")
- suspend fun get(tripId: String): StopTimeEntity?
+ suspend fun getForTrip(tripId: String): StopTimeEntity?
@Query("SELECT * FROM StopTime WHERE tripId IN (:tripIds)")
- suspend fun get(tripIds: List): List
+ suspend fun getForTrips(tripIds: List): List
+
+ @Query("SELECT * FROM StopTime WHERE stopId == :stopId")
+ suspend fun getForStop(stopId: String): List
+
+ @Query("""
+ SELECT * FROM StopTime
+ INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end`
+ INNER JOIN Trip ON Trip.serviceId == Service.id
+ WHERE StopTime.tripId == Trip.id
+ AND StopTime.stopId == :stopId
+ """)
+ suspend fun getForStopDated(stopId: String, days: Int, date: Int): List
@Insert
suspend fun insertAll(vararg stopTimes: StopTimeEntity)
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 4b14a95..027aaa8 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,50 +1,23 @@
package moe.lava.banksia.room.entity
-import kotlinx.datetime.DayOfWeek
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
import kotlinx.datetime.LocalDate
import moe.lava.banksia.model.Service
+import moe.lava.banksia.util.deserialiseDaysBitflag
+import moe.lava.banksia.util.serialise
+@Entity("Service")
data class ServiceEntity(
- val id: String,
- val days: Int,
+ @PrimaryKey val id: String,
+ @ColumnInfo(index = true) 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,
- Parser.deserialiseDays(days),
+ days.deserialiseDaysBitflag(),
LocalDate.fromEpochDays(start),
LocalDate.fromEpochDays(end),
)
@@ -52,7 +25,7 @@ data class ServiceEntity(
fun Service.asEntity() = ServiceEntity(
id,
- ServiceEntity.Parser.serialiseDays(days),
+ days.serialise(),
start.toEpochDays().toInt(),
end.toEpochDays().toInt(),
)
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 9b0aac8..bb20ff1 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,6 +3,7 @@ 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
@@ -11,6 +12,10 @@ 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 ca7e9a7..3753d44 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,6 +4,7 @@ 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
@@ -11,8 +12,10 @@ 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")],
)
data class TripEntity(
@PrimaryKey val id: String,
@@ -23,8 +26,24 @@ data class TripEntity(
val directionId: String,
val blockId: String,
val wheelchairAccessible: String,
-) {
- fun asModel() = Trip(id, routeId, serviceId, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible)
+)
+
+fun Trip.Companion.from(tripEntity: TripEntity, serviceEntity: ServiceEntity): Trip {
+ if (tripEntity.serviceId != serviceEntity.id) {
+ throw IllegalArgumentException("trip and service id mismatch (${tripEntity.serviceId} != ${serviceEntity.id})")
+ }
+ return with(tripEntity) {
+ Trip(
+ id = id,
+ routeId = routeId,
+ service = serviceEntity.asModel(),
+ shapeId = shapeId,
+ tripHeadsign = tripHeadsign,
+ directionId = directionId,
+ blockId = blockId,
+ wheelchairAccessible = wheelchairAccessible
+ )
+ }
}
-fun Trip.asEntity() = TripEntity(id, routeId, serviceId, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible)
+fun Trip.asEntity() = TripEntity(id, routeId, service.id, 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 0d6896d..3ff5702 100644
--- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt
@@ -1,5 +1,6 @@
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
new file mode 100644
index 0000000..87d3244
--- /dev/null
+++ b/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt
@@ -0,0 +1,36 @@
+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/ui/build.gradle.kts b/ui/build.gradle.kts
index 100228c..201a10f 100644
--- a/ui/build.gradle.kts
+++ b/ui/build.gradle.kts
@@ -1,25 +1,31 @@
-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.androidApplication)
+ alias(libs.plugins.androidMultiplatformLibrary)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.secretsGradle)
}
kotlin {
- androidTarget {
- @OptIn(ExperimentalKotlinGradlePluginApi::class)
+ android {
+ namespace = "moe.lava.banksia.ui"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
+
+ androidResources {
+ enable = true
+ }
}
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
+ freeCompilerArgs.add("-Xexplicit-backing-fields")
}
listOf(
@@ -35,9 +41,6 @@ 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 {
@@ -66,47 +69,16 @@ 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 {
- debugImplementation(compose.uiTooling)
+ androidRuntimeClasspath(libs.compose.ui.tooling)
}
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
new file mode 100644
index 0000000..324b0b3
--- /dev/null
+++ b/ui/maps/build.gradle.kts
@@ -0,0 +1,56 @@
+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
new file mode 100644
index 0000000..1df9cb1
--- /dev/null
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt
@@ -0,0 +1,85 @@
+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
new file mode 100644
index 0000000..52b7250
--- /dev/null
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt
@@ -0,0 +1,37 @@
+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
new file mode 100644
index 0000000..a9fe8b2
--- /dev/null
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt
@@ -0,0 +1,27 @@
+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
new file mode 100644
index 0000000..32a910c
--- /dev/null
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt
@@ -0,0 +1,40 @@
+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
new file mode 100644
index 0000000..c137394
--- /dev/null
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt
@@ -0,0 +1,6 @@
+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
new file mode 100644
index 0000000..523e438
--- /dev/null
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt
@@ -0,0 +1,19 @@
+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/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt
similarity index 81%
rename from ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt
rename to ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt
index 2bc80af..710cebb 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt
@@ -1,4 +1,4 @@
-package moe.lava.banksia.ui.utils.map
+package moe.lava.banksia.ui.map.util
import moe.lava.banksia.util.Point
diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt
similarity index 74%
rename from ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt
rename to ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt
index 335f668..4adf3b1 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt
@@ -1,4 +1,4 @@
-package moe.lava.banksia.ui.utils.map
+package moe.lava.banksia.ui.map.util
import moe.lava.banksia.util.Point
diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt
new file mode 100644
index 0000000..9326b2a
--- /dev/null
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt
@@ -0,0 +1,28 @@
+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/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt
similarity index 79%
rename from ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt
rename to ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt
index d9529e4..146d74b 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt
+++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt
@@ -1,4 +1,4 @@
-package moe.lava.banksia.ui.utils.map
+package moe.lava.banksia.ui.map.util
import androidx.compose.ui.graphics.Color
import moe.lava.banksia.util.Point
diff --git a/ui/shared/build.gradle.kts b/ui/shared/build.gradle.kts
new file mode 100644
index 0000000..c784fed
--- /dev/null
+++ b/ui/shared/build.gradle.kts
@@ -0,0 +1,50 @@
+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/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt b/ui/shared/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt
similarity index 100%
rename from ui/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt
rename to ui/shared/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt
diff --git a/ui/src/commonMain/composeResources/drawable/bus.xml b/ui/shared/src/commonMain/composeResources/drawable/bus.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/bus.xml
rename to ui/shared/src/commonMain/composeResources/drawable/bus.xml
diff --git a/ui/src/commonMain/composeResources/drawable/bus_background.xml b/ui/shared/src/commonMain/composeResources/drawable/bus_background.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/bus_background.xml
rename to ui/shared/src/commonMain/composeResources/drawable/bus_background.xml
diff --git a/ui/src/commonMain/composeResources/drawable/bus_icon.xml b/ui/shared/src/commonMain/composeResources/drawable/bus_icon.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/bus_icon.xml
rename to ui/shared/src/commonMain/composeResources/drawable/bus_icon.xml
diff --git a/ui/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/ui/shared/src/commonMain/composeResources/drawable/compose-multiplatform.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/compose-multiplatform.xml
rename to ui/shared/src/commonMain/composeResources/drawable/compose-multiplatform.xml
diff --git a/ui/shared/src/commonMain/composeResources/drawable/my_location_24.xml b/ui/shared/src/commonMain/composeResources/drawable/my_location_24.xml
new file mode 100644
index 0000000..e69de29
diff --git a/ui/src/commonMain/composeResources/drawable/train.xml b/ui/shared/src/commonMain/composeResources/drawable/train.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/train.xml
rename to ui/shared/src/commonMain/composeResources/drawable/train.xml
diff --git a/ui/src/commonMain/composeResources/drawable/train_background.xml b/ui/shared/src/commonMain/composeResources/drawable/train_background.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/train_background.xml
rename to ui/shared/src/commonMain/composeResources/drawable/train_background.xml
diff --git a/ui/src/commonMain/composeResources/drawable/train_icon.xml b/ui/shared/src/commonMain/composeResources/drawable/train_icon.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/train_icon.xml
rename to ui/shared/src/commonMain/composeResources/drawable/train_icon.xml
diff --git a/ui/src/commonMain/composeResources/drawable/tram.xml b/ui/shared/src/commonMain/composeResources/drawable/tram.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/tram.xml
rename to ui/shared/src/commonMain/composeResources/drawable/tram.xml
diff --git a/ui/src/commonMain/composeResources/drawable/tram_background.xml b/ui/shared/src/commonMain/composeResources/drawable/tram_background.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/tram_background.xml
rename to ui/shared/src/commonMain/composeResources/drawable/tram_background.xml
diff --git a/ui/src/commonMain/composeResources/drawable/tram_icon.xml b/ui/shared/src/commonMain/composeResources/drawable/tram_icon.xml
similarity index 100%
rename from ui/src/commonMain/composeResources/drawable/tram_icon.xml
rename to ui/shared/src/commonMain/composeResources/drawable/tram_icon.xml
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
new file mode 100644
index 0000000..e84d765
--- /dev/null
+++ b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt
@@ -0,0 +1,52 @@
+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/ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt
similarity index 51%
rename from ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt
rename to ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt
index c06fd1e..992b910 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt
+++ b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt
@@ -1,27 +1,8 @@
-package moe.lava.banksia.ui.components
+package moe.lava.banksia.ui.extensions
-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
@@ -33,7 +14,6 @@ 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,
@@ -49,31 +29,31 @@ const val VLINE_PURPLE = 0xFF8F1A95
fun RouteType.getUIProperties(): RouteTypeProperties {
val colour = when (this) {
- MetroTrain -> TRAIN_BLUE
- MetroTram -> TRAM_GREEN
- MetroBus -> BUS_ORANGE
- RegionalTrain -> VLINE_PURPLE
- RegionalCoach -> VLINE_PURPLE
- RegionalBus -> VLINE_PURPLE
- SkyBus -> BUS_ORANGE
- Interstate -> BUS_ORANGE
+ 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
}
val (drawable, background, icon) = when (this) {
- MetroTrain,
- RegionalTrain,
- Interstate -> Triple(
+ RouteType.MetroTrain,
+ RouteType.RegionalTrain,
+ RouteType.Interstate -> Triple(
Res.drawable.train, Res.drawable.train_background, Res.drawable.train_icon
)
- MetroTram -> Triple(
+ RouteType.MetroTram -> Triple(
Res.drawable.tram, Res.drawable.tram_background, Res.drawable.tram_icon
)
- MetroBus,
- RegionalCoach,
- RegionalBus,
- SkyBus -> Triple(
+ RouteType.MetroBus,
+ RouteType.RegionalCoach,
+ RouteType.RegionalBus,
+ RouteType.SkyBus -> Triple(
Res.drawable.bus, Res.drawable.bus_background, Res.drawable.bus_icon
)
}
@@ -102,35 +82,3 @@ 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/platform/BanksiaTheme.kt b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt
similarity index 100%
rename from ui/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt
rename to ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt
diff --git a/ui/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt b/ui/shared/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt
similarity index 100%
rename from ui/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt
rename to ui/shared/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt
diff --git a/ui/src/commonMain/composeResources/drawable/my_location_24.xml b/ui/src/commonMain/composeResources/drawable/my_location_24.xml
deleted file mode 100644
index 683a624..0000000
--- a/ui/src/commonMain/composeResources/drawable/my_location_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt
similarity index 52%
rename from ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt
rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt
index 8d525f3..fa0354d 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt
+++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt
@@ -1,4 +1,4 @@
-package moe.lava.banksia.ui.layout
+package moe.lava.banksia.ui.layout.info
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
@@ -7,19 +7,15 @@ 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
@@ -28,17 +24,12 @@ 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
@@ -77,7 +68,7 @@ fun InfoPanel(
when (state) {
is InfoPanelState.Route -> RouteInfoPanel(state, onEvent)
is InfoPanelState.Stop -> StopInfoPanel(state, onEvent)
- is InfoPanelState.Run -> RunInfoPanel(state, onEvent)
+ is InfoPanelState.Trip -> TripInfoPanel(state, onEvent)
is InfoPanelState.None -> throw UnsupportedOperationException()
}
@@ -96,82 +87,3 @@ fun InfoPanel(
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/RouteInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt
new file mode 100644
index 0000000..b55b7c1
--- /dev/null
+++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt
@@ -0,0 +1,32 @@
+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.ui.components.RouteIcon
+import moe.lava.banksia.ui.screens.map.MapScreenEvent
+import moe.lava.banksia.ui.state.InfoPanelState
+
+@Composable
+internal 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
+ )
+ }
+ }
+}
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
new file mode 100644
index 0000000..731ef88
--- /dev/null
+++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt
@@ -0,0 +1,63 @@
+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
+import moe.lava.banksia.ui.screens.map.MapScreenEvent
+import moe.lava.banksia.ui.state.InfoPanelState
+
+@Composable
+internal 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/TripInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt
new file mode 100644
index 0000000..2d221b2
--- /dev/null
+++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt
@@ -0,0 +1,32 @@
+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.ui.components.RouteIcon
+import moe.lava.banksia.ui.screens.map.MapScreenEvent
+import moe.lava.banksia.ui.state.InfoPanelState
+
+@Composable
+internal fun TripInfoPanel(
+ state: InfoPanelState.Trip,
+ 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
+ )
+ }
+ }
+}
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 15388be..70b8ed4 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt
+++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt
@@ -35,17 +35,15 @@ 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.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(
@@ -78,14 +76,20 @@ fun MapScreen(
Scaffold {
Maps(
modifier = Modifier.fillMaxSize(),
- state = mapState,
- onEvent = viewModel::handleEvent,
- cameraPositionFlow = viewModel.cameraChangeEmitter,
- extInsets = WindowInsets(top = with(LocalDensity.current) {
+ insets = WindowInsets(top = with(LocalDensity.current) {
SearchBarDefaults.InputFieldHeight.roundToPx()
}, bottom = sheetState.bottomInset),
- setLastKnownLocation = viewModel::setLastKnownLocation,
+ stops = mapState.stops,
+// vehicles = mapState.vehicles,
+ onStopClicked = { stop ->
+ viewModel.handleEvent(MapScreenEvent.SelectStop(stop))
+ },
+// onEvent = viewModel::handleEvent,
+// cameraPositionFlow = viewModel.cameraChangeEmitter,
+// 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 99ac1fa..a3435af 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt
+++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt
@@ -13,41 +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.components.getUIProperties
+import moe.lava.banksia.ui.map.util.CameraPosition
+import moe.lava.banksia.ui.map.util.CameraPositionBounds
+import moe.lava.banksia.ui.map.util.Marker
import moe.lava.banksia.ui.state.InfoPanelState
import moe.lava.banksia.ui.state.MapState
import moe.lava.banksia.ui.state.SearchState
-import moe.lava.banksia.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.Instant
+import kotlin.time.Duration.Companion.minutes
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 typeIdPair: Pair?) : MapScreenEvent()
+ data class SelectStop(val id: String?) : MapScreenEvent()
data class SearchUpdate(val text: String) : MapScreenEvent()
}
data class InternalState(
val route: String? = null,
- val stop: Pair? = null,
+ val stop: String? = null,
val run: String? = null,
)
@@ -55,6 +55,7 @@ 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) {
@@ -92,7 +93,7 @@ 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.typeIdPair, run = null)
+ is MapScreenEvent.SelectStop -> state = state.copy(stop = event.id, run = null)
is MapScreenEvent.SearchUpdate -> searchUpdate(event.text)
}
}
@@ -186,7 +187,7 @@ class MapScreenViewModel(
.onEach { run ->
if (routeName == null) {
iInfoState.update {
- InfoPanelState.Run(
+ InfoPanelState.Trip(
direction = run.destinationName,
type = RouteType.MetroTrain, // XXX HACK TODO FIXME
)
@@ -195,7 +196,7 @@ class MapScreenViewModel(
}
iInfoState.update {
- InfoPanelState.Run(
+ InfoPanelState.Trip(
direction = run.destinationName,
type = RouteType.MetroTrain, // FIXME HACK XXX TODO
routeName = routeName,
@@ -206,12 +207,11 @@ class MapScreenViewModel(
}
// [TODO]: Cleanup
- private suspend fun switchStop(pair: Pair?) {
- if (pair == null) {
+ private suspend fun switchStop(id: String?) {
+ if (id == null) {
iInfoState.update { InfoPanelState.None }
return
}
- val (type, id) = pair
val stop = stopRepository.get(id)
// val stop = ptvService.stop(routeType, stopId)
@@ -226,36 +226,24 @@ class MapScreenViewModel(
)
}
- 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(" | "))
- }
+ 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"
+ }
+ }
+ InfoPanelState.Stop.Departure(headsign, times)
+ }
iInfoState.update {
if (it !is InfoPanelState.Stop)
it
@@ -264,7 +252,7 @@ class MapScreenViewModel(
}
}
- private suspend fun buildPolylines(route: PtvRoute) {
+ /*private suspend fun buildPolylines(route: PtvRoute) {
val routeWithGeo = if (route.geopath.isEmpty())
ptvService.route(route.routeId, true)
else
@@ -294,9 +282,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)
@@ -317,19 +305,16 @@ 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
deleted file mode 100644
index fe20f9f..0000000
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt
+++ /dev/null
@@ -1,210 +0,0 @@
-@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
index b0acbec..5b73914 100644
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt
+++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt
@@ -16,14 +16,6 @@ sealed class 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,
@@ -35,4 +27,12 @@ sealed class InfoPanelState {
data class Departure(val directionName: String, val formattedTimes: String)
}
+
+ data class Trip(
+ val direction: String,
+ val type: RouteType,
+ val routeName: String? = null,
+ ) : InfoPanelState() {
+ override val loading = routeName == null
+ }
}
diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt
index ff71bf4..82ba204 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.utils.map.Marker
-import moe.lava.banksia.ui.utils.map.Polyline
+import moe.lava.banksia.ui.map.util.Marker
+import moe.lava.banksia.ui.map.util.Polyline
data class MapState(
val stops: List = listOf(),
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
deleted file mode 100644
index 2efe33d..0000000
--- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-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()
-}