From 74338d6dce1fac7f17ba46ac4827658b9917c87a Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Mon, 2 Mar 2026 00:09:33 +1100 Subject: [PATCH 01/33] refactor: split composeApp to client and ui also renamed client.datasource to client.data, which made me realise .gitignore was ignoring `data` and therefore some gtfsr source files :sob: --- .gitignore | 2 +- client/build.gradle.kts | 55 ++++++++++++++++++ .../data/route}/RouteLocalDataSource.kt | 2 +- .../data/route}/RouteRemoteDataSource.kt | 2 +- .../client/data/stop}/StopLocalDataSource.kt | 2 +- .../client/data/stop}/StopRemoteDataSource.kt | 2 +- .../lava/banksia/client/di/ClientModule.kt | 13 ++--- .../client/repository/RouteRepository.kt | 4 +- .../client/repository/StopRepository.kt | 4 +- settings.gradle.kts | 3 +- .../lava/banksia/data/gtfsr/GtfsRealtime.kt | 12 ++++ .../data/gtfsr/RealtimeVehiclePosition.kt | 22 +++++++ {composeApp => ui}/build.gradle.kts | 5 +- .../src/androidMain/AndroidManifest.xml | 0 .../moe/lava/banksia/ui/MainActivity.kt | 0 .../ui/platform/BanksiaTheme.android.kt | 0 .../drawable-v24/ic_launcher_foreground.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-hdpi/ic_launcher_round.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher_round.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher_round.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin .../src/androidMain/res/raw/def_mapstyle.json | 0 .../src/androidMain/res/values/strings.xml | 0 .../composeResources/drawable/bus.xml | 0 .../drawable/bus_background.xml | 0 .../composeResources/drawable/bus_icon.xml | 0 .../drawable/compose-multiplatform.xml | 0 .../drawable/my_location_24.xml | 0 .../composeResources/drawable/train.xml | 0 .../drawable/train_background.xml | 0 .../composeResources/drawable/train_icon.xml | 0 .../composeResources/drawable/tram.xml | 0 .../drawable/tram_background.xml | 0 .../composeResources/drawable/tram_icon.xml | 0 .../kotlin/moe/lava/banksia/ui/App.kt | 4 +- .../lava/banksia/ui/components/RouteIcon.kt | 0 .../moe/lava/banksia/ui/di/AppModule.kt | 12 ++++ .../lava/banksia/ui/layout/AppBottomSheet.kt | 0 .../moe/lava/banksia/ui/layout/InfoPanel.kt | 0 .../moe/lava/banksia/ui/layout/Searcher.kt | 0 .../lava/banksia/ui/platform/BanksiaTheme.kt | 0 .../lava/banksia/ui/screens/map/MapScreen.kt | 0 .../ui/screens/map/MapScreenViewModel.kt | 0 .../moe/lava/banksia/ui/screens/map/Maps.kt | 0 .../lava/banksia/ui/state/InfoPanelState.kt | 0 .../moe/lava/banksia/ui/state/MapState.kt | 0 .../moe/lava/banksia/ui/state/SearchState.kt | 0 .../banksia/ui/utils/map/CameraPosition.kt | 0 .../ui/utils/map/CameraPositionBounds.kt | 0 .../moe/lava/banksia/ui/utils/map/Marker.kt | 0 .../moe/lava/banksia/ui/utils/map/Polyline.kt | 0 .../moe/lava/banksia/ui/MainViewController.kt | 0 .../banksia/ui/platform/BanksiaTheme.ios.kt | 0 62 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 client/build.gradle.kts rename {composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local => client/src/commonMain/kotlin/moe/lava/banksia/client/data/route}/RouteLocalDataSource.kt (88%) rename {composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote => client/src/commonMain/kotlin/moe/lava/banksia/client/data/route}/RouteRemoteDataSource.kt (87%) rename {composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local => client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop}/StopLocalDataSource.kt (90%) rename {composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote => client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop}/StopRemoteDataSource.kt (87%) rename {composeApp => client}/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt (77%) rename {composeApp => client}/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt (82%) rename {composeApp => client}/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt (80%) create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt rename {composeApp => ui}/build.gradle.kts (98%) rename {composeApp => ui}/src/androidMain/AndroidManifest.xml (100%) rename {composeApp => ui}/src/androidMain/kotlin/moe/lava/banksia/ui/MainActivity.kt (100%) rename {composeApp => ui}/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt (100%) rename {composeApp => ui}/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml (100%) rename {composeApp => ui}/src/androidMain/res/drawable/ic_launcher_background.xml (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-hdpi/ic_launcher.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-mdpi/ic_launcher.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-xhdpi/ic_launcher.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {composeApp => ui}/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename {composeApp => ui}/src/androidMain/res/raw/def_mapstyle.json (100%) rename {composeApp => ui}/src/androidMain/res/values/strings.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/bus.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/bus_background.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/bus_icon.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/compose-multiplatform.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/my_location_24.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/train.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/train_background.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/train_icon.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/tram.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/tram_background.xml (100%) rename {composeApp => ui}/src/commonMain/composeResources/drawable/tram_icon.xml (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt (87%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt (100%) create mode 100644 ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/layout/AppBottomSheet.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/layout/Searcher.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt (100%) rename {composeApp => ui}/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt (100%) rename {composeApp => ui}/src/iosMain/kotlin/moe/lava/banksia/ui/MainViewController.kt (100%) rename {composeApp => ui}/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt (100%) diff --git a/.gitignore b/.gitignore index 408e3b0..426800f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ captures secrets.properties shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt -data/ +/data/ diff --git a/client/build.gradle.kts b/client/build.gradle.kts new file mode 100644 index 0000000..f02ec51 --- /dev/null +++ b/client/build.gradle.kts @@ -0,0 +1,55 @@ +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) +} + +kotlin { + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } + + iosX64() + 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.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(projects.shared) + } + } +} + +android { + namespace = "moe.lava.banksia.client" + compileSdk = libs.versions.android.compileSdk.get().toInt() + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + } +} diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local/RouteLocalDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt similarity index 88% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local/RouteLocalDataSource.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt index bfbb204..e89d4e9 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local/RouteLocalDataSource.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.client.datasource.local +package moe.lava.banksia.client.data.route import moe.lava.banksia.model.Route import moe.lava.banksia.room.dao.RouteDao diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote/RouteRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote/RouteRemoteDataSource.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt index 861a3d8..cbe9804 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote/RouteRemoteDataSource.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.client.datasource.remote +package moe.lava.banksia.client.data.route import io.ktor.client.HttpClient import io.ktor.client.call.body diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local/StopLocalDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local/StopLocalDataSource.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt index 1b418a0..486aae0 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/local/StopLocalDataSource.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.client.datasource.local +package moe.lava.banksia.client.data.stop import moe.lava.banksia.model.Stop import moe.lava.banksia.room.dao.RouteDao diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote/StopRemoteDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote/StopRemoteDataSource.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt index f708cec..47c2f80 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/datasource/remote/StopRemoteDataSource.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.client.datasource.remote +package moe.lava.banksia.client.data.stop import io.ktor.client.HttpClient import io.ktor.client.call.body diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt similarity index 77% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt index 2002745..a39a3ae 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt @@ -8,17 +8,15 @@ import io.ktor.client.plugins.plugin import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json import moe.lava.banksia.Constants -import moe.lava.banksia.client.datasource.local.RouteLocalDataSource -import moe.lava.banksia.client.datasource.local.StopLocalDataSource -import moe.lava.banksia.client.datasource.remote.RouteRemoteDataSource -import moe.lava.banksia.client.datasource.remote.StopRemoteDataSource +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.repository.RouteRepository import moe.lava.banksia.client.repository.StopRepository import moe.lava.banksia.data.ptv.PtvService -import moe.lava.banksia.ui.screens.map.MapScreenViewModel import moe.lava.banksia.util.log import org.koin.core.module.dsl.singleOf -import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val ClientModule = module { @@ -52,7 +50,4 @@ val ClientModule = module { // Repositories singleOf(::RouteRepository) singleOf(::StopRepository) - - // ViewModel - viewModelOf(::MapScreenViewModel) } diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt similarity index 82% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt index 49e397d..22a6bcc 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt @@ -2,8 +2,8 @@ package moe.lava.banksia.client.repository import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.client.datasource.local.RouteLocalDataSource -import moe.lava.banksia.client.datasource.remote.RouteRemoteDataSource +import moe.lava.banksia.client.data.route.RouteLocalDataSource +import moe.lava.banksia.client.data.route.RouteRemoteDataSource class RouteRepository( private val local: RouteLocalDataSource, diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt similarity index 80% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt rename to client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt index c9eedce..690616a 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt @@ -2,8 +2,8 @@ package moe.lava.banksia.client.repository import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.client.datasource.local.StopLocalDataSource -import moe.lava.banksia.client.datasource.remote.StopRemoteDataSource +import moe.lava.banksia.client.data.stop.StopLocalDataSource +import moe.lava.banksia.client.data.stop.StopRemoteDataSource class StopRepository( private val local: StopLocalDataSource, diff --git a/settings.gradle.kts b/settings.gradle.kts index 3649a7a..a33c5ec 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ dependencyResolutionManagement { } } -include(":composeApp") +include(":client") include(":server") include(":shared") +include(":ui") diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt new file mode 100644 index 0000000..172238f --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.data.gtfsr + +import com.google.transit.realtime.FeedMessage + +abstract class GtfsRealtime(protected val data: FeedMessage) { + companion object { + inline fun parse(ctor: (FeedMessage) -> T, data: ByteArray): T { + val message = FeedMessage.ADAPTER.decode(data) + return ctor(message) + } + } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt new file mode 100644 index 0000000..979f1f5 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.data.gtfsr + +import com.google.transit.realtime.FeedMessage +import moe.lava.banksia.util.Point + +class RealtimeVehiclePositions(data: FeedMessage) : GtfsRealtime(data) { + private val positions = mutableMapOf() + + init { + data.entity + .mapNotNull { ent -> + if (ent.vehicle?.position == null) return@mapNotNull null + ent.id to ent.vehicle.position.run { + Point(latitude.toDouble(), longitude.toDouble()) + } + } + .let { positions.putAll(it) } + } + + fun getAll() = positions.toMap() + fun forTrip(tripId: String) = positions[tripId] +} diff --git a/composeApp/build.gradle.kts b/ui/build.gradle.kts similarity index 98% rename from composeApp/build.gradle.kts rename to ui/build.gradle.kts index 02c6e0f..100228c 100644 --- a/composeApp/build.gradle.kts +++ b/ui/build.gradle.kts @@ -34,7 +34,6 @@ kotlin { } sourceSets { - androidMain.dependencies { implementation(libs.compose.ui.tooling.preview) implementation(libs.androidx.activity.compose) @@ -63,8 +62,10 @@ kotlin { implementation(libs.maplibre.compose) implementation(libs.moko.geo) implementation(libs.moko.geo.compose) - implementation(projects.shared) implementation(libs.ui.backhandler) + + implementation(projects.client) + implementation(projects.shared) } } } diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/ui/src/androidMain/AndroidManifest.xml similarity index 100% rename from composeApp/src/androidMain/AndroidManifest.xml rename to ui/src/androidMain/AndroidManifest.xml diff --git a/composeApp/src/androidMain/kotlin/moe/lava/banksia/ui/MainActivity.kt b/ui/src/androidMain/kotlin/moe/lava/banksia/ui/MainActivity.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/moe/lava/banksia/ui/MainActivity.kt rename to ui/src/androidMain/kotlin/moe/lava/banksia/ui/MainActivity.kt diff --git a/composeApp/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt b/ui/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt rename to ui/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt diff --git a/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/ui/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml rename to ui/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/ui/src/androidMain/res/drawable/ic_launcher_background.xml similarity index 100% rename from composeApp/src/androidMain/res/drawable/ic_launcher_background.xml rename to ui/src/androidMain/res/drawable/ic_launcher_background.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/ui/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml rename to ui/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ui/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to ui/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/ui/src/androidMain/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png rename to ui/src/androidMain/res/mipmap-hdpi/ic_launcher.png diff --git a/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/ui/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png rename to ui/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/ui/src/androidMain/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png rename to ui/src/androidMain/res/mipmap-mdpi/ic_launcher.png diff --git a/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/ui/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png rename to ui/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/ui/src/androidMain/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png rename to ui/src/androidMain/res/mipmap-xhdpi/ic_launcher.png diff --git a/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/ui/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png rename to ui/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/ui/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png rename to ui/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png diff --git a/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/ui/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png rename to ui/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/ui/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png rename to ui/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/ui/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png rename to ui/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/composeApp/src/androidMain/res/raw/def_mapstyle.json b/ui/src/androidMain/res/raw/def_mapstyle.json similarity index 100% rename from composeApp/src/androidMain/res/raw/def_mapstyle.json rename to ui/src/androidMain/res/raw/def_mapstyle.json diff --git a/composeApp/src/androidMain/res/values/strings.xml b/ui/src/androidMain/res/values/strings.xml similarity index 100% rename from composeApp/src/androidMain/res/values/strings.xml rename to ui/src/androidMain/res/values/strings.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/bus.xml b/ui/src/commonMain/composeResources/drawable/bus.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/bus.xml rename to ui/src/commonMain/composeResources/drawable/bus.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/bus_background.xml b/ui/src/commonMain/composeResources/drawable/bus_background.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/bus_background.xml rename to ui/src/commonMain/composeResources/drawable/bus_background.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/bus_icon.xml b/ui/src/commonMain/composeResources/drawable/bus_icon.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/bus_icon.xml rename to ui/src/commonMain/composeResources/drawable/bus_icon.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/ui/src/commonMain/composeResources/drawable/compose-multiplatform.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml rename to ui/src/commonMain/composeResources/drawable/compose-multiplatform.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/my_location_24.xml b/ui/src/commonMain/composeResources/drawable/my_location_24.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/my_location_24.xml rename to ui/src/commonMain/composeResources/drawable/my_location_24.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/train.xml b/ui/src/commonMain/composeResources/drawable/train.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/train.xml rename to ui/src/commonMain/composeResources/drawable/train.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/train_background.xml b/ui/src/commonMain/composeResources/drawable/train_background.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/train_background.xml rename to ui/src/commonMain/composeResources/drawable/train_background.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/train_icon.xml b/ui/src/commonMain/composeResources/drawable/train_icon.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/train_icon.xml rename to ui/src/commonMain/composeResources/drawable/train_icon.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/tram.xml b/ui/src/commonMain/composeResources/drawable/tram.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/tram.xml rename to ui/src/commonMain/composeResources/drawable/tram.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/tram_background.xml b/ui/src/commonMain/composeResources/drawable/tram_background.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/tram_background.xml rename to ui/src/commonMain/composeResources/drawable/tram_background.xml diff --git a/composeApp/src/commonMain/composeResources/drawable/tram_icon.xml b/ui/src/commonMain/composeResources/drawable/tram_icon.xml similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/tram_icon.xml rename to ui/src/commonMain/composeResources/drawable/tram_icon.xml diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt index 3e41bbb..453e1ee 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt @@ -3,8 +3,8 @@ package moe.lava.banksia.ui import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi -import moe.lava.banksia.client.di.ClientModule import moe.lava.banksia.di.CommonModules +import moe.lava.banksia.ui.di.AppModule import moe.lava.banksia.ui.screens.map.MapScreen import org.koin.compose.KoinMultiplatformApplication import org.koin.core.annotation.KoinExperimentalAPI @@ -14,7 +14,7 @@ import org.koin.dsl.koinConfiguration @Composable fun App() { KoinMultiplatformApplication(config = koinConfiguration { - modules(CommonModules, ClientModule) + modules(CommonModules, AppModule) }) { MapScreen() } diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt new file mode 100644 index 0000000..4c93644 --- /dev/null +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.ui.di + +import moe.lava.banksia.client.di.ClientModule +import moe.lava.banksia.ui.screens.map.MapScreenViewModel +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module + +val AppModule = module { + includes(ClientModule) + // ViewModel + viewModelOf(::MapScreenViewModel) +} diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/layout/AppBottomSheet.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/AppBottomSheet.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/layout/AppBottomSheet.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/AppBottomSheet.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/InfoPanel.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/layout/Searcher.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/Searcher.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/layout/Searcher.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/Searcher.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/MapState.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPosition.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/CameraPositionBounds.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt diff --git a/composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt rename to ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Polyline.kt diff --git a/composeApp/src/iosMain/kotlin/moe/lava/banksia/ui/MainViewController.kt b/ui/src/iosMain/kotlin/moe/lava/banksia/ui/MainViewController.kt similarity index 100% rename from composeApp/src/iosMain/kotlin/moe/lava/banksia/ui/MainViewController.kt rename to ui/src/iosMain/kotlin/moe/lava/banksia/ui/MainViewController.kt diff --git a/composeApp/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt b/ui/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt similarity index 100% rename from composeApp/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt rename to ui/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt From 2b64fdcda91a2f3b6004b6345a92ee9a7b455ee7 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 25 Mar 2026 21:10:54 +1100 Subject: [PATCH 02/33] refactor: migrate to agp9 --- androidApp/build.gradle.kts | 53 ++++++++++++++++++ .../src/main}/AndroidManifest.xml | 3 - .../moe/lava/banksia/ui/MainActivity.kt | 0 .../drawable-v24/ic_launcher_foreground.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../src/main}/res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-hdpi/ic_launcher_round.png | Bin .../src/main}/res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher_round.png | Bin .../main}/res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher_round.png | Bin .../main}/res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin .../main}/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin .../src/main}/res/raw/def_mapstyle.json | 0 .../src/main}/res/values/strings.xml | 0 build.gradle.kts | 2 +- client/build.gradle.kts | 21 ++----- gradle/libs.versions.toml | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 1 + shared/build.gradle.kts | 23 ++------ ui/build.gradle.kts | 46 ++++----------- 26 files changed, 79 insertions(+), 76 deletions(-) create mode 100644 androidApp/build.gradle.kts rename {ui/src/androidMain => androidApp/src/main}/AndroidManifest.xml (91%) rename {ui/src/androidMain => androidApp/src/main}/kotlin/moe/lava/banksia/ui/MainActivity.kt (100%) rename {ui/src/androidMain => androidApp/src/main}/res/drawable-v24/ic_launcher_foreground.xml (100%) rename {ui/src/androidMain => androidApp/src/main}/res/drawable/ic_launcher_background.xml (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-hdpi/ic_launcher.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-hdpi/ic_launcher_round.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-mdpi/ic_launcher.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-mdpi/ic_launcher_round.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-xhdpi/ic_launcher.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-xhdpi/ic_launcher_round.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-xxhdpi/ic_launcher_round.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename {ui/src/androidMain => androidApp/src/main}/res/raw/def_mapstyle.json (100%) rename {ui/src/androidMain => androidApp/src/main}/res/values/strings.xml (100%) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts new file mode 100644 index 0000000..a0a8986 --- /dev/null +++ b/androidApp/build.gradle.kts @@ -0,0 +1,53 @@ +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) + } + } + + 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"> - Date: Wed, 25 Mar 2026 21:14:28 +1100 Subject: [PATCH 03/33] refactor: use gradle toolchain --- gradle/gradle-daemon-jvm.properties | 13 +++++++++++++ settings.gradle.kts | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 gradle/gradle-daemon-jvm.properties 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/settings.gradle.kts b/settings.gradle.kts index b46ff65..0fd7f2c 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 { From aab03ced07036333726bc29cfb407755a977c1da Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 25 Mar 2026 21:53:13 +1100 Subject: [PATCH 04/33] fix(shared/room): add indices according to warnings --- .../moe.lava.banksia.room.Database/4.json | 368 ++++++++++++++++++ .../moe.lava.banksia.room.Database/5.json | 368 ++++++++++++++++++ .../data/ptv/structures/PtvRouteType.kt | 2 +- .../kotlin/moe/lava/banksia/room/Database.kt | 4 +- .../banksia/room/entity/StopTimeEntity.kt | 5 + .../lava/banksia/room/entity/TripEntity.kt | 2 + 6 files changed, 746 insertions(+), 3 deletions(-) create mode 100644 shared/schemas/moe.lava.banksia.room.Database/4.json create mode 100644 shared/schemas/moe.lava.banksia.room.Database/5.json 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/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/room/Database.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt index 163461a..2b5da00 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -7,12 +7,12 @@ 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.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.ShapeEntity import moe.lava.banksia.room.entity.StopEntity @@ -22,7 +22,7 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( - version = 3, + version = 5, entities = [ RouteEntity::class, ShapeEntity::class, 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..c97d264 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 = true), + Index("stopId", unique = true), + ], 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..fc30e4e 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 @@ -13,6 +14,7 @@ import moe.lava.banksia.model.Trip ForeignKey(RouteEntity::class, parentColumns = ["id"], childColumns = ["routeId"], onDelete = CASCADE), ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE), ], + indices = [Index("shapeId")], ) data class TripEntity( @PrimaryKey val id: String, From a79c95829e3dc74d43266c1f0461e62082454d2f Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Thu, 26 Mar 2026 02:55:46 +1100 Subject: [PATCH 05/33] refactor(ui): split into shared, maps, and main modules --- androidApp/build.gradle.kts | 4 + client/build.gradle.kts | 1 - .../data/stoptime/StopTimePtvDataSource.kt | 44 ++++ .../lava/banksia/client/di/ClientModule.kt | 4 + .../client/repository/StopTimeRepository.kt | 13 ++ settings.gradle.kts | 2 + shared/build.gradle.kts | 2 - .../moe/lava/banksia/util/BoxedValue.kt | 1 + ui/build.gradle.kts | 8 +- ui/maps/build.gradle.kts | 56 +++++ .../moe/lava/banksia/ui/map/MapLibreMaps.kt | 81 +++++++ .../kotlin/moe/lava/banksia/ui/map/Maps.kt | 37 +++ .../lava/banksia/ui/map/MapsPositionState.kt | 27 +++ .../moe/lava/banksia/ui/map/mappers/Marker.kt | 40 ++++ .../lava/banksia/ui/map/mappers/Position.kt | 6 + .../lava/banksia/ui/map/mappers/RouteType.kt | 19 ++ .../banksia/ui/map/util}/CameraPosition.kt | 2 +- .../ui/map/util}/CameraPositionBounds.kt | 2 +- .../moe/lava/banksia/ui/map/util/Marker.kt | 28 +++ .../moe/lava/banksia/ui/map/util}/Polyline.kt | 2 +- ui/shared/build.gradle.kts | 50 +++++ .../ui/platform/BanksiaTheme.android.kt | 0 .../composeResources/drawable/bus.xml | 0 .../drawable/bus_background.xml | 0 .../composeResources/drawable/bus_icon.xml | 0 .../drawable/compose-multiplatform.xml | 0 .../drawable/my_location_24.xml | 0 .../composeResources/drawable/train.xml | 0 .../drawable/train_background.xml | 0 .../composeResources/drawable/train_icon.xml | 0 .../composeResources/drawable/tram.xml | 0 .../drawable/tram_background.xml | 0 .../composeResources/drawable/tram_icon.xml | 0 .../lava/banksia/ui/components/RouteIcon.kt | 52 +++++ .../lava/banksia/ui/extensions/RouteType.kt} | 86 ++----- .../lava/banksia/ui/platform/BanksiaTheme.kt | 0 .../banksia/ui/platform/BanksiaTheme.ios.kt | 0 .../drawable/my_location_24.xml | 5 - .../lava/banksia/ui/screens/map/MapScreen.kt | 20 +- .../ui/screens/map/MapScreenViewModel.kt | 89 ++++---- .../moe/lava/banksia/ui/screens/map/Maps.kt | 210 ------------------ .../moe/lava/banksia/ui/state/MapState.kt | 4 +- .../moe/lava/banksia/ui/utils/map/Marker.kt | 22 -- 43 files changed, 539 insertions(+), 378 deletions(-) create mode 100644 client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt create mode 100644 client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt create mode 100644 ui/maps/build.gradle.kts create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt rename ui/{src/commonMain/kotlin/moe/lava/banksia/ui/utils/map => maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util}/CameraPosition.kt (81%) rename ui/{src/commonMain/kotlin/moe/lava/banksia/ui/utils/map => maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util}/CameraPositionBounds.kt (74%) create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt rename ui/{src/commonMain/kotlin/moe/lava/banksia/ui/utils/map => maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util}/Polyline.kt (79%) create mode 100644 ui/shared/build.gradle.kts rename ui/{ => shared}/src/androidMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.android.kt (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/bus.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/bus_background.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/bus_icon.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/compose-multiplatform.xml (100%) create mode 100644 ui/shared/src/commonMain/composeResources/drawable/my_location_24.xml rename ui/{ => shared}/src/commonMain/composeResources/drawable/train.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/train_background.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/train_icon.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/tram.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/tram_background.xml (100%) rename ui/{ => shared}/src/commonMain/composeResources/drawable/tram_icon.xml (100%) create mode 100644 ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt rename ui/{src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt => shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt} (51%) rename ui/{ => shared}/src/commonMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.kt (100%) rename ui/{ => shared}/src/iosMain/kotlin/moe/lava/banksia/ui/platform/BanksiaTheme.ios.kt (100%) delete mode 100644 ui/src/commonMain/composeResources/drawable/my_location_24.xml delete mode 100644 ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/Maps.kt delete mode 100644 ui/src/commonMain/kotlin/moe/lava/banksia/ui/utils/map/Marker.kt diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index a0a8986..b8b100b 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -13,6 +13,10 @@ kotlin { } } + compilerOptions { + freeCompilerArgs.add("-Xexplicit-backing-fields") + } + dependencies { implementation(projects.ui) implementation(libs.androidx.activity.compose) diff --git a/client/build.gradle.kts b/client/build.gradle.kts index a64d48e..29dbc08 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -20,7 +20,6 @@ kotlin { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") } - iosX64() iosArm64() iosSimulatorArm64() diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt new file mode 100644 index 0000000..9f00b47 --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt @@ -0,0 +1,44 @@ +package moe.lava.banksia.client.data.stoptime + +import moe.lava.banksia.data.ptv.PtvService +import moe.lava.banksia.model.RouteType +import moe.lava.banksia.model.StopTime + +class StopTimePtvDataSource( + private val ptvService: PtvService, +) { + suspend fun getForStop(type: RouteType, stopId: String): List { + return listOf() +// val res = ptvService.departures(type, stopId) +// // 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(" | ")) +// } + } +} 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..1507a94 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,10 @@ 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.StopTimePtvDataSource 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 +48,10 @@ val ClientModule = module { singleOf(::RouteRemoteDataSource) singleOf(::StopLocalDataSource) singleOf(::StopRemoteDataSource) + singleOf(::StopTimePtvDataSource) // 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..648f942 --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt @@ -0,0 +1,13 @@ +package moe.lava.banksia.client.repository + +import moe.lava.banksia.client.data.stoptime.StopTimePtvDataSource +import moe.lava.banksia.model.StopTime + +class StopTimeRepository( + private val ptv: StopTimePtvDataSource, +) { + // TODO + suspend fun getForStop(id: String): List { + return listOf() + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0fd7f2c..4688423 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,3 +36,5 @@ 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 defedb5..953d790 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -27,7 +27,6 @@ kotlin { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") } - iosX64() iosArm64() iosSimulatorArm64() @@ -59,7 +58,6 @@ 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) 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/ui/build.gradle.kts b/ui/build.gradle.kts index 79e5b75..201a10f 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -25,6 +25,7 @@ kotlin { compilerOptions { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + freeCompilerArgs.add("-Xexplicit-backing-fields") } listOf( @@ -68,6 +69,8 @@ kotlin { implementation(projects.client) implementation(projects.shared) + implementation(projects.ui.maps) + implementation(projects.ui.shared) } } } @@ -79,8 +82,3 @@ dependencies { 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..d3e1a50 --- /dev/null +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt @@ -0,0 +1,81 @@ +package moe.lava.banksia.ui.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.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.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 + ) + ) + + MaplibreMap( + modifier = modifier, + baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/positron"), + 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/screens/map/MapScreen.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt index 15388be..f622fcb 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt @@ -38,14 +38,12 @@ 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.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..a65b52f 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt @@ -15,39 +15,35 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch 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 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 +51,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 +89,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) } } @@ -206,12 +203,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 +222,30 @@ 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 != null } + .groupBy { it.headsign!! } + .map { (headsign, stopTimes) -> + InfoPanelState.Stop.Departure(headsign, "...") + // TODO +// val tmsF = stopTimes.map { time -> +// 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 min = (time.departureTime.time - 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") +// } + } iInfoState.update { if (it !is InfoPanelState.Stop) it @@ -264,7 +254,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 +284,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 +307,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/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() -} From 8925c943abbff29d2c2770942ec146880772f2af Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sat, 28 Mar 2026 21:22:48 +1100 Subject: [PATCH 06/33] feat(ui/map): switch to protomaps has POIs and also just looks a bit prettier :3 --- .../kotlin/moe/lava/banksia/Constants.kt.skeleton | 1 + .../kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt index d3e1a50..1df9cb1 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt @@ -1,5 +1,6 @@ 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 @@ -9,6 +10,7 @@ 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 @@ -42,9 +44,11 @@ internal fun MapLibreMaps( ) ) + val variant = if (isSystemInDarkTheme()) "dark" else "light" + MaplibreMap( modifier = modifier, - baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/positron"), + baseStyle = BaseStyle.Uri("https://api.protomaps.com/styles/v5/$variant/en.json?key=${Constants.protomapsKey}"), cameraState = camPos, options = MapOptions( ornamentOptions = OrnamentOptions( From b5f2ec102dfa2e19a80dc8865597dd8d55f0b235 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sat, 28 Mar 2026 22:13:37 +1100 Subject: [PATCH 07/33] fix(room): make stoptime's fkids non-unique ajsodfijasfoiasjgifasngiuash --- .gitignore | 1 + .../moe.lava.banksia.room.Database/6.json | 368 ++++++++++++++++++ .../kotlin/moe/lava/banksia/room/Database.kt | 2 +- .../banksia/room/entity/StopTimeEntity.kt | 4 +- 4 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 shared/schemas/moe.lava.banksia.room.Database/6.json 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/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/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt index 2b5da00..90a577f 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -22,7 +22,7 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( - version = 5, + version = 6, entities = [ RouteEntity::class, ShapeEntity::class, 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 c97d264..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 @@ -13,8 +13,8 @@ import moe.lava.banksia.model.StopTime "StopTime", primaryKeys = ["tripId", "stopId"], indices = [ - Index("tripId", unique = true), - Index("stopId", unique = true), + Index("tripId", unique = false), + Index("stopId", unique = false), ], foreignKeys = [ ForeignKey(TripEntity::class, parentColumns = ["id"], childColumns = ["tripId"], onDelete = CASCADE), From 72b9fb2757f218a7c6c57f069b0771b011d11294 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Tue, 31 Mar 2026 20:09:48 +1100 Subject: [PATCH 08/33] feat: stop times/departures reimpl based on gtfs --- .../data/stoptime/StopTimeLocalDataSource.kt | 28 ++ .../data/stoptime/StopTimePtvDataSource.kt | 44 -- .../data/stoptime/StopTimeRemoteDataSource.kt | 36 ++ .../client/data/trip/TripRemoteDataSource.kt | 18 + .../lava/banksia/client/di/ClientModule.kt | 6 +- .../client/repository/StopTimeRepository.kt | 15 +- .../moe/lava/banksia/server/Application.kt | 32 +- .../lava/banksia/server/gtfs/GtfsHandler.kt | 62 ++- .../server/gtfs/structures/GtfsService.kt | 18 + .../moe.lava.banksia.room.Database/7.json | 415 +++++++++++++++++ .../moe.lava.banksia.room.Database/8.json | 426 ++++++++++++++++++ .../moe.lava.banksia.room.Database/9.json | 426 ++++++++++++++++++ .../moe/lava/banksia/di/CommonModules.kt | 1 + .../moe/lava/banksia/model/FutureTime.kt | 8 + .../moe/lava/banksia/model/StopTimeDated.kt | 26 ++ .../kotlin/moe/lava/banksia/model/Trip.kt | 2 +- .../kotlin/moe/lava/banksia/room/Database.kt | 6 +- .../moe/lava/banksia/room/dao/ServiceDao.kt | 29 ++ .../moe/lava/banksia/room/dao/StopTimeDao.kt | 16 +- .../lava/banksia/room/entity/ServiceEntity.kt | 47 +- .../lava/banksia/room/entity/TripEntity.kt | 23 +- .../lava/banksia/util/DayOfWeekExtension.kt | 36 ++ .../ui/screens/map/MapScreenViewModel.kt | 38 +- 23 files changed, 1630 insertions(+), 128 deletions(-) create mode 100644 client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt delete mode 100644 client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt create mode 100644 client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt create mode 100644 client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt create mode 100644 server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt create mode 100644 shared/schemas/moe.lava.banksia.room.Database/7.json create mode 100644 shared/schemas/moe.lava.banksia.room.Database/8.json create mode 100644 shared/schemas/moe.lava.banksia.room.Database/9.json create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt new file mode 100644 index 0000000..3640b1f --- /dev/null +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt @@ -0,0 +1,28 @@ +package moe.lava.banksia.client.data.stoptime + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.model.StopTimeDated +import moe.lava.banksia.model.atDate +import moe.lava.banksia.room.dao.StopTimeDao +import moe.lava.banksia.util.serialise +import kotlin.time.Clock + +class StopTimeLocalDataSource( + private val stopTimeDao: StopTimeDao, +) { + suspend fun getAtStop( + stopId: String, + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), + ): List { + return stopTimeDao + .getForStopDated( + stopId, + listOf(date.dayOfWeek).serialise(), + date.toEpochDays().toInt(), + ) + .map { it.asModel().atDate(date) } + .sortedBy { it.departureTime } + } +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt deleted file mode 100644 index 9f00b47..0000000 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimePtvDataSource.kt +++ /dev/null @@ -1,44 +0,0 @@ -package moe.lava.banksia.client.data.stoptime - -import moe.lava.banksia.data.ptv.PtvService -import moe.lava.banksia.model.RouteType -import moe.lava.banksia.model.StopTime - -class StopTimePtvDataSource( - private val ptvService: PtvService, -) { - suspend fun getForStop(type: RouteType, stopId: String): List { - return listOf() -// val res = ptvService.departures(type, stopId) -// // 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(" | ")) -// } - } -} 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 1507a94..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,7 +12,8 @@ import moe.lava.banksia.client.data.route.RouteLocalDataSource import moe.lava.banksia.client.data.route.RouteRemoteDataSource import moe.lava.banksia.client.data.stop.StopLocalDataSource import moe.lava.banksia.client.data.stop.StopRemoteDataSource -import moe.lava.banksia.client.data.stoptime.StopTimePtvDataSource +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 @@ -48,7 +49,8 @@ val ClientModule = module { singleOf(::RouteRemoteDataSource) singleOf(::StopLocalDataSource) singleOf(::StopRemoteDataSource) - singleOf(::StopTimePtvDataSource) + singleOf(::StopTimeLocalDataSource) + singleOf(::StopTimeRemoteDataSource) // Repositories singleOf(::RouteRepository) 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 index 648f942..4f54840 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt +++ b/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt @@ -1,13 +1,16 @@ package moe.lava.banksia.client.repository -import moe.lava.banksia.client.data.stoptime.StopTimePtvDataSource -import moe.lava.banksia.model.StopTime +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 ptv: StopTimePtvDataSource, + private val local: StopTimeLocalDataSource, + private val remote: StopTimeRemoteDataSource, ) { - // TODO - suspend fun getForStop(id: String): List { - return listOf() + suspend fun getForStop(id: String): List { + return local + .getAtStop(id) + .ifEmpty { remote.getAtStop(id) } } } 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/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/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 90a577f..89bc24a 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -8,12 +8,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import moe.lava.banksia.room.converter.RouteTypeConverter import moe.lava.banksia.room.dao.RouteDao +import moe.lava.banksia.room.dao.ServiceDao import moe.lava.banksia.room.dao.ShapeDao import moe.lava.banksia.room.dao.StopDao import moe.lava.banksia.room.dao.StopTimeDao import moe.lava.banksia.room.dao.TripDao import moe.lava.banksia.room.dao.VersionMetadataDao import moe.lava.banksia.room.entity.RouteEntity +import moe.lava.banksia.room.entity.ServiceEntity import moe.lava.banksia.room.entity.ShapeEntity import moe.lava.banksia.room.entity.StopEntity import moe.lava.banksia.room.entity.StopTimeEntity @@ -22,9 +24,10 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( - version = 6, + 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/TripEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt index fc30e4e..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 @@ -12,6 +12,7 @@ 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")], @@ -25,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/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/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 a65b52f..82e9ecc 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,6 +13,8 @@ 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 @@ -30,6 +32,8 @@ import moe.lava.banksia.util.BoxedValue.Companion.box import moe.lava.banksia.util.LoopFlow.Companion.waitUntilSubscribed import moe.lava.banksia.util.Point import moe.lava.banksia.util.log +import kotlin.time.Clock +import kotlin.time.Duration.Companion.minutes sealed class MapScreenEvent { data object DismissState : MapScreenEvent() @@ -223,28 +227,22 @@ class MapScreenViewModel( } val departures = stopTimeRepository.getForStop(id) - .filter { it.headsign != null } + .filter { !it.headsign.isNullOrBlank() } .groupBy { it.headsign!! } .map { (headsign, stopTimes) -> - InfoPanelState.Stop.Departure(headsign, "...") - // TODO -// val tmsF = stopTimes.map { time -> -// 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 min = (time.departureTime.time - 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 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) From f0ce780bba327a08f0fcf9b15273d0b74db3c168 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Tue, 31 Mar 2026 20:21:39 +1100 Subject: [PATCH 09/33] refactor(ui): split up infopanel into files --- .../banksia/ui/layout/{ => info}/InfoPanel.kt | 92 +------------------ .../banksia/ui/layout/info/RouteInfoPanel.kt | 32 +++++++ .../banksia/ui/layout/info/StopInfoPanel.kt | 63 +++++++++++++ .../banksia/ui/layout/info/TripInfoPanel.kt | 32 +++++++ .../lava/banksia/ui/screens/map/MapScreen.kt | 2 +- .../ui/screens/map/MapScreenViewModel.kt | 4 +- .../lava/banksia/ui/state/InfoPanelState.kt | 16 ++-- 7 files changed, 140 insertions(+), 101 deletions(-) rename ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/{ => info}/InfoPanel.kt (52%) create mode 100644 ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt create mode 100644 ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt create mode 100644 ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt 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 f622fcb..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,9 +35,9 @@ 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 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 82e9ecc..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 @@ -187,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 ) @@ -196,7 +196,7 @@ class MapScreenViewModel( } iInfoState.update { - InfoPanelState.Run( + InfoPanelState.Trip( direction = run.destinationName, type = RouteType.MetroTrain, // FIXME HACK XXX TODO routeName = routeName, 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 + } } From aad5ae4024bc842d09b1baabd07d2f49af147619 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Tue, 31 Mar 2026 20:53:21 +1100 Subject: [PATCH 10/33] refactor(ui/info): split up info panel state --- .../lava/banksia/ui/layout/info/InfoPanel.kt | 20 +++++++--- .../banksia/ui/layout/info/RouteInfoPanel.kt | 16 ++++++-- .../banksia/ui/layout/info/StopInfoPanel.kt | 20 ++++++++-- .../banksia/ui/layout/info/TripInfoPanel.kt | 17 +++++++-- .../lava/banksia/ui/screens/map/MapScreen.kt | 2 +- .../ui/screens/map/MapScreenViewModel.kt | 24 ++++++++---- .../lava/banksia/ui/state/InfoPanelState.kt | 38 ------------------- 7 files changed, 73 insertions(+), 64 deletions(-) delete mode 100644 ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt index fa0354d..55eac69 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt @@ -30,15 +30,23 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay -import moe.lava.banksia.ui.screens.map.MapScreenEvent -import moe.lava.banksia.ui.state.InfoPanelState import kotlin.time.Duration.Companion.milliseconds +sealed class InfoPanelEvent + +sealed class InfoPanelState { + abstract val loading: Boolean + + data object None : InfoPanelState() { + override val loading = false + } +} + @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun InfoPanel( state: InfoPanelState, - onEvent: (MapScreenEvent) -> Unit, + onEvent: (InfoPanelEvent) -> Unit, onPeekHeightChange: (Dp) -> Unit, ) { if (state is InfoPanelState.None) @@ -66,9 +74,9 @@ fun InfoPanel( ) { Box { when (state) { - is InfoPanelState.Route -> RouteInfoPanel(state, onEvent) - is InfoPanelState.Stop -> StopInfoPanel(state, onEvent) - is InfoPanelState.Trip -> TripInfoPanel(state, onEvent) + is RouteInfoPanelState -> RouteInfoPanel(state, onEvent) + is StopInfoPanelState -> StopInfoPanel(state, onEvent) + is TripInfoPanelState -> TripInfoPanel(state, onEvent) is InfoPanelState.None -> throw UnsupportedOperationException() } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt index b55b7c1..655caca 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt @@ -9,14 +9,22 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import moe.lava.banksia.model.RouteType import moe.lava.banksia.ui.components.RouteIcon -import moe.lava.banksia.ui.screens.map.MapScreenEvent -import moe.lava.banksia.ui.state.InfoPanelState + +sealed class RouteInfoPanelEvent : InfoPanelEvent() + +data class RouteInfoPanelState( + val name: String, + val type: RouteType, +) : InfoPanelState() { + override val loading = false +} @Composable internal fun RouteInfoPanel( - state: InfoPanelState.Route, - onEvent: (MapScreenEvent) -> Unit, + state: RouteInfoPanelState, + onEvent: (RouteInfoPanelEvent) -> Unit, ) { Column(Modifier.fillMaxWidth()) { Row { diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt index 731ef88..dbe3b29 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt @@ -16,13 +16,25 @@ 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 + +sealed class StopInfoPanelEvent : InfoPanelEvent() + +data class StopInfoPanelState( + val id: String, + val name: String, + val subname: String? = null, + val departures: List? = null, +) : InfoPanelState() { + override val loading: Boolean + get() = departures == null + + data class Departure(val directionName: String, val formattedTimes: String) +} @Composable internal fun StopInfoPanel( - state: InfoPanelState.Stop, - onEvent: (MapScreenEvent) -> Unit, + state: StopInfoPanelState, + onEvent: (StopInfoPanelEvent) -> Unit, ) { Column(Modifier.fillMaxWidth()) { Text( diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt index 2d221b2..7b7dcf9 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt @@ -9,14 +9,23 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import moe.lava.banksia.model.RouteType import moe.lava.banksia.ui.components.RouteIcon -import moe.lava.banksia.ui.screens.map.MapScreenEvent -import moe.lava.banksia.ui.state.InfoPanelState + +sealed class TripInfoPanelEvent : InfoPanelEvent() + +data class TripInfoPanelState( + val direction: String, + val type: RouteType, + val routeName: String? = null, +) : InfoPanelState() { + override val loading = routeName == null +} @Composable internal fun TripInfoPanel( - state: InfoPanelState.Trip, - onEvent: (MapScreenEvent) -> Unit, + state: TripInfoPanelState, + onEvent: (TripInfoPanelEvent) -> Unit, ) { Column(Modifier.fillMaxWidth()) { Row { diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt index 70b8ed4..f4319be 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt @@ -38,9 +38,9 @@ import moe.lava.banksia.ui.layout.AppBottomSheet import moe.lava.banksia.ui.layout.Searcher import moe.lava.banksia.ui.layout.SheetStateWrapper import moe.lava.banksia.ui.layout.info.InfoPanel +import moe.lava.banksia.ui.layout.info.InfoPanelState import moe.lava.banksia.ui.map.Maps import moe.lava.banksia.ui.platform.BanksiaTheme -import moe.lava.banksia.ui.state.InfoPanelState import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel 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 a3435af..a65e0a7 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 @@ -21,10 +21,14 @@ import moe.lava.banksia.client.repository.StopTimeRepository import moe.lava.banksia.data.ptv.PtvService import moe.lava.banksia.model.Route import moe.lava.banksia.model.RouteType +import moe.lava.banksia.ui.layout.info.InfoPanelEvent +import moe.lava.banksia.ui.layout.info.InfoPanelState +import moe.lava.banksia.ui.layout.info.RouteInfoPanelState +import moe.lava.banksia.ui.layout.info.StopInfoPanelState +import moe.lava.banksia.ui.layout.info.TripInfoPanelState import moe.lava.banksia.ui.map.util.CameraPosition import moe.lava.banksia.ui.map.util.CameraPositionBounds import moe.lava.banksia.ui.map.util.Marker -import moe.lava.banksia.ui.state.InfoPanelState import moe.lava.banksia.ui.state.MapState import moe.lava.banksia.ui.state.SearchState import moe.lava.banksia.util.BoxedValue @@ -99,6 +103,12 @@ class MapScreenViewModel( } } + fun handleEvent(event: InfoPanelEvent) { + viewModelScope.launch { +// when (event) { } + } + } + fun bindTracker(locationTracker: LocationTracker) { locationTrackerJob = locationTracker.getLocationsFlow() .onEach { lastKnownLocation = Point(it.latitude, it.longitude) } @@ -162,7 +172,7 @@ class MapScreenViewModel( val route = routeRepository.get(routeId) // val gtfsRoute = ptvService.route(routeId) iInfoState.update { - InfoPanelState.Route( + RouteInfoPanelState( name = route.name, type = route.type, ) @@ -187,7 +197,7 @@ class MapScreenViewModel( .onEach { run -> if (routeName == null) { iInfoState.update { - InfoPanelState.Trip( + TripInfoPanelState( direction = run.destinationName, type = RouteType.MetroTrain, // XXX HACK TODO FIXME ) @@ -196,7 +206,7 @@ class MapScreenViewModel( } iInfoState.update { - InfoPanelState.Trip( + TripInfoPanelState( direction = run.destinationName, type = RouteType.MetroTrain, // FIXME HACK XXX TODO routeName = routeName, @@ -219,7 +229,7 @@ class MapScreenViewModel( val name = split[0] val subname = split.getOrNull(1) iInfoState.update { - InfoPanelState.Stop( + StopInfoPanelState( id = stop.id, name = name, subname = subname, @@ -242,10 +252,10 @@ class MapScreenViewModel( "${diff}mn" } } - InfoPanelState.Stop.Departure(headsign, times) + StopInfoPanelState.Departure(headsign, times) } iInfoState.update { - if (it !is InfoPanelState.Stop) + if (it !is StopInfoPanelState) it else it.copy(departures = departures) diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt deleted file mode 100644 index 5b73914..0000000 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/InfoPanelState.kt +++ /dev/null @@ -1,38 +0,0 @@ -package moe.lava.banksia.ui.state - -import moe.lava.banksia.model.RouteType - -sealed class InfoPanelState { - abstract val loading: Boolean - - data object None : InfoPanelState() { - override val loading = false - } - - data class Route( - val name: String, - val type: RouteType, - ) : InfoPanelState() { - override val loading = false - } - - data class Stop( - val id: String, - val name: String, - val subname: String? = null, - val departures: List? = null, - ) : InfoPanelState() { - override val loading: Boolean - get() = departures == null - - data class Departure(val directionName: String, val formattedTimes: String) - } - - data class Trip( - val direction: String, - val type: RouteType, - val routeName: String? = null, - ) : InfoPanelState() { - override val loading = routeName == null - } -} From 018149742069bca8de7ecb465f551c1070cffa3f Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Tue, 31 Mar 2026 23:12:54 +1100 Subject: [PATCH 11/33] refactor(server): split gtfs into its own module --- server/build.gradle.kts | 9 + server/gtfs/build.gradle.kts | 20 ++ .../lava/banksia/server/gtfs/GtfsParser.kt} | 180 ++++++------------ .../server/gtfs/structures/GtfsRoute.kt | 2 +- .../server/gtfs/structures/GtfsService.kt | 2 +- .../server/gtfs/structures/GtfsShape.kt | 2 +- .../server/gtfs/structures/GtfsStop.kt | 2 +- .../server/gtfs/structures/GtfsStopTime.kt | 2 +- .../server/gtfs/structures/GtfsTrip.kt | 2 +- server/gtfs_rt/build.gradle.kts | 31 +++ .../moe/lava/banksia/server/Application.kt | 5 +- .../moe/lava/banksia/server/GtfsImporter.kt | 95 +++++++++ .../lava/banksia/server/di/ServerModules.kt | 7 +- settings.gradle.kts | 2 + .../moe/lava/banksia/model/RouteType.kt | 4 + .../room/converter/RouteTypeConverter.kt | 2 +- .../moe/lava/banksia/room/dao/ShapeDao.kt | 4 + .../ui/screens/map/MapScreenViewModel.kt | 2 +- 18 files changed, 241 insertions(+), 132 deletions(-) create mode 100644 server/gtfs/build.gradle.kts rename server/{src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt => gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt} (69%) rename server/{ => gtfs}/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt (91%) rename server/{ => gtfs}/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt (91%) rename server/{ => gtfs}/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt (90%) rename server/{ => gtfs}/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt (92%) rename server/{ => gtfs}/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt (95%) rename server/{ => gtfs}/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt (92%) create mode 100644 server/gtfs_rt/build.gradle.kts create mode 100644 server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 2f7d989..39f75f3 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -12,8 +12,17 @@ application { applicationDefaultJvmArgs = listOf("-Dio.ktor.development=${extra["io.ktor.development"] ?: "false"}") } +kotlin { + compilerOptions { + freeCompilerArgs.add("-Xexplicit-backing-fields") + } +} + dependencies { implementation(projects.shared) + implementation(projects.server.gtfs) + implementation(projects.server.gtfsRt) + implementation(libs.logback) implementation(libs.koin.core) implementation(libs.koin.ktor) diff --git a/server/gtfs/build.gradle.kts b/server/gtfs/build.gradle.kts new file mode 100644 index 0000000..e83e745 --- /dev/null +++ b/server/gtfs/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSerialization) +} + +kotlin { + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + freeCompilerArgs.add("-Xexplicit-backing-fields") + } +} + +dependencies { + implementation(projects.shared) + implementation(libs.kotlinx.serialization.csv) + implementation(libs.kotlinx.datetime) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.okhttp) +} diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt similarity index 69% rename from server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt rename to server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index 28d50af..b3fd80c 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsHandler.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -9,6 +9,9 @@ import io.ktor.client.statement.bodyAsChannel import io.ktor.util.cio.writeChannel import io.ktor.util.logging.Logger import io.ktor.utils.io.copyAndClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onCompletion import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.serialization.decodeFromString @@ -16,14 +19,12 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.serializer import moe.lava.banksia.Constants import moe.lava.banksia.model.Route +import moe.lava.banksia.model.RouteType import moe.lava.banksia.model.Service import moe.lava.banksia.model.Shape import moe.lava.banksia.model.Stop import moe.lava.banksia.model.StopTime import moe.lava.banksia.model.Trip -import moe.lava.banksia.room.Database -import moe.lava.banksia.room.converter.RouteTypeConverter -import moe.lava.banksia.room.entity.asEntity import moe.lava.banksia.server.gtfs.structures.GtfsRoute import moe.lava.banksia.server.gtfs.structures.GtfsService import moe.lava.banksia.server.gtfs.structures.GtfsShape @@ -33,19 +34,26 @@ import moe.lava.banksia.server.gtfs.structures.GtfsTrip import moe.lava.banksia.util.Point import java.io.File import java.util.zip.ZipFile -import kotlin.time.Clock import kotlin.time.ExperimentalTime -class GtfsHandler( +sealed class GtfsData { + data class RouteChunk(val routes: List) : GtfsData() + data class ServiceChunk(val services: List) : GtfsData() + data class ShapeChunk(val shapes: List) : GtfsData() + data class StopChunk(val stops: List) : GtfsData() + data class StopTimeChunk(val stopTimes: List) : GtfsData() + data class TripChunk(val trips: List) : GtfsData() +} + +class GtfsParser( private val log: Logger, private val client: HttpClient, - private val db: Database, ) { private val csv = CsvFormat(StringDeferringConfig(EmptySerializersModule())) private val datasetPath = File("/tmp/banksia", "dataset.zip") @OptIn(ExperimentalTime::class) - suspend fun update(datasetUrl: String, date: Long? = null) { + suspend fun update(datasetUrl: String): Flow { val parentDir = datasetPath.parentFile @Suppress("SimplifyBooleanWithConstants", "KotlinConstantConditions") if (parentDir.exists() && !Constants.devMode) @@ -74,39 +82,56 @@ class GtfsHandler( extractAll(datasetPath) } - addRoutes(files) - addStops(files) - addShapes(files) - val services = addServices(files) - val trips = addTrips(files, services.associateBy { it.id }) - addStopTimes(files, trips.associateBy { it.id }) + log.info("parsing...") + return parse(files) + .onCompletion { + @Suppress("KotlinConstantConditions") + if (!Constants.devMode) { + parentDir.deleteRecursively() + } - updateMetadata(date ?: Clock.System.now().epochSeconds) - - @Suppress("KotlinConstantConditions") - if (!Constants.devMode) { - parentDir.deleteRecursively() - } - - log.info("done!") + log.info("done!") + } } - private suspend fun updateMetadata(date: Long) { - val dao = db.versionMetadataDao - log.info("updating metadata...") - dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) - } - - private suspend fun addRoutes(files: List) { - val dao = db.routeDao - log.info("parsing routes...") - val routes = files + private fun parse(files: List) = flow { + files .filter { it.name == "routes.txt" } - .flatMap { fd -> parseRoutes(fd) } + .forEach { emit(GtfsData.RouteChunk(parseRoutes(it))) } - log.info("inserting routes...") - dao.deleteAll() - dao.insertAll(*routes.map { it.asEntity() }.toTypedArray()) + files + .filter { it.name == "stops.txt" } + .forEach { emit(GtfsData.StopChunk(parseStops(it))) } + + files + .filter { it.name == "shapes.txt" } + .forEach { emit(GtfsData.ShapeChunk(parseShapes(it))) } + + val services = files + .filter { it.name == "calendar.txt" } + .flatMap { fd -> + parseServices(fd) + .also { emit(GtfsData.ServiceChunk(it)) } + } + .associateBy { it.id } + + val trips = files + .filter { it.name == "trips.txt" } + .flatMap { fd -> + parseTrips(fd, services) + .also { emit(GtfsData.TripChunk(it)) } + } + .associateBy { it.id } + + files + .filter { it.name == "stop_times.txt" } + .forEach { fd -> + log.info("parsing stop times for ${fd.parent}...") + parseStopTimes(fd, trips) { seq -> + seq.chunked(1000000) + .forEach { emit(GtfsData.StopTimeChunk(it)) } + } + } } private fun parseRoutes(fd: File) = @@ -114,24 +139,12 @@ class GtfsHandler( .map { with(it) { Route( id = route_id, - type = RouteTypeConverter.from(fd.parentFile.name.toInt()), + type = RouteType.from(fd.parentFile.name.toInt()), number = route_short_name, name = route_long_name, ) } } - private suspend fun addShapes(files: List) { - val dao = db.shapeDao - log.info("parsing shapes...") - val shapes = files - .filter { it.name == "shapes.txt" } - .flatMap { fd -> parseShapes(fd) } - - log.info("inserting shapes...") - dao.deleteAll() - dao.insertAll(*shapes.map { it.asEntity() }.toTypedArray()) - } - private fun parseShapes(fd: File) = fd.parseCsv() .groupBy { it.shape_id } @@ -143,29 +156,6 @@ class GtfsHandler( Shape(id, points) } - private suspend fun addStops(files: List) { - val dao = db.stopDao - log.info("parsing stops...") - val stops = files - .filter { it.name == "stops.txt" } - .flatMap { fd -> parseStops(fd) } - - log.info("inserting stops...") - dao.deleteAll() - stops - .groupBy { it.id } - .forEach { (id, gstops) -> - if (gstops.size > 1) { - if (gstops.withIndex().any { (i, stop) -> i != 0 && stop != gstops[i - 1] }) { - gstops.forEach { - log.info("duplicate $id: $it") - } - } - } - } - dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) - } - private fun parseStops(fd: File) = fd.parseCsv() .map { with(it) { @@ -180,26 +170,6 @@ class GtfsHandler( ) } } - private suspend fun addStopTimes(files: List, trips: Map) { - val dao = db.stopTimeDao - dao.deleteAll() - log.info("parsing stop times...") - files - .filter { it.name == "stop_times.txt" } - .forEach { fd -> - log.info("parsing stop times for ${fd.parent}...") - parseStopTimes(fd, trips) { seq -> - seq.chunked(1000000) - .forEach { queue -> - log.info("converting stop times (${queue.size}) for ${fd.parent}...") - val conv = queue.map { it.asEntity() }.toTypedArray() - log.info("inserting stop times (${conv.size}) for ${fd.parent}...") - dao.insertOrReplaceAll(*conv) - } - } - } - } - private inline fun parseStopTimes(fd: File, trips: Map, block: (Sequence) -> Unit) = fd.parseCsvSequence { seq -> seq @@ -217,20 +187,6 @@ class GtfsHandler( .let { block(it) } } - private suspend fun addServices(files: List): List { - val dao = db.serviceDao - log.info("parsing services...") - val services = files - .filter { it.name == "calendar.txt" } - .flatMap { fd -> parseServices(fd) } - - log.info("inserting services...") - dao.deleteAll() - dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) - - return services - } - private fun parseServices(fd: File) = fd.parseCsv() .map { with(it) { @@ -251,20 +207,6 @@ class GtfsHandler( ) } } - private suspend fun addTrips(files: List, services: Map): List { - val dao = db.tripDao - log.info("parsing trips...") - val trips = files - .filter { it.name == "trips.txt" } - .flatMap { fd -> parseTrips(fd, services) } - - log.info("inserting trips...") - dao.deleteAll() - dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) - - return trips - } - private fun parseTrips(fd: File, services: Map) = fd.parseCsv() .map { with(it) { diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt similarity index 91% rename from server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt rename to server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt index c4eabeb..4b1bad9 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsRoute.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -data class GtfsRoute( +internal data class GtfsRoute( val route_id: String, val agency_id: String, val route_short_name: String, diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt similarity index 91% rename from server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt rename to server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt index 9347b5e..1bf9573 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsService.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -data class GtfsService( +internal data class GtfsService( val service_id: String, val monday: Int, val tuesday: Int, diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt similarity index 90% rename from server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt rename to server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt index 19cdfb5..32231ab 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsShape.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -data class GtfsShape( +internal data class GtfsShape( val shape_id: String, val shape_pt_lat: Double, val shape_pt_lon: Double, diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt similarity index 92% rename from server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt rename to server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt index 023a3e1..cb1a018 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStop.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -data class GtfsStop( +internal data class GtfsStop( val stop_id: String, val stop_name: String, val stop_lat: Double, diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt similarity index 95% rename from server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt rename to server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt index 61e8a1c..76de3cd 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt @@ -5,7 +5,7 @@ import moe.lava.banksia.model.FutureTime @Suppress("PropertyName") @Serializable -data class GtfsStopTime( +internal data class GtfsStopTime( val trip_id: String, val arrival_time: String, val departure_time: String, diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt similarity index 92% rename from server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt rename to server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt index fcfc864..0b0d865 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsTrip.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Suppress("PropertyName") @Serializable -data class GtfsTrip( +internal data class GtfsTrip( val route_id: String, val service_id: String, val trip_id: String, diff --git a/server/gtfs_rt/build.gradle.kts b/server/gtfs_rt/build.gradle.kts new file mode 100644 index 0000000..9e54231 --- /dev/null +++ b/server/gtfs_rt/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.wire) +} + +kotlin { + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } +} + +dependencies { + implementation(projects.shared) + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.serialization.protobuf) +} + +wire { + sourcePath { + srcDir("src/commonMain/proto") + } + kotlin {} +} 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 76ee8ba..1bfacaa 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -26,7 +26,6 @@ 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 @@ -67,8 +66,8 @@ fun Application.module() { ?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip" call.respondText("received") launch(context = Dispatchers.IO) { - val handler by inject() - handler.update(datasetUrl) + val importer by inject() + importer.import(datasetUrl) } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt new file mode 100644 index 0000000..794e8de --- /dev/null +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -0,0 +1,95 @@ +package moe.lava.banksia.server + +import io.ktor.util.logging.Logger +import moe.lava.banksia.model.Route +import moe.lava.banksia.model.Service +import moe.lava.banksia.model.Shape +import moe.lava.banksia.model.Stop +import moe.lava.banksia.model.StopTime +import moe.lava.banksia.model.Trip +import moe.lava.banksia.room.Database +import moe.lava.banksia.room.entity.asEntity +import moe.lava.banksia.server.gtfs.GtfsData +import moe.lava.banksia.server.gtfs.GtfsParser +import kotlin.time.Clock + +class GtfsImporter( + private val parser: GtfsParser, + private val database: Database, + private val log: Logger, +) { + suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) { + database.routeDao.deleteAll() + database.serviceDao.deleteAll() + database.shapeDao.deleteAll() + database.stopDao.deleteAll() + database.stopTimeDao.deleteAll() + database.tripDao.deleteAll() + + parser.update(url).collect { chunk -> + when (chunk) { + is GtfsData.RouteChunk -> addRoutes(chunk.routes) + is GtfsData.ServiceChunk -> addServices(chunk.services) + is GtfsData.ShapeChunk -> addShapes(chunk.shapes) + is GtfsData.StopChunk -> addStops(chunk.stops) + is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) + is GtfsData.TripChunk -> addTrips(chunk.trips) + } + } + + updateMetadata(date) + } + + private suspend fun updateMetadata(date: Long) { + val dao = database.versionMetadataDao + log.info("updating metadata...") + dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) + } + + private suspend fun addRoutes(routes: List) { + val dao = database.routeDao + log.info("inserting routes...") + dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) + } + + private suspend fun addServices(services: List) { + val dao = database.serviceDao + log.info("inserting services...") + dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) + } + + private suspend fun addShapes(shapes: List) { + val dao = database.shapeDao + log.info("inserting shapes...") + dao.insertOrReplaceAll(*shapes.map { it.asEntity() }.toTypedArray()) + } + + private suspend fun addStops(stops: List) { + val dao = database.stopDao + log.info("inserting stops...") + stops + .groupBy { it.id } + .forEach { (id, gstops) -> + if (gstops.size > 1) { + if (gstops.withIndex().any { (i, stop) -> i != 0 && stop != gstops[i - 1] }) { + gstops.forEach { + log.warn("duplicate $id: $it") + } + } + } + } + dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) + } + + private suspend fun addStopTimes(stopTimes: List) { + val dao = database.stopTimeDao + log.info("inserting ${stopTimes.size} stoptimes...") + dao.insertOrReplaceAll(*stopTimes.map { it.asEntity() }.toTypedArray()) + } + + private suspend fun addTrips(trips: List) { + val dao = database.tripDao + log.info("inserting ${trips.size} trips...") + dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) + } +} diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index c7b650c..b4e3878 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,13 +1,16 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient -import moe.lava.banksia.server.gtfs.GtfsHandler +import moe.lava.banksia.server.GtfsImporter +import moe.lava.banksia.server.gtfs.GtfsParser import moe.lava.banksia.server.gtfsr.GtfsrService import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val ServerModules = module { single { HttpClient() } - singleOf(::GtfsHandler) + singleOf(::GtfsParser) singleOf(::GtfsrService) + + singleOf(::GtfsImporter) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 4688423..72f0696 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,6 +34,8 @@ dependencyResolutionManagement { include(":androidApp") include(":client") include(":server") +include(":server:gtfs") +include(":server:gtfs_rt") include(":shared") include(":ui") include(":ui:maps") diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt index 08a9c53..a51f132 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt @@ -13,4 +13,8 @@ enum class RouteType(val value: Int) { SkyBus(11), Interstate(10), ; + + companion object { + fun from(value: Int) = RouteType.entries.first { it.value == value } + } } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt index 8927f14..9ceb612 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt @@ -5,7 +5,7 @@ import moe.lava.banksia.model.RouteType object RouteTypeConverter { @TypeConverter - fun from(value: Int) = RouteType.entries.first { it.value == value } + fun from(value: Int) = RouteType.from(value) @TypeConverter fun to(routeType: RouteType) = routeType.value diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt index c48735a..ae4d53a 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt @@ -3,6 +3,7 @@ package moe.lava.banksia.room.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query import moe.lava.banksia.room.entity.ShapeEntity @@ -14,6 +15,9 @@ interface ShapeDao { @Insert suspend fun insertAll(vararg shapes: ShapeEntity) + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg shapes: ShapeEntity) + @Delete suspend fun delete(shape: ShapeEntity) diff --git a/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 a65e0a7..2e19c68 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 @@ -49,7 +49,7 @@ sealed class MapScreenEvent { data class SearchUpdate(val text: String) : MapScreenEvent() } -data class InternalState( +private data class InternalState( val route: String? = null, val stop: String? = null, val run: String? = null, From ed9d294afca7ed2478c4199a34bc219e83374c2f Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 1 Apr 2026 16:32:01 +1100 Subject: [PATCH 12/33] refactor(server): move gtfsrt to separate module --- server/gtfs_rt/build.gradle.kts | 3 +- .../banksia/server/gtfsrt}/GtfsRealtime.kt | 2 +- .../banksia/server/gtfsrt/GtfsrtArchiver.kt | 109 ++++++++++++ .../banksia/server/gtfsrt/GtfsrtService.kt | 87 ++++++++++ .../server/gtfsrt/RealtimeVehiclePositions.kt | 2 +- .../src/main}/proto/gtfs-realtime.proto | 0 .../moe/lava/banksia/server/Application.kt | 6 +- .../lava/banksia/server/di/ServerModules.kt | 4 +- .../lava/banksia/server/gtfsr/GtfsrService.kt | 164 ------------------ shared/build.gradle.kts | 8 - 10 files changed, 205 insertions(+), 180 deletions(-) rename {shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr => server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt}/GtfsRealtime.kt (89%) create mode 100644 server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt create mode 100644 server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt rename shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt => server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt (94%) rename {shared/src/commonMain => server/gtfs_rt/src/main}/proto/gtfs-realtime.proto (100%) delete mode 100644 server/src/main/kotlin/moe/lava/banksia/server/gtfsr/GtfsrService.kt diff --git a/server/gtfs_rt/build.gradle.kts b/server/gtfs_rt/build.gradle.kts index 9e54231..934d8bc 100644 --- a/server/gtfs_rt/build.gradle.kts +++ b/server/gtfs_rt/build.gradle.kts @@ -7,6 +7,7 @@ plugins { kotlin { compilerOptions { freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + freeCompilerArgs.add("-Xexplicit-backing-fields") } } @@ -25,7 +26,7 @@ dependencies { wire { sourcePath { - srcDir("src/commonMain/proto") + srcDir("src/main/proto") } kotlin {} } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsRealtime.kt similarity index 89% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt rename to server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsRealtime.kt index 172238f..128f141 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/GtfsRealtime.kt +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsRealtime.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.data.gtfsr +package moe.lava.banksia.server.gtfsrt import com.google.transit.realtime.FeedMessage diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt new file mode 100644 index 0000000..30a9fd3 --- /dev/null +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt @@ -0,0 +1,109 @@ +package moe.lava.banksia.server.gtfsrt + +import com.google.transit.realtime.FeedMessage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import moe.lava.banksia.util.log +import java.io.File +import kotlin.time.Instant + +private const val BASE_DIR = "./data/gtfsr-archive/" + +internal class GtfsrtArchiver { + private var started = false + + suspend fun start(flow: SharedFlow>) { + if (started) { + log("GtfsrtArchiver", "Tried to start when already started") + return + } + started = true + coroutineScope { + launch { compressJob() } + + flow.collect { (type, rawData) -> + val data = try { + FeedMessage.ADAPTER.decode(rawData) + } catch (e: Throwable) { + log("gtfsr $type", "Failed to parse proto: $e") + return@collect + } + val timestamp = data.header_.timestamp + ?: return@collect log("gtfsr $type", "Failed to read proto timestamp") + + val time = Instant.fromEpochSeconds(timestamp).toLocalDateTime(TimeZone.currentSystemDefault()) + + val base = File(BASE_DIR, type) + val previousParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7).toString().padStart(2, '0')}") + val currentParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7 + 1).toString().padStart(2, '0')}") + val target = File(currentParent, "${timestamp}.proto") + + if (previousParent.isDirectory) { + enqueueCompression(previousParent) + } + + if (!target.exists()) { + try { + if (!target.parentFile.isDirectory) { + target.parentFile.mkdirs() + } + target.writeBytes(rawData) + } catch (e: Throwable) { + log("gtfsr $type", "Failed to write ${target}: $e") + } + } + } + } + } + + private val cqueue = mutableSetOf() + private val ignore = mutableSetOf() + private val cmut = Mutex() + private suspend fun enqueueCompression(fd: File) { + cmut.withLock { cqueue.add(fd) } + } + + private suspend fun compressJob() { + while(true) { + while(true) { + val next = cmut.withLock { cqueue.firstOrNull() } + ?: break + if (!next.isDirectory) { + cmut.withLock { cqueue.remove(next) } + continue + } + if (next in ignore) continue + + withContext(Dispatchers.IO) { + val proc = ProcessBuilder( + "tar", "-acf", + "${next.absolutePath}.tar.zst", + next.absolutePath + ).start() + val exitCode = proc.waitFor() + if (exitCode == 0) { + if (next.deleteRecursively()) { + cmut.withLock { cqueue.remove(next) } + } else { + log("CompressJob", "Failed to delete $next") + ignore.add(next) + } + } else { + val msg = proc.errorStream.readAllBytes().decodeToString() + log("CompressJob", "Failed to delete $next (exit code $exitCode") + log("CompressJob", msg) + } + } + } + delay(30000) + } + } +} diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt new file mode 100644 index 0000000..8b30b2f --- /dev/null +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt @@ -0,0 +1,87 @@ +package moe.lava.banksia.server.gtfsrt + +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.url +import io.ktor.client.statement.bodyAsText +import io.ktor.client.statement.readRawBytes +import io.ktor.http.isSuccess +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import moe.lava.banksia.Constants +import moe.lava.banksia.util.LogScope +import moe.lava.banksia.util.log + +private val types = arrayOf( + "metro/trip-updates", + "metro/vehicle-positions", + "metro/service-alerts", + "tram/trip-updates", + "tram/vehicle-positions", + "tram/service-alerts", + "bus/trip-updates", + "bus/vehicle-positions", + "vline/trip-updates", + "vline/vehicle-positions", +) + +class GtfsrtService( + private val client: HttpClient, +) { + private val archiver = GtfsrtArchiver() + private var started = false + + internal val rawMessages: SharedFlow> + field = MutableSharedFlow>() + + fun start( + scope: CoroutineScope, + enableArchiving: Boolean = false, + ) { + if (started) { + log("GtfsrtService", "Tried to start when already started") + return + } + + if (enableArchiving) { + scope.launch { archiver.start(rawMessages) } + } + + scope.launch { fetch() } + } + + private suspend fun fetch() { + coroutineScope { + types.map { type -> + launch(context = Dispatchers.IO) { + val logger = LogScope("gtfsr $type") + try { + val res = client.get { + url("https://api.opendata.transport.vic.gov.au/opendata/public-transport/gtfs/realtime/v1/${type}") + header("KeyId", Constants.opendataKey) + } + if (!res.status.isSuccess()) { + logger.log("${res.status} | ${res.bodyAsText()}") + } else { + val bytes = res.readRawBytes() + rawMessages.emit(type to bytes) + } + } catch (e: Throwable) { + logger.log("$e") + logger.log(e.stackTraceToString()) + } + } + }.joinAll() + } + + delay(10000) + fetch() + } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt similarity index 94% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt rename to server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt index 979f1f5..abebe76 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/data/gtfsr/RealtimeVehiclePosition.kt +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.data.gtfsr +package moe.lava.banksia.server.gtfsrt import com.google.transit.realtime.FeedMessage import moe.lava.banksia.util.Point diff --git a/shared/src/commonMain/proto/gtfs-realtime.proto b/server/gtfs_rt/src/main/proto/gtfs-realtime.proto similarity index 100% rename from shared/src/commonMain/proto/gtfs-realtime.proto rename to server/gtfs_rt/src/main/proto/gtfs-realtime.proto 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 1bfacaa..b33fb9d 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -26,7 +26,7 @@ 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.gtfsr.GtfsrService +import moe.lava.banksia.server.gtfsrt.GtfsrtService import moe.lava.banksia.util.serialise import org.koin.dsl.module import org.koin.ktor.ext.inject @@ -49,8 +49,8 @@ fun Application.module() { @Suppress("KotlinConstantConditions") if (!Constants.devMode) { - val gtfsr by inject() - launch { gtfsr.start() } + val gtfsr by inject() + launch { gtfsr.start(this, true) } } routing { diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index b4e3878..d51379e 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -3,14 +3,14 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser -import moe.lava.banksia.server.gtfsr.GtfsrService +import moe.lava.banksia.server.gtfsrt.GtfsrtService import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val ServerModules = module { single { HttpClient() } singleOf(::GtfsParser) - singleOf(::GtfsrService) + singleOf(::GtfsrtService) singleOf(::GtfsImporter) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/gtfsr/GtfsrService.kt b/server/src/main/kotlin/moe/lava/banksia/server/gtfsr/GtfsrService.kt deleted file mode 100644 index 5a0b1dc..0000000 --- a/server/src/main/kotlin/moe/lava/banksia/server/gtfsr/GtfsrService.kt +++ /dev/null @@ -1,164 +0,0 @@ -package moe.lava.banksia.server.gtfsr - -import com.google.transit.realtime.FeedMessage -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.client.request.header -import io.ktor.client.request.url -import io.ktor.client.statement.bodyAsText -import io.ktor.client.statement.readRawBytes -import io.ktor.http.isSuccess -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import moe.lava.banksia.Constants -import moe.lava.banksia.util.LogScope -import moe.lava.banksia.util.log -import java.io.File -import java.time.Instant -import java.time.ZoneId - -private const val BASE_DIR = "./data/gtfsr-archive/" - -class GtfsrService(private val client: HttpClient) { - private var started = false - private val latest = mutableMapOf() - - fun latestFor(type: String) = latest[type] - - private val iFlow = MutableSharedFlow>() - val flow = iFlow.asSharedFlow() - - companion object { - val types = arrayOf( - "metro/trip-updates", - "metro/vehicle-positions", - "metro/service-alerts", - "tram/trip-updates", - "tram/vehicle-positions", - "tram/service-alerts", - "bus/trip-updates", - "bus/vehicle-positions", - "vline/trip-updates", - "vline/vehicle-positions", - ) - } - - suspend fun start() { - if (started) { - log("GtfsrService", "Tried to start when already started") - return - } - started = true - coroutineScope { - launch { compressJob() } - - while (true) { - val results = mutableMapOf() - types.map { type -> - launch(context = Dispatchers.IO) { - val logger = LogScope("gtfsr $type") - try { - val res = client.get { - url("https://api.opendata.transport.vic.gov.au/opendata/public-transport/gtfs/realtime/v1/${type}") - header("KeyId", Constants.opendataKey) - } - if (!res.status.isSuccess()) { - logger.log("${res.status} | ${res.bodyAsText()}") - } else { - results[type] = res.readRawBytes() - } - } catch (e: Throwable) { - logger.log("$e") - logger.log(e.stackTraceToString()) - } - } - }.joinAll() - - results.forEach { (type, data) -> - val dec = try { - FeedMessage.ADAPTER.decode(data) - } catch (e: Throwable) { - log("gtfsr $type", "Failed to parse proto: $e") - return@forEach - } - val timestamp = dec.header_.timestamp - ?: return@forEach log("gtfsr $type", "Failed to read proto timestamp") - - val time = Instant.ofEpochSecond(timestamp).atZone(ZoneId.systemDefault()) - - val base = File(BASE_DIR, type) - val previousParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7).toString().padStart(2, '0')}") - val currentParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7 + 1).toString().padStart(2, '0')}") - val target = File(currentParent, "${timestamp}.proto") - - if (previousParent.isDirectory) { - enqueueCompression(previousParent) - } - - if (!target.exists()) { - try { - if (!target.parentFile.isDirectory) { - target.parentFile.mkdirs() - } - target.writeBytes(data) - } catch (e: Throwable) { - log("gtfsr $type", "Failed to write ${target}: $e") - } - } - } - delay(10000) - } - } - } - - private val cqueue = mutableSetOf() - private val ignore = mutableSetOf() - private val cmut = Mutex() - private suspend fun enqueueCompression(fd: File) { - cmut.withLock { cqueue.add(fd) } - } - - private suspend fun compressJob() { - while(true) { - while(true) { - val next = cmut.withLock { cqueue.firstOrNull() } - ?: break - if (!next.isDirectory) { - cmut.withLock { cqueue.remove(next) } - continue - } - if (next in ignore) continue - - withContext(Dispatchers.IO) { - val proc = ProcessBuilder( - "tar", "-acf", - "${next.absolutePath}.tar.zst", - next.absolutePath - ).start() - val exitCode = proc.waitFor() - if (exitCode == 0) { - if (next.deleteRecursively()) { - cmut.withLock { cqueue.remove(next) } - } else { - log("CompressJob", "Failed to delete $next") - ignore.add(next) - } - } else { - val msg = proc.errorStream.readAllBytes().decodeToString() - log("CompressJob", "Failed to delete $next (exit code $exitCode") - log("CompressJob", msg) - } - } - } - delay(30000) - } - } -} diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 953d790..b4ed8ad 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -6,7 +6,6 @@ plugins { alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.ksp) alias(libs.plugins.room) - alias(libs.plugins.wire) } room { @@ -62,10 +61,3 @@ dependencies { add("kspIosSimulatorArm64", libs.room.compiler) add("kspJvm", libs.room.compiler) } - -wire { - sourcePath { - srcDir("src/commonMain/proto") - } - kotlin {} -} From b568ed547a4f31adb1afdc991e42373bd80598dd Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 1 Apr 2026 16:43:02 +1100 Subject: [PATCH 13/33] chore: bump and cleanup dependencies --- gradle/libs.versions.toml | 42 +++++++++++++++------------------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70676f5..e171b55 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,38 +3,35 @@ agp = "9.1.0" android-compileSdk = "36" android-minSdk = "24" android-targetSdk = "36" -androidx-activityCompose = "1.12.4" -androidx-appcompat = "1.7.0" -androidx-constraintlayout = "2.2.1" -androidx-core-ktx = "1.15.0" -androidx-espresso-core = "3.6.1" -androidx-lifecycle = "2.9.6" -androidx-material = "1.12.0" -androidx-test-junit = "1.2.1" -compose-multiplatform = "1.11.0-alpha02" +androidx-activity= "1.13.0" +androidx-lifecycle = "2.10.0" +compose-multiplatform = "1.11.0-alpha04" composeunstyled = "1.49.6" coroutines = "1.10.2" geo = "0.8.0" -junit = "4.13.2" -koin = "4.1.1" -kotlin = "2.3.10" +koin = "4.2.0" +kotlin = "2.3.20" kotlinxDatetime = "0.7.1" kotlinxSerializationCsv = "0.2.18" kotlinxSerialization = "1.10.0" ksp = "2.3.4" -ktor = "3.4.0" +ktor = "3.4.1" logback = "1.5.32" maplibre = "0.12.1" material = "1.7.3" -material3 = "1.11.0-alpha02" -okio = "3.16.4" +material3 = "1.11.0-alpha04" +okio = "3.17.0" playServicesLocation = "21.3.0" sqlite = "2.6.2" room = "2.8.4" secretsGradlePlugin = "2.0.1" -wire = "5.5.0" +wire = "6.1.0" [libraries] +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "compose-multiplatform" } compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "compose-multiplatform" } compose-material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "material" } @@ -44,18 +41,11 @@ compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "compose-mu compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "compose-multiplatform" } compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "compose-multiplatform" } composeunstyled = { module = "com.composables:composeunstyled", version.ref = "composeunstyled" } -moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" } -moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } -androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } -androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } -androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } -androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } -koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } @@ -73,11 +63,11 @@ ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "k ktor-server-tests = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre" } +moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" } +moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } -room-common = { group = "androidx.room", name = "room-common", version.ref = "room" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } -room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" } secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } From 91d4fe25a63c61aea2cb10774e9cf54b4c990103 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 1 Apr 2026 17:00:35 +1100 Subject: [PATCH 14/33] feat(server/gtfs): use transaction for imports --- .../moe/lava/banksia/server/Application.kt | 15 -------- .../moe/lava/banksia/server/GtfsImporter.kt | 38 +++++++++++-------- 2 files changed, 22 insertions(+), 31 deletions(-) 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 b33fb9d..cfbb026 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -132,21 +132,6 @@ fun Application.module() { routeDao.stops(routeId) } call.respond(stops.map { it.asModel() }) -// val stops = withContext(Dispatchers.IO) { -// val stopDao by inject() -// val stopTimeDao by inject() -// val tripDao by inject() -// -// tripDao.getByRoute(routeId) -// .map { it.id } -// .let { stopTimeDao.get(it) } -// .flatMap { it.asModel().stopInfos } -// .map { it.stopId } -// .let { stopDao.get(it) } -// .map { it.asModel() } -// } -// call.respond(stops) - } get("/stoptimes/by_stop/{stop_id}") { val stopId = call.parameters["stop_id"]!! diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 794e8de..9a18108 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -1,5 +1,7 @@ package moe.lava.banksia.server +import androidx.room.immediateTransaction +import androidx.room.useWriterConnection import io.ktor.util.logging.Logger import moe.lava.banksia.model.Route import moe.lava.banksia.model.Service @@ -19,25 +21,29 @@ class GtfsImporter( private val log: Logger, ) { suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) { - database.routeDao.deleteAll() - database.serviceDao.deleteAll() - database.shapeDao.deleteAll() - database.stopDao.deleteAll() - database.stopTimeDao.deleteAll() - database.tripDao.deleteAll() + database.useWriterConnection { transactor -> + transactor.immediateTransaction { + database.routeDao.deleteAll() + database.serviceDao.deleteAll() + database.shapeDao.deleteAll() + database.stopDao.deleteAll() + database.stopTimeDao.deleteAll() + database.tripDao.deleteAll() - parser.update(url).collect { chunk -> - when (chunk) { - is GtfsData.RouteChunk -> addRoutes(chunk.routes) - is GtfsData.ServiceChunk -> addServices(chunk.services) - is GtfsData.ShapeChunk -> addShapes(chunk.shapes) - is GtfsData.StopChunk -> addStops(chunk.stops) - is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) - is GtfsData.TripChunk -> addTrips(chunk.trips) + parser.update(url).collect { chunk -> + when (chunk) { + is GtfsData.RouteChunk -> addRoutes(chunk.routes) + is GtfsData.ServiceChunk -> addServices(chunk.services) + is GtfsData.ShapeChunk -> addShapes(chunk.shapes) + is GtfsData.StopChunk -> addStops(chunk.stops) + is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) + is GtfsData.TripChunk -> addTrips(chunk.trips) + } + } + + updateMetadata(date) } } - - updateMetadata(date) } private suspend fun updateMetadata(date: Long) { From c9aeeb99c1dcafbc17ca4dd1cb0acf9da2e64e43 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 1 Apr 2026 17:23:59 +1100 Subject: [PATCH 15/33] fix(server/gtfs): chunk stop times into smaller blocks --- .../kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt | 2 +- .../main/kotlin/moe/lava/banksia/server/GtfsImporter.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index b3fd80c..ed4fd25 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -128,7 +128,7 @@ class GtfsParser( .forEach { fd -> log.info("parsing stop times for ${fd.parent}...") parseStopTimes(fd, trips) { seq -> - seq.chunked(1000000) + seq.chunked(10000) .forEach { emit(GtfsData.StopTimeChunk(it)) } } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 9a18108..8928cda 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -36,7 +36,7 @@ class GtfsImporter( is GtfsData.ServiceChunk -> addServices(chunk.services) is GtfsData.ShapeChunk -> addShapes(chunk.shapes) is GtfsData.StopChunk -> addStops(chunk.stops) - is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) + is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) is GtfsData.TripChunk -> addTrips(chunk.trips) } } @@ -50,24 +50,28 @@ class GtfsImporter( val dao = database.versionMetadataDao log.info("updating metadata...") dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) + log.info("done") } private suspend fun addRoutes(routes: List) { val dao = database.routeDao log.info("inserting routes...") dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) + log.info("done") } private suspend fun addServices(services: List) { val dao = database.serviceDao log.info("inserting services...") dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) + log.info("done") } private suspend fun addShapes(shapes: List) { val dao = database.shapeDao log.info("inserting shapes...") dao.insertOrReplaceAll(*shapes.map { it.asEntity() }.toTypedArray()) + log.info("done") } private suspend fun addStops(stops: List) { @@ -85,17 +89,20 @@ class GtfsImporter( } } dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) + log.info("done") } private suspend fun addStopTimes(stopTimes: List) { val dao = database.stopTimeDao log.info("inserting ${stopTimes.size} stoptimes...") dao.insertOrReplaceAll(*stopTimes.map { it.asEntity() }.toTypedArray()) + log.info("done") } private suspend fun addTrips(trips: List) { val dao = database.tripDao log.info("inserting ${trips.size} trips...") dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) + log.info("done") } } From 58649b6171039d7021654c5100c584dafbb4ad44 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 1 Apr 2026 19:31:31 +1100 Subject: [PATCH 16/33] feat(server/gtfs): service exception support --- .../lava/banksia/server/gtfs/GtfsParser.kt | 19 +- .../gtfs/structures/GtfsServiceException.kt | 11 + .../moe/lava/banksia/server/GtfsImporter.kt | 10 + .../moe.lava.banksia.room.Database/10.json | 477 ++++++++++++++++++ .../moe/lava/banksia/di/CommonModules.kt | 1 + .../lava/banksia/model/ServiceException.kt | 11 + .../kotlin/moe/lava/banksia/room/Database.kt | 7 +- .../banksia/room/dao/ServiceExceptionDao.kt | 29 ++ .../moe/lava/banksia/room/dao/StopTimeDao.kt | 6 +- .../room/entity/ServiceExceptionEntity.kt | 28 + 10 files changed, 595 insertions(+), 4 deletions(-) create mode 100644 server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsServiceException.kt create mode 100644 shared/schemas/moe.lava.banksia.room.Database/10.json create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index ed4fd25..21e239c 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -21,12 +21,14 @@ import moe.lava.banksia.Constants import moe.lava.banksia.model.Route import moe.lava.banksia.model.RouteType import moe.lava.banksia.model.Service +import moe.lava.banksia.model.ServiceException import moe.lava.banksia.model.Shape import moe.lava.banksia.model.Stop import moe.lava.banksia.model.StopTime import moe.lava.banksia.model.Trip import moe.lava.banksia.server.gtfs.structures.GtfsRoute import moe.lava.banksia.server.gtfs.structures.GtfsService +import moe.lava.banksia.server.gtfs.structures.GtfsServiceException import moe.lava.banksia.server.gtfs.structures.GtfsShape import moe.lava.banksia.server.gtfs.structures.GtfsStop import moe.lava.banksia.server.gtfs.structures.GtfsStopTime @@ -39,6 +41,7 @@ import kotlin.time.ExperimentalTime sealed class GtfsData { data class RouteChunk(val routes: List) : GtfsData() data class ServiceChunk(val services: List) : GtfsData() + data class ServiceExceptionChunk(val exceptions: List) : GtfsData() data class ShapeChunk(val shapes: List) : GtfsData() data class StopChunk(val stops: List) : GtfsData() data class StopTimeChunk(val stopTimes: List) : GtfsData() @@ -77,7 +80,7 @@ class GtfsParser( .listFiles { it.isDirectory } .flatMap { d -> d.listFiles { f -> f.extension == "txt" }.toList() } .ifEmpty { extractAll(datasetPath) } - .filter { it.parentFile.name == "2" } +// .filter { it.parentFile.name == "2" } } else { extractAll(datasetPath) } @@ -115,6 +118,10 @@ class GtfsParser( } .associateBy { it.id } + files + .filter { it.name == "calendar_dates.txt" } + .forEach { emit(GtfsData.ServiceExceptionChunk(parseServiceExceptions(it))) } + val trips = files .filter { it.name == "trips.txt" } .flatMap { fd -> @@ -207,6 +214,16 @@ class GtfsParser( ) } } + private fun parseServiceExceptions(fd: File) = + fd.parseCsv() + .map { with(it) { + ServiceException( + serviceId = service_id, + date = LocalDate.parse(date, LocalDate.Formats.ISO_BASIC), + type = exception_type, + ) + } } + private fun parseTrips(fd: File, services: Map) = fd.parseCsv() .map { with(it) { diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsServiceException.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsServiceException.kt new file mode 100644 index 0000000..a31aff0 --- /dev/null +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsServiceException.kt @@ -0,0 +1,11 @@ +package moe.lava.banksia.server.gtfs.structures + +import kotlinx.serialization.Serializable + +@Suppress("PropertyName") +@Serializable +internal data class GtfsServiceException( + val service_id: String, + val date: String, + val exception_type: Int, +) diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 8928cda..04ea373 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -5,6 +5,7 @@ import androidx.room.useWriterConnection import io.ktor.util.logging.Logger import moe.lava.banksia.model.Route import moe.lava.banksia.model.Service +import moe.lava.banksia.model.ServiceException import moe.lava.banksia.model.Shape import moe.lava.banksia.model.Stop import moe.lava.banksia.model.StopTime @@ -25,6 +26,7 @@ class GtfsImporter( transactor.immediateTransaction { database.routeDao.deleteAll() database.serviceDao.deleteAll() + database.serviceExceptionDao.deleteAll() database.shapeDao.deleteAll() database.stopDao.deleteAll() database.stopTimeDao.deleteAll() @@ -34,6 +36,7 @@ class GtfsImporter( when (chunk) { is GtfsData.RouteChunk -> addRoutes(chunk.routes) is GtfsData.ServiceChunk -> addServices(chunk.services) + is GtfsData.ServiceExceptionChunk -> addServiceExceptions(chunk.exceptions) is GtfsData.ShapeChunk -> addShapes(chunk.shapes) is GtfsData.StopChunk -> addStops(chunk.stops) is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) @@ -67,6 +70,13 @@ class GtfsImporter( log.info("done") } + private suspend fun addServiceExceptions(exceptions: List) { + val dao = database.serviceExceptionDao + log.info("inserting exceptions...") + dao.insertOrReplaceAll(*exceptions.map { it.asEntity() }.toTypedArray()) + log.info("done") + } + private suspend fun addShapes(shapes: List) { val dao = database.shapeDao log.info("inserting shapes...") diff --git a/shared/schemas/moe.lava.banksia.room.Database/10.json b/shared/schemas/moe.lava.banksia.room.Database/10.json new file mode 100644 index 0000000..751e946 --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/10.json @@ -0,0 +1,477 @@ +{ + "formatVersion": 1, + "database": { + "version": 10, + "identityHash": "5b90bc800bfae6d22124ea0a6a906ca7", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "ServiceException", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", + "fields": [ + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serviceId", + "date" + ] + }, + "indices": [ + { + "name": "index_ServiceException_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_ServiceException_type", + "unique": false, + "columnNames": [ + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5b90bc800bfae6d22124ea0a6a906ca7')" + ] + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt index 8658342..1a39cfb 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt @@ -10,6 +10,7 @@ val CommonModules = module { single { get().versionMetadataDao } single { get().routeDao } single { get().serviceDao } + single { get().serviceExceptionDao } single { get().shapeDao } single { get().stopDao } single { get().stopTimeDao } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt new file mode 100644 index 0000000..305ede4 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt @@ -0,0 +1,11 @@ +package moe.lava.banksia.model + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable + +@Serializable +data class ServiceException( + val serviceId: String, + val date: LocalDate, + val type: Int, +) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt index 89bc24a..31a5579 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.IO import moe.lava.banksia.room.converter.RouteTypeConverter import moe.lava.banksia.room.dao.RouteDao import moe.lava.banksia.room.dao.ServiceDao +import moe.lava.banksia.room.dao.ServiceExceptionDao import moe.lava.banksia.room.dao.ShapeDao import moe.lava.banksia.room.dao.StopDao import moe.lava.banksia.room.dao.StopTimeDao @@ -16,6 +17,7 @@ import moe.lava.banksia.room.dao.TripDao import moe.lava.banksia.room.dao.VersionMetadataDao import moe.lava.banksia.room.entity.RouteEntity import moe.lava.banksia.room.entity.ServiceEntity +import moe.lava.banksia.room.entity.ServiceExceptionEntity import moe.lava.banksia.room.entity.ShapeEntity import moe.lava.banksia.room.entity.StopEntity import moe.lava.banksia.room.entity.StopTimeEntity @@ -24,10 +26,11 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( - version = 9, + version = 10, entities = [ RouteEntity::class, ServiceEntity::class, + ServiceExceptionEntity::class, ShapeEntity::class, StopEntity::class, StopTimeEntity::class, @@ -37,6 +40,7 @@ import androidx.room.Database as DatabaseAnnotation autoMigrations = [ AutoMigration(from = 1, to = 2), AutoMigration(from = 2, to = 3), + AutoMigration(from = 9, to = 10), ] ) @TypeConverters(RouteTypeConverter::class) @@ -44,6 +48,7 @@ abstract class Database : RoomDatabase() { abstract val versionMetadataDao: VersionMetadataDao abstract val routeDao: RouteDao abstract val serviceDao: ServiceDao + abstract val serviceExceptionDao: ServiceExceptionDao abstract val shapeDao: ShapeDao abstract val stopDao: StopDao abstract val stopTimeDao: StopTimeDao diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt new file mode 100644 index 0000000..123b0c6 --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.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.ServiceExceptionEntity + +@Dao +interface ServiceExceptionDao { + @Query("SELECT * FROM ServiceException") + suspend fun getAll(): List + + @Query("SELECT * FROM ServiceException WHERE serviceId == :id") + suspend fun get(id: String): List + + @Insert + suspend fun insertAll(vararg exceptions: ServiceExceptionEntity) + + @Insert(onConflict = REPLACE) + suspend fun insertOrReplaceAll(vararg exceptions: ServiceExceptionEntity) + + @Delete + suspend fun delete(service: ServiceExceptionEntity) + + @Query("DELETE FROM ServiceException") + suspend fun deleteAll() +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt index d5e1744..cd377cc 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 @@ -22,11 +22,13 @@ interface StopTimeDao { suspend fun getForStop(stopId: String): List @Query(""" - SELECT * FROM StopTime + SELECT DISTINCT StopTime.* FROM StopTime INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` INNER JOIN Trip ON Trip.serviceId == Service.id + LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date WHERE StopTime.tripId == Trip.id - AND StopTime.stopId == :stopId + AND StopTime.stopId == :stopId + AND ServiceException.type IS NULL """) suspend fun getForStopDated(stopId: String, days: Int, date: Int): List diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt new file mode 100644 index 0000000..313246d --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt @@ -0,0 +1,28 @@ +package moe.lava.banksia.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import kotlinx.datetime.LocalDate +import moe.lava.banksia.model.ServiceException + +@Entity( + "ServiceException", + primaryKeys = ["serviceId", "date"] +) +data class ServiceExceptionEntity( + @ColumnInfo(index = true) val serviceId: String, + val date: Int, + @ColumnInfo(index = true) val type: Int, +) { + fun asModel() = ServiceException( + serviceId, + LocalDate.fromEpochDays(date), + type, + ) +} + +fun ServiceException.asEntity() = ServiceExceptionEntity( + serviceId, + date.toEpochDays().toInt(), + type, +) From c55e3a32322f3036e63a5535e6c9810d9d5c64a5 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 1 Apr 2026 20:37:58 +1100 Subject: [PATCH 17/33] feat(server): better support for parent stops - add datafixer to add parent stops for likely candidates - this is mainly for bus hubs, the heuristic is the existence of a platform code and missing parent - use parent stops as default in route_stops - support parent stops for stoptime querying --- .../moe/lava/banksia/server/Application.kt | 12 +- .../moe/lava/banksia/server/GtfsDataFixer.kt | 43 ++ .../lava/banksia/server/di/ServerModules.kt | 2 + .../moe.lava.banksia.room.Database/11.json | 498 ++++++++++++++++++ .../kotlin/moe/lava/banksia/model/Stop.kt | 2 +- .../kotlin/moe/lava/banksia/room/Database.kt | 20 +- .../moe/lava/banksia/room/dao/RouteDao.kt | 21 +- .../moe/lava/banksia/room/dao/StopDao.kt | 10 + .../moe/lava/banksia/room/dao/StopTimeDao.kt | 2 +- .../lava/banksia/room/entity/StopEntity.kt | 17 +- .../lava/banksia/room/entity/TripEntity.kt | 2 +- 11 files changed, 616 insertions(+), 13 deletions(-) create mode 100644 server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt create mode 100644 shared/schemas/moe.lava.banksia.room.Database/11.json 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 cfbb026..2d33793 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -54,6 +54,13 @@ fun Application.module() { } routing { + if (Constants.devMode) { + get("/fixup") { + call.respondText("received") + val fixer by inject() + fixer.addParentsToStops() + } + } get("/update") { val key = call.parameters["key"] if (key != Constants.updateKey) { @@ -66,8 +73,11 @@ fun Application.module() { ?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip" call.respondText("received") launch(context = Dispatchers.IO) { + val fixer by inject() val importer by inject() importer.import(datasetUrl) + + fixer.addParentsToStops() } } @@ -123,7 +133,7 @@ fun Application.module() { } get("/route_stops/{route_id}") { val routeId = call.parameters["route_id"]!! - val useParent = call.queryParameters["parent"] in listOf("true", "1") + val useParent = call.queryParameters["parent"] !in listOf("false", "0") val stops = withContext(Dispatchers.IO) { val routeDao by inject() if (useParent) diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt new file mode 100644 index 0000000..d3d307e --- /dev/null +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -0,0 +1,43 @@ +package moe.lava.banksia.server + +import moe.lava.banksia.room.Database +import moe.lava.banksia.room.entity.StopEntity +import moe.lava.banksia.util.log +import java.security.MessageDigest + +class GtfsDataFixer( + private val database: Database, +) { + suspend fun addParentsToStops() { + val dao = database.stopDao + val stops = dao.getAllParentless() + stops + .groupBy { it.name.split("/")[0] } + .filter { (_, stops) -> stops.size > 1 } + .forEach { (name, stops) -> + val avgLat = stops.map { it.lat }.average() + val avgLng = stops.map { it.lng }.average() + val hash = name.sha256().substring(0, 7) + val parentId = "bsia:df1:$hash" + val parent = StopEntity( + id = parentId, + name = name, + lat = avgLat, + lng = avgLng, + parent = "", + hasWheelChairBoarding = stops.all { it.hasWheelChairBoarding }, + level = "", + platformCode = "", + ) + log("datafixer", "inserting ${parentId} for ${stops.size} children") + dao.insertAll(parent) + database.stopDao.updateParents(stops.map { it.id }, parentId) + } + } +} + +private fun String.sha256() = + MessageDigest + .getInstance("SHA-256") + .digest(this.toByteArray()) + .joinToString("") { "%02x".format(it) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index d51379e..6ee4365 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,6 +1,7 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient +import moe.lava.banksia.server.GtfsDataFixer import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser import moe.lava.banksia.server.gtfsrt.GtfsrtService @@ -12,5 +13,6 @@ val ServerModules = module { singleOf(::GtfsParser) singleOf(::GtfsrtService) + singleOf(::GtfsDataFixer) singleOf(::GtfsImporter) } diff --git a/shared/schemas/moe.lava.banksia.room.Database/11.json b/shared/schemas/moe.lava.banksia.room.Database/11.json new file mode 100644 index 0000000..6fc2976 --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/11.json @@ -0,0 +1,498 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "c4be3d0c2a25f8c5c33132646a070d0e", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "ServiceException", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", + "fields": [ + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serviceId", + "date" + ] + }, + "indices": [ + { + "name": "index_ServiceException_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_ServiceException_type", + "unique": false, + "columnNames": [ + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT" + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ], + "foreignKeys": [ + { + "table": "Stop", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "parent" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c4be3d0c2a25f8c5c33132646a070d0e')" + ] + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt index df10a58..e1060bb 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt @@ -8,7 +8,7 @@ data class Stop( val id: String, val name: String, val pos: Point, - val parent: String, + val parent: String?, val hasWheelChairBoarding: Boolean, val level: String, val platformCode: String, diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt index 31a5579..7c39ebf 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -3,7 +3,11 @@ package moe.lava.banksia.room import androidx.room.AutoMigration import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.room.util.foreignKeyCheck +import androidx.sqlite.SQLiteConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import androidx.sqlite.execSQL import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import moe.lava.banksia.room.converter.RouteTypeConverter @@ -26,7 +30,7 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( - version = 10, + version = 11, entities = [ RouteEntity::class, ServiceEntity::class, @@ -59,7 +63,21 @@ abstract class Database : RoomDatabase() { base.fallbackToDestructiveMigration(true) .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.IO) + .addMigrations(MIGRATION_10_11) // .fallbackToDestructiveMigration(true) .build() } } + +val MIGRATION_10_11 = object : Migration(10, 11) { + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_Stop` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)") + connection.execSQL("INSERT INTO `_new_Stop` (`id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode`) SELECT `id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode` FROM `Stop`") + connection.execSQL("UPDATE `_new_Stop` SET `parent` = NULL WHERE `parent` == \"\"") + connection.execSQL("DROP TABLE `Stop`") + connection.execSQL("ALTER TABLE `_new_Stop` RENAME TO `Stop`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `Stop` (`parent`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `Trip` (`serviceId`)") + foreignKeyCheck(connection, "Stop") + } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt index 0174f0f..94ce892 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt @@ -37,13 +37,22 @@ interface RouteDao { """) suspend fun stops(id: String): List + // I vibecoded this, sorry @Query(""" - SELECT Stop.* FROM Stop - INNER JOIN Stop Child ON Child.parent == Stop.id - INNER JOIN StopTime ON StopTime.stopId == Child.id - INNER JOIN Trip ON Trip.id == StopTime.tripId - WHERE Trip.routeId == :id - GROUP BY Stop.id + WITH Tree AS ( + SELECT Stop.* FROM Stop + INNER JOIN StopTime ON StopTime.stopId == Stop.id + INNER JOIN Trip ON Trip.id == StopTime.tripId + WHERE Trip.routeId == :id + GROUP BY Stop.id + + UNION ALL + + SELECT s.* + FROM Stop s + INNER JOIN Tree t ON s.id = t.parent + ) + SELECT DISTINCT * FROM Tree WHERE parent IS NULL; """) suspend fun stopsParent(id: String): List } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt index f6b2ef2..869ae29 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt @@ -12,6 +12,13 @@ interface StopDao { @Query("SELECT * FROM Stop") suspend fun getAll(): List + @Query(""" + SELECT * FROM Stop + WHERE platformCode <> "" + AND parent == "" + """) + suspend fun getAllParentless(): List + @Query("SELECT * FROM Stop WHERE id == :id") suspend fun get(id: String): StopEntity? @@ -29,4 +36,7 @@ interface StopDao { @Query("DELETE FROM Stop") suspend fun deleteAll() + + @Query("UPDATE Stop SET parent = :parent WHERE id IN (:ids)") + suspend fun updateParents(ids: List, parent: String) } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt index cd377cc..82e0e4b 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 @@ -27,7 +27,7 @@ interface StopTimeDao { INNER JOIN Trip ON Trip.serviceId == Service.id LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date WHERE StopTime.tripId == Trip.id - AND StopTime.stopId == :stopId + AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) AND ServiceException.type IS NULL """) suspend fun getForStopDated(stopId: String, days: Int, date: Int): List diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt index 9c6cf15..9ce7bfb 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt @@ -2,17 +2,30 @@ package moe.lava.banksia.room.entity import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.Companion.SET_NULL import androidx.room.PrimaryKey import moe.lava.banksia.model.Stop import moe.lava.banksia.util.Point -@Entity("Stop") +@Entity( + "Stop", + foreignKeys = [ + ForeignKey( + StopEntity::class, + parentColumns = ["id"], + childColumns = ["parent"], + onDelete = SET_NULL, + deferred = true, + ), + ] +) data class StopEntity( @PrimaryKey val id: String, val name: String, val lat: Double, val lng: Double, - @ColumnInfo(index = true) val parent: String, + @ColumnInfo(index = true) val parent: String?, val hasWheelChairBoarding: Boolean, val level: String, val platformCode: String, diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt index 3753d44..12bda02 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 @@ -15,7 +15,7 @@ import moe.lava.banksia.model.Trip ForeignKey(ServiceEntity::class, parentColumns = ["id"], childColumns = ["serviceId"], onDelete = CASCADE), ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE), ], - indices = [Index("shapeId")], + indices = [Index("shapeId"), Index("serviceId")], ) data class TripEntity( @PrimaryKey val id: String, From 104a77b27e1895f654529d382378cb60415fc76b Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 1 Apr 2026 22:48:04 +1100 Subject: [PATCH 18/33] refactor: split up room into a module, and move client module --- .../client/data/stop/StopLocalDataSource.kt | 12 ----- server/build.gradle.kts | 2 + .../moe/lava/banksia/server/Application.kt | 17 +++--- .../moe/lava/banksia/server/GtfsDataFixer.kt | 4 +- .../moe/lava/banksia/server/GtfsImporter.kt | 4 +- .../lava/banksia/server/di/ServerModules.kt | 3 ++ settings.gradle.kts | 3 +- {client => shared/data}/build.gradle.kts | 23 +++++--- .../lava/banksia/core/data/DataDiModule.kt | 25 +++++---- .../data/repositories}/RouteRepository.kt | 8 +-- .../core/data/repositories}/StopRepository.kt | 8 +-- .../data/repositories}/StopTimeRepository.kt | 8 +-- .../sources}/route/RouteLocalDataSource.kt | 8 +-- .../sources}/route/RouteRemoteDataSource.kt | 4 +- .../data/sources/stop/StopLocalDataSource.kt | 12 +++++ .../sources}/stop/StopRemoteDataSource.kt | 4 +- .../stoptime/StopTimeLocalDataSource.kt | 6 +-- .../stoptime/StopTimeRemoteDataSource.kt | 4 +- .../sources}/trip/TripRemoteDataSource.kt | 4 +- shared/room/build.gradle.kts | 54 +++++++++++++++++++ .../1.json | 0 .../10.json | 0 .../11.json | 0 .../2.json | 0 .../3.json | 0 .../4.json | 0 .../5.json | 0 .../6.json | 0 .../7.json | 0 .../8.json | 0 .../9.json | 0 .../core/room/RoomDiModule.android.kt} | 8 +-- .../moe/lava/banksia/core}/room/Database.kt | 36 ++++++------- .../lava/banksia/core/room/RoomDiModule.kt} | 18 +++++-- .../room/converter/RouteTypeConverter.kt | 2 +- .../room/converter/ShapePathConverter.kt | 2 +- .../lava/banksia/core}/room/dao/RouteDao.kt | 6 +-- .../lava/banksia/core}/room/dao/ServiceDao.kt | 4 +- .../core}/room/dao/ServiceExceptionDao.kt | 4 +- .../lava/banksia/core}/room/dao/ShapeDao.kt | 4 +- .../lava/banksia/core}/room/dao/StopDao.kt | 4 +- .../banksia/core}/room/dao/StopTimeDao.kt | 4 +- .../lava/banksia/core}/room/dao/TripDao.kt | 4 +- .../core}/room/dao/VersionMetadataDao.kt | 4 +- .../banksia/core}/room/entity/RouteEntity.kt | 2 +- .../core}/room/entity/ServiceEntity.kt | 2 +- .../room/entity/ServiceExceptionEntity.kt | 2 +- .../banksia/core}/room/entity/ShapeEntity.kt | 4 +- .../banksia/core}/room/entity/StopEntity.kt | 2 +- .../core}/room/entity/StopTimeEntity.kt | 2 +- .../banksia/core}/room/entity/TripEntity.kt | 2 +- .../room/entity/VersionMetadataEntity.kt | 2 +- .../banksia/core/room/RoomDiModule.ios.kt} | 8 +-- .../banksia/core/room/RoomDiModule.jvm.kt} | 8 +-- .../moe/lava/banksia/di/CoreDiModule.kt | 5 ++ .../moe/lava/banksia/di/PlatformModule.kt | 21 -------- ui/build.gradle.kts | 2 +- .../kotlin/moe/lava/banksia/ui/App.kt | 4 +- .../moe/lava/banksia/ui/di/AppModule.kt | 5 +- .../ui/screens/map/MapScreenViewModel.kt | 6 +-- 60 files changed, 220 insertions(+), 170 deletions(-) delete mode 100644 client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt rename {client => shared/data}/build.gradle.kts (67%) rename client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt (65%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/repository => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories}/RouteRepository.kt (71%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/repository => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories}/StopRepository.kt (69%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/repository => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories}/StopTimeRepository.kt (55%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/data => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources}/route/RouteLocalDataSource.kt (53%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/data => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources}/route/RouteRemoteDataSource.kt (71%) create mode 100644 shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt rename {client/src/commonMain/kotlin/moe/lava/banksia/client/data => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources}/stop/StopRemoteDataSource.kt (73%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/data => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources}/stoptime/StopTimeLocalDataSource.kt (83%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/data => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources}/stoptime/StopTimeRemoteDataSource.kt (92%) rename {client/src/commonMain/kotlin/moe/lava/banksia/client/data => shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources}/trip/TripRemoteDataSource.kt (82%) create mode 100644 shared/room/build.gradle.kts rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/1.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/10.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/11.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/2.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/3.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/4.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/5.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/6.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/7.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/8.json (100%) rename shared/{schemas/moe.lava.banksia.room.Database => room/schemas/moe.lava.banksia.core.room.Database}/9.json (100%) rename shared/{src/androidMain/kotlin/moe/lava/banksia/di/PlatformModule.android.kt => room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt} (71%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/Database.kt (75%) rename shared/{src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt => room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt} (51%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/converter/RouteTypeConverter.kt (84%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/converter/ShapePathConverter.kt (96%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/RouteDao.kt (91%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/ServiceDao.kt (87%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/ServiceExceptionDao.kt (88%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/ShapeDao.kt (86%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/StopDao.kt (91%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/StopTimeDao.kt (94%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/TripDao.kt (89%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/dao/VersionMetadataDao.kt (89%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/RouteEntity.kt (90%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/ServiceEntity.kt (94%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/ServiceExceptionEntity.kt (93%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/ShapeEntity.kt (79%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/StopEntity.kt (95%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/StopTimeEntity.kt (97%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/TripEntity.kt (97%) rename shared/{src/commonMain/kotlin/moe/lava/banksia => room/src/commonMain/kotlin/moe/lava/banksia/core}/room/entity/VersionMetadataEntity.kt (91%) rename shared/{src/iosMain/kotlin/moe/lava/banksia/di/PlatformModule.ios.kt => room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt} (56%) rename shared/{src/jvmMain/kotlin/moe/lava/banksia/di/PlatformModule.jvm.kt => room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt} (66%) create mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt delete mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/di/PlatformModule.kt diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt b/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt deleted file mode 100644 index 486aae0..0000000 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopLocalDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package moe.lava.banksia.client.data.stop - -import moe.lava.banksia.model.Stop -import moe.lava.banksia.room.dao.RouteDao -import moe.lava.banksia.room.dao.StopDao -import moe.lava.banksia.room.entity.asEntity - -class StopLocalDataSource(private val dao: StopDao, private val routeDao: RouteDao) { - suspend fun get(id: String) = dao.get(id) - suspend fun getByRoute(id: String) = routeDao.stops(id) - suspend fun save(vararg stops: Stop) = dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) -} diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 39f75f3..e982c8c 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -39,4 +39,6 @@ dependencies { implementation(libs.sqlite.bundled) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) + + implementation(projects.shared.room) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt index 2d33793..eb0de33 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -19,12 +19,11 @@ 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.core.room.dao.RouteDao +import moe.lava.banksia.core.room.dao.StopDao +import moe.lava.banksia.core.room.dao.StopTimeDao +import moe.lava.banksia.core.room.dao.VersionMetadataDao import moe.lava.banksia.model.atDate -import moe.lava.banksia.room.dao.RouteDao -import moe.lava.banksia.room.dao.StopDao -import moe.lava.banksia.room.dao.StopTimeDao -import moe.lava.banksia.room.dao.VersionMetadataDao import moe.lava.banksia.server.di.ServerModules import moe.lava.banksia.server.gtfsrt.GtfsrtService import moe.lava.banksia.util.serialise @@ -44,14 +43,12 @@ fun Application.module() { } install(Koin) { modules(module { single { log } }) - modules(CommonModules, ServerModules) + modules(ServerModules) } + val gtfsr by inject() @Suppress("KotlinConstantConditions") - if (!Constants.devMode) { - val gtfsr by inject() - launch { gtfsr.start(this, true) } - } + launch { gtfsr.start(this, !Constants.devMode) } routing { if (Constants.devMode) { diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt index d3d307e..10d6f11 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.server -import moe.lava.banksia.room.Database -import moe.lava.banksia.room.entity.StopEntity +import moe.lava.banksia.core.room.Database +import moe.lava.banksia.core.room.entity.StopEntity import moe.lava.banksia.util.log import java.security.MessageDigest diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 04ea373..060457a 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -3,6 +3,8 @@ package moe.lava.banksia.server import androidx.room.immediateTransaction import androidx.room.useWriterConnection import io.ktor.util.logging.Logger +import moe.lava.banksia.core.room.Database +import moe.lava.banksia.core.room.entity.asEntity import moe.lava.banksia.model.Route import moe.lava.banksia.model.Service import moe.lava.banksia.model.ServiceException @@ -10,8 +12,6 @@ import moe.lava.banksia.model.Shape import moe.lava.banksia.model.Stop import moe.lava.banksia.model.StopTime import moe.lava.banksia.model.Trip -import moe.lava.banksia.room.Database -import moe.lava.banksia.room.entity.asEntity import moe.lava.banksia.server.gtfs.GtfsData import moe.lava.banksia.server.gtfs.GtfsParser import kotlin.time.Clock diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index 6ee4365..7c7fc0b 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,6 +1,7 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient +import moe.lava.banksia.core.room.roomDiModule import moe.lava.banksia.server.GtfsDataFixer import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser @@ -9,6 +10,8 @@ import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val ServerModules = module { + includes(roomDiModule) + single { HttpClient() } singleOf(::GtfsParser) singleOf(::GtfsrtService) diff --git a/settings.gradle.kts b/settings.gradle.kts index 72f0696..7096522 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,11 +32,12 @@ dependencyResolutionManagement { } include(":androidApp") -include(":client") include(":server") include(":server:gtfs") include(":server:gtfs_rt") include(":shared") +include(":shared:data") +include(":shared:room") include(":ui") include(":ui:maps") include(":ui:shared") diff --git a/client/build.gradle.kts b/shared/data/build.gradle.kts similarity index 67% rename from client/build.gradle.kts rename to shared/data/build.gradle.kts index 29dbc08..1d4b956 100644 --- a/client/build.gradle.kts +++ b/shared/data/build.gradle.kts @@ -4,11 +4,12 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.ksp) } kotlin { android { - namespace = "moe.lava.banksia.client" + namespace = "moe.lava.banksia.core.data" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { @@ -23,21 +24,29 @@ kotlin { iosArm64() iosSimulatorArm64() + jvm() + sourceSets { androidMain.dependencies { - implementation(libs.compose.ui.tooling.preview) - implementation(libs.androidx.activity.compose) - implementation(libs.kotlinx.coroutines.android) - implementation(libs.play.services.location) + implementation(libs.koin.compose) + implementation(libs.ktor.client.okhttp) } commonMain.dependencies { + implementation(libs.okio) implementation(libs.koin.core) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.datetime) implementation(libs.ktor.client.core) implementation(libs.ktor.client.contentnegotiation) implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.serialization.protobuf) + implementation(projects.shared) + implementation(projects.shared.room) + } + iosMain.dependencies { + implementation(libs.ktor.client.darwin) } } } diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt similarity index 65% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt index f22c7db..86694cc 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/di/ClientModule.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.client.di +package moe.lava.banksia.core.data import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpSend @@ -8,21 +8,24 @@ import io.ktor.client.plugins.plugin import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json import moe.lava.banksia.Constants -import moe.lava.banksia.client.data.route.RouteLocalDataSource -import moe.lava.banksia.client.data.route.RouteRemoteDataSource -import moe.lava.banksia.client.data.stop.StopLocalDataSource -import moe.lava.banksia.client.data.stop.StopRemoteDataSource -import moe.lava.banksia.client.data.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.client.data.stoptime.StopTimeRemoteDataSource -import moe.lava.banksia.client.repository.RouteRepository -import moe.lava.banksia.client.repository.StopRepository -import moe.lava.banksia.client.repository.StopTimeRepository +import moe.lava.banksia.core.data.repositories.RouteRepository +import moe.lava.banksia.core.data.repositories.StopRepository +import moe.lava.banksia.core.data.repositories.StopTimeRepository +import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource +import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource +import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource +import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource +import moe.lava.banksia.core.room.roomDiModule import moe.lava.banksia.data.ptv.PtvService import moe.lava.banksia.util.log import org.koin.core.module.dsl.singleOf import org.koin.dsl.module -val ClientModule = module { +val dataDiModule = module { + includes(roomDiModule) + // HTTP Clients singleOf(::PtvService) single { diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt similarity index 71% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt index 22a6bcc..fdeb95f 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/RouteRepository.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.client.repository +package moe.lava.banksia.core.data.repositories import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.client.data.route.RouteLocalDataSource -import moe.lava.banksia.client.data.route.RouteRemoteDataSource +import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource +import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource -class RouteRepository( +class RouteRepository internal constructor( private val local: RouteLocalDataSource, private val remote: RouteRemoteDataSource, ) { diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt similarity index 69% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt index 690616a..d83a9cd 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopRepository.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.client.repository +package moe.lava.banksia.core.data.repositories import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.client.data.stop.StopLocalDataSource -import moe.lava.banksia.client.data.stop.StopRemoteDataSource +import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource +import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource -class StopRepository( +class StopRepository internal constructor( private val local: StopLocalDataSource, private val remote: StopRemoteDataSource, ) { diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt similarity index 55% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt index 4f54840..50cc66a 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/repository/StopTimeRepository.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt @@ -1,10 +1,10 @@ -package moe.lava.banksia.client.repository +package moe.lava.banksia.core.data.repositories -import moe.lava.banksia.client.data.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.client.data.stoptime.StopTimeRemoteDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource import moe.lava.banksia.model.StopTimeDated -class StopTimeRepository( +class StopTimeRepository internal constructor( private val local: StopTimeLocalDataSource, private val remote: StopTimeRemoteDataSource, ) { diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt similarity index 53% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt index e89d4e9..79d432a 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteLocalDataSource.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt @@ -1,10 +1,10 @@ -package moe.lava.banksia.client.data.route +package moe.lava.banksia.core.data.sources.route +import moe.lava.banksia.core.room.dao.RouteDao +import moe.lava.banksia.core.room.entity.asEntity import moe.lava.banksia.model.Route -import moe.lava.banksia.room.dao.RouteDao -import moe.lava.banksia.room.entity.asEntity -class RouteLocalDataSource(private val dao: RouteDao) { +internal class RouteLocalDataSource(private val dao: RouteDao) { suspend fun get(id: String) = dao.get(id) suspend fun getAll() = dao.getAll() suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt similarity index 71% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt index cbe9804..a111e82 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/route/RouteRemoteDataSource.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.client.data.route +package moe.lava.banksia.core.data.sources.route import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get import moe.lava.banksia.model.Route -class RouteRemoteDataSource(val client: HttpClient) { +internal class RouteRemoteDataSource(val client: HttpClient) { suspend fun get(id: String) = client.get("routes/${id}").body() suspend fun getAll() = client.get("routes").body>() } diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt new file mode 100644 index 0000000..c8b429b --- /dev/null +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt @@ -0,0 +1,12 @@ +package moe.lava.banksia.core.data.sources.stop + +import moe.lava.banksia.core.room.dao.RouteDao +import moe.lava.banksia.core.room.dao.StopDao +import moe.lava.banksia.core.room.entity.asEntity +import moe.lava.banksia.model.Stop + +internal class StopLocalDataSource(private val dao: StopDao, private val routeDao: RouteDao) { + suspend fun get(id: String) = dao.get(id) + suspend fun getByRoute(id: String) = routeDao.stops(id) + suspend fun save(vararg stops: Stop) = dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) +} diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt similarity index 73% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt index 47c2f80..c63f718 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stop/StopRemoteDataSource.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.client.data.stop +package moe.lava.banksia.core.data.sources.stop import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get import moe.lava.banksia.model.Stop -class StopRemoteDataSource(val client: HttpClient) { +internal class StopRemoteDataSource(val client: HttpClient) { suspend fun get(id: String) = client.get("stops/${id}").body() suspend fun getByRoute(id: String) = client.get("route_stops/${id}").body>() } diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt similarity index 83% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt index 3640b1f..cf09cc0 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeLocalDataSource.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt @@ -1,15 +1,15 @@ -package moe.lava.banksia.client.data.stoptime +package moe.lava.banksia.core.data.sources.stoptime import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn +import moe.lava.banksia.core.room.dao.StopTimeDao import moe.lava.banksia.model.StopTimeDated import moe.lava.banksia.model.atDate -import moe.lava.banksia.room.dao.StopTimeDao import moe.lava.banksia.util.serialise import kotlin.time.Clock -class StopTimeLocalDataSource( +internal class StopTimeLocalDataSource( private val stopTimeDao: StopTimeDao, ) { suspend fun getAtStop( diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt similarity index 92% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt index baf26e7..ef841bb 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/stoptime/StopTimeRemoteDataSource.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.client.data.stoptime +package moe.lava.banksia.core.data.sources.stoptime import io.ktor.client.HttpClient import io.ktor.client.call.body @@ -10,7 +10,7 @@ import kotlinx.datetime.todayIn import moe.lava.banksia.model.StopTimeDated import kotlin.time.Clock -class StopTimeRemoteDataSource( +internal class StopTimeRemoteDataSource( private val client: HttpClient, ) { suspend fun getAtStop( diff --git a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt similarity index 82% rename from client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt rename to shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt index 8b46fbd..2055414 100644 --- a/client/src/commonMain/kotlin/moe/lava/banksia/client/data/trip/TripRemoteDataSource.kt +++ b/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.client.data.trip +package moe.lava.banksia.core.data.sources.trip import io.ktor.client.HttpClient import kotlinx.datetime.DayOfWeek @@ -7,7 +7,7 @@ import kotlinx.datetime.todayIn import moe.lava.banksia.model.Trip import kotlin.time.Clock -class TripRemoteDataSource( +internal class TripRemoteDataSource( private val client: HttpClient, ) { suspend fun get( diff --git a/shared/room/build.gradle.kts b/shared/room/build.gradle.kts new file mode 100644 index 0000000..7f0f5eb --- /dev/null +++ b/shared/room/build.gradle.kts @@ -0,0 +1,54 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.ksp) + alias(libs.plugins.room) +} + +room { + schemaDirectory("$projectDir/schemas") +} + +kotlin { + android { + namespace = "moe.lava.banksia.core.room" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } + + iosArm64() + iosSimulatorArm64() + + jvm() + + sourceSets { + commonMain.dependencies { + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.room.runtime) + implementation(libs.sqlite.bundled) + + implementation(projects.shared) + } + } +} + +dependencies { + add("kspAndroid", libs.room.compiler) + add("kspIosArm64", libs.room.compiler) + add("kspIosSimulatorArm64", libs.room.compiler) + add("kspJvm", libs.room.compiler) +} diff --git a/shared/schemas/moe.lava.banksia.room.Database/1.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/1.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/1.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/1.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/10.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/10.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/10.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/10.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/11.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/11.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/11.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/11.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/2.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/2.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/2.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/2.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/3.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/3.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/3.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/3.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/4.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/4.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/4.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/4.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/5.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/5.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/5.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/5.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/6.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/6.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/6.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/6.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/7.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/7.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/7.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/7.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/8.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/8.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/8.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/8.json diff --git a/shared/schemas/moe.lava.banksia.room.Database/9.json b/shared/room/schemas/moe.lava.banksia.core.room.Database/9.json similarity index 100% rename from shared/schemas/moe.lava.banksia.room.Database/9.json rename to shared/room/schemas/moe.lava.banksia.core.room.Database/9.json diff --git a/shared/src/androidMain/kotlin/moe/lava/banksia/di/PlatformModule.android.kt b/shared/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt similarity index 71% rename from shared/src/androidMain/kotlin/moe/lava/banksia/di/PlatformModule.android.kt rename to shared/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt index 0447f4b..8cd01e6 100644 --- a/shared/src/androidMain/kotlin/moe/lava/banksia/di/PlatformModule.android.kt +++ b/shared/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt @@ -1,12 +1,10 @@ -package moe.lava.banksia.di +package moe.lava.banksia.core.room import android.content.Context import androidx.room.Room import androidx.room.RoomDatabase -import moe.lava.banksia.room.Database import org.koin.core.parameter.ParametersHolder import org.koin.core.scope.Scope -import org.koin.dsl.module class AndroidDatabaseBuilder(val ctx: Context) : PlatformDatabaseBuilder { override fun getBuilder(): RoomDatabase.Builder { @@ -19,7 +17,5 @@ class AndroidDatabaseBuilder(val ctx: Context) : PlatformDatabaseBuilder { } } -actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = +internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = AndroidDatabaseBuilder(get()) - -internal actual val ExtPlatformModule = module { } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt similarity index 75% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt index 7c39ebf..006b749 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room +package moe.lava.banksia.core.room import androidx.room.AutoMigration import androidx.room.RoomDatabase @@ -10,23 +10,23 @@ import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.sqlite.execSQL import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO -import moe.lava.banksia.room.converter.RouteTypeConverter -import moe.lava.banksia.room.dao.RouteDao -import moe.lava.banksia.room.dao.ServiceDao -import moe.lava.banksia.room.dao.ServiceExceptionDao -import moe.lava.banksia.room.dao.ShapeDao -import moe.lava.banksia.room.dao.StopDao -import moe.lava.banksia.room.dao.StopTimeDao -import moe.lava.banksia.room.dao.TripDao -import moe.lava.banksia.room.dao.VersionMetadataDao -import moe.lava.banksia.room.entity.RouteEntity -import moe.lava.banksia.room.entity.ServiceEntity -import moe.lava.banksia.room.entity.ServiceExceptionEntity -import moe.lava.banksia.room.entity.ShapeEntity -import moe.lava.banksia.room.entity.StopEntity -import moe.lava.banksia.room.entity.StopTimeEntity -import moe.lava.banksia.room.entity.TripEntity -import moe.lava.banksia.room.entity.VersionMetadataEntity +import moe.lava.banksia.core.room.converter.RouteTypeConverter +import moe.lava.banksia.core.room.dao.RouteDao +import moe.lava.banksia.core.room.dao.ServiceDao +import moe.lava.banksia.core.room.dao.ServiceExceptionDao +import moe.lava.banksia.core.room.dao.ShapeDao +import moe.lava.banksia.core.room.dao.StopDao +import moe.lava.banksia.core.room.dao.StopTimeDao +import moe.lava.banksia.core.room.dao.TripDao +import moe.lava.banksia.core.room.dao.VersionMetadataDao +import moe.lava.banksia.core.room.entity.RouteEntity +import moe.lava.banksia.core.room.entity.ServiceEntity +import moe.lava.banksia.core.room.entity.ServiceExceptionEntity +import moe.lava.banksia.core.room.entity.ShapeEntity +import moe.lava.banksia.core.room.entity.StopEntity +import moe.lava.banksia.core.room.entity.StopTimeEntity +import moe.lava.banksia.core.room.entity.TripEntity +import moe.lava.banksia.core.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt similarity index 51% rename from shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt index 1a39cfb..67d4fd2 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CommonModules.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt @@ -1,12 +1,14 @@ -package moe.lava.banksia.di +package moe.lava.banksia.core.room -import moe.lava.banksia.room.Database +import androidx.room.RoomDatabase +import org.koin.core.parameter.ParametersHolder +import org.koin.core.scope.Scope import org.koin.dsl.module -val CommonModules = module { - includes(PlatformModule) - +val roomDiModule = module { + single { provideDatabaseBuilder(it) } single { Database.build(get().getBuilder()) } + single { get().versionMetadataDao } single { get().routeDao } single { get().serviceDao } @@ -16,3 +18,9 @@ val CommonModules = module { single { get().stopTimeDao } single { get().tripDao } } + +internal interface PlatformDatabaseBuilder { + fun getBuilder(): RoomDatabase.Builder +} + +internal expect fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt similarity index 84% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt index 9ceb612..f9da212 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/RouteTypeConverter.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.converter +package moe.lava.banksia.core.room.converter import androidx.room.TypeConverter import moe.lava.banksia.model.RouteType diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/ShapePathConverter.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt similarity index 96% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/ShapePathConverter.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt index 08a8064..5304c33 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/converter/ShapePathConverter.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.converter +package moe.lava.banksia.core.room.converter import androidx.room.TypeConverter import moe.lava.banksia.model.ShapePath diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt similarity index 91% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt index 94ce892..c791f81 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt @@ -1,12 +1,12 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.room.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query -import moe.lava.banksia.room.entity.RouteEntity -import moe.lava.banksia.room.entity.StopEntity +import moe.lava.banksia.core.room.entity.RouteEntity +import moe.lava.banksia.core.room.entity.StopEntity @Dao interface RouteDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt similarity index 87% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt index 6fc2906..e459cdf 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.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 +import moe.lava.banksia.core.room.entity.ServiceEntity @Dao interface ServiceDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt similarity index 88% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt index 123b0c6..86feb75 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ServiceExceptionDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.room.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query -import moe.lava.banksia.room.entity.ServiceExceptionEntity +import moe.lava.banksia.core.room.entity.ServiceExceptionEntity @Dao interface ServiceExceptionDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt similarity index 86% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt index ae4d53a..446a923 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/ShapeDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.room.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query -import moe.lava.banksia.room.entity.ShapeEntity +import moe.lava.banksia.core.room.entity.ShapeEntity @Dao interface ShapeDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt similarity index 91% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt index 869ae29..99f5a2d 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.room.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query -import moe.lava.banksia.room.entity.StopEntity +import moe.lava.banksia.core.room.entity.StopEntity @Dao interface StopDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt similarity index 94% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt index 82e0e4b..4670b6e 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.room.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query -import moe.lava.banksia.room.entity.StopTimeEntity +import moe.lava.banksia.core.room.entity.StopTimeEntity @Dao interface StopTimeDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/TripDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt similarity index 89% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/TripDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt index 9778a1a..1798f61 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/TripDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.room.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query -import moe.lava.banksia.room.entity.TripEntity +import moe.lava.banksia.core.room.entity.TripEntity @Dao interface TripDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/VersionMetadataDao.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt similarity index 89% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/VersionMetadataDao.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt index b96102e..357770d 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/VersionMetadataDao.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt @@ -1,10 +1,10 @@ -package moe.lava.banksia.room.dao +package moe.lava.banksia.core.room.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query -import moe.lava.banksia.room.entity.VersionMetadataEntity +import moe.lava.banksia.core.room.entity.VersionMetadataEntity @Dao interface VersionMetadataDao { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/RouteEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt similarity index 90% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/RouteEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt index cc690d6..5c5d70f 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/RouteEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt similarity index 94% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt index 027aaa8..054b016 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt similarity index 93% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt index 313246d..21bac2f 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ServiceExceptionEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ShapeEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt similarity index 79% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ShapeEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt index 87ca671..0010a7d 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/ShapeEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt @@ -1,11 +1,11 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverters +import moe.lava.banksia.core.room.converter.ShapePathConverter import moe.lava.banksia.model.Shape import moe.lava.banksia.model.ShapePath -import moe.lava.banksia.room.converter.ShapePathConverter @Entity("Shape") @TypeConverters(ShapePathConverter::class) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt similarity index 95% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt index 9ce7bfb..9434f11 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt similarity index 97% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt index bb20ff1..f9aac97 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopTimeEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.Entity import androidx.room.ForeignKey diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt similarity index 97% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt index 12bda02..28cce11 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.ColumnInfo import androidx.room.Entity diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/VersionMetadataEntity.kt b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt similarity index 91% rename from shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/VersionMetadataEntity.kt rename to shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt index fc00b44..a79998c 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/VersionMetadataEntity.kt +++ b/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.room.entity +package moe.lava.banksia.core.room.entity import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/shared/src/iosMain/kotlin/moe/lava/banksia/di/PlatformModule.ios.kt b/shared/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt similarity index 56% rename from shared/src/iosMain/kotlin/moe/lava/banksia/di/PlatformModule.ios.kt rename to shared/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt index 8597856..3c74852 100644 --- a/shared/src/iosMain/kotlin/moe/lava/banksia/di/PlatformModule.ios.kt +++ b/shared/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt @@ -1,10 +1,8 @@ -package moe.lava.banksia.di +package moe.lava.banksia.core.room import androidx.room.RoomDatabase -import moe.lava.banksia.room.Database import org.koin.core.parameter.ParametersHolder import org.koin.core.scope.Scope -import org.koin.dsl.module class IosDatabaseBuilder() : PlatformDatabaseBuilder { override fun getBuilder(): RoomDatabase.Builder { @@ -12,7 +10,5 @@ class IosDatabaseBuilder() : PlatformDatabaseBuilder { } } -actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = +internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = IosDatabaseBuilder() - -internal actual val ExtPlatformModule = module { } diff --git a/shared/src/jvmMain/kotlin/moe/lava/banksia/di/PlatformModule.jvm.kt b/shared/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt similarity index 66% rename from shared/src/jvmMain/kotlin/moe/lava/banksia/di/PlatformModule.jvm.kt rename to shared/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt index 3e93241..38c35ce 100644 --- a/shared/src/jvmMain/kotlin/moe/lava/banksia/di/PlatformModule.jvm.kt +++ b/shared/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt @@ -1,11 +1,9 @@ -package moe.lava.banksia.di +package moe.lava.banksia.core.room import androidx.room.Room import androidx.room.RoomDatabase -import moe.lava.banksia.room.Database import org.koin.core.parameter.ParametersHolder import org.koin.core.scope.Scope -import org.koin.dsl.module import java.io.File class JvmDatabaseBuilder() : PlatformDatabaseBuilder { @@ -17,7 +15,5 @@ class JvmDatabaseBuilder() : PlatformDatabaseBuilder { } } -actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = +internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = JvmDatabaseBuilder() - -internal actual val ExtPlatformModule = module { } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt new file mode 100644 index 0000000..332929a --- /dev/null +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt @@ -0,0 +1,5 @@ +package moe.lava.banksia.di + +import org.koin.dsl.module + +val coreDiModule = module { } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/PlatformModule.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/di/PlatformModule.kt deleted file mode 100644 index 6f29f14..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/di/PlatformModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package moe.lava.banksia.di - -import androidx.room.RoomDatabase -import moe.lava.banksia.room.Database -import org.koin.core.module.Module -import org.koin.core.parameter.ParametersHolder -import org.koin.core.scope.Scope -import org.koin.dsl.module - -interface PlatformDatabaseBuilder { - fun getBuilder(): RoomDatabase.Builder -} - -expect fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder - -internal expect val ExtPlatformModule: Module - -internal val PlatformModule = module { - includes(ExtPlatformModule) - single { provideDatabaseBuilder(it) } -} diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 201a10f..b1ffe9b 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -67,8 +67,8 @@ kotlin { implementation(libs.moko.geo.compose) implementation(libs.ui.backhandler) - implementation(projects.client) implementation(projects.shared) + implementation(projects.shared.data) implementation(projects.ui.maps) implementation(projects.ui.shared) } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt index 453e1ee..2d71fce 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt @@ -3,7 +3,7 @@ package moe.lava.banksia.ui import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi -import moe.lava.banksia.di.CommonModules +import moe.lava.banksia.di.coreDiModule import moe.lava.banksia.ui.di.AppModule import moe.lava.banksia.ui.screens.map.MapScreen import org.koin.compose.KoinMultiplatformApplication @@ -14,7 +14,7 @@ import org.koin.dsl.koinConfiguration @Composable fun App() { KoinMultiplatformApplication(config = koinConfiguration { - modules(CommonModules, AppModule) + modules(coreDiModule, AppModule) }) { MapScreen() } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt index 4c93644..cff36fb 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt @@ -1,12 +1,13 @@ package moe.lava.banksia.ui.di -import moe.lava.banksia.client.di.ClientModule +import moe.lava.banksia.core.data.dataDiModule import moe.lava.banksia.ui.screens.map.MapScreenViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val AppModule = module { - includes(ClientModule) + includes(dataDiModule) + // ViewModel viewModelOf(::MapScreenViewModel) } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt index 2e19c68..bb45232 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt @@ -15,9 +15,9 @@ 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.core.data.repositories.RouteRepository +import moe.lava.banksia.core.data.repositories.StopRepository +import moe.lava.banksia.core.data.repositories.StopTimeRepository import moe.lava.banksia.data.ptv.PtvService import moe.lava.banksia.model.Route import moe.lava.banksia.model.RouteType From c912723c78921df9da8dd83e946dc206cbfc43c3 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Thu, 2 Apr 2026 01:57:08 +1100 Subject: [PATCH 19/33] refactor: shared -> core --- .gitignore | 2 +- {shared => core}/build.gradle.kts | 2 +- {shared => core}/data/build.gradle.kts | 4 ++-- .../lava/banksia/core/data/DataDiModule.kt | 4 ++-- .../core/data/repositories/RouteRepository.kt | 0 .../core/data/repositories/StopRepository.kt | 0 .../data/repositories/StopTimeRepository.kt | 2 +- .../sources/route/RouteLocalDataSource.kt | 2 +- .../sources/route/RouteRemoteDataSource.kt | 2 +- .../data/sources/stop/StopLocalDataSource.kt | 2 +- .../data/sources/stop/StopRemoteDataSource.kt | 2 +- .../stoptime/StopTimeLocalDataSource.kt | 6 +++--- .../stoptime/StopTimeRemoteDataSource.kt | 2 +- .../data/sources/trip/TripRemoteDataSource.kt | 2 +- {shared => core}/room/build.gradle.kts | 2 +- .../1.json | 0 .../10.json | 0 .../11.json | 0 .../2.json | 0 .../3.json | 0 .../4.json | 0 .../5.json | 0 .../6.json | 0 .../7.json | 0 .../8.json | 0 .../9.json | 0 .../banksia/core/room/RoomDiModule.android.kt | 0 .../moe/lava/banksia/core/room/Database.kt | 0 .../lava/banksia/core/room/RoomDiModule.kt | 0 .../core/room/converter/RouteTypeConverter.kt | 2 +- .../core/room/converter/ShapePathConverter.kt | 4 ++-- .../lava/banksia/core/room/dao/RouteDao.kt | 0 .../lava/banksia/core/room/dao/ServiceDao.kt | 0 .../core/room/dao/ServiceExceptionDao.kt | 0 .../lava/banksia/core/room/dao/ShapeDao.kt | 0 .../moe/lava/banksia/core/room/dao/StopDao.kt | 0 .../lava/banksia/core/room/dao/StopTimeDao.kt | 0 .../moe/lava/banksia/core/room/dao/TripDao.kt | 0 .../core/room/dao/VersionMetadataDao.kt | 0 .../banksia/core/room/entity/RouteEntity.kt | 4 ++-- .../banksia/core/room/entity/ServiceEntity.kt | 6 +++--- .../room/entity/ServiceExceptionEntity.kt | 2 +- .../banksia/core/room/entity/ShapeEntity.kt | 4 ++-- .../banksia/core/room/entity/StopEntity.kt | 4 ++-- .../core/room/entity/StopTimeEntity.kt | 6 +++--- .../banksia/core/room/entity/TripEntity.kt | 2 +- .../core/room/entity/VersionMetadataEntity.kt | 2 +- .../banksia/core/room/RoomDiModule.ios.kt | 0 .../banksia/core/room/RoomDiModule.jvm.kt | 0 .../banksia/core}/util/Logging.android.kt | 2 +- .../lava/banksia/core}/Constants.kt.skeleton | 0 .../lava/banksia/core}/model/FutureTime.kt | 4 ++-- .../moe/lava/banksia/core}/model/Route.kt | 2 +- .../moe/lava/banksia/core}/model/RouteType.kt | 4 ++-- .../moe/lava/banksia/core}/model/Run.kt | 2 +- .../moe/lava/banksia/core}/model/Service.kt | 2 +- .../banksia/core}/model/ServiceException.kt | 2 +- .../moe/lava/banksia/core}/model/Shape.kt | 4 ++-- .../moe/lava/banksia/core}/model/Stop.kt | 4 ++-- .../moe/lava/banksia/core}/model/StopTime.kt | 2 +- .../lava/banksia/core}/model/StopTimeDated.kt | 2 +- .../moe/lava/banksia/core}/model/Trip.kt | 2 +- .../banksia/core}/model/VersionMetadata.kt | 2 +- .../moe/lava/banksia/core}/util/BoxedValue.kt | 2 +- .../moe/lava/banksia/core}/util/CacheMap.kt | 2 +- .../banksia/core}/util/DayOfWeekExtension.kt | 2 +- .../moe/lava/banksia/core}/util/Logging.kt | 2 +- .../moe/lava/banksia/core}/util/LoopFlow.kt | 2 +- .../moe/lava/banksia/core}/util/Point.kt | 2 +- .../moe/lava/banksia/data/ptv/PtvService.kt | 12 +++++------ .../data/ptv/structures/PtvDeparture.kt | 0 .../data/ptv/structures/PtvDirection.kt | 0 .../banksia/data/ptv/structures/PtvGeopath.kt | 0 .../banksia/data/ptv/structures/PtvRoute.kt | 2 +- .../data/ptv/structures/PtvRouteType.kt | 2 +- .../banksia/data/ptv/structures/PtvRun.kt | 0 .../banksia/data/ptv/structures/PtvStop.kt | 0 .../lava/banksia/core}/util/Logging.ios.kt | 2 +- .../lava/banksia/core}/util/Logging.jvm.kt | 2 +- server/build.gradle.kts | 5 ++--- server/gtfs/build.gradle.kts | 2 +- .../lava/banksia/server/gtfs/GtfsParser.kt | 20 +++++++++---------- .../server/gtfs/structures/GtfsStopTime.kt | 2 +- server/gtfs_rt/build.gradle.kts | 2 +- .../banksia/server/gtfsrt/GtfsrtArchiver.kt | 2 +- .../banksia/server/gtfsrt/GtfsrtService.kt | 6 +++--- .../server/gtfsrt/RealtimeVehiclePositions.kt | 2 +- .../moe/lava/banksia/server/Application.kt | 6 +++--- .../moe/lava/banksia/server/GtfsDataFixer.kt | 2 +- .../moe/lava/banksia/server/GtfsImporter.kt | 14 ++++++------- settings.gradle.kts | 6 +++--- .../moe/lava/banksia/di/CoreDiModule.kt | 5 ----- ui/build.gradle.kts | 4 ++-- ui/maps/build.gradle.kts | 2 +- .../moe/lava/banksia/ui/map/MapLibreMaps.kt | 2 +- .../kotlin/moe/lava/banksia/ui/map/Maps.kt | 2 +- .../lava/banksia/ui/map/MapsPositionState.kt | 2 +- .../moe/lava/banksia/ui/map/mappers/Marker.kt | 2 +- .../lava/banksia/ui/map/mappers/Position.kt | 2 +- .../lava/banksia/ui/map/mappers/RouteType.kt | 2 +- .../banksia/ui/map/util/CameraPosition.kt | 2 +- .../ui/map/util/CameraPositionBounds.kt | 2 +- .../moe/lava/banksia/ui/map/util/Marker.kt | 4 ++-- .../moe/lava/banksia/ui/map/util/Polyline.kt | 2 +- ui/shared/build.gradle.kts | 2 +- .../lava/banksia/ui/components/RouteIcon.kt | 8 ++++---- .../lava/banksia/ui/extensions/RouteType.kt | 2 +- .../kotlin/moe/lava/banksia/ui/App.kt | 3 +-- .../banksia/ui/layout/info/RouteInfoPanel.kt | 2 +- .../banksia/ui/layout/info/TripInfoPanel.kt | 2 +- .../ui/screens/map/MapScreenViewModel.kt | 14 ++++++------- .../moe/lava/banksia/ui/state/SearchState.kt | 2 +- 112 files changed, 133 insertions(+), 140 deletions(-) rename {shared => core}/build.gradle.kts (97%) rename {shared => core}/data/build.gradle.kts (93%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt (96%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt (100%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt (100%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt (91%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt (91%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt (89%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt (92%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt (90%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt (84%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt (95%) rename {shared => core}/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt (91%) rename {shared => core}/room/build.gradle.kts (96%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/1.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/10.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/11.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/2.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/3.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/4.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/5.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/6.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/7.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/8.json (100%) rename {shared => core}/room/schemas/moe.lava.banksia.core.room.Database/9.json (100%) rename {shared => core}/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt (84%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt (93%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt (100%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt (80%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt (81%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt (92%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt (83%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt (92%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt (89%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt (97%) rename {shared => core}/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt (89%) rename {shared => core}/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt (100%) rename {shared => core}/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt (100%) rename {shared/src/androidMain/kotlin/moe/lava/banksia => core/src/androidMain/kotlin/moe/lava/banksia/core}/util/Logging.android.kt (87%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/Constants.kt.skeleton (100%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/FutureTime.kt (94%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/Route.kt (82%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/RouteType.kt (72%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/Run.kt (52%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/Service.kt (87%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/ServiceException.kt (84%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/Shape.kt (67%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/Stop.kt (77%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/StopTime.kt (88%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/StopTimeDated.kt (94%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/Trip.kt (89%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/model/VersionMetadata.kt (79%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/util/BoxedValue.kt (87%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/util/CacheMap.kt (97%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/util/DayOfWeekExtension.kt (96%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/util/Logging.kt (88%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/util/LoopFlow.kt (98%) rename {shared/src/commonMain/kotlin/moe/lava/banksia => core/src/commonMain/kotlin/moe/lava/banksia/core}/util/Point.kt (75%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt (97%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt (100%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt (100%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt (100%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt (94%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt (97%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt (100%) rename {shared => core}/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt (100%) rename {shared/src/iosMain/kotlin/moe/lava/banksia => core/src/iosMain/kotlin/moe/lava/banksia/core}/util/Logging.ios.kt (83%) rename {shared/src/jvmMain/kotlin/moe/lava/banksia => core/src/jvmMain/kotlin/moe/lava/banksia/core}/util/Logging.jvm.kt (86%) delete mode 100644 shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt diff --git a/.gitignore b/.gitignore index 83f099d..975a370 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,6 @@ captures **/xcshareddata/WorkspaceSettings.xcsettings secrets.properties -shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt +/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt /data/ /data diff --git a/shared/build.gradle.kts b/core/build.gradle.kts similarity index 97% rename from shared/build.gradle.kts rename to core/build.gradle.kts index b4ed8ad..99f7156 100644 --- a/shared/build.gradle.kts +++ b/core/build.gradle.kts @@ -14,7 +14,7 @@ room { kotlin { android { - namespace = "moe.lava.banksia.shared" + namespace = "moe.lava.banksia.core" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { diff --git a/shared/data/build.gradle.kts b/core/data/build.gradle.kts similarity index 93% rename from shared/data/build.gradle.kts rename to core/data/build.gradle.kts index 1d4b956..e0fea0c 100644 --- a/shared/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -42,8 +42,8 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.protobuf) - implementation(projects.shared) - implementation(projects.shared.room) + implementation(projects.core) + implementation(projects.core.room) } iosMain.dependencies { implementation(libs.ktor.client.darwin) diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt similarity index 96% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt index 86694cc..6529a92 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt @@ -7,7 +7,7 @@ import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.plugin import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json -import moe.lava.banksia.Constants +import moe.lava.banksia.core.Constants import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository import moe.lava.banksia.core.data.repositories.StopTimeRepository @@ -18,8 +18,8 @@ import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource import moe.lava.banksia.core.room.roomDiModule +import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService -import moe.lava.banksia.util.log import org.koin.core.module.dsl.singleOf import org.koin.dsl.module diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt similarity index 100% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt similarity index 100% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt similarity index 91% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt index 50cc66a..34aa570 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt @@ -2,7 +2,7 @@ package moe.lava.banksia.core.data.repositories import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource -import moe.lava.banksia.model.StopTimeDated +import moe.lava.banksia.core.model.StopTimeDated class StopTimeRepository internal constructor( private val local: StopTimeLocalDataSource, diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt similarity index 91% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt index 79d432a..ca267c3 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt @@ -1,8 +1,8 @@ package moe.lava.banksia.core.data.sources.route +import moe.lava.banksia.core.model.Route import moe.lava.banksia.core.room.dao.RouteDao import moe.lava.banksia.core.room.entity.asEntity -import moe.lava.banksia.model.Route internal class RouteLocalDataSource(private val dao: RouteDao) { suspend fun get(id: String) = dao.get(id) diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt similarity index 89% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt index a111e82..bdcbfc1 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt @@ -3,7 +3,7 @@ package moe.lava.banksia.core.data.sources.route import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get -import moe.lava.banksia.model.Route +import moe.lava.banksia.core.model.Route internal class RouteRemoteDataSource(val client: HttpClient) { suspend fun get(id: String) = client.get("routes/${id}").body() diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt similarity index 92% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt index c8b429b..8e0d8ab 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt @@ -1,9 +1,9 @@ package moe.lava.banksia.core.data.sources.stop +import moe.lava.banksia.core.model.Stop import moe.lava.banksia.core.room.dao.RouteDao import moe.lava.banksia.core.room.dao.StopDao import moe.lava.banksia.core.room.entity.asEntity -import moe.lava.banksia.model.Stop internal class StopLocalDataSource(private val dao: StopDao, private val routeDao: RouteDao) { suspend fun get(id: String) = dao.get(id) diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt similarity index 90% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt index c63f718..f39afd3 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt @@ -3,7 +3,7 @@ package moe.lava.banksia.core.data.sources.stop import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get -import moe.lava.banksia.model.Stop +import moe.lava.banksia.core.model.Stop internal class StopRemoteDataSource(val client: HttpClient) { suspend fun get(id: String) = client.get("stops/${id}").body() diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt similarity index 84% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt index cf09cc0..c5ce4e7 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt @@ -3,10 +3,10 @@ package moe.lava.banksia.core.data.sources.stoptime import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn +import moe.lava.banksia.core.model.StopTimeDated +import moe.lava.banksia.core.model.atDate import moe.lava.banksia.core.room.dao.StopTimeDao -import moe.lava.banksia.model.StopTimeDated -import moe.lava.banksia.model.atDate -import moe.lava.banksia.util.serialise +import moe.lava.banksia.core.util.serialise import kotlin.time.Clock internal class StopTimeLocalDataSource( diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt similarity index 95% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt index ef841bb..0633a18 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt @@ -7,7 +7,7 @@ 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 moe.lava.banksia.core.model.StopTimeDated import kotlin.time.Clock internal class StopTimeRemoteDataSource( diff --git a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt similarity index 91% rename from shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt rename to core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt index 2055414..d1067d8 100644 --- a/shared/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt @@ -4,7 +4,7 @@ import io.ktor.client.HttpClient import kotlinx.datetime.DayOfWeek import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn -import moe.lava.banksia.model.Trip +import moe.lava.banksia.core.model.Trip import kotlin.time.Clock internal class TripRemoteDataSource( diff --git a/shared/room/build.gradle.kts b/core/room/build.gradle.kts similarity index 96% rename from shared/room/build.gradle.kts rename to core/room/build.gradle.kts index 7f0f5eb..31a7393 100644 --- a/shared/room/build.gradle.kts +++ b/core/room/build.gradle.kts @@ -41,7 +41,7 @@ kotlin { implementation(libs.room.runtime) implementation(libs.sqlite.bundled) - implementation(projects.shared) + implementation(projects.core) } } } diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/1.json b/core/room/schemas/moe.lava.banksia.core.room.Database/1.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/1.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/1.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/10.json b/core/room/schemas/moe.lava.banksia.core.room.Database/10.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/10.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/10.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/11.json b/core/room/schemas/moe.lava.banksia.core.room.Database/11.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/11.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/11.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/2.json b/core/room/schemas/moe.lava.banksia.core.room.Database/2.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/2.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/2.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/3.json b/core/room/schemas/moe.lava.banksia.core.room.Database/3.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/3.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/3.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/4.json b/core/room/schemas/moe.lava.banksia.core.room.Database/4.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/4.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/4.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/5.json b/core/room/schemas/moe.lava.banksia.core.room.Database/5.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/5.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/5.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/6.json b/core/room/schemas/moe.lava.banksia.core.room.Database/6.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/6.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/6.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/7.json b/core/room/schemas/moe.lava.banksia.core.room.Database/7.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/7.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/7.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/8.json b/core/room/schemas/moe.lava.banksia.core.room.Database/8.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/8.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/8.json diff --git a/shared/room/schemas/moe.lava.banksia.core.room.Database/9.json b/core/room/schemas/moe.lava.banksia.core.room.Database/9.json similarity index 100% rename from shared/room/schemas/moe.lava.banksia.core.room.Database/9.json rename to core/room/schemas/moe.lava.banksia.core.room.Database/9.json diff --git a/shared/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt b/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt similarity index 100% rename from shared/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt rename to core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt similarity index 84% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt index f9da212..f588a66 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.core.room.converter import androidx.room.TypeConverter -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType object RouteTypeConverter { @TypeConverter diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt similarity index 93% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt index 5304c33..b914cff 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt @@ -1,8 +1,8 @@ package moe.lava.banksia.core.room.converter import androidx.room.TypeConverter -import moe.lava.banksia.model.ShapePath -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.model.ShapePath +import moe.lava.banksia.core.util.Point object ShapePathConverter { @TypeConverter diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt similarity index 100% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt similarity index 80% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt index 5c5d70f..8feda0b 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt @@ -2,8 +2,8 @@ package moe.lava.banksia.core.room.entity import androidx.room.Entity import androidx.room.PrimaryKey -import moe.lava.banksia.model.Route -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.Route +import moe.lava.banksia.core.model.RouteType @Entity("Route") data class RouteEntity( diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt similarity index 81% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt index 054b016..4a1c8b7 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt @@ -4,9 +4,9 @@ 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 +import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.util.deserialiseDaysBitflag +import moe.lava.banksia.core.util.serialise @Entity("Service") data class ServiceEntity( diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt similarity index 92% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt index 21bac2f..1ac45d3 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt @@ -3,7 +3,7 @@ package moe.lava.banksia.core.room.entity import androidx.room.ColumnInfo import androidx.room.Entity import kotlinx.datetime.LocalDate -import moe.lava.banksia.model.ServiceException +import moe.lava.banksia.core.model.ServiceException @Entity( "ServiceException", diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt similarity index 83% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt index 0010a7d..a19147d 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt @@ -3,9 +3,9 @@ package moe.lava.banksia.core.room.entity import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverters +import moe.lava.banksia.core.model.Shape +import moe.lava.banksia.core.model.ShapePath import moe.lava.banksia.core.room.converter.ShapePathConverter -import moe.lava.banksia.model.Shape -import moe.lava.banksia.model.ShapePath @Entity("Shape") @TypeConverters(ShapePathConverter::class) diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt similarity index 92% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt index 9434f11..f59c5da 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt @@ -5,8 +5,8 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.ForeignKey.Companion.SET_NULL import androidx.room.PrimaryKey -import moe.lava.banksia.model.Stop -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.core.util.Point @Entity( "Stop", diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt similarity index 89% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt index f9aac97..d96036d 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt @@ -5,9 +5,9 @@ import androidx.room.ForeignKey import androidx.room.ForeignKey.Companion.CASCADE import androidx.room.Index import kotlinx.serialization.ExperimentalSerializationApi -import moe.lava.banksia.model.FutureTime -import moe.lava.banksia.model.FutureTime.Companion.asInt -import moe.lava.banksia.model.StopTime +import moe.lava.banksia.core.model.FutureTime +import moe.lava.banksia.core.model.FutureTime.Companion.asInt +import moe.lava.banksia.core.model.StopTime @Entity( "StopTime", diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt similarity index 97% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt index 28cce11..7928c60 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt @@ -6,7 +6,7 @@ import androidx.room.ForeignKey import androidx.room.ForeignKey.Companion.CASCADE import androidx.room.Index import androidx.room.PrimaryKey -import moe.lava.banksia.model.Trip +import moe.lava.banksia.core.model.Trip @Entity( "Trip", diff --git a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt similarity index 89% rename from shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt rename to core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt index a79998c..1e7cab9 100644 --- a/shared/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt @@ -2,7 +2,7 @@ package moe.lava.banksia.core.room.entity import androidx.room.Entity import androidx.room.PrimaryKey -import moe.lava.banksia.model.VersionMetadata +import moe.lava.banksia.core.model.VersionMetadata @Entity( "VersionMetadata", diff --git a/shared/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt b/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt similarity index 100% rename from shared/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt rename to core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt diff --git a/shared/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt similarity index 100% rename from shared/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt rename to core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt diff --git a/shared/src/androidMain/kotlin/moe/lava/banksia/util/Logging.android.kt b/core/src/androidMain/kotlin/moe/lava/banksia/core/util/Logging.android.kt similarity index 87% rename from shared/src/androidMain/kotlin/moe/lava/banksia/util/Logging.android.kt rename to core/src/androidMain/kotlin/moe/lava/banksia/core/util/Logging.android.kt index 31c3072..e0b792e 100644 --- a/shared/src/androidMain/kotlin/moe/lava/banksia/util/Logging.android.kt +++ b/core/src/androidMain/kotlin/moe/lava/banksia/core/util/Logging.android.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util import android.util.Log diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton b/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton similarity index 100% rename from shared/src/commonMain/kotlin/moe/lava/banksia/Constants.kt.skeleton rename to core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/FutureTime.kt similarity index 94% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/FutureTime.kt index 91c5c77..7c77309 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/FutureTime.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/FutureTime.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate @@ -12,7 +12,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import moe.lava.banksia.model.FutureTime.Companion.asInt +import moe.lava.banksia.core.model.FutureTime.Companion.asInt @Serializable(FutureTimeSerialiser::class) data class FutureTime( diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Route.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Route.kt similarity index 82% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/Route.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/Route.kt index 9cfff0f..b2741f4 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Route.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Route.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/RouteType.kt similarity index 72% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/RouteType.kt index a51f132..86555a6 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/RouteType.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/RouteType.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.serialization.Serializable @@ -15,6 +15,6 @@ enum class RouteType(val value: Int) { ; companion object { - fun from(value: Int) = RouteType.entries.first { it.value == value } + fun from(value: Int) = entries.first { it.value == value } } } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Run.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Run.kt similarity index 52% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/Run.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/Run.kt index 328a4b0..69799bf 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Run.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Run.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model data class Run( val ref: String, diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Service.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Service.kt similarity index 87% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/Service.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/Service.kt index a57fb82..8568397 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Service.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Service.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/ServiceException.kt similarity index 84% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/ServiceException.kt index 305ede4..ef2f918 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/ServiceException.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/ServiceException.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.datetime.LocalDate import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Shape.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Shape.kt similarity index 67% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/Shape.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/Shape.kt index 6299ca0..7b71427 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Shape.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Shape.kt @@ -1,7 +1,7 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.serialization.Serializable -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point typealias ShapePath = List diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt similarity index 77% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt index e1060bb..73e6f02 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt @@ -1,7 +1,7 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.serialization.Serializable -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point @Serializable data class Stop( diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTime.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt similarity index 88% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTime.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt index 682839d..70657dc 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTime.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt similarity index 94% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt index 55288fa..1bd75c6 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/StopTimeDated.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt similarity index 89% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt index 81d3f8d..753f653 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Trip.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/VersionMetadata.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/VersionMetadata.kt similarity index 79% rename from shared/src/commonMain/kotlin/moe/lava/banksia/model/VersionMetadata.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/model/VersionMetadata.kt index 1770b23..2ee4f28 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/VersionMetadata.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/VersionMetadata.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.model +package moe.lava.banksia.core.model import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/BoxedValue.kt similarity index 87% rename from shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/util/BoxedValue.kt index 3ff5702..f761518 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/BoxedValue.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/BoxedValue.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util /** Wraps an arbitrary value, such that equality checks are forced to be done by reference */ class BoxedValue(val value: T) { diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/CacheMap.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/CacheMap.kt similarity index 97% rename from shared/src/commonMain/kotlin/moe/lava/banksia/util/CacheMap.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/util/CacheMap.kt index e41cef6..22236c6 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/CacheMap.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/CacheMap.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/DayOfWeekExtension.kt similarity index 96% rename from shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/util/DayOfWeekExtension.kt index 87d3244..7feca0d 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/DayOfWeekExtension.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/DayOfWeekExtension.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util import kotlinx.datetime.DayOfWeek diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/Logging.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Logging.kt similarity index 88% rename from shared/src/commonMain/kotlin/moe/lava/banksia/util/Logging.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/util/Logging.kt index 7f26800..9d5f55a 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/Logging.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Logging.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util fun error(tag: String, throwable: Throwable) = error(tag, "", throwable) expect fun log(tag: String, msg: String) diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/LoopFlow.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/LoopFlow.kt similarity index 98% rename from shared/src/commonMain/kotlin/moe/lava/banksia/util/LoopFlow.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/util/LoopFlow.kt index ee3e826..ec21d62 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/LoopFlow.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/LoopFlow.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/util/Point.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Point.kt similarity index 75% rename from shared/src/commonMain/kotlin/moe/lava/banksia/util/Point.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/core/util/Point.kt index 4aae7d4..4db05e2 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/util/Point.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/util/Point.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt similarity index 97% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt index 77ab12d..54717a2 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/PtvService.kt @@ -16,7 +16,12 @@ import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.delay import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import moe.lava.banksia.Constants +import moe.lava.banksia.core.Constants +import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.core.util.LoopFlow.Companion.initWith +import moe.lava.banksia.core.util.error +import moe.lava.banksia.core.util.log +import moe.lava.banksia.core.util.loopFlow import moe.lava.banksia.data.ptv.structures.PtvDeparture import moe.lava.banksia.data.ptv.structures.PtvDirection import moe.lava.banksia.data.ptv.structures.PtvRoute @@ -24,11 +29,6 @@ import moe.lava.banksia.data.ptv.structures.PtvRouteType import moe.lava.banksia.data.ptv.structures.PtvRouteType.Companion.asPtvType import moe.lava.banksia.data.ptv.structures.PtvRun import moe.lava.banksia.data.ptv.structures.PtvStop -import moe.lava.banksia.model.RouteType -import moe.lava.banksia.util.LoopFlow.Companion.initWith -import moe.lava.banksia.util.error -import moe.lava.banksia.util.log -import moe.lava.banksia.util.loopFlow import okio.ByteString.Companion.encodeUtf8 import kotlin.random.Random diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt similarity index 100% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDeparture.kt diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt similarity index 100% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvDirection.kt diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt similarity index 100% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvGeopath.kt diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt similarity index 94% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt index 3178328..4aae762 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRoute.kt @@ -2,7 +2,7 @@ package moe.lava.banksia.data.ptv.structures import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType @Serializable data class PtvRoute( diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt similarity index 97% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt index c9988bf..d8808f1 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRouteType.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType object PtvRouteTypeSerialiser : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt similarity index 100% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvRun.kt diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt b/core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt similarity index 100% rename from shared/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt rename to core/src/commonMain/kotlin/moe/lava/banksia/data/ptv/structures/PtvStop.kt diff --git a/shared/src/iosMain/kotlin/moe/lava/banksia/util/Logging.ios.kt b/core/src/iosMain/kotlin/moe/lava/banksia/core/util/Logging.ios.kt similarity index 83% rename from shared/src/iosMain/kotlin/moe/lava/banksia/util/Logging.ios.kt rename to core/src/iosMain/kotlin/moe/lava/banksia/core/util/Logging.ios.kt index b58b89a..014c1d2 100644 --- a/shared/src/iosMain/kotlin/moe/lava/banksia/util/Logging.ios.kt +++ b/core/src/iosMain/kotlin/moe/lava/banksia/core/util/Logging.ios.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util actual fun log(tag: String, msg: String) { TODO("Not yet implemented") diff --git a/shared/src/jvmMain/kotlin/moe/lava/banksia/util/Logging.jvm.kt b/core/src/jvmMain/kotlin/moe/lava/banksia/core/util/Logging.jvm.kt similarity index 86% rename from shared/src/jvmMain/kotlin/moe/lava/banksia/util/Logging.jvm.kt rename to core/src/jvmMain/kotlin/moe/lava/banksia/core/util/Logging.jvm.kt index 0a1ea10..de7fdaa 100644 --- a/shared/src/jvmMain/kotlin/moe/lava/banksia/util/Logging.jvm.kt +++ b/core/src/jvmMain/kotlin/moe/lava/banksia/core/util/Logging.jvm.kt @@ -1,4 +1,4 @@ -package moe.lava.banksia.util +package moe.lava.banksia.core.util actual fun log(tag: String, msg: String) { println("[$tag] $msg") diff --git a/server/build.gradle.kts b/server/build.gradle.kts index e982c8c..0883c85 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -19,7 +19,8 @@ kotlin { } dependencies { - implementation(projects.shared) + implementation(projects.core) + implementation(projects.core.room) implementation(projects.server.gtfs) implementation(projects.server.gtfsRt) @@ -39,6 +40,4 @@ dependencies { implementation(libs.sqlite.bundled) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) - - implementation(projects.shared.room) } diff --git a/server/gtfs/build.gradle.kts b/server/gtfs/build.gradle.kts index e83e745..8f6d646 100644 --- a/server/gtfs/build.gradle.kts +++ b/server/gtfs/build.gradle.kts @@ -11,7 +11,7 @@ kotlin { } dependencies { - implementation(projects.shared) + implementation(projects.core) implementation(libs.kotlinx.serialization.csv) implementation(libs.kotlinx.datetime) implementation(libs.ktor.client.contentnegotiation) diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index 21e239c..e726a46 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -17,15 +17,16 @@ import kotlinx.datetime.LocalDate import kotlinx.serialization.decodeFromString import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.serializer -import moe.lava.banksia.Constants -import moe.lava.banksia.model.Route -import moe.lava.banksia.model.RouteType -import moe.lava.banksia.model.Service -import moe.lava.banksia.model.ServiceException -import moe.lava.banksia.model.Shape -import moe.lava.banksia.model.Stop -import moe.lava.banksia.model.StopTime -import moe.lava.banksia.model.Trip +import moe.lava.banksia.core.Constants +import moe.lava.banksia.core.model.Route +import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.model.ServiceException +import moe.lava.banksia.core.model.Shape +import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.Trip +import moe.lava.banksia.core.util.Point import moe.lava.banksia.server.gtfs.structures.GtfsRoute import moe.lava.banksia.server.gtfs.structures.GtfsService import moe.lava.banksia.server.gtfs.structures.GtfsServiceException @@ -33,7 +34,6 @@ import moe.lava.banksia.server.gtfs.structures.GtfsShape import moe.lava.banksia.server.gtfs.structures.GtfsStop import moe.lava.banksia.server.gtfs.structures.GtfsStopTime import moe.lava.banksia.server.gtfs.structures.GtfsTrip -import moe.lava.banksia.util.Point import java.io.File import java.util.zip.ZipFile import kotlin.time.ExperimentalTime diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt index 76de3cd..33da78f 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.server.gtfs.structures import kotlinx.serialization.Serializable -import moe.lava.banksia.model.FutureTime +import moe.lava.banksia.core.model.FutureTime @Suppress("PropertyName") @Serializable diff --git a/server/gtfs_rt/build.gradle.kts b/server/gtfs_rt/build.gradle.kts index 934d8bc..2887e0b 100644 --- a/server/gtfs_rt/build.gradle.kts +++ b/server/gtfs_rt/build.gradle.kts @@ -12,7 +12,7 @@ kotlin { } dependencies { - implementation(projects.shared) + implementation(projects.core) implementation(libs.okio) implementation(libs.koin.core) implementation(libs.ktor.client.core) diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt index 30a9fd3..7b9229b 100644 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime -import moe.lava.banksia.util.log +import moe.lava.banksia.core.util.log import java.io.File import kotlin.time.Instant diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt index 8b30b2f..6f46ed7 100644 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtService.kt @@ -15,9 +15,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch -import moe.lava.banksia.Constants -import moe.lava.banksia.util.LogScope -import moe.lava.banksia.util.log +import moe.lava.banksia.core.Constants +import moe.lava.banksia.core.util.LogScope +import moe.lava.banksia.core.util.log private val types = arrayOf( "metro/trip-updates", diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt index abebe76..4466b91 100644 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/RealtimeVehiclePositions.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.server.gtfsrt import com.google.transit.realtime.FeedMessage -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point class RealtimeVehiclePositions(data: FeedMessage) : GtfsRealtime(data) { private val positions = mutableMapOf() 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 eb0de33..2fe7bf6 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -18,15 +18,15 @@ 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.core.Constants +import moe.lava.banksia.core.model.atDate import moe.lava.banksia.core.room.dao.RouteDao import moe.lava.banksia.core.room.dao.StopDao import moe.lava.banksia.core.room.dao.StopTimeDao import moe.lava.banksia.core.room.dao.VersionMetadataDao -import moe.lava.banksia.model.atDate +import moe.lava.banksia.core.util.serialise import moe.lava.banksia.server.di.ServerModules import moe.lava.banksia.server.gtfsrt.GtfsrtService -import moe.lava.banksia.util.serialise import org.koin.dsl.module import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt index 10d6f11..b2620f4 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -2,7 +2,7 @@ package moe.lava.banksia.server import moe.lava.banksia.core.room.Database import moe.lava.banksia.core.room.entity.StopEntity -import moe.lava.banksia.util.log +import moe.lava.banksia.core.util.log import java.security.MessageDigest class GtfsDataFixer( diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 060457a..a012da7 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -3,15 +3,15 @@ package moe.lava.banksia.server import androidx.room.immediateTransaction import androidx.room.useWriterConnection import io.ktor.util.logging.Logger +import moe.lava.banksia.core.model.Route +import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.model.ServiceException +import moe.lava.banksia.core.model.Shape +import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.room.Database import moe.lava.banksia.core.room.entity.asEntity -import moe.lava.banksia.model.Route -import moe.lava.banksia.model.Service -import moe.lava.banksia.model.ServiceException -import moe.lava.banksia.model.Shape -import moe.lava.banksia.model.Stop -import moe.lava.banksia.model.StopTime -import moe.lava.banksia.model.Trip import moe.lava.banksia.server.gtfs.GtfsData import moe.lava.banksia.server.gtfs.GtfsParser import kotlin.time.Clock diff --git a/settings.gradle.kts b/settings.gradle.kts index 7096522..335422d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,9 +35,9 @@ include(":androidApp") include(":server") include(":server:gtfs") include(":server:gtfs_rt") -include(":shared") -include(":shared:data") -include(":shared:room") +include(":core") +include(":core:data") +include(":core:room") include(":ui") include(":ui:maps") include(":ui:shared") diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt deleted file mode 100644 index 332929a..0000000 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/di/CoreDiModule.kt +++ /dev/null @@ -1,5 +0,0 @@ -package moe.lava.banksia.di - -import org.koin.dsl.module - -val coreDiModule = module { } diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index b1ffe9b..018c8e6 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -67,8 +67,8 @@ kotlin { implementation(libs.moko.geo.compose) implementation(libs.ui.backhandler) - implementation(projects.shared) - implementation(projects.shared.data) + implementation(projects.core) + implementation(projects.core.data) implementation(projects.ui.maps) implementation(projects.ui.shared) } diff --git a/ui/maps/build.gradle.kts b/ui/maps/build.gradle.kts index 324b0b3..4e859d1 100644 --- a/ui/maps/build.gradle.kts +++ b/ui/maps/build.gradle.kts @@ -49,7 +49,7 @@ kotlin { implementation(libs.compose.material3) implementation(libs.compose.ui) - implementation(projects.shared) + implementation(projects.core) implementation(projects.ui.shared) } } diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt index 1df9cb1..a870653 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt @@ -10,7 +10,7 @@ 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.core.Constants import moe.lava.banksia.ui.map.mappers.routeColorExpression import moe.lava.banksia.ui.platform.BanksiaTheme import org.maplibre.compose.camera.CameraPosition diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt index 52b7250..92a9695 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/Maps.kt @@ -4,11 +4,11 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import moe.lava.banksia.core.util.Point import moe.lava.banksia.ui.map.mappers.asFeatures import moe.lava.banksia.ui.map.mappers.toPosition import moe.lava.banksia.ui.map.util.Marker import moe.lava.banksia.ui.platform.BanksiaTheme -import moe.lava.banksia.util.Point internal val MELBOURNE = Point(-37.8136, 144.9631) internal val MELBOURNE_POS = MELBOURNE.toPosition() diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt index a9fe8b2..b3dab0a 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point class MapsPositionState internal constructor( private val scope: CoroutineScope diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt index 32a910c..3fe99c2 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Marker.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.ui.map.mappers import kotlinx.serialization.Serializable -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.ui.map.util.Marker import org.maplibre.compose.sources.GeoJsonData import org.maplibre.spatialk.geojson.FeatureCollection diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt index c137394..ed568c2 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/Position.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.map.mappers -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point import org.maplibre.spatialk.geojson.Position internal fun Point.toPosition() = Position(lng, lat) diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt index 523e438..584c76f 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/RouteType.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.ui.map.mappers import androidx.compose.runtime.Composable -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.ui.extensions.getUIProperties import moe.lava.banksia.ui.platform.BanksiaTheme import org.maplibre.compose.expressions.dsl.case diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt index 710cebb..aba2858 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPosition.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.map.util -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point data class CameraPosition( val centre: Point = Point(-37.8136, 144.9631), diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt index 4adf3b1..9381262 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/CameraPositionBounds.kt @@ -1,5 +1,5 @@ package moe.lava.banksia.ui.map.util -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point data class CameraPositionBounds(val northeast: Point, val southwest: Point) diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt index 9326b2a..ac33868 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Marker.kt @@ -1,8 +1,8 @@ package moe.lava.banksia.ui.map.util import kotlinx.serialization.Serializable -import moe.lava.banksia.model.RouteType -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.core.util.Point @Serializable sealed class Marker { diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt index 146d74b..04b8dc6 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/util/Polyline.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.map.util import androidx.compose.ui.graphics.Color -import moe.lava.banksia.util.Point +import moe.lava.banksia.core.util.Point data class Polyline(val points: List, val colour: Color) diff --git a/ui/shared/build.gradle.kts b/ui/shared/build.gradle.kts index c784fed..e379840 100644 --- a/ui/shared/build.gradle.kts +++ b/ui/shared/build.gradle.kts @@ -35,7 +35,7 @@ kotlin { implementation(libs.compose.ui) implementation(libs.compose.ui.tooling.preview) - implementation(projects.shared) + implementation(projects.core) } } } diff --git a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt index e84d765..90914ae 100644 --- a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt +++ b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/components/RouteIcon.kt @@ -11,10 +11,10 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import moe.lava.banksia.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.core.model.RouteType +import moe.lava.banksia.core.model.RouteType.MetroBus +import moe.lava.banksia.core.model.RouteType.MetroTrain +import moe.lava.banksia.core.model.RouteType.MetroTram import moe.lava.banksia.ui.extensions.getUIProperties import org.jetbrains.compose.resources.painterResource diff --git a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt index 992b910..805f572 100644 --- a/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt +++ b/ui/shared/src/commonMain/kotlin/moe/lava/banksia/ui/extensions/RouteType.kt @@ -1,8 +1,8 @@ package moe.lava.banksia.ui.extensions import androidx.compose.ui.graphics.Color +import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.data.ptv.structures.PtvRouteType -import moe.lava.banksia.model.RouteType import moe.lava.banksia.resources.Res import moe.lava.banksia.resources.bus import moe.lava.banksia.resources.bus_background diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt index 2d71fce..f74dc1a 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/App.kt @@ -3,7 +3,6 @@ package moe.lava.banksia.ui import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi -import moe.lava.banksia.di.coreDiModule import moe.lava.banksia.ui.di.AppModule import moe.lava.banksia.ui.screens.map.MapScreen import org.koin.compose.KoinMultiplatformApplication @@ -14,7 +13,7 @@ import org.koin.dsl.koinConfiguration @Composable fun App() { KoinMultiplatformApplication(config = koinConfiguration { - modules(coreDiModule, AppModule) + modules(AppModule) }) { MapScreen() } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt index 655caca..a1a97d3 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/RouteInfoPanel.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.ui.components.RouteIcon sealed class RouteInfoPanelEvent : InfoPanelEvent() diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt index 7b7dcf9..29bdd37 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/TripInfoPanel.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.ui.components.RouteIcon sealed class TripInfoPanelEvent : InfoPanelEvent() 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 bb45232..53600f4 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 @@ -18,9 +18,14 @@ import kotlinx.datetime.toInstant import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository import moe.lava.banksia.core.data.repositories.StopTimeRepository +import moe.lava.banksia.core.model.Route +import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.core.util.BoxedValue +import moe.lava.banksia.core.util.BoxedValue.Companion.box +import moe.lava.banksia.core.util.LoopFlow.Companion.waitUntilSubscribed +import moe.lava.banksia.core.util.Point +import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService -import moe.lava.banksia.model.Route -import moe.lava.banksia.model.RouteType import moe.lava.banksia.ui.layout.info.InfoPanelEvent import moe.lava.banksia.ui.layout.info.InfoPanelState import moe.lava.banksia.ui.layout.info.RouteInfoPanelState @@ -31,11 +36,6 @@ import moe.lava.banksia.ui.map.util.CameraPositionBounds import moe.lava.banksia.ui.map.util.Marker import moe.lava.banksia.ui.state.MapState import moe.lava.banksia.ui.state.SearchState -import moe.lava.banksia.util.BoxedValue -import moe.lava.banksia.util.BoxedValue.Companion.box -import moe.lava.banksia.util.LoopFlow.Companion.waitUntilSubscribed -import moe.lava.banksia.util.Point -import moe.lava.banksia.util.log import kotlin.time.Clock import kotlin.time.Duration.Companion.minutes diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt index 05429cb..9f60514 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/state/SearchState.kt @@ -1,6 +1,6 @@ package moe.lava.banksia.ui.state -import moe.lava.banksia.model.RouteType +import moe.lava.banksia.core.model.RouteType data class SearchState( val entries: List = listOf(), From 4cdb9a305cd96ab12a5bfd2d198eacdd7060ea7a Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Thu, 2 Apr 2026 02:28:10 +1100 Subject: [PATCH 20/33] fix(ui): make setting camera position work again --- .../moe/lava/banksia/ui/map/MapLibreMaps.kt | 19 +++++++++++++++++-- .../lava/banksia/ui/map/MapsPositionState.kt | 10 ++++++---- .../banksia/ui/map/mappers/CameraPosition.kt | 15 +++++++++++++++ .../lava/banksia/ui/screens/map/MapScreen.kt | 9 +++++++++ .../ui/screens/map/MapScreenViewModel.kt | 5 ----- 5 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/CameraPosition.kt diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt index a870653..d76c1f4 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt @@ -6,12 +6,15 @@ import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.safeDrawing import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch import kotlinx.serialization.json.JsonObject import moe.lava.banksia.core.Constants import moe.lava.banksia.ui.map.mappers.routeColorExpression +import moe.lava.banksia.ui.map.mappers.toMapPosition import moe.lava.banksia.ui.platform.BanksiaTheme import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.rememberCameraState @@ -26,6 +29,7 @@ import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.util.ClickResult import org.maplibre.spatialk.geojson.Feature import org.maplibre.spatialk.geojson.Geometry +import kotlin.time.Duration.Companion.seconds @Composable internal fun MapLibreMaps( @@ -34,7 +38,7 @@ internal fun MapLibreMaps( positionState: MapsPositionState, stops: GeoJsonData.Features?, // vehicles: GeoJsonData.Features?, - stopInnerColor: Color, + stopInnerColor: Color = BanksiaTheme.colors.surface, onStopClicked: (Feature) -> Unit, ) { val camPos = rememberCameraState( @@ -43,6 +47,17 @@ internal fun MapLibreMaps( target = MELBOURNE_POS ) ) + val scope = rememberCoroutineScope() + scope.launch { + positionState.updates.collect { + val (position, box) = it.toMapPosition() + if (box != null) { + camPos.animateTo(box, duration = 1.seconds) + } else { + camPos.animateTo(position, duration = 1.seconds) + } + } + } val variant = if (isSystemInDarkTheme()) "dark" else "light" @@ -63,7 +78,7 @@ internal fun MapLibreMaps( CircleLayer( id = "maps-stops0", source = stopsSource, - color = const(BanksiaTheme.colors.surface), + color = const(stopInnerColor), radius = const(3.dp), strokeWidth = const(2.dp), strokeColor = routeColorExpression, diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt index b3dab0a..94421a7 100644 --- a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapsPositionState.kt @@ -7,16 +7,18 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch -import moe.lava.banksia.core.util.Point +import moe.lava.banksia.ui.map.util.CameraPosition class MapsPositionState internal constructor( private val scope: CoroutineScope ) { - internal val updates: SharedFlow + internal val updates: SharedFlow field = MutableSharedFlow() - fun update(position: Point) { - scope.launch { updates.emit(position) } + fun update(position: CameraPosition) { + scope.launch { + updates.emit(position) + } } } diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/CameraPosition.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/CameraPosition.kt new file mode 100644 index 0000000..b463c18 --- /dev/null +++ b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/mappers/CameraPosition.kt @@ -0,0 +1,15 @@ +package moe.lava.banksia.ui.map.mappers + +import moe.lava.banksia.ui.map.util.CameraPosition +import org.maplibre.spatialk.geojson.BoundingBox +import org.maplibre.compose.camera.CameraPosition as MLCameraPosition + +internal fun CameraPosition.toMapPosition() = Pair( + MLCameraPosition(target = this.centre.toPosition(), zoom = 16.0), + this.bounds?.let { + BoundingBox( + southwest = it.southwest.toPosition(), + northeast = it.northeast.toPosition(), + ) + } +) diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreen.kt index f4319be..1303bf5 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 @@ -40,6 +40,7 @@ import moe.lava.banksia.ui.layout.SheetStateWrapper import moe.lava.banksia.ui.layout.info.InfoPanel import moe.lava.banksia.ui.layout.info.InfoPanelState import moe.lava.banksia.ui.map.Maps +import moe.lava.banksia.ui.map.rememberMapsPositionState import moe.lava.banksia.ui.platform.BanksiaTheme import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel @@ -64,6 +65,13 @@ fun MapScreen( val sheetState = SheetStateWrapper.create() var searchExpandedState by rememberSaveable { mutableStateOf(false) } + val mapsPositionState = rememberMapsPositionState() + scope.launch { + viewModel.cameraChangeEmitter.collect { + mapsPositionState.update(it.value) + } + } + LaunchedEffect(infoState) { if (infoState !is InfoPanelState.None) { sheetState.peek() @@ -80,6 +88,7 @@ fun MapScreen( SearchBarDefaults.InputFieldHeight.roundToPx() }, bottom = sheetState.bottomInset), stops = mapState.stops, + positionState = mapsPositionState, // vehicles = mapState.vehicles, onStopClicked = { stop -> viewModel.handleEvent(MapScreenEvent.SelectStop(stop)) diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt index 53600f4..c4bd768 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 @@ -116,11 +116,6 @@ class MapScreenViewModel( } fun centreCameraToLocation() { - viewModelScope.launch { - log("msvm", "getting..") - val routes = routeRepository.getAll() - log("msvm", routes.joinToString("\n")) - } lastKnownLocation?.let { location -> viewModelScope.launch { log("bvm", "emitting $location") From e7caeca356764f7d1efe9d07f96f4a7bfd2d5b86 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Thu, 2 Apr 2026 21:55:45 +1100 Subject: [PATCH 21/33] refactor: core:data -> core:data:client --- core/data/build.gradle.kts | 20 ------- core/data/client/build.gradle.kts | 54 +++++++++++++++++++ .../banksia/core/data/ClientDataDiModule.kt} | 12 +++-- .../repositories/ClientRouteRepository.kt | 25 +++++++++ .../data/repositories/ClientStopRepository.kt | 22 ++++++++ .../repositories/ClientStopTimeRepository.kt | 16 ++++++ .../sources/route/RouteLocalDataSource.kt | 0 .../sources/route/RouteRemoteDataSource.kt | 0 .../data/sources/stop/StopLocalDataSource.kt | 0 .../data/sources/stop/StopRemoteDataSource.kt | 0 .../stoptime/StopTimeLocalDataSource.kt | 0 .../stoptime/StopTimeRemoteDataSource.kt | 0 .../data/sources/trip/TripRemoteDataSource.kt | 0 core/data/server/build.gradle.kts | 21 ++++++++ .../core/data/repositories/RouteRepository.kt | 25 ++------- .../core/data/repositories/StopRepository.kt | 22 ++------ .../data/repositories/StopTimeRepository.kt | 13 +---- server/build.gradle.kts | 2 +- settings.gradle.kts | 2 + ui/build.gradle.kts | 2 +- .../moe/lava/banksia/ui/di/AppModule.kt | 4 +- 21 files changed, 162 insertions(+), 78 deletions(-) create mode 100644 core/data/client/build.gradle.kts rename core/data/{src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt => client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt} (81%) create mode 100644 core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt create mode 100644 core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt create mode 100644 core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt rename core/data/{ => client}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt (100%) rename core/data/{ => client}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt (100%) rename core/data/{ => client}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt (100%) rename core/data/{ => client}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt (100%) rename core/data/{ => client}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt (100%) rename core/data/{ => client}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt (100%) rename core/data/{ => client}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt (100%) create mode 100644 core/data/server/build.gradle.kts diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index e0fea0c..6f78b5c 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -2,9 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) - alias(libs.plugins.ksp) } kotlin { @@ -27,26 +25,8 @@ kotlin { jvm() sourceSets { - androidMain.dependencies { - implementation(libs.koin.compose) - implementation(libs.ktor.client.okhttp) - } commonMain.dependencies { - implementation(libs.okio) - implementation(libs.koin.core) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.contentnegotiation) - implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.serialization.json) - implementation(libs.kotlinx.serialization.protobuf) - implementation(projects.core) - implementation(projects.core.room) - } - iosMain.dependencies { - implementation(libs.ktor.client.darwin) } } } diff --git a/core/data/client/build.gradle.kts b/core/data/client/build.gradle.kts new file mode 100644 index 0000000..e9848f3 --- /dev/null +++ b/core/data/client/build.gradle.kts @@ -0,0 +1,54 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.ksp) +} + +kotlin { + android { + namespace = "moe.lava.banksia.core.data.client" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } + + iosArm64() + iosSimulatorArm64() + + jvm() + + sourceSets { + androidMain.dependencies { + implementation(libs.koin.compose) + implementation(libs.ktor.client.okhttp) + } + commonMain.dependencies { + api(projects.core.data) + + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.serialization.protobuf) + + implementation(projects.core) + implementation(projects.core.room) + } + iosMain.dependencies { + implementation(libs.ktor.client.darwin) + } + } +} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt similarity index 81% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt index 6529a92..01e961c 100644 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt @@ -8,6 +8,9 @@ import io.ktor.client.plugins.plugin import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json import moe.lava.banksia.core.Constants +import moe.lava.banksia.core.data.repositories.ClientRouteRepository +import moe.lava.banksia.core.data.repositories.ClientStopRepository +import moe.lava.banksia.core.data.repositories.ClientStopTimeRepository import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository import moe.lava.banksia.core.data.repositories.StopTimeRepository @@ -21,9 +24,10 @@ import moe.lava.banksia.core.room.roomDiModule import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind import org.koin.dsl.module -val dataDiModule = module { +val clientDataDiModule = module { includes(roomDiModule) // HTTP Clients @@ -56,7 +60,7 @@ val dataDiModule = module { singleOf(::StopTimeRemoteDataSource) // Repositories - singleOf(::RouteRepository) - singleOf(::StopRepository) - singleOf(::StopTimeRepository) + singleOf(::ClientRouteRepository) bind RouteRepository::class + singleOf(::ClientStopRepository) bind StopRepository::class + singleOf(::ClientStopTimeRepository) bind StopTimeRepository::class } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt new file mode 100644 index 0000000..70a8905 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt @@ -0,0 +1,25 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource +import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource + +internal class ClientRouteRepository internal constructor( + private val local: RouteLocalDataSource, + private val remote: RouteRemoteDataSource, +) : RouteRepository { + private val mutex = Mutex() + override suspend fun getAll() = mutex.withLock { + local + .getAll() + .map { it.asModel() } + .ifEmpty { + remote + .getAll() + .also { local.save(*it.toTypedArray()) } + } + } + + override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt new file mode 100644 index 0000000..a5fd300 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource +import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource + +internal class ClientStopRepository internal constructor( + private val local: StopLocalDataSource, + private val remote: StopRemoteDataSource, +) : StopRepository { + private val mutex = Mutex() + + override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } + override suspend fun getByRoute(id: String) = mutex.withLock { + local + .getByRoute(id) + .map { it.asModel() } + .ifEmpty { null } + ?: remote.getByRoute(id) + } +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt new file mode 100644 index 0000000..aea3159 --- /dev/null +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt @@ -0,0 +1,16 @@ +package moe.lava.banksia.core.data.repositories + +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource +import moe.lava.banksia.core.model.StopTimeDated + +internal class ClientStopTimeRepository internal constructor( + private val local: StopTimeLocalDataSource, + private val remote: StopTimeRemoteDataSource, +) : StopTimeRepository { + override suspend fun getForStop(id: String): List { + return local + .getAtStop(id) + .ifEmpty { remote.getAtStop(id) } + } +} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt similarity index 100% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt similarity index 100% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt similarity index 100% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt similarity index 100% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt similarity index 100% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt similarity index 100% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt similarity index 100% rename from core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt rename to core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt diff --git a/core/data/server/build.gradle.kts b/core/data/server/build.gradle.kts new file mode 100644 index 0000000..eaa309b --- /dev/null +++ b/core/data/server/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSerialization) +} + +kotlin { + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } +} + +dependencies { + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + + api(projects.core.data) + implementation(projects.core) + implementation(projects.core.room) +} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt index fdeb95f..fbb663f 100644 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt @@ -1,25 +1,8 @@ package moe.lava.banksia.core.data.repositories -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource -import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource +import moe.lava.banksia.core.model.Route -class RouteRepository internal constructor( - private val local: RouteLocalDataSource, - private val remote: RouteRemoteDataSource, -) { - private val mutex = Mutex() - suspend fun getAll() = mutex.withLock { - local - .getAll() - .map { it.asModel() } - .ifEmpty { - remote - .getAll() - .also { local.save(*it.toTypedArray()) } - } - } - - suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } +interface RouteRepository { + suspend fun get(id: String): Route + suspend fun getAll(): List } diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt index d83a9cd..c663f89 100644 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopRepository.kt @@ -1,22 +1,8 @@ package moe.lava.banksia.core.data.repositories -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource -import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource +import moe.lava.banksia.core.model.Stop -class StopRepository internal constructor( - private val local: StopLocalDataSource, - private val remote: StopRemoteDataSource, -) { - private val mutex = Mutex() - - suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } - suspend fun getByRoute(id: String) = mutex.withLock { - local - .getByRoute(id) - .map { it.asModel() } - .ifEmpty { null } - ?: remote.getByRoute(id) - } +interface StopRepository { + suspend fun get(id: String): Stop + suspend fun getByRoute(id: String): List } diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt index 34aa570..87d01ac 100644 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt @@ -1,16 +1,7 @@ package moe.lava.banksia.core.data.repositories -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource import moe.lava.banksia.core.model.StopTimeDated -class StopTimeRepository internal constructor( - private val local: StopTimeLocalDataSource, - private val remote: StopTimeRemoteDataSource, -) { - suspend fun getForStop(id: String): List { - return local - .getAtStop(id) - .ifEmpty { remote.getAtStop(id) } - } +interface StopTimeRepository { + suspend fun getForStop(id: String): List } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 0883c85..4150bb2 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -5,7 +5,7 @@ plugins { application } -group = "moe.lava.banksia" +group = "moe.lava.banksia.server" version = "1.0.0" application { mainClass.set("moe.lava.banksia.server.ApplicationKt") diff --git a/settings.gradle.kts b/settings.gradle.kts index 335422d..9abcf7f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,6 +37,8 @@ include(":server:gtfs") include(":server:gtfs_rt") include(":core") include(":core:data") +include(":core:data:client") +include(":core:data:server") include(":core:room") include(":ui") include(":ui:maps") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 018c8e6..9c5c7bd 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -68,7 +68,7 @@ kotlin { implementation(libs.ui.backhandler) implementation(projects.core) - implementation(projects.core.data) + implementation(projects.core.data.client) implementation(projects.ui.maps) implementation(projects.ui.shared) } diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt index cff36fb..a2b4d7e 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt @@ -1,12 +1,12 @@ package moe.lava.banksia.ui.di -import moe.lava.banksia.core.data.dataDiModule +import moe.lava.banksia.core.data.clientDataDiModule import moe.lava.banksia.ui.screens.map.MapScreenViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val AppModule = module { - includes(dataDiModule) + includes(clientDataDiModule) // ViewModel viewModelOf(::MapScreenViewModel) From 959b022cf9a18b56aa6eecb7257ceb639efdcac8 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sat, 11 Apr 2026 21:33:33 +1000 Subject: [PATCH 22/33] feat(core/room): swappable databases --- .../core/room/DatabaseManager.android.kt | 22 ++++++ .../banksia/core/room/RoomDiModule.android.kt | 21 ------ .../lava/banksia/core/room/DatabaseManager.kt | 7 ++ .../lava/banksia/core/room/RoomDiModule.kt | 30 +++----- .../banksia/core/room/DatabaseManager.ios.kt | 8 +++ .../banksia/core/room/RoomDiModule.ios.kt | 14 ---- .../banksia/core/room/DatabaseManager.jvm.kt | 69 +++++++++++++++++++ .../banksia/core/room/RoomDiModule.jvm.kt | 19 ----- 8 files changed, 117 insertions(+), 73 deletions(-) create mode 100644 core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt delete mode 100644 core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt create mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt create mode 100644 core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt delete mode 100644 core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt create mode 100644 core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt delete mode 100644 core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt diff --git a/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt b/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt new file mode 100644 index 0000000..999ee4b --- /dev/null +++ b/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.core.room + +import android.content.Context +import androidx.room.Room +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.core.component.inject + +actual class DatabaseManager actual constructor() : KoinComponent { + private val ctx by inject() + + actual val database by lazy { + val ctx = get().applicationContext + val dbFile = ctx.getDatabasePath("room.db") + val builder = Room.databaseBuilder( + context = ctx, + name = dbFile.absolutePath, + ) + + Database.build(builder) + } +} diff --git a/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt b/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt deleted file mode 100644 index 8cd01e6..0000000 --- a/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.android.kt +++ /dev/null @@ -1,21 +0,0 @@ -package moe.lava.banksia.core.room - -import android.content.Context -import androidx.room.Room -import androidx.room.RoomDatabase -import org.koin.core.parameter.ParametersHolder -import org.koin.core.scope.Scope - -class AndroidDatabaseBuilder(val ctx: Context) : PlatformDatabaseBuilder { - override fun getBuilder(): RoomDatabase.Builder { - val appContext = ctx.applicationContext - val dbFile = appContext.getDatabasePath("room.db") - return Room.databaseBuilder( - context = appContext, - name = dbFile.absolutePath - ) - } -} - -internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = - AndroidDatabaseBuilder(get()) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt new file mode 100644 index 0000000..bfe32a9 --- /dev/null +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt @@ -0,0 +1,7 @@ +package moe.lava.banksia.core.room + +import org.koin.core.component.KoinComponent + +expect class DatabaseManager() : KoinComponent { + val database: Database +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt index 67d4fd2..85c56fc 100644 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt @@ -1,26 +1,18 @@ package moe.lava.banksia.core.room -import androidx.room.RoomDatabase -import org.koin.core.parameter.ParametersHolder -import org.koin.core.scope.Scope +import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val roomDiModule = module { - single { provideDatabaseBuilder(it) } - single { Database.build(get().getBuilder()) } + singleOf(::DatabaseManager) + factory { get().database } - single { get().versionMetadataDao } - single { get().routeDao } - single { get().serviceDao } - single { get().serviceExceptionDao } - single { get().shapeDao } - single { get().stopDao } - single { get().stopTimeDao } - single { get().tripDao } + factory { get().versionMetadataDao } + factory { get().routeDao } + factory { get().serviceDao } + factory { get().serviceExceptionDao } + factory { get().shapeDao } + factory { get().stopDao } + factory { get().stopTimeDao } + factory { get().tripDao } } - -internal interface PlatformDatabaseBuilder { - fun getBuilder(): RoomDatabase.Builder -} - -internal expect fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder diff --git a/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt b/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt new file mode 100644 index 0000000..34e370e --- /dev/null +++ b/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt @@ -0,0 +1,8 @@ +package moe.lava.banksia.core.room + +import org.koin.core.component.KoinComponent + +actual class DatabaseManager actual constructor() : KoinComponent { + actual val database: Database + get() = TODO("Not yet implemented") +} diff --git a/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt b/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt deleted file mode 100644 index 3c74852..0000000 --- a/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.ios.kt +++ /dev/null @@ -1,14 +0,0 @@ -package moe.lava.banksia.core.room - -import androidx.room.RoomDatabase -import org.koin.core.parameter.ParametersHolder -import org.koin.core.scope.Scope - -class IosDatabaseBuilder() : PlatformDatabaseBuilder { - override fun getBuilder(): RoomDatabase.Builder { - TODO("Not yet implemented") - } -} - -internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = - IosDatabaseBuilder() diff --git a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt new file mode 100644 index 0000000..b686c58 --- /dev/null +++ b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt @@ -0,0 +1,69 @@ +package moe.lava.banksia.core.room + +import androidx.room.Room +import androidx.room.RoomDatabase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import moe.lava.banksia.core.util.error +import org.koin.core.component.KoinComponent +import java.io.File +import kotlin.system.exitProcess + +actual class DatabaseManager : KoinComponent { + private var liveDatabase: Database = Database.build(getBuilder()) + actual val database get() = liveDatabase + + private fun getBuilder(path: String = "./data/room.db"): RoomDatabase.Builder { + val dbFile = File(path) + return Room.databaseBuilder( + name = dbFile.absolutePath, + ).setJournalMode(RoomDatabase.JournalMode.TRUNCATE) + } + + fun makeAlt() = Database.build(getBuilder("./data/room_alt.db")) + + private fun deleteAll(file: File): Boolean { + val r1 = file.delete() + val r2 = File(file.parentFile, file.name + ".lck").delete() + val r3 = File(file.parentFile, file.name + "-journal").delete() + return r1 && r2 && r3 + } + + private fun renameAll(from: File, to: File): Boolean { + val r1 = from.renameTo(to) + val r2 = File(from.parentFile, from.name + ".lck").renameTo(File(to.parentFile, to.name + ".lck")) + val r3 = File(from.parentFile, from.name + "-journal").renameTo(File(to.parentFile, to.name + "-journal")) + return r1 && r2 && r3 + } + + fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { + val live = File("./data/room.db") + val alt = File("./data/room_alt.db") + val old = File("./data/room_old.db") + + if (!renameAll(live, old)) { + error("DatabaseManager", "Failed to rename database from live to old (${live.absolutePath} -> ${old.absolutePath})") + return + } + if (!renameAll(alt, live)) { + error("DatabaseManager", "Failed to rename database from alt to live, trying to undo.. (${alt.absolutePath} -> ${live.absolutePath})") + if (!live.renameTo(old)) { + error("DatabaseManager", "Failed to undo, critical failure, exiting..") + exitProcess(1) + } + return + } + val oldDatabase = liveDatabase + liveDatabase = Database.build(getBuilder()) + + scope.launch { + delay(5000) + if (!deleteAll(old)) { + error("DatabaseManager", "Failed to unlink old database, stray files! (${old.absolutePath})") + } + oldDatabase.close() + } + } +} diff --git a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt deleted file mode 100644 index 38c35ce..0000000 --- a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.jvm.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.lava.banksia.core.room - -import androidx.room.Room -import androidx.room.RoomDatabase -import org.koin.core.parameter.ParametersHolder -import org.koin.core.scope.Scope -import java.io.File - -class JvmDatabaseBuilder() : PlatformDatabaseBuilder { - override fun getBuilder(): RoomDatabase.Builder { - val dbFile = File("./data/room.db") - return Room.databaseBuilder( - name = dbFile.absolutePath, - ) - } -} - -internal actual fun Scope.provideDatabaseBuilder(p: ParametersHolder): PlatformDatabaseBuilder = - JvmDatabaseBuilder() From 0524eda5d2670904816df8c688e5101f81c30e56 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sat, 11 Apr 2026 21:59:26 +1000 Subject: [PATCH 23/33] feat(server): use lazy swappable database --- .../lava/banksia/server/gtfs/GtfsParser.kt | 2 +- .../moe/lava/banksia/server/Application.kt | 29 +++----- .../moe/lava/banksia/server/GtfsDataFixer.kt | 2 +- .../moe/lava/banksia/server/GtfsImporter.kt | 73 ++++++++----------- .../lava/banksia/server/di/ServerModules.kt | 5 +- 5 files changed, 49 insertions(+), 62 deletions(-) diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index e726a46..58ae094 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -135,7 +135,7 @@ class GtfsParser( .forEach { fd -> log.info("parsing stop times for ${fd.parent}...") parseStopTimes(fd, trips) { seq -> - seq.chunked(10000) + seq.chunked(1000000) .forEach { emit(GtfsData.StopTimeChunk(it)) } } } 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 2fe7bf6..91a9640 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -28,7 +28,7 @@ import moe.lava.banksia.core.util.serialise import moe.lava.banksia.server.di.ServerModules import moe.lava.banksia.server.gtfsrt.GtfsrtService import org.koin.dsl.module -import org.koin.ktor.ext.inject +import org.koin.ktor.ext.get import org.koin.ktor.plugin.Koin import kotlin.time.Clock @@ -46,16 +46,14 @@ fun Application.module() { modules(ServerModules) } - val gtfsr by inject() @Suppress("KotlinConstantConditions") - launch { gtfsr.start(this, !Constants.devMode) } + launch { get().start(this, !Constants.devMode) } routing { if (Constants.devMode) { get("/fixup") { call.respondText("received") - val fixer by inject() - fixer.addParentsToStops() + get().addParentsToStops() } } get("/update") { @@ -70,16 +68,13 @@ fun Application.module() { ?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip" call.respondText("received") launch(context = Dispatchers.IO) { - val fixer by inject() - val importer by inject() - importer.import(datasetUrl) - - fixer.addParentsToStops() + get().import(datasetUrl) + get().addParentsToStops() } } get("/metadata/{type?}") { - val dao by inject() + val dao = get() val type = call.parameters["type"] if (type == null) { call.respond(dao.getAll().map { it.asModel() }) @@ -96,7 +91,7 @@ fun Application.module() { get("/routes") { val routes = withContext(context = Dispatchers.IO) { - inject().value.getAll() + get().getAll() } val res = routes.map { it.asModel() } call.respond(res) @@ -104,7 +99,7 @@ fun Application.module() { get("/routes/{route_id}") { val routeId = call.parameters["route_id"]!! val route = withContext(context = Dispatchers.IO) { - inject().value.get(routeId) + get().get(routeId) } if (route != null) call.respond(route.asModel()) @@ -113,7 +108,7 @@ fun Application.module() { } get("/stops") { val routes = withContext(context = Dispatchers.IO) { - inject().value.getAll() + get().getAll() } val res = routes.map { it.asModel() } call.respond(res) @@ -121,7 +116,7 @@ fun Application.module() { get("/stops/{stop_id}") { val stopId = call.parameters["stop_id"]!! val stop = withContext(context = Dispatchers.IO) { - inject().value.get(stopId) + get().get(stopId) } if (stop != null) call.respond(stop.asModel()) @@ -132,7 +127,7 @@ fun Application.module() { val routeId = call.parameters["route_id"]!! val useParent = call.queryParameters["parent"] !in listOf("false", "0") val stops = withContext(Dispatchers.IO) { - val routeDao by inject() + val routeDao = get() if (useParent) routeDao.stopsParent(routeId) else @@ -146,7 +141,7 @@ fun Application.module() { ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) } ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) val times = withContext(context = Dispatchers.IO) { - inject().value + get() .getForStopDated( stopId, listOf(date.dayOfWeek).serialise(), diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt index b2620f4..42040b7 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -24,7 +24,7 @@ class GtfsDataFixer( name = name, lat = avgLat, lng = avgLng, - parent = "", + parent = null, hasWheelChairBoarding = stops.all { it.hasWheelChairBoarding }, level = "", platformCode = "", diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index a012da7..5c8dc37 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -1,7 +1,5 @@ package moe.lava.banksia.server -import androidx.room.immediateTransaction -import androidx.room.useWriterConnection import io.ktor.util.logging.Logger import moe.lava.banksia.core.model.Route import moe.lava.banksia.core.model.Service @@ -11,6 +9,7 @@ import moe.lava.banksia.core.model.Stop import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.room.Database +import moe.lava.banksia.core.room.DatabaseManager import moe.lava.banksia.core.room.entity.asEntity import moe.lava.banksia.server.gtfs.GtfsData import moe.lava.banksia.server.gtfs.GtfsParser @@ -18,74 +17,66 @@ import kotlin.time.Clock class GtfsImporter( private val parser: GtfsParser, - private val database: Database, + private val dbm: DatabaseManager, private val log: Logger, ) { suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) { - database.useWriterConnection { transactor -> - transactor.immediateTransaction { - database.routeDao.deleteAll() - database.serviceDao.deleteAll() - database.serviceExceptionDao.deleteAll() - database.shapeDao.deleteAll() - database.stopDao.deleteAll() - database.stopTimeDao.deleteAll() - database.tripDao.deleteAll() + val database = dbm.makeAlt() - parser.update(url).collect { chunk -> - when (chunk) { - is GtfsData.RouteChunk -> addRoutes(chunk.routes) - is GtfsData.ServiceChunk -> addServices(chunk.services) - is GtfsData.ServiceExceptionChunk -> addServiceExceptions(chunk.exceptions) - is GtfsData.ShapeChunk -> addShapes(chunk.shapes) - is GtfsData.StopChunk -> addStops(chunk.stops) - is GtfsData.StopTimeChunk -> addStopTimes(chunk.stopTimes) - is GtfsData.TripChunk -> addTrips(chunk.trips) - } - } - - updateMetadata(date) + parser.update(url).collect { chunk -> + when (chunk) { + is GtfsData.RouteChunk -> database.addRoutes(chunk.routes) + is GtfsData.ServiceChunk -> database.addServices(chunk.services) + is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions) + is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes) + is GtfsData.StopChunk -> database.addStops(chunk.stops) + is GtfsData.StopTimeChunk -> database.addStopTimes(chunk.stopTimes) + is GtfsData.TripChunk -> database.addTrips(chunk.trips) } } + + database.updateMetadata(date) + database.close() + dbm.swap() } - private suspend fun updateMetadata(date: Long) { - val dao = database.versionMetadataDao + private suspend fun Database.updateMetadata(date: Long) { + val dao = versionMetadataDao log.info("updating metadata...") dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) log.info("done") } - private suspend fun addRoutes(routes: List) { - val dao = database.routeDao + private suspend fun Database.addRoutes(routes: List) { + val dao = routeDao log.info("inserting routes...") dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) log.info("done") } - private suspend fun addServices(services: List) { - val dao = database.serviceDao + private suspend fun Database.addServices(services: List) { + val dao = serviceDao log.info("inserting services...") dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) log.info("done") } - private suspend fun addServiceExceptions(exceptions: List) { - val dao = database.serviceExceptionDao + private suspend fun Database.addServiceExceptions(exceptions: List) { + val dao = serviceExceptionDao log.info("inserting exceptions...") dao.insertOrReplaceAll(*exceptions.map { it.asEntity() }.toTypedArray()) log.info("done") } - private suspend fun addShapes(shapes: List) { - val dao = database.shapeDao + private suspend fun Database.addShapes(shapes: List) { + val dao = shapeDao log.info("inserting shapes...") dao.insertOrReplaceAll(*shapes.map { it.asEntity() }.toTypedArray()) log.info("done") } - private suspend fun addStops(stops: List) { - val dao = database.stopDao + private suspend fun Database.addStops(stops: List) { + val dao = stopDao log.info("inserting stops...") stops .groupBy { it.id } @@ -102,15 +93,15 @@ class GtfsImporter( log.info("done") } - private suspend fun addStopTimes(stopTimes: List) { - val dao = database.stopTimeDao + private suspend fun Database.addStopTimes(stopTimes: List) { + val dao = stopTimeDao log.info("inserting ${stopTimes.size} stoptimes...") dao.insertOrReplaceAll(*stopTimes.map { it.asEntity() }.toTypedArray()) log.info("done") } - private suspend fun addTrips(trips: List) { - val dao = database.tripDao + private suspend fun Database.addTrips(trips: List) { + val dao = tripDao log.info("inserting ${trips.size} trips...") dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) log.info("done") diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index 7c7fc0b..a8a7541 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -6,6 +6,7 @@ import moe.lava.banksia.server.GtfsDataFixer import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser import moe.lava.banksia.server.gtfsrt.GtfsrtService +import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf import org.koin.dsl.module @@ -16,6 +17,6 @@ val ServerModules = module { singleOf(::GtfsParser) singleOf(::GtfsrtService) - singleOf(::GtfsDataFixer) - singleOf(::GtfsImporter) + factoryOf(::GtfsDataFixer) + factoryOf(::GtfsImporter) } From 38bcdc54bce73525075434dedbdff511d7322331 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sat, 11 Apr 2026 23:34:47 +1000 Subject: [PATCH 24/33] fix(server/gtfs): null empty parents --- .../src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index 58ae094..b95b232 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -170,7 +170,7 @@ class GtfsParser( id = stop_id, name = stop_name, pos = Point(stop_lat, stop_lon), - parent = parent_station, + parent = parent_station.ifEmpty { null }, hasWheelChairBoarding = wheelchair_boarding == "1", level = level_id, platformCode = platform_code, From 27f2a08d779a1bb5133ce3cd2bda799f5b24389c Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sun, 12 Apr 2026 00:31:41 +1000 Subject: [PATCH 25/33] feat(server): add fixup endpoint and move update endpoint --- .../kotlin/moe/lava/banksia/server/Application.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 91a9640..0981b80 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -56,7 +56,19 @@ fun Application.module() { get().addParentsToStops() } } - get("/update") { + get("/manage/fixup") { + val key = call.parameters["key"] + if (key != Constants.updateKey) { + call.respond(HttpStatusCode.Forbidden) + return@get + } + + call.respondText("fixing") + launch(context = Dispatchers.IO) { + get().addParentsToStops() + } + } + get("/manage/update") { val key = call.parameters["key"] if (key != Constants.updateKey) { call.respond(HttpStatusCode.Forbidden) From 29a804b0fb73db55f85de58530a36cb33845e9ec Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sun, 12 Apr 2026 00:32:28 +1000 Subject: [PATCH 26/33] fix(core/room/server): ignore non-existing files in file operations --- .../banksia/core/room/DatabaseManager.jvm.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt index b686c58..360d2dd 100644 --- a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt +++ b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt @@ -25,17 +25,17 @@ actual class DatabaseManager : KoinComponent { fun makeAlt() = Database.build(getBuilder("./data/room_alt.db")) private fun deleteAll(file: File): Boolean { - val r1 = file.delete() - val r2 = File(file.parentFile, file.name + ".lck").delete() - val r3 = File(file.parentFile, file.name + "-journal").delete() - return r1 && r2 && r3 + val r1 = file.takeIf { it.exists() }?.delete() + val r2 = File(file.parentFile, file.name + ".lck").takeIf { it.exists() }?.delete() + val r3 = File(file.parentFile, file.name + "-journal").takeIf { it.exists() }?.delete() + return r1 != false && r2 != false && r3 != false } private fun renameAll(from: File, to: File): Boolean { - val r1 = from.renameTo(to) - val r2 = File(from.parentFile, from.name + ".lck").renameTo(File(to.parentFile, to.name + ".lck")) - val r3 = File(from.parentFile, from.name + "-journal").renameTo(File(to.parentFile, to.name + "-journal")) - return r1 && r2 && r3 + val r1 = from.takeIf { it.exists() }?.renameTo(to) + val r2 = File(from.parentFile, from.name + ".lck").takeIf { it.exists() }?.renameTo(File(to.parentFile, to.name + ".lck")) + val r3 = File(from.parentFile, from.name + "-journal").takeIf { it.exists() }?.renameTo(File(to.parentFile, to.name + "-journal")) + return r1 != false && r2 != false && r3 != false } fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { From 415ce8d88f8e8818b5b427c5bd3642caf98a8a1d Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sun, 12 Apr 2026 01:02:53 +1000 Subject: [PATCH 27/33] fix(core/room): use null instead of empty parent --- .../commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt | 2 +- server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt index 99f5a2d..7edb560 100644 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt +++ b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt @@ -15,7 +15,7 @@ interface StopDao { @Query(""" SELECT * FROM Stop WHERE platformCode <> "" - AND parent == "" + AND parent IS NULL """) suspend fun getAllParentless(): List diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt index 42040b7..c74930d 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -31,7 +31,7 @@ class GtfsDataFixer( ) log("datafixer", "inserting ${parentId} for ${stops.size} children") dao.insertAll(parent) - database.stopDao.updateParents(stops.map { it.id }, parentId) + dao.updateParents(stops.map { it.id }, parentId) } } } From ef630b6d58644136a646621b95ccd312f1bca304 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sun, 12 Apr 2026 20:43:45 +1000 Subject: [PATCH 28/33] fix(server/gtfs): resolve duplicate stops more intelligently --- .../lava/banksia/server/gtfs/GtfsParser.kt | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index b95b232..f8d9832 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -38,6 +38,8 @@ import java.io.File import java.util.zip.ZipFile import kotlin.time.ExperimentalTime +private typealias StopWithSource = Pair + sealed class GtfsData { data class RouteChunk(val routes: List) : GtfsData() data class ServiceChunk(val services: List) : GtfsData() @@ -104,7 +106,8 @@ class GtfsParser( files .filter { it.name == "stops.txt" } - .forEach { emit(GtfsData.StopChunk(parseStops(it))) } + .flatMap { parseStops(it) } + .let { emit(GtfsData.StopChunk(fixupDuplicateStops(it))) } files .filter { it.name == "shapes.txt" } @@ -163,10 +166,10 @@ class GtfsParser( Shape(id, points) } - private fun parseStops(fd: File) = + private fun parseStops(fd: File): List = fd.parseCsv() .map { with(it) { - Stop( + fd.parentFile.name to Stop( id = stop_id, name = stop_name, pos = Point(stop_lat, stop_lon), @@ -292,4 +295,46 @@ class GtfsParser( } block(csv.decodeToSequence(iter, csv.serializersModule.serializer())) } + + // Type priority used to resolve duplicates, preferring the first one in the chain + private val typePriorityRanking = listOf( + RouteType.MetroTrain, + RouteType.RegionalTrain, + RouteType.MetroTram, + RouteType.MetroBus, + RouteType.RegionalBus, + RouteType.SkyBus, + ).map { it.value.toString() } + + @Suppress("LoggingStringTemplateAsArgument") // ? + private fun fixupDuplicateStops(stops: List): List { + return stops + .groupBy { (_, stops) -> stops.id } + .map { (id, stops) -> + // Just return it if no duplicate + if (stops.size == 1) return@map stops[0].second + + // Just return the first one if all the stops' data match + if (stops.withIndex().all { (idx, stop) -> idx == 0 || stop.second == stops[idx - 1].second }) + return@map stops[0].second + + // Find first stop ordered by the types + val res = typePriorityRanking + .firstNotNullOfOrNull { type -> + stops.find { it.first == type } + } + + val (_, stop) = if (res == null) { + log.warn("Cannot resolve duplicate stop ${id}, using first one") + stops.forEach { (type, stop) -> log.warn(" - ($type): $stop") } + stops[0] + } else { + log.debug("Resolving $id for type ${res.first}") + stops.forEach { (type, stop) -> log.debug("${if (res.first == type) "*" else " "} - ($type): $stop") } + res + } + + stop + } + } } From ff2af310fbba3436e6dd2709b23e91beb40a252f Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Thu, 30 Apr 2026 17:26:41 +1000 Subject: [PATCH 29/33] feat(server): use envvar for devmode constant --- .../kotlin/moe/lava/banksia/core/Constants.kt.skeleton | 2 +- server/src/main/kotlin/moe/lava/banksia/server/Application.kt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton b/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton index 15b3c58..909f642 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/Constants.kt.skeleton @@ -6,7 +6,7 @@ object Constants { const val opendataKey: String = "" const val serverUrl: String = "https://banksia.lava.moe/api/" // TODO - const val devMode: Boolean = false + var devMode: Boolean = false const val updateKey: String = "" const val protomapsKey: String = "" } 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 0981b80..73e7888 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -33,11 +33,14 @@ import org.koin.ktor.plugin.Koin import kotlin.time.Clock fun main() { + if (System.getenv("BANKSIA_PRODUCTION") == "1") Constants.devMode = false + embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module) .start(wait = true) } fun Application.module() { + log.info("devMode: ${Constants.devMode}") install(ContentNegotiation) { json() } From f1770744dbd07998f8ccd9fb79c1c4699cc29f47 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Sat, 2 May 2026 02:31:18 +1000 Subject: [PATCH 30/33] refactor(core): switch from room to sqldelight sqldelight provides far more control over the sql and allows me to make more optimisations such as removing generated rowid etc. sql also just looks better than the annotation hell from room. --- build.gradle.kts | 1 + core/build.gradle.kts | 15 - core/data/client/build.gradle.kts | 2 +- .../banksia/core/data/ClientDataDiModule.kt | 4 +- .../repositories/ClientRouteRepository.kt | 1 + .../data/repositories/ClientStopRepository.kt | 1 + .../sources/route/RouteLocalDataSource.kt | 23 +- .../data/sources/stop/StopLocalDataSource.kt | 24 +- .../stoptime/StopTimeLocalDataSource.kt | 27 +- core/data/server/build.gradle.kts | 1 - .../1.json | 72 --- .../10.json | 477 ----------------- .../11.json | 498 ------------------ .../2.json | 315 ----------- .../3.json | 339 ------------ .../4.json | 368 ------------- .../5.json | 368 ------------- .../6.json | 368 ------------- .../7.json | 415 --------------- .../8.json | 426 --------------- .../9.json | 426 --------------- .../core/room/DatabaseManager.android.kt | 22 - .../moe/lava/banksia/core/room/Database.kt | 83 --- .../lava/banksia/core/room/RoomDiModule.kt | 18 - .../core/room/converter/RouteTypeConverter.kt | 12 - .../core/room/converter/ShapePathConverter.kt | 43 -- .../lava/banksia/core/room/dao/RouteDao.kt | 58 -- .../lava/banksia/core/room/dao/ServiceDao.kt | 29 - .../core/room/dao/ServiceExceptionDao.kt | 29 - .../lava/banksia/core/room/dao/ShapeDao.kt | 26 - .../moe/lava/banksia/core/room/dao/StopDao.kt | 42 -- .../lava/banksia/core/room/dao/StopTimeDao.kt | 46 -- .../moe/lava/banksia/core/room/dao/TripDao.kt | 32 -- .../core/room/dao/VersionMetadataDao.kt | 27 - .../banksia/core/room/entity/RouteEntity.kt | 18 - .../banksia/core/room/entity/ServiceEntity.kt | 31 -- .../room/entity/ServiceExceptionEntity.kt | 28 - .../banksia/core/room/entity/ShapeEntity.kt | 19 - .../banksia/core/room/entity/StopEntity.kt | 36 -- .../core/room/entity/StopTimeEntity.kt | 53 -- .../banksia/core/room/entity/TripEntity.kt | 49 -- .../core/room/entity/VersionMetadataEntity.kt | 19 - .../banksia/core/room/DatabaseManager.ios.kt | 8 - .../banksia/core/room/DatabaseManager.jvm.kt | 69 --- core/{room => sqld}/build.gradle.kts | 36 +- .../core/sqld/DatabaseManager.android.kt | 14 + .../banksia/core/sqld}/DatabaseManager.kt | 4 +- .../lava/banksia/core/sqld/SqldDiModule.kt | 16 + .../lava/banksia/core/sqld/mappers/Route.kt | 14 + .../lava/banksia/core/sqld/mappers/Service.kt | 21 + .../core/sqld/mappers/ServiceException.kt | 17 + .../lava/banksia/core/sqld/mappers/Shape.kt | 52 ++ .../lava/banksia/core/sqld/mappers/Stop.kt | 26 + .../banksia/core/sqld/mappers/StopTime.kt | 25 + .../lava/banksia/core/sqld/mappers/Trip.kt | 32 ++ .../moe/lava/banksia/core/sqld/Route.sq | 15 + .../moe/lava/banksia/core/sqld/Service.sq | 11 + .../banksia/core/sqld/ServiceException.sq | 9 + .../moe/lava/banksia/core/sqld/Shape.sq | 7 + .../moe/lava/banksia/core/sqld/Stop.sq | 54 ++ .../moe/lava/banksia/core/sqld/StopTime.sq | 23 + .../moe/lava/banksia/core/sqld/Trip.sq | 15 + .../banksia/core/sqld/DatabaseManager.ios.kt | 10 + .../banksia/core/sqld/DatabaseManager.jvm.kt | 58 ++ .../moe/lava/banksia/core/model/Stop.kt | 4 +- .../moe/lava/banksia/core/model/Trip.kt | 6 +- gradle/libs.versions.toml | 11 +- server/build.gradle.kts | 4 +- .../lava/banksia/server/gtfs/GtfsParser.kt | 16 +- .../moe/lava/banksia/server/Application.kt | 60 +-- .../moe/lava/banksia/server/GtfsDataFixer.kt | 22 +- .../moe/lava/banksia/server/GtfsImporter.kt | 82 +-- .../lava/banksia/server/di/ServerModules.kt | 4 +- settings.gradle.kts | 2 +- 74 files changed, 601 insertions(+), 5037 deletions(-) delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/1.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/10.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/11.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/2.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/3.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/4.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/5.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/6.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/7.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/8.json delete mode 100644 core/room/schemas/moe.lava.banksia.core.room.Database/9.json delete mode 100644 core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt delete mode 100644 core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt delete mode 100644 core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt delete mode 100644 core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt rename core/{room => sqld}/build.gradle.kts (55%) create mode 100644 core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt rename core/{room/src/commonMain/kotlin/moe/lava/banksia/core/room => sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld}/DatabaseManager.kt (58%) create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq create mode 100644 core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt create mode 100644 core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0687328..9434477 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.sqldelight) apply false alias(libs.plugins.wire) apply false } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 99f7156..3dd2ee6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -4,12 +4,6 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) - alias(libs.plugins.ksp) - alias(libs.plugins.room) -} - -room { - schemaDirectory("$projectDir/schemas") } kotlin { @@ -46,18 +40,9 @@ kotlin { implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.protobuf) - implementation(libs.room.runtime) - implementation(libs.sqlite.bundled) } iosMain.dependencies { implementation(libs.ktor.client.darwin) } } } - -dependencies { - add("kspAndroid", libs.room.compiler) - add("kspIosArm64", libs.room.compiler) - add("kspIosSimulatorArm64", libs.room.compiler) - add("kspJvm", libs.room.compiler) -} diff --git a/core/data/client/build.gradle.kts b/core/data/client/build.gradle.kts index e9848f3..c6d5e5d 100644 --- a/core/data/client/build.gradle.kts +++ b/core/data/client/build.gradle.kts @@ -45,7 +45,7 @@ kotlin { implementation(libs.kotlinx.serialization.protobuf) implementation(projects.core) - implementation(projects.core.room) + implementation(projects.core.sqld) } iosMain.dependencies { implementation(libs.ktor.client.darwin) diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt index 01e961c..2cbfcaa 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt @@ -20,7 +20,7 @@ import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource -import moe.lava.banksia.core.room.roomDiModule +import moe.lava.banksia.core.sqld.sqldDiModule import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService import org.koin.core.module.dsl.singleOf @@ -28,7 +28,7 @@ import org.koin.dsl.bind import org.koin.dsl.module val clientDataDiModule = module { - includes(roomDiModule) + includes(sqldDiModule) // HTTP Clients singleOf(::PtvService) diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt index 70a8905..467399a 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource +import moe.lava.banksia.core.sqld.mappers.asModel internal class ClientRouteRepository internal constructor( private val local: RouteLocalDataSource, diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt index a5fd300..0aee84e 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource +import moe.lava.banksia.core.sqld.mappers.asModel internal class ClientStopRepository internal constructor( private val local: StopLocalDataSource, diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt index ca267c3..8e6af0e 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt @@ -1,11 +1,22 @@ package moe.lava.banksia.core.data.sources.route +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.room.dao.RouteDao -import moe.lava.banksia.core.room.entity.asEntity +import moe.lava.banksia.core.sqld.RouteQueries +import moe.lava.banksia.core.sqld.mappers.asDb -internal class RouteLocalDataSource(private val dao: RouteDao) { - suspend fun get(id: String) = dao.get(id) - suspend fun getAll() = dao.getAll() - suspend fun save(vararg routes: Route) = dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) +internal class RouteLocalDataSource(private val queries: RouteQueries) { + suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() } + suspend fun getAll() = withContext(Dispatchers.IO) { queries.getAll().executeAsList() } + suspend fun save(vararg routes: Route) { + withContext(Dispatchers.IO) { + queries.transaction { + routes.forEach { + queries.insert(it.asDb()) + } + } + } + } } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt index 8e0d8ab..524d123 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt @@ -1,12 +1,22 @@ package moe.lava.banksia.core.data.sources.stop +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.room.dao.RouteDao -import moe.lava.banksia.core.room.dao.StopDao -import moe.lava.banksia.core.room.entity.asEntity +import moe.lava.banksia.core.sqld.StopQueries +import moe.lava.banksia.core.sqld.mappers.asDb -internal class StopLocalDataSource(private val dao: StopDao, private val routeDao: RouteDao) { - suspend fun get(id: String) = dao.get(id) - suspend fun getByRoute(id: String) = routeDao.stops(id) - suspend fun save(vararg stops: Stop) = dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) +internal class StopLocalDataSource(private val queries: StopQueries) { + suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() } + suspend fun getByRoute(id: String) = withContext(Dispatchers.IO) { queries.getByRoute(id).executeAsList() } + suspend fun save(vararg stops: Stop) { + withContext(Dispatchers.IO) { + queries.transaction { + stops.forEach { + queries.insert(it.asDb()) + } + } + } + } } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt index c5ce4e7..78ca64b 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt @@ -1,28 +1,35 @@ package moe.lava.banksia.core.data.sources.stoptime +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn import moe.lava.banksia.core.model.StopTimeDated import moe.lava.banksia.core.model.atDate -import moe.lava.banksia.core.room.dao.StopTimeDao +import moe.lava.banksia.core.sqld.StopTimeQueries +import moe.lava.banksia.core.sqld.mappers.asModel import moe.lava.banksia.core.util.serialise import kotlin.time.Clock internal class StopTimeLocalDataSource( - private val stopTimeDao: StopTimeDao, + private val queries: StopTimeQueries, ) { suspend fun getAtStop( stopId: String, date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), ): List { - return stopTimeDao - .getForStopDated( - stopId, - listOf(date.dayOfWeek).serialise(), - date.toEpochDays().toInt(), - ) - .map { it.asModel().atDate(date) } - .sortedBy { it.departureTime } + return withContext(Dispatchers.IO) { + queries + .getForStopDated( + listOf(date.dayOfWeek).serialise().toLong(), + date.toEpochDays(), + stopId, + ) + .executeAsList() + .map { it.asModel().atDate(date) } + .sortedBy { it.departureTime } + } } } diff --git a/core/data/server/build.gradle.kts b/core/data/server/build.gradle.kts index eaa309b..d2296d7 100644 --- a/core/data/server/build.gradle.kts +++ b/core/data/server/build.gradle.kts @@ -17,5 +17,4 @@ dependencies { api(projects.core.data) implementation(projects.core) - implementation(projects.core.room) } diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/1.json b/core/room/schemas/moe.lava.banksia.core.room.Database/1.json deleted file mode 100644 index 037062e..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/1.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 1, - "identityHash": "e536f5a9b1408377bcc449195169648c", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e536f5a9b1408377bcc449195169648c')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/10.json b/core/room/schemas/moe.lava.banksia.core.room.Database/10.json deleted file mode 100644 index 751e946..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/10.json +++ /dev/null @@ -1,477 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 10, - "identityHash": "5b90bc800bfae6d22124ea0a6a906ca7", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Service", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "days", - "columnName": "days", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "start", - "columnName": "start", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "end", - "columnName": "end", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Service_days", - "unique": false, - "columnNames": [ - "days" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" - } - ] - }, - { - "tableName": "ServiceException", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", - "fields": [ - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "date", - "columnName": "date", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "serviceId", - "date" - ] - }, - "indices": [ - { - "name": "index_ServiceException_serviceId", - "unique": false, - "columnNames": [ - "serviceId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" - }, - { - "name": "index_ServiceException_type", - "unique": false, - "columnNames": [ - "type" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" - } - ] - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": false, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": false, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Service", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "serviceId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5b90bc800bfae6d22124ea0a6a906ca7')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/11.json b/core/room/schemas/moe.lava.banksia.core.room.Database/11.json deleted file mode 100644 index 6fc2976..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/11.json +++ /dev/null @@ -1,498 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 11, - "identityHash": "c4be3d0c2a25f8c5c33132646a070d0e", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Service", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "days", - "columnName": "days", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "start", - "columnName": "start", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "end", - "columnName": "end", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Service_days", - "unique": false, - "columnNames": [ - "days" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" - } - ] - }, - { - "tableName": "ServiceException", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", - "fields": [ - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "date", - "columnName": "date", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "serviceId", - "date" - ] - }, - "indices": [ - { - "name": "index_ServiceException_serviceId", - "unique": false, - "columnNames": [ - "serviceId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" - }, - { - "name": "index_ServiceException_type", - "unique": false, - "columnNames": [ - "type" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" - } - ] - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT" - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ], - "foreignKeys": [ - { - "table": "Stop", - "onDelete": "SET NULL", - "onUpdate": "NO ACTION", - "columns": [ - "parent" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": false, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": false, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_serviceId", - "unique": false, - "columnNames": [ - "serviceId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `${TABLE_NAME}` (`serviceId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Service", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "serviceId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c4be3d0c2a25f8c5c33132646a070d0e')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/2.json b/core/room/schemas/moe.lava.banksia.core.room.Database/2.json deleted file mode 100644 index 04a14e3..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/2.json +++ /dev/null @@ -1,315 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 2, - "identityHash": "83ece554400bb035c267dc2414c23293", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '83ece554400bb035c267dc2414c23293')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/3.json b/core/room/schemas/moe.lava.banksia.core.room.Database/3.json deleted file mode 100644 index e769926..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/3.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 3, - "identityHash": "5a7252ab3bcae4d0d0950024b19ba002", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a7252ab3bcae4d0d0950024b19ba002')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/4.json b/core/room/schemas/moe.lava.banksia.core.room.Database/4.json deleted file mode 100644 index 783b3ee..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/4.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 4, - "identityHash": "4426fd2ccc826d9d9d9021546b105850", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": true, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": true, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/5.json b/core/room/schemas/moe.lava.banksia.core.room.Database/5.json deleted file mode 100644 index c4a786d..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/5.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 5, - "identityHash": "4426fd2ccc826d9d9d9021546b105850", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": true, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": true, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/6.json b/core/room/schemas/moe.lava.banksia.core.room.Database/6.json deleted file mode 100644 index 5ab26dc..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/6.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 6, - "identityHash": "5f52de4cc0ddbcf02a0d8be4cf4d4cfd", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": false, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": false, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5f52de4cc0ddbcf02a0d8be4cf4d4cfd')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/7.json b/core/room/schemas/moe.lava.banksia.core.room.Database/7.json deleted file mode 100644 index d4c62b2..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/7.json +++ /dev/null @@ -1,415 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 7, - "identityHash": "15c94df0a62438ff28d451c074c94c59", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Service", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "days", - "columnName": "days", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "start", - "columnName": "start", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "end", - "columnName": "end", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Service_days", - "unique": false, - "columnNames": [ - "days" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" - } - ] - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": false, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": false, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '15c94df0a62438ff28d451c074c94c59')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/8.json b/core/room/schemas/moe.lava.banksia.core.room.Database/8.json deleted file mode 100644 index 9240dd5..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/8.json +++ /dev/null @@ -1,426 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 8, - "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Service", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "days", - "columnName": "days", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "start", - "columnName": "start", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "end", - "columnName": "end", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Service_days", - "unique": false, - "columnNames": [ - "days" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" - } - ] - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": false, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": false, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Service", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "serviceId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')" - ] - } -} \ No newline at end of file diff --git a/core/room/schemas/moe.lava.banksia.core.room.Database/9.json b/core/room/schemas/moe.lava.banksia.core.room.Database/9.json deleted file mode 100644 index 2359dbd..0000000 --- a/core/room/schemas/moe.lava.banksia.core.room.Database/9.json +++ /dev/null @@ -1,426 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 9, - "identityHash": "6e0f07bf1af88b2e37b5ad7c38a3fb2a", - "entities": [ - { - "tableName": "Route", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "number", - "columnName": "number", - "affinity": "TEXT" - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Service", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "days", - "columnName": "days", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "start", - "columnName": "start", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "end", - "columnName": "end", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Service_days", - "unique": false, - "columnNames": [ - "days" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" - } - ] - }, - { - "tableName": "Shape", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "path", - "columnName": "path", - "affinity": "BLOB", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - } - }, - { - "tableName": "Stop", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lat", - "columnName": "lat", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "lng", - "columnName": "lng", - "affinity": "REAL", - "notNull": true - }, - { - "fieldPath": "parent", - "columnName": "parent", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "hasWheelChairBoarding", - "columnName": "hasWheelChairBoarding", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "level", - "columnName": "level", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "platformCode", - "columnName": "platformCode", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Stop_parent", - "unique": false, - "columnNames": [ - "parent" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" - } - ] - }, - { - "tableName": "StopTime", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "tripId", - "columnName": "tripId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "stopId", - "columnName": "stopId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "arrivalTime", - "columnName": "arrivalTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "departureTime", - "columnName": "departureTime", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "headsign", - "columnName": "headsign", - "affinity": "TEXT" - }, - { - "fieldPath": "pickupType", - "columnName": "pickupType", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "dropOffType", - "columnName": "dropOffType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "tripId", - "stopId" - ] - }, - "indices": [ - { - "name": "index_StopTime_tripId", - "unique": false, - "columnNames": [ - "tripId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" - }, - { - "name": "index_StopTime_stopId", - "unique": false, - "columnNames": [ - "stopId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" - } - ], - "foreignKeys": [ - { - "table": "Trip", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "tripId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Stop", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "stopId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Trip", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "routeId", - "columnName": "routeId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "serviceId", - "columnName": "serviceId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "shapeId", - "columnName": "shapeId", - "affinity": "TEXT" - }, - { - "fieldPath": "tripHeadsign", - "columnName": "tripHeadsign", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "directionId", - "columnName": "directionId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "blockId", - "columnName": "blockId", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "wheelchairAccessible", - "columnName": "wheelchairAccessible", - "affinity": "TEXT", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "id" - ] - }, - "indices": [ - { - "name": "index_Trip_shapeId", - "unique": false, - "columnNames": [ - "shapeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" - }, - { - "name": "index_Trip_routeId", - "unique": false, - "columnNames": [ - "routeId" - ], - "orders": [], - "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" - } - ], - "foreignKeys": [ - { - "table": "Route", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "routeId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Service", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "serviceId" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Shape", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "shapeId" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "VersionMetadata", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", - "fields": [ - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "lastUpdated", - "columnName": "lastUpdated", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "autoGenerate": false, - "columnNames": [ - "type" - ] - } - } - ], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e0f07bf1af88b2e37b5ad7c38a3fb2a')" - ] - } -} \ No newline at end of file diff --git a/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt b/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt deleted file mode 100644 index 999ee4b..0000000 --- a/core/room/src/androidMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.android.kt +++ /dev/null @@ -1,22 +0,0 @@ -package moe.lava.banksia.core.room - -import android.content.Context -import androidx.room.Room -import org.koin.core.component.KoinComponent -import org.koin.core.component.get -import org.koin.core.component.inject - -actual class DatabaseManager actual constructor() : KoinComponent { - private val ctx by inject() - - actual val database by lazy { - val ctx = get().applicationContext - val dbFile = ctx.getDatabasePath("room.db") - val builder = Room.databaseBuilder( - context = ctx, - name = dbFile.absolutePath, - ) - - Database.build(builder) - } -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt deleted file mode 100644 index 006b749..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/Database.kt +++ /dev/null @@ -1,83 +0,0 @@ -package moe.lava.banksia.core.room - -import androidx.room.AutoMigration -import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import androidx.room.migration.Migration -import androidx.room.util.foreignKeyCheck -import androidx.sqlite.SQLiteConnection -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.sqlite.execSQL -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import moe.lava.banksia.core.room.converter.RouteTypeConverter -import moe.lava.banksia.core.room.dao.RouteDao -import moe.lava.banksia.core.room.dao.ServiceDao -import moe.lava.banksia.core.room.dao.ServiceExceptionDao -import moe.lava.banksia.core.room.dao.ShapeDao -import moe.lava.banksia.core.room.dao.StopDao -import moe.lava.banksia.core.room.dao.StopTimeDao -import moe.lava.banksia.core.room.dao.TripDao -import moe.lava.banksia.core.room.dao.VersionMetadataDao -import moe.lava.banksia.core.room.entity.RouteEntity -import moe.lava.banksia.core.room.entity.ServiceEntity -import moe.lava.banksia.core.room.entity.ServiceExceptionEntity -import moe.lava.banksia.core.room.entity.ShapeEntity -import moe.lava.banksia.core.room.entity.StopEntity -import moe.lava.banksia.core.room.entity.StopTimeEntity -import moe.lava.banksia.core.room.entity.TripEntity -import moe.lava.banksia.core.room.entity.VersionMetadataEntity -import androidx.room.Database as DatabaseAnnotation - -@DatabaseAnnotation( - version = 11, - entities = [ - RouteEntity::class, - ServiceEntity::class, - ServiceExceptionEntity::class, - ShapeEntity::class, - StopEntity::class, - StopTimeEntity::class, - TripEntity::class, - VersionMetadataEntity::class, - ], - autoMigrations = [ - AutoMigration(from = 1, to = 2), - AutoMigration(from = 2, to = 3), - AutoMigration(from = 9, to = 10), - ] -) -@TypeConverters(RouteTypeConverter::class) -abstract class Database : RoomDatabase() { - abstract val versionMetadataDao: VersionMetadataDao - abstract val routeDao: RouteDao - abstract val serviceDao: ServiceDao - abstract val serviceExceptionDao: ServiceExceptionDao - abstract val shapeDao: ShapeDao - abstract val stopDao: StopDao - abstract val stopTimeDao: StopTimeDao - abstract val tripDao: TripDao - - companion object { - fun build(base: Builder) = - base.fallbackToDestructiveMigration(true) - .setDriver(BundledSQLiteDriver()) - .setQueryCoroutineContext(Dispatchers.IO) - .addMigrations(MIGRATION_10_11) -// .fallbackToDestructiveMigration(true) - .build() - } -} - -val MIGRATION_10_11 = object : Migration(10, 11) { - override fun migrate(connection: SQLiteConnection) { - connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_Stop` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)") - connection.execSQL("INSERT INTO `_new_Stop` (`id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode`) SELECT `id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode` FROM `Stop`") - connection.execSQL("UPDATE `_new_Stop` SET `parent` = NULL WHERE `parent` == \"\"") - connection.execSQL("DROP TABLE `Stop`") - connection.execSQL("ALTER TABLE `_new_Stop` RENAME TO `Stop`") - connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `Stop` (`parent`)") - connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `Trip` (`serviceId`)") - foreignKeyCheck(connection, "Stop") - } -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt deleted file mode 100644 index 85c56fc..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/RoomDiModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.lava.banksia.core.room - -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -val roomDiModule = module { - singleOf(::DatabaseManager) - factory { get().database } - - factory { get().versionMetadataDao } - factory { get().routeDao } - factory { get().serviceDao } - factory { get().serviceExceptionDao } - factory { get().shapeDao } - factory { get().stopDao } - factory { get().stopTimeDao } - factory { get().tripDao } -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt deleted file mode 100644 index f588a66..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/RouteTypeConverter.kt +++ /dev/null @@ -1,12 +0,0 @@ -package moe.lava.banksia.core.room.converter - -import androidx.room.TypeConverter -import moe.lava.banksia.core.model.RouteType - -object RouteTypeConverter { - @TypeConverter - fun from(value: Int) = RouteType.from(value) - - @TypeConverter - fun to(routeType: RouteType) = routeType.value -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt deleted file mode 100644 index b914cff..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/converter/ShapePathConverter.kt +++ /dev/null @@ -1,43 +0,0 @@ -package moe.lava.banksia.core.room.converter - -import androidx.room.TypeConverter -import moe.lava.banksia.core.model.ShapePath -import moe.lava.banksia.core.util.Point - -object ShapePathConverter { - @TypeConverter - fun from(value: ByteArray): ShapePath { - return value - .asIterable() - .chunked(8) { - (it[0].toLong() and 0xFF) or - (it[1].toLong() and 0xFF shl 8) or - (it[2].toLong() and 0xFF shl 16) or - (it[3].toLong() and 0xFF shl 24) or - (it[4].toLong() and 0xFF shl 32) or - (it[5].toLong() and 0xFF shl 40) or - (it[6].toLong() and 0xFF shl 48) or - (it[7].toLong() and 0xFF shl 56) - } - .map { Double.fromBits(it) } - .chunked(2) - .map { (lat, lng) -> Point(lat, lng) } - } - - @TypeConverter - fun to(path: ShapePath): ByteArray { - return path - .flatMap { (lat, lng) -> listOf(lat.toBits(), lng.toBits()) } - .flatMap { i -> listOf( - i.toByte(), - (i shr 8).toByte(), - (i shr 16).toByte(), - (i shr 24).toByte(), - (i shr 32).toByte(), - (i shr 40).toByte(), - (i shr 48).toByte(), - (i shr 56).toByte(), - ) } - .toByteArray() - } -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt deleted file mode 100644 index c791f81..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/RouteDao.kt +++ /dev/null @@ -1,58 +0,0 @@ -package moe.lava.banksia.core.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.core.room.entity.RouteEntity -import moe.lava.banksia.core.room.entity.StopEntity - -@Dao -interface RouteDao { - @Query("SELECT * FROM Route") - suspend fun getAll(): List - - @Query("SELECT * FROM Route WHERE id == :id") - suspend fun get(id: String): RouteEntity? - - @Insert - suspend fun insertAll(vararg routes: RouteEntity) - - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg routes: RouteEntity) - - @Delete - suspend fun delete(route: RouteEntity) - - @Query("DELETE FROM Route") - suspend fun deleteAll() - - @Query(""" - SELECT Stop.* FROM Stop - INNER JOIN StopTime ON StopTime.stopId == Stop.id - INNER JOIN Trip ON Trip.id == StopTime.tripId - WHERE Trip.routeId == :id - GROUP BY Stop.id - """) - suspend fun stops(id: String): List - - // I vibecoded this, sorry - @Query(""" - WITH Tree AS ( - SELECT Stop.* FROM Stop - INNER JOIN StopTime ON StopTime.stopId == Stop.id - INNER JOIN Trip ON Trip.id == StopTime.tripId - WHERE Trip.routeId == :id - GROUP BY Stop.id - - UNION ALL - - SELECT s.* - FROM Stop s - INNER JOIN Tree t ON s.id = t.parent - ) - SELECT DISTINCT * FROM Tree WHERE parent IS NULL; - """) - suspend fun stopsParent(id: String): List -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt deleted file mode 100644 index e459cdf..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceDao.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.lava.banksia.core.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.core.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/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt deleted file mode 100644 index 86feb75..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ServiceExceptionDao.kt +++ /dev/null @@ -1,29 +0,0 @@ -package moe.lava.banksia.core.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.core.room.entity.ServiceExceptionEntity - -@Dao -interface ServiceExceptionDao { - @Query("SELECT * FROM ServiceException") - suspend fun getAll(): List - - @Query("SELECT * FROM ServiceException WHERE serviceId == :id") - suspend fun get(id: String): List - - @Insert - suspend fun insertAll(vararg exceptions: ServiceExceptionEntity) - - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg exceptions: ServiceExceptionEntity) - - @Delete - suspend fun delete(service: ServiceExceptionEntity) - - @Query("DELETE FROM ServiceException") - suspend fun deleteAll() -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt deleted file mode 100644 index 446a923..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/ShapeDao.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.lava.banksia.core.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.core.room.entity.ShapeEntity - -@Dao -interface ShapeDao { - @Query("SELECT * FROM Shape WHERE id == :id") - suspend fun get(id: String): ShapeEntity? - - @Insert - suspend fun insertAll(vararg shapes: ShapeEntity) - - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg shapes: ShapeEntity) - - @Delete - suspend fun delete(shape: ShapeEntity) - - @Query("DELETE FROM Shape") - suspend fun deleteAll() -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt deleted file mode 100644 index 7edb560..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopDao.kt +++ /dev/null @@ -1,42 +0,0 @@ -package moe.lava.banksia.core.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.core.room.entity.StopEntity - -@Dao -interface StopDao { - @Query("SELECT * FROM Stop") - suspend fun getAll(): List - - @Query(""" - SELECT * FROM Stop - WHERE platformCode <> "" - AND parent IS NULL - """) - suspend fun getAllParentless(): List - - @Query("SELECT * FROM Stop WHERE id == :id") - suspend fun get(id: String): StopEntity? - - @Query("SELECT * FROM Stop WHERE id IN (:ids)") - suspend fun get(ids: List): List - - @Insert - suspend fun insertAll(vararg stops: StopEntity) - - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg stops: StopEntity) - - @Delete - suspend fun delete(stop: StopEntity) - - @Query("DELETE FROM Stop") - suspend fun deleteAll() - - @Query("UPDATE Stop SET parent = :parent WHERE id IN (:ids)") - suspend fun updateParents(ids: List, parent: String) -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt deleted file mode 100644 index 4670b6e..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/StopTimeDao.kt +++ /dev/null @@ -1,46 +0,0 @@ -package moe.lava.banksia.core.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.core.room.entity.StopTimeEntity - -@Dao -interface StopTimeDao { - @Query("SELECT * FROM StopTime") - suspend fun getAll(): List - - @Query("SELECT * FROM StopTime WHERE tripId == :tripId") - suspend fun getForTrip(tripId: String): StopTimeEntity? - - @Query("SELECT * FROM StopTime WHERE tripId IN (:tripIds)") - suspend fun getForTrips(tripIds: List): List - - @Query("SELECT * FROM StopTime WHERE stopId == :stopId") - suspend fun getForStop(stopId: String): List - - @Query(""" - SELECT DISTINCT StopTime.* FROM StopTime - INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` - INNER JOIN Trip ON Trip.serviceId == Service.id - LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date - WHERE StopTime.tripId == Trip.id - AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) - AND ServiceException.type IS NULL - """) - suspend fun getForStopDated(stopId: String, days: Int, date: Int): List - - @Insert - suspend fun insertAll(vararg stopTimes: StopTimeEntity) - - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg stopTimes: StopTimeEntity) - - @Delete - suspend fun delete(stopTime: StopTimeEntity) - - @Query("DELETE FROM StopTime") - suspend fun deleteAll() -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt deleted file mode 100644 index 1798f61..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/TripDao.kt +++ /dev/null @@ -1,32 +0,0 @@ -package moe.lava.banksia.core.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.core.room.entity.TripEntity - -@Dao -interface TripDao { - @Query("SELECT * FROM Trip") - suspend fun getAll(): List - - @Query("SELECT * FROM Trip WHERE id == :id") - suspend fun get(id: String): TripEntity? - - @Query("SELECT * FROM Trip WHERE routeId == :id") - suspend fun getByRoute(id: String): List - - @Insert - suspend fun insertAll(vararg trips: TripEntity) - - @Insert(onConflict = REPLACE) - suspend fun insertOrReplaceAll(vararg trips: TripEntity) - - @Delete - suspend fun delete(trip: TripEntity) - - @Query("DELETE FROM Trip") - suspend fun deleteAll() -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt deleted file mode 100644 index 357770d..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/dao/VersionMetadataDao.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.banksia.core.room.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy.Companion.REPLACE -import androidx.room.Query -import moe.lava.banksia.core.room.entity.VersionMetadataEntity - -@Dao -interface VersionMetadataDao { - @Query("SELECT * FROM VersionMetadata WHERE type == :type") - suspend fun get(type: String): VersionMetadataEntity? - - @Query("SELECT * FROM VersionMetadata") - suspend fun getAll(): List - - @Insert(onConflict = REPLACE) - suspend fun update(vararg data: VersionMetadataEntity) - - suspend fun update(vararg data: Pair) { - update(*data.map { (type, lastUpdated) -> VersionMetadataEntity(type, lastUpdated) }.toTypedArray()) - } - - suspend fun update(lastUpdated: Long, types: Collection) { - update(*types.map { VersionMetadataEntity(it, lastUpdated) }.toTypedArray()) - } -} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt deleted file mode 100644 index 8feda0b..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/RouteEntity.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.lava.banksia.core.room.entity - -import androidx.room.Entity -import androidx.room.PrimaryKey -import moe.lava.banksia.core.model.Route -import moe.lava.banksia.core.model.RouteType - -@Entity("Route") -data class RouteEntity( - @PrimaryKey val id: String, - val type: RouteType, - val number: String?, - val name: String, -) { - fun asModel() = Route(id, type, number, name) -} - -fun Route.asEntity() = RouteEntity(id, type, number, name) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt deleted file mode 100644 index 4a1c8b7..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceEntity.kt +++ /dev/null @@ -1,31 +0,0 @@ -package moe.lava.banksia.core.room.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.model.Service -import moe.lava.banksia.core.util.deserialiseDaysBitflag -import moe.lava.banksia.core.util.serialise - -@Entity("Service") -data class ServiceEntity( - @PrimaryKey val id: String, - @ColumnInfo(index = true) val days: Int, - val start: Int, - val end: Int, -) { - fun asModel() = Service( - id, - days.deserialiseDaysBitflag(), - LocalDate.fromEpochDays(start), - LocalDate.fromEpochDays(end), - ) -} - -fun Service.asEntity() = ServiceEntity( - id, - days.serialise(), - start.toEpochDays().toInt(), - end.toEpochDays().toInt(), -) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt deleted file mode 100644 index 1ac45d3..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ServiceExceptionEntity.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.lava.banksia.core.room.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.model.ServiceException - -@Entity( - "ServiceException", - primaryKeys = ["serviceId", "date"] -) -data class ServiceExceptionEntity( - @ColumnInfo(index = true) val serviceId: String, - val date: Int, - @ColumnInfo(index = true) val type: Int, -) { - fun asModel() = ServiceException( - serviceId, - LocalDate.fromEpochDays(date), - type, - ) -} - -fun ServiceException.asEntity() = ServiceExceptionEntity( - serviceId, - date.toEpochDays().toInt(), - type, -) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt deleted file mode 100644 index a19147d..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/ShapeEntity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.lava.banksia.core.room.entity - -import androidx.room.Entity -import androidx.room.PrimaryKey -import androidx.room.TypeConverters -import moe.lava.banksia.core.model.Shape -import moe.lava.banksia.core.model.ShapePath -import moe.lava.banksia.core.room.converter.ShapePathConverter - -@Entity("Shape") -@TypeConverters(ShapePathConverter::class) -data class ShapeEntity( - @PrimaryKey val id: String, - val path: ShapePath, -) { - fun asModel() = Shape(id, path) -} - -fun Shape.asEntity() = ShapeEntity(id, path) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt deleted file mode 100644 index f59c5da..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopEntity.kt +++ /dev/null @@ -1,36 +0,0 @@ -package moe.lava.banksia.core.room.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.ForeignKey.Companion.SET_NULL -import androidx.room.PrimaryKey -import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.util.Point - -@Entity( - "Stop", - foreignKeys = [ - ForeignKey( - StopEntity::class, - parentColumns = ["id"], - childColumns = ["parent"], - onDelete = SET_NULL, - deferred = true, - ), - ] -) -data class StopEntity( - @PrimaryKey val id: String, - val name: String, - val lat: Double, - val lng: Double, - @ColumnInfo(index = true) val parent: String?, - val hasWheelChairBoarding: Boolean, - val level: String, - val platformCode: String, -) { - fun asModel() = Stop(id, name, Point(lat, lng), parent, hasWheelChairBoarding, level, platformCode) -} - -fun Stop.asEntity() = StopEntity(id, name, pos.lat, pos.lng, parent, hasWheelChairBoarding, level, platformCode) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt deleted file mode 100644 index d96036d..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/StopTimeEntity.kt +++ /dev/null @@ -1,53 +0,0 @@ -package moe.lava.banksia.core.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.core.model.FutureTime -import moe.lava.banksia.core.model.FutureTime.Companion.asInt -import moe.lava.banksia.core.model.StopTime - -@Entity( - "StopTime", - primaryKeys = ["tripId", "stopId"], - indices = [ - Index("tripId", unique = false), - Index("stopId", unique = false), - ], - foreignKeys = [ - ForeignKey(TripEntity::class, parentColumns = ["id"], childColumns = ["tripId"], onDelete = CASCADE), - ForeignKey(StopEntity::class, parentColumns = ["id"], childColumns = ["stopId"], onDelete = CASCADE), - ] -) -data class StopTimeEntity( - val tripId: String, - val stopId: String, - val arrivalTime: Int, - val departureTime: Int, - val headsign: String?, - val pickupType: Int, - val dropOffType: Int, -) { - fun asModel() = StopTime( - tripId, - stopId, - FutureTime.fromInt(arrivalTime), - FutureTime.fromInt(departureTime), - headsign, - pickupType, - dropOffType, - ) -} - -@OptIn(ExperimentalSerializationApi::class) -fun StopTime.asEntity() = StopTimeEntity( - tripId, - stopId, - arrivalTime.asInt(), - departureTime.asInt(), - headsign, - pickupType, - dropOffType, -) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt deleted file mode 100644 index 7928c60..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/TripEntity.kt +++ /dev/null @@ -1,49 +0,0 @@ -package moe.lava.banksia.core.room.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.ForeignKey.Companion.CASCADE -import androidx.room.Index -import androidx.room.PrimaryKey -import moe.lava.banksia.core.model.Trip - -@Entity( - "Trip", - foreignKeys = [ - ForeignKey(RouteEntity::class, parentColumns = ["id"], childColumns = ["routeId"], onDelete = CASCADE), - ForeignKey(ServiceEntity::class, parentColumns = ["id"], childColumns = ["serviceId"], onDelete = CASCADE), - ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE), - ], - indices = [Index("shapeId"), Index("serviceId")], -) -data class TripEntity( - @PrimaryKey val id: String, - @ColumnInfo(index = true) val routeId: String, - val serviceId: String, - val shapeId: String?, - val tripHeadsign: String, - val directionId: String, - val blockId: String, - val wheelchairAccessible: String, -) - -fun Trip.Companion.from(tripEntity: TripEntity, serviceEntity: ServiceEntity): Trip { - if (tripEntity.serviceId != serviceEntity.id) { - throw IllegalArgumentException("trip and service id mismatch (${tripEntity.serviceId} != ${serviceEntity.id})") - } - return with(tripEntity) { - Trip( - id = id, - routeId = routeId, - service = serviceEntity.asModel(), - shapeId = shapeId, - tripHeadsign = tripHeadsign, - directionId = directionId, - blockId = blockId, - wheelchairAccessible = wheelchairAccessible - ) - } -} - -fun Trip.asEntity() = TripEntity(id, routeId, service.id, shapeId, tripHeadsign, directionId, blockId, wheelchairAccessible) diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt b/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt deleted file mode 100644 index 1e7cab9..0000000 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/entity/VersionMetadataEntity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.lava.banksia.core.room.entity - -import androidx.room.Entity -import androidx.room.PrimaryKey -import moe.lava.banksia.core.model.VersionMetadata - -@Entity( - "VersionMetadata", -) -data class VersionMetadataEntity( - /** Entity type this metadata applies to */ - @PrimaryKey val type: String, - /** Last updated */ - val lastUpdated: Long, -) { - fun asModel() = VersionMetadata(type, lastUpdated) -} - -fun VersionMetadata.asEntity() = VersionMetadataEntity(type, lastUpdated) diff --git a/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt b/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt deleted file mode 100644 index 34e370e..0000000 --- a/core/room/src/iosMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.ios.kt +++ /dev/null @@ -1,8 +0,0 @@ -package moe.lava.banksia.core.room - -import org.koin.core.component.KoinComponent - -actual class DatabaseManager actual constructor() : KoinComponent { - actual val database: Database - get() = TODO("Not yet implemented") -} diff --git a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt b/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt deleted file mode 100644 index 360d2dd..0000000 --- a/core/room/src/jvmMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.jvm.kt +++ /dev/null @@ -1,69 +0,0 @@ -package moe.lava.banksia.core.room - -import androidx.room.Room -import androidx.room.RoomDatabase -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import moe.lava.banksia.core.util.error -import org.koin.core.component.KoinComponent -import java.io.File -import kotlin.system.exitProcess - -actual class DatabaseManager : KoinComponent { - private var liveDatabase: Database = Database.build(getBuilder()) - actual val database get() = liveDatabase - - private fun getBuilder(path: String = "./data/room.db"): RoomDatabase.Builder { - val dbFile = File(path) - return Room.databaseBuilder( - name = dbFile.absolutePath, - ).setJournalMode(RoomDatabase.JournalMode.TRUNCATE) - } - - fun makeAlt() = Database.build(getBuilder("./data/room_alt.db")) - - private fun deleteAll(file: File): Boolean { - val r1 = file.takeIf { it.exists() }?.delete() - val r2 = File(file.parentFile, file.name + ".lck").takeIf { it.exists() }?.delete() - val r3 = File(file.parentFile, file.name + "-journal").takeIf { it.exists() }?.delete() - return r1 != false && r2 != false && r3 != false - } - - private fun renameAll(from: File, to: File): Boolean { - val r1 = from.takeIf { it.exists() }?.renameTo(to) - val r2 = File(from.parentFile, from.name + ".lck").takeIf { it.exists() }?.renameTo(File(to.parentFile, to.name + ".lck")) - val r3 = File(from.parentFile, from.name + "-journal").takeIf { it.exists() }?.renameTo(File(to.parentFile, to.name + "-journal")) - return r1 != false && r2 != false && r3 != false - } - - fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { - val live = File("./data/room.db") - val alt = File("./data/room_alt.db") - val old = File("./data/room_old.db") - - if (!renameAll(live, old)) { - error("DatabaseManager", "Failed to rename database from live to old (${live.absolutePath} -> ${old.absolutePath})") - return - } - if (!renameAll(alt, live)) { - error("DatabaseManager", "Failed to rename database from alt to live, trying to undo.. (${alt.absolutePath} -> ${live.absolutePath})") - if (!live.renameTo(old)) { - error("DatabaseManager", "Failed to undo, critical failure, exiting..") - exitProcess(1) - } - return - } - val oldDatabase = liveDatabase - liveDatabase = Database.build(getBuilder()) - - scope.launch { - delay(5000) - if (!deleteAll(old)) { - error("DatabaseManager", "Failed to unlink old database, stray files! (${old.absolutePath})") - } - oldDatabase.close() - } - } -} diff --git a/core/room/build.gradle.kts b/core/sqld/build.gradle.kts similarity index 55% rename from core/room/build.gradle.kts rename to core/sqld/build.gradle.kts index 31a7393..d86247a 100644 --- a/core/room/build.gradle.kts +++ b/core/sqld/build.gradle.kts @@ -4,17 +4,12 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidMultiplatformLibrary) - alias(libs.plugins.ksp) - alias(libs.plugins.room) -} - -room { - schemaDirectory("$projectDir/schemas") + alias(libs.plugins.sqldelight) } kotlin { android { - namespace = "moe.lava.banksia.core.room" + namespace = "moe.lava.banksia.core.sqld" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { @@ -22,33 +17,36 @@ kotlin { } } - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - } - iosArm64() iosSimulatorArm64() jvm() sourceSets { + androidMain.dependencies { + implementation(libs.sqldelight.driver.android) + } commonMain.dependencies { implementation(libs.okio) implementation(libs.koin.core) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.serialization.json) - implementation(libs.room.runtime) - implementation(libs.sqlite.bundled) implementation(projects.core) } + nativeMain.dependencies { + implementation(libs.sqldelight.driver.native) + } + jvmMain.dependencies { + implementation(libs.sqldelight.driver.jvm) + } } } -dependencies { - add("kspAndroid", libs.room.compiler) - add("kspIosArm64", libs.room.compiler) - add("kspIosSimulatorArm64", libs.room.compiler) - add("kspJvm", libs.room.compiler) +sqldelight { + databases { + register("BanksiaDatabase") { + packageName.set("moe.lava.banksia.core.sqld") + } + } } diff --git a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt new file mode 100644 index 0000000..b9f2247 --- /dev/null +++ b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt @@ -0,0 +1,14 @@ +package moe.lava.banksia.core.sqld + +import android.content.Context +import app.cash.sqldelight.driver.android.AndroidSqliteDriver +import org.koin.core.component.KoinComponent +import org.koin.core.component.get + +actual class DatabaseManager actual constructor() : KoinComponent { + actual val database by lazy { + val ctx = get().applicationContext + val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "timetable.db") + BanksiaDatabase(driver) + } +} diff --git a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt similarity index 58% rename from core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt rename to core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt index bfe32a9..c6b29f1 100644 --- a/core/room/src/commonMain/kotlin/moe/lava/banksia/core/room/DatabaseManager.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt @@ -1,7 +1,7 @@ -package moe.lava.banksia.core.room +package moe.lava.banksia.core.sqld import org.koin.core.component.KoinComponent expect class DatabaseManager() : KoinComponent { - val database: Database + val database: BanksiaDatabase } diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt new file mode 100644 index 0000000..24ab9bd --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt @@ -0,0 +1,16 @@ +package moe.lava.banksia.core.sqld + +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val sqldDiModule = module { + singleOf(::DatabaseManager) + factory { get().database } + factory { get().routeQueries } + factory { get().serviceQueries } + factory { get().serviceExceptionQueries } + factory { get().shapeQueries } + factory { get().stopQueries } + factory { get().stopTimeQueries } + factory { get().tripQueries } +} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt new file mode 100644 index 0000000..f3a5521 --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Route.kt @@ -0,0 +1,14 @@ +package moe.lava.banksia.core.sqld.mappers + +import moe.lava.banksia.core.model.Route +import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.core.sqld.Route as DbRoute + +fun DbRoute.asModel() = Route( + id = id, + type = RouteType.from(type.toInt()), + number = number, + name = name, +) + +fun Route.asDb() = DbRoute(id, type.value.toLong(), number, name) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt new file mode 100644 index 0000000..dbda5ea --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Service.kt @@ -0,0 +1,21 @@ +package moe.lava.banksia.core.sqld.mappers + +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.util.deserialiseDaysBitflag +import moe.lava.banksia.core.util.serialise +import moe.lava.banksia.core.sqld.Service as DbService + +fun DbService.asModel() = Service( + id = id, + days = days.toInt().deserialiseDaysBitflag(), + start = LocalDate.fromEpochDays(start), + end = LocalDate.fromEpochDays(end), +) + +fun Service.asDb() = DbService( + id = id, + days = days.serialise().toLong(), + start = start.toEpochDays(), + end = end.toEpochDays(), +) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt new file mode 100644 index 0000000..ef0d201 --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/ServiceException.kt @@ -0,0 +1,17 @@ +package moe.lava.banksia.core.sqld.mappers + +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.model.ServiceException +import moe.lava.banksia.core.sqld.ServiceException as DbServiceException + +fun DbServiceException.asModel() = ServiceException( + serviceId = serviceId, + date = LocalDate.fromEpochDays(date), + type = type.toInt(), +) + +fun ServiceException.asDb() = DbServiceException( + serviceId = serviceId, + type = date.toEpochDays(), + date = type.toLong(), +) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt new file mode 100644 index 0000000..4a8d7db --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Shape.kt @@ -0,0 +1,52 @@ +package moe.lava.banksia.core.sqld.mappers + +import moe.lava.banksia.core.model.Shape +import moe.lava.banksia.core.model.ShapePath +import moe.lava.banksia.core.util.Point +import moe.lava.banksia.core.sqld.Shape as DbShape + +fun DbShape.asModel() = Shape( + id = id, + path = bytesToPath(path), +) + +fun Shape.asDb() = DbShape( + id = id, + path = bytesFromPath(path), +) + +private fun bytesToPath(value: ByteArray): ShapePath { + return value + .asSequence() + .asIterable() + .chunked(8) { + (it[0].toLong() and 0xFF) or + (it[1].toLong() and 0xFF shl 8) or + (it[2].toLong() and 0xFF shl 16) or + (it[3].toLong() and 0xFF shl 24) or + (it[4].toLong() and 0xFF shl 32) or + (it[5].toLong() and 0xFF shl 40) or + (it[6].toLong() and 0xFF shl 48) or + (it[7].toLong() and 0xFF shl 56) + } + .map { Double.fromBits(it) } + .chunked(2) + .map { (lat, lng) -> Point(lat, lng) } + .toList() +} + +private fun bytesFromPath(path: ShapePath): ByteArray { + return path + .flatMap { (lat, lng) -> listOf(lat.toBits(), lng.toBits()) } + .flatMap { i -> listOf( + i.toByte(), + (i shr 8).toByte(), + (i shr 16).toByte(), + (i shr 24).toByte(), + (i shr 32).toByte(), + (i shr 40).toByte(), + (i shr 48).toByte(), + (i shr 56).toByte(), + ) } + .toByteArray() +} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt new file mode 100644 index 0000000..3bf6b54 --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Stop.kt @@ -0,0 +1,26 @@ +package moe.lava.banksia.core.sqld.mappers + +import moe.lava.banksia.core.model.Stop +import moe.lava.banksia.core.util.Point +import moe.lava.banksia.core.sqld.Stop as DbStop + +fun DbStop.asModel() = Stop( + id = id, + name = name, + pos = Point(lat, lng), + parent = parent, + hasWheelChairBoarding = hasWheelChairBoarding == 1L, + level = level, + platformCode = platformCode, +) + +fun Stop.asDb() = DbStop( + id = id, + name = name, + lat = pos.lat, + lng = pos.lng, + parent = parent, + hasWheelChairBoarding = if (hasWheelChairBoarding) 1L else 0L, + level = level, + platformCode = platformCode +) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt new file mode 100644 index 0000000..c8b1c44 --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt @@ -0,0 +1,25 @@ +package moe.lava.banksia.core.sqld.mappers + +import moe.lava.banksia.core.model.FutureTime +import moe.lava.banksia.core.model.FutureTime.Companion.asInt +import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.sqld.StopTime as DbStopTime + +fun DbStopTime.asModel() = StopTime( + tripId = tripId, + stopId = stopId, + arrivalTime = FutureTime.fromInt(arrivalTime.toInt()), + departureTime = FutureTime.fromInt(departureTime.toInt()), + headsign = null, + pickupType = pickupType.toInt(), + dropOffType = dropOffType.toInt(), +) + +fun StopTime.asDb() = DbStopTime( + tripId = tripId, + stopId = stopId, + arrivalTime = arrivalTime.asInt().toLong(), + departureTime = departureTime.asInt().toLong(), + pickupType = pickupType.toLong(), + dropOffType = dropOffType.toLong(), +) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt new file mode 100644 index 0000000..36cdad5 --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt @@ -0,0 +1,32 @@ +package moe.lava.banksia.core.sqld.mappers + +import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.model.Trip +import moe.lava.banksia.core.sqld.Trip as DbTrip + +fun DbTrip.asModel(service: Service): Trip { + if (serviceId != service.id) { + throw IllegalArgumentException("trip and service id mismatch (${serviceId} != ${service.id})") + } + return Trip( + id = id, + routeId = routeId, + service = service, + shapeId = shapeId, + tripHeadsign = tripHeadsign, + directionId = directionId, + blockId = blockId, + wheelchairAccessible = wheelchairAccessible == 1L + ) +} + +fun Trip.asDb() = DbTrip( + id = id, + routeId = routeId, + serviceId = service.id, + shapeId = shapeId, + tripHeadsign = tripHeadsign, + directionId = directionId, + blockId = blockId, + wheelchairAccessible = if (wheelchairAccessible) 1L else 0L +) diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq new file mode 100644 index 0000000..f1617f7 --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq @@ -0,0 +1,15 @@ +CREATE TABLE Route ( + id TEXT PRIMARY KEY NOT NULL, + type INTEGER NOT NULL, + number TEXT, + name TEXT NOT NULL +); + +getAll: +SELECT * FROM Route; + +get: +SELECT * FROM Route WHERE id == ?; + +insert: +INSERT INTO Route VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq new file mode 100644 index 0000000..a1c5fad --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Service.sq @@ -0,0 +1,11 @@ +CREATE TABLE Service ( + id TEXT PRIMARY KEY NOT NULL, + days INTEGER NOT NULL, + start INTEGER NOT NULL, + end INTEGER NOT NULL +); + +CREATE INDEX idx_Service_days ON Service (days); + +insert: +INSERT INTO Service VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq new file mode 100644 index 0000000..332f198 --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/ServiceException.sq @@ -0,0 +1,9 @@ +CREATE TABLE ServiceException ( + serviceId TEXT NOT NULL, + type INTEGER NOT NULL, + date INTEGER NOT NULL, + PRIMARY KEY (serviceId, type) +); + +insert: +INSERT INTO ServiceException VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq new file mode 100644 index 0000000..8734200 --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Shape.sq @@ -0,0 +1,7 @@ +CREATE TABLE Shape ( + id TEXT PRIMARY KEY NOT NULL, + path BLOB NOT NULL +); + +insert: +INSERT INTO Shape VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq new file mode 100644 index 0000000..3ac06b9 --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq @@ -0,0 +1,54 @@ +CREATE TABLE Stop ( + id TEXT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + lat REAL NOT NULL, + lng REAL NOT NULL, + parent TEXT REFERENCES Stop(id), + hasWheelChairBoarding INTEGER NOT NULL, + level TEXT, + platformCode TEXT +); + +CREATE INDEX idx_Stop_parent ON Stop (parent); + +getAll: +SELECT * FROM Stop; + +getAllParentless: +SELECT * FROM Stop WHERE platformCode IS NULL AND parent IS NULL; + +get: +SELECT * FROM Stop WHERE id == ?; + +getMany: +SELECT * FROM Stop WHERE id IN ?; + +insert: +INSERT INTO Stop VALUES ?; + +updateParents: +UPDATE Stop SET parent = ? WHERE id IN ?; + +getByRoute: +SELECT Stop.* FROM Stop +INNER JOIN StopTime ON StopTime.stopId == Stop.id +INNER JOIN Trip ON Trip.id == StopTime.tripId +WHERE Trip.routeId == :id +GROUP BY Stop.id; + +-- I vibecoded this, sorry +getParentsByRoute: +WITH RECURSIVE Tree AS ( + SELECT Stop.* FROM Stop + INNER JOIN StopTime ON StopTime.stopId == Stop.id + INNER JOIN Trip ON Trip.id == StopTime.tripId + WHERE Trip.routeId == :id + GROUP BY Stop.id + + UNION ALL + + SELECT s.* + FROM Stop s + INNER JOIN Tree t ON s.id = t.parent +) +SELECT DISTINCT * FROM Tree WHERE parent IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq new file mode 100644 index 0000000..f2dc824 --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq @@ -0,0 +1,23 @@ +CREATE TABLE StopTime ( + tripId TEXT NOT NULL REFERENCES Trip (id), + stopId TEXT NOT NULL REFERENCES Stop (id), + arrivalTime INTEGER NOT NULL, + departureTime INTEGER NOT NULL, + pickupType INTEGER NOT NULL, + dropOffType INTEGER NOT NULL, + PRIMARY KEY (tripId, stopId) +) WITHOUT ROWID; + +CREATE INDEX idx_StopTime_stopId ON StopTime (stopId); + +insert: +INSERT OR REPLACE INTO StopTime VALUES ?; + +getForStopDated: +SELECT DISTINCT StopTime.* FROM StopTime +INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` +INNER JOIN Trip ON Trip.serviceId == Service.id +LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date +WHERE StopTime.tripId == Trip.id + AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) + AND ServiceException.type IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq new file mode 100644 index 0000000..0fd3c1f --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq @@ -0,0 +1,15 @@ +CREATE TABLE Trip ( + id TEXT PRIMARY KEY NOT NULL, + routeId TEXT NOT NULL REFERENCES Route (id), + serviceId TEXT NOT NULL REFERENCES Service (id), + shapeId TEXT NOT NULL REFERENCES Shape (id), + tripHeadsign TEXT NOT NULL, + directionId TEXT NOT NULL, + blockId TEXT, + wheelchairAccessible INTEGER NOT NULL +); + +CREATE INDEX idx_Trip_serviceId ON Trip (serviceId); + +insert: +INSERT OR REPLACE INTO Trip VALUES ?; diff --git a/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt b/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt new file mode 100644 index 0000000..b91b786 --- /dev/null +++ b/core/sqld/src/iosMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.ios.kt @@ -0,0 +1,10 @@ +package moe.lava.banksia.core.sqld + +import app.cash.sqldelight.driver.native.NativeSqliteDriver + +actual class DatabaseManager actual constructor() : org.koin.core.component.KoinComponent { + actual val database by lazy { + val driver = NativeSqliteDriver(BanksiaDatabase.Schema, "timetable.db") + BanksiaDatabase(driver) + } +} diff --git a/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt b/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt new file mode 100644 index 0000000..5bf9f5c --- /dev/null +++ b/core/sqld/src/jvmMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.jvm.kt @@ -0,0 +1,58 @@ +package moe.lava.banksia.core.sqld + +import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import moe.lava.banksia.core.util.error +import org.koin.core.component.KoinComponent +import java.io.File +import java.util.Properties +import kotlin.system.exitProcess + +private const val DBNAME = "timetable" + +actual class DatabaseManager actual constructor() : KoinComponent { + private var driver = connect() + actual val database get() = BanksiaDatabase(driver) + + private fun connect(path: String = "./data/${DBNAME}.db") = + JdbcSqliteDriver("jdbc:sqlite:${path}", Properties(), BanksiaDatabase.Schema) + .apply { execute(null, "PRAGMA journal_mode = OFF;", 0) } + + fun makeAlt() = run { + File("./data/${DBNAME}_alt.db").takeIf { it.exists() }?.delete() + val driver = connect("./data/${DBNAME}_alt.db") + BanksiaDatabase(driver) to { driver.close() } + } + + fun swap(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) { + val live = File("./data/${DBNAME}.db") + val alt = File("./data/${DBNAME}_alt.db") + val old = File("./data/${DBNAME}_old.db") + + if (live.takeIf { it.exists() }?.renameTo(old) == false) { + error("DatabaseManager", "Failed to rename database from live to old (${live.absolutePath} -> ${old.absolutePath})") + return + } + if (alt.takeIf { it.exists() }?.renameTo(live) == false) { + error("DatabaseManager", "Failed to rename database from alt to live, trying to undo.. (${alt.absolutePath} -> ${live.absolutePath})") + if (!live.renameTo(old)) { + error("DatabaseManager", "Failed to undo, critical failure, exiting..") + exitProcess(1) + } + return + } + val oldDriver = driver + driver = connect() + + scope.launch { + delay(5000) + if (old.takeIf { it.exists() }?.delete() == false) { + error("DatabaseManager", "Failed to unlink old database, stray files! (${old.absolutePath})") + } + oldDriver.close() + } + } +} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt index 73e6f02..bbe6fbf 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Stop.kt @@ -10,6 +10,6 @@ data class Stop( val pos: Point, val parent: String?, val hasWheelChairBoarding: Boolean, - val level: String, - val platformCode: String, + val level: String?, + val platformCode: String?, ) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt index 753f653..6edb538 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt @@ -7,9 +7,9 @@ data class Trip( val id: String, val routeId: String, val service: Service, - val shapeId: String?, + val shapeId: String, val tripHeadsign: String, val directionId: String, - val blockId: String, - val wheelchairAccessible: String, + val blockId: String?, + val wheelchairAccessible: Boolean, ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e171b55..02ca8cc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,9 +22,8 @@ material = "1.7.3" material3 = "1.11.0-alpha04" okio = "3.17.0" playServicesLocation = "21.3.0" -sqlite = "2.6.2" -room = "2.8.4" secretsGradlePlugin = "2.0.1" +sqldelight = "2.3.2" wire = "6.1.0" [libraries] @@ -67,10 +66,10 @@ moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" } moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" } okio = { module = "com.squareup.okio:okio", version.ref = "okio" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" } -room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } -room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } -sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" } secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } +sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } +sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } +sqldelight-driver-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" } ui-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "compose-multiplatform" } [plugins] @@ -83,6 +82,6 @@ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" } -room = { id = "androidx.room", version.ref = "room" } secretsGradle = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" } +sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } wire = { id = "com.squareup.wire", version.ref = "wire" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 4150bb2..93da532 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -20,7 +20,7 @@ kotlin { dependencies { implementation(projects.core) - implementation(projects.core.room) + implementation(projects.core.sqld) implementation(projects.server.gtfs) implementation(projects.server.gtfsRt) @@ -36,8 +36,6 @@ dependencies { implementation(libs.ktor.server.contentnegotiation) implementation(libs.ktor.server.core) implementation(libs.ktor.server.netty) - implementation(libs.room.runtime) - implementation(libs.sqlite.bundled) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) } diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index f8d9832..1a5ec29 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -175,8 +175,8 @@ class GtfsParser( pos = Point(stop_lat, stop_lon), parent = parent_station.ifEmpty { null }, hasWheelChairBoarding = wheelchair_boarding == "1", - level = level_id, - platformCode = platform_code, + level = level_id.ifEmpty { null }, + platformCode = platform_code.ifEmpty { null }, ) } } @@ -210,7 +210,7 @@ class GtfsParser( if (sunday == 1) add(DayOfWeek.SUNDAY) } Service( - id = service_id, + id = "${fd.parentFile.name}_${service_id}", days = days, start = LocalDate.parse(start_date, LocalDate.Formats.ISO_BASIC), end = LocalDate.parse(end_date, LocalDate.Formats.ISO_BASIC), @@ -221,7 +221,7 @@ class GtfsParser( fd.parseCsv() .map { with(it) { ServiceException( - serviceId = service_id, + serviceId = "${fd.parentFile.name}_${service_id}", date = LocalDate.parse(date, LocalDate.Formats.ISO_BASIC), type = exception_type, ) @@ -233,12 +233,12 @@ class GtfsParser( Trip( id = trip_id, routeId = route_id, - service = services[service_id]!!, - shapeId = shape_id.ifEmpty { null }, + service = services["${fd.parentFile.name}_${service_id}"]!!, + shapeId = shape_id, tripHeadsign = trip_headsign, directionId = direction_id, - blockId = block_id, - wheelchairAccessible = wheelchair_accessible, + blockId = block_id.ifEmpty { null }, + wheelchairAccessible = wheelchair_accessible == "1", ) } } 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 73e7888..9ffd03a 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -20,10 +20,10 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn import moe.lava.banksia.core.Constants import moe.lava.banksia.core.model.atDate -import moe.lava.banksia.core.room.dao.RouteDao -import moe.lava.banksia.core.room.dao.StopDao -import moe.lava.banksia.core.room.dao.StopTimeDao -import moe.lava.banksia.core.room.dao.VersionMetadataDao +import moe.lava.banksia.core.sqld.RouteQueries +import moe.lava.banksia.core.sqld.StopQueries +import moe.lava.banksia.core.sqld.StopTimeQueries +import moe.lava.banksia.core.sqld.mappers.asModel import moe.lava.banksia.core.util.serialise import moe.lava.banksia.server.di.ServerModules import moe.lava.banksia.server.gtfsrt.GtfsrtService @@ -88,25 +88,9 @@ fun Application.module() { } } - get("/metadata/{type?}") { - val dao = get() - val type = call.parameters["type"] - if (type == null) { - call.respond(dao.getAll().map { it.asModel() }) - return@get - } - - val data = dao.get(type)?.asModel() - if (data == null) { - call.respond(HttpStatusCode.NotFound) - } else { - call.respond(data) - } - } - get("/routes") { val routes = withContext(context = Dispatchers.IO) { - get().getAll() + get().getAll().executeAsList() } val res = routes.map { it.asModel() } call.respond(res) @@ -114,16 +98,17 @@ fun Application.module() { get("/routes/{route_id}") { val routeId = call.parameters["route_id"]!! val route = withContext(context = Dispatchers.IO) { - get().get(routeId) + get().get(routeId).executeAsOneOrNull() } - if (route != null) + if (route != null) { call.respond(route.asModel()) - else + } else { call.respond(HttpStatusCode.NotFound) + } } get("/stops") { val routes = withContext(context = Dispatchers.IO) { - get().getAll() + get().getAll().executeAsList() } val res = routes.map { it.asModel() } call.respond(res) @@ -131,22 +116,24 @@ fun Application.module() { get("/stops/{stop_id}") { val stopId = call.parameters["stop_id"]!! val stop = withContext(context = Dispatchers.IO) { - get().get(stopId) + get().get(stopId).executeAsOneOrNull() } - if (stop != null) + if (stop != null) { call.respond(stop.asModel()) - else + } else { call.respond(HttpStatusCode.NotFound) + } } get("/route_stops/{route_id}") { val routeId = call.parameters["route_id"]!! val useParent = call.queryParameters["parent"] !in listOf("false", "0") val stops = withContext(Dispatchers.IO) { - val routeDao = get() - if (useParent) - routeDao.stopsParent(routeId) - else - routeDao.stops(routeId) + val queries = get() + if (useParent) { + queries.getParentsByRoute(routeId).executeAsList() + } else { + queries.getByRoute(routeId).executeAsList() + } } call.respond(stops.map { it.asModel() }) } @@ -156,12 +143,13 @@ fun Application.module() { ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) } ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) val times = withContext(context = Dispatchers.IO) { - get() + get() .getForStopDated( + listOf(date.dayOfWeek).serialise().toLong(), + date.toEpochDays(), stopId, - listOf(date.dayOfWeek).serialise(), - date.toEpochDays().toInt(), ) + .executeAsList() .map { it.asModel().atDate(date) } .sortedBy { it.departureTime } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt index c74930d..97892e0 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -1,16 +1,16 @@ package moe.lava.banksia.server -import moe.lava.banksia.core.room.Database -import moe.lava.banksia.core.room.entity.StopEntity +import moe.lava.banksia.core.sqld.BanksiaDatabase import moe.lava.banksia.core.util.log import java.security.MessageDigest +import moe.lava.banksia.core.sqld.Stop as DbStop class GtfsDataFixer( - private val database: Database, + private val database: BanksiaDatabase, ) { - suspend fun addParentsToStops() { - val dao = database.stopDao - val stops = dao.getAllParentless() + fun addParentsToStops() { + val queries = database.stopQueries + val stops = queries.getAllParentless().executeAsList() stops .groupBy { it.name.split("/")[0] } .filter { (_, stops) -> stops.size > 1 } @@ -19,19 +19,21 @@ class GtfsDataFixer( val avgLng = stops.map { it.lng }.average() val hash = name.sha256().substring(0, 7) val parentId = "bsia:df1:$hash" - val parent = StopEntity( + val parent = DbStop( id = parentId, name = name, lat = avgLat, lng = avgLng, parent = null, - hasWheelChairBoarding = stops.all { it.hasWheelChairBoarding }, + hasWheelChairBoarding = if (stops.all { it.hasWheelChairBoarding == 1L }) 1L else 0L, level = "", platformCode = "", ) log("datafixer", "inserting ${parentId} for ${stops.size} children") - dao.insertAll(parent) - dao.updateParents(stops.map { it.id }, parentId) + queries.transaction { + queries.insert(parent) + queries.updateParents(parentId, stops.map { it.id }) + } } } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 5c8dc37..17ab1f5 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -8,12 +8,12 @@ import moe.lava.banksia.core.model.Shape import moe.lava.banksia.core.model.Stop import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.Trip -import moe.lava.banksia.core.room.Database -import moe.lava.banksia.core.room.DatabaseManager -import moe.lava.banksia.core.room.entity.asEntity +import moe.lava.banksia.core.sqld.DatabaseManager +import moe.lava.banksia.core.sqld.mappers.asDb import moe.lava.banksia.server.gtfs.GtfsData import moe.lava.banksia.server.gtfs.GtfsParser import kotlin.time.Clock +import moe.lava.banksia.core.sqld.BanksiaDatabase as Database class GtfsImporter( private val parser: GtfsParser, @@ -21,7 +21,7 @@ class GtfsImporter( private val log: Logger, ) { suspend fun import(url: String, date: Long = Clock.System.now().epochSeconds) { - val database = dbm.makeAlt() + val (database, close) = dbm.makeAlt() parser.update(url).collect { chunk -> when (chunk) { @@ -35,48 +35,51 @@ class GtfsImporter( } } - database.updateMetadata(date) - database.close() + close() dbm.swap() } - private suspend fun Database.updateMetadata(date: Long) { - val dao = versionMetadataDao - log.info("updating metadata...") - dao.update(date, listOf("routes", "stops", "shapes", "trips", "stop_times")) - log.info("done") - } - - private suspend fun Database.addRoutes(routes: List) { - val dao = routeDao + private fun Database.addRoutes(routes: List) { log.info("inserting routes...") - dao.insertOrReplaceAll(*routes.map { it.asEntity() }.toTypedArray()) + routeQueries.transaction { + routes.forEach { + routeQueries.insert(it.asDb()) + } + } log.info("done") } - private suspend fun Database.addServices(services: List) { - val dao = serviceDao + private fun Database.addServices(services: List) { log.info("inserting services...") - dao.insertOrReplaceAll(*services.map { it.asEntity() }.toTypedArray()) + serviceQueries.transaction { + services.forEach { + serviceQueries.insert(it.asDb()) + } + } log.info("done") } - private suspend fun Database.addServiceExceptions(exceptions: List) { - val dao = serviceExceptionDao + private fun Database.addServiceExceptions(exceptions: List) { log.info("inserting exceptions...") - dao.insertOrReplaceAll(*exceptions.map { it.asEntity() }.toTypedArray()) + serviceExceptionQueries.transaction { + exceptions.forEach { + serviceExceptionQueries.insert(it.asDb()) + } + } log.info("done") } - private suspend fun Database.addShapes(shapes: List) { - val dao = shapeDao + private fun Database.addShapes(shapes: List) { log.info("inserting shapes...") - dao.insertOrReplaceAll(*shapes.map { it.asEntity() }.toTypedArray()) + shapeQueries.transaction { + shapes.forEach { + shapeQueries.insert(it.asDb()) + } + } log.info("done") } - private suspend fun Database.addStops(stops: List) { - val dao = stopDao + private fun Database.addStops(stops: List) { log.info("inserting stops...") stops .groupBy { it.id } @@ -89,21 +92,32 @@ class GtfsImporter( } } } - dao.insertOrReplaceAll(*stops.map { it.asEntity() }.toTypedArray()) + + stopQueries.transaction { + stops.forEach { + stopQueries.insert(it.asDb()) + } + } log.info("done") } - private suspend fun Database.addStopTimes(stopTimes: List) { - val dao = stopTimeDao + private fun Database.addStopTimes(stopTimes: List) { log.info("inserting ${stopTimes.size} stoptimes...") - dao.insertOrReplaceAll(*stopTimes.map { it.asEntity() }.toTypedArray()) + stopTimeQueries.transaction { + stopTimes.forEach { + stopTimeQueries.insert(it.asDb()) + } + } log.info("done") } - private suspend fun Database.addTrips(trips: List) { - val dao = tripDao + private fun Database.addTrips(trips: List) { log.info("inserting ${trips.size} trips...") - dao.insertOrReplaceAll(*trips.map { it.asEntity() }.toTypedArray()) + tripQueries.transaction { + trips.forEach { + tripQueries.insert(it.asDb()) + } + } log.info("done") } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index a8a7541..881becf 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient -import moe.lava.banksia.core.room.roomDiModule +import moe.lava.banksia.core.sqld.sqldDiModule import moe.lava.banksia.server.GtfsDataFixer import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser @@ -11,7 +11,7 @@ import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val ServerModules = module { - includes(roomDiModule) + includes(sqldDiModule) single { HttpClient() } singleOf(::GtfsParser) diff --git a/settings.gradle.kts b/settings.gradle.kts index 9abcf7f..78831ff 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,7 +39,7 @@ include(":core") include(":core:data") include(":core:data:client") include(":core:data:server") -include(":core:room") +include(":core:sqld") include(":ui") include(":ui:maps") include(":ui:shared") From 102c02840724fb99560ce076d808714dbf1048e1 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Tue, 5 May 2026 03:23:11 +1000 Subject: [PATCH 31/33] refactor: optimisation around stoptimes - moved stoptime related functionality into new core:data:stoptime module - will feature all the different realtime stoptime sources to be integrated later - create proper database schema for future migrations - deduplicate trips into stoppingpatterns, since many trips share the exact same stopping pattern - stoptimes are now linked to stoppingpatterns instead - stoppingpattern ids are generated from a hash composed of all stoptimes - stoptimes now use deltas for arrival time to save space --- core/data/build.gradle.kts | 1 + .../banksia/core/data/ClientDataDiModule.kt | 8 +- .../repositories/ClientStopTimeRepository.kt | 16 ---- .../data/sources/trip/TripRemoteDataSource.kt | 18 ---- .../data/repositories/StopTimeRepository.kt | 7 -- core/data/stoptime/build.gradle.kts | 60 +++++++++++++ .../core/data/StopTimeDataDiModule.client.kt | 11 +++ .../repositories/StopTimeRepository.client.kt | 19 ++++ .../stoptime/StopTimeRemoteDataSource.kt | 18 +--- .../banksia/core/data/StopTimeDataDiModule.kt | 13 +++ .../data/repositories/StopTimeRepository.kt | 15 ++++ .../stoptime/StopTimeLocalDataSource.kt | 22 ++--- .../core/data/StopTimeDataDiModule.jvm.kt | 9 ++ .../repositories/StopTimeRepository.jvm.kt | 13 +++ core/sqld/build.gradle.kts | 1 + .../core/sqld/DatabaseManager.android.kt | 4 +- .../lava/banksia/core/sqld/DatabaseManager.kt | 4 +- .../lava/banksia/core/sqld/SqldDiModule.kt | 1 + .../banksia/core/sqld/mappers/StopTime.kt | 18 ++-- .../core/sqld/mappers/StoppingPattern.kt | 22 +++++ .../lava/banksia/core/sqld/mappers/Trip.kt | 27 +++--- .../moe/lava/banksia/core/sqld/Stop.sq | 10 +-- .../moe/lava/banksia/core/sqld/StopTime.sq | 11 +-- .../lava/banksia/core/sqld/StoppingPattern.sq | 10 +++ .../moe/lava/banksia/core/sqld/Trip.sq | 12 ++- .../src/commonMain/sqldelight/schema/1.db | Bin 0 -> 81920 bytes .../banksia/core/sqld/DatabaseManager.ios.kt | 5 +- .../banksia/core/sqld/DatabaseManager.jvm.kt | 4 +- .../moe/lava/banksia/core/model/StopTime.kt | 39 ++++++-- .../lava/banksia/core/model/StopTimeDated.kt | 26 ------ .../banksia/core/model/StoppingPattern.kt | 16 ++++ .../moe/lava/banksia/core/model/Trip.kt | 14 +-- .../lava/banksia/server/gtfs/GtfsParser.kt | 84 ++++++++++++++---- .../server/gtfs/structures/GtfsStopTime.kt | 2 +- .../moe/lava/banksia/server/Application.kt | 2 +- .../moe/lava/banksia/server/GtfsImporter.kt | 24 ++--- server/src/main/resources/logback.xml | 2 +- settings.gradle.kts | 1 + .../ui/screens/map/MapScreenViewModel.kt | 50 ++++++----- 39 files changed, 396 insertions(+), 223 deletions(-) delete mode 100644 core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt delete mode 100644 core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt delete mode 100644 core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt create mode 100644 core/data/stoptime/build.gradle.kts create mode 100644 core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt create mode 100644 core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt rename core/data/{client/src/commonMain => stoptime/src/clientMain}/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt (54%) create mode 100644 core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt create mode 100644 core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt rename core/data/{client => stoptime}/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt (58%) create mode 100644 core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt create mode 100644 core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt create mode 100644 core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt create mode 100644 core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq create mode 100644 core/sqld/src/commonMain/sqldelight/schema/1.db delete mode 100644 core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt create mode 100644 core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 6f78b5c..8c89aff 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { sourceSets { commonMain.dependencies { implementation(projects.core) + api(projects.core.data.stoptime) } } } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt index 2cbfcaa..0384f88 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt +++ b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt @@ -10,16 +10,12 @@ import kotlinx.serialization.json.Json import moe.lava.banksia.core.Constants import moe.lava.banksia.core.data.repositories.ClientRouteRepository import moe.lava.banksia.core.data.repositories.ClientStopRepository -import moe.lava.banksia.core.data.repositories.ClientStopTimeRepository import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository -import moe.lava.banksia.core.data.repositories.StopTimeRepository import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource import moe.lava.banksia.core.sqld.sqldDiModule import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService @@ -29,6 +25,7 @@ import org.koin.dsl.module val clientDataDiModule = module { includes(sqldDiModule) + includes(stopTimeDataDiModule) // HTTP Clients singleOf(::PtvService) @@ -56,11 +53,8 @@ val clientDataDiModule = module { singleOf(::RouteRemoteDataSource) singleOf(::StopLocalDataSource) singleOf(::StopRemoteDataSource) - singleOf(::StopTimeLocalDataSource) - singleOf(::StopTimeRemoteDataSource) // Repositories singleOf(::ClientRouteRepository) bind RouteRepository::class singleOf(::ClientStopRepository) bind StopRepository::class - singleOf(::ClientStopTimeRepository) bind StopTimeRepository::class } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt deleted file mode 100644 index aea3159..0000000 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopTimeRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource -import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource -import moe.lava.banksia.core.model.StopTimeDated - -internal class ClientStopTimeRepository internal constructor( - private val local: StopTimeLocalDataSource, - private val remote: StopTimeRemoteDataSource, -) : StopTimeRepository { - override suspend fun getForStop(id: String): List { - return local - .getAtStop(id) - .ifEmpty { remote.getAtStop(id) } - } -} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt b/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt deleted file mode 100644 index d1067d8..0000000 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/trip/TripRemoteDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.lava.banksia.core.data.sources.trip - -import io.ktor.client.HttpClient -import kotlinx.datetime.DayOfWeek -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.Trip -import kotlin.time.Clock - -internal class TripRemoteDataSource( - private val client: HttpClient, -) { - suspend fun get( - day: DayOfWeek? = Clock.System.todayIn(TimeZone.currentSystemDefault()).dayOfWeek, - ): List { - return listOf() - } -} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt deleted file mode 100644 index 87d01ac..0000000 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.lava.banksia.core.data.repositories - -import moe.lava.banksia.core.model.StopTimeDated - -interface StopTimeRepository { - suspend fun getForStop(id: String): List -} diff --git a/core/data/stoptime/build.gradle.kts b/core/data/stoptime/build.gradle.kts new file mode 100644 index 0000000..086e749 --- /dev/null +++ b/core/data/stoptime/build.gradle.kts @@ -0,0 +1,60 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) + alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.ksp) +} + +kotlin { + android { + namespace = "moe.lava.banksia.core.data.stoptime" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + freeCompilerArgs.add("-Xexpect-actual-classes") + } + + iosArm64() + iosSimulatorArm64() + + jvm() + + sourceSets { + val clientMain by creating { + dependsOn(commonMain.get()) + } + + androidMain.get().dependsOn(clientMain) + iosArm64Main.get().dependsOn(clientMain) + iosSimulatorArm64Main.get().dependsOn(clientMain) + + androidMain.dependencies { + implementation(libs.ktor.client.okhttp) + } + commonMain.dependencies { + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.serialization.protobuf) + + implementation(projects.core) + implementation(projects.core.sqld) + } + iosMain.dependencies { + implementation(libs.ktor.client.darwin) + } + } +} diff --git a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt new file mode 100644 index 0000000..2f83304 --- /dev/null +++ b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt @@ -0,0 +1,11 @@ +package moe.lava.banksia.core.data + +import moe.lava.banksia.core.data.repositories.StopTimeRepository +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal actual val platformModule = module { + singleOf(::StopTimeRepository) + singleOf(::StopTimeRemoteDataSource) +} diff --git a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt new file mode 100644 index 0000000..ecaff8e --- /dev/null +++ b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt @@ -0,0 +1,19 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.flow.flow +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import moe.lava.banksia.core.data.sources.stoptime.StopTimeRemoteDataSource + +actual class StopTimeRepository internal constructor( + private val local: StopTimeLocalDataSource, + private val remote: StopTimeRemoteDataSource, +) { + actual suspend fun getForStop(id: String, date: LocalDate) = flow { + emit(local.getAtStop(id, date)) + + remote.getAtStop(id, date) + .takeIf { it.isNotEmpty() } + ?.let { emit(it) } + } +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt similarity index 54% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt rename to core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt index 0633a18..1d338ce 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt +++ b/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt @@ -7,7 +7,7 @@ import io.ktor.client.request.parameter import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.StopTimeDated +import moe.lava.banksia.core.model.StopTime import kotlin.time.Clock internal class StopTimeRemoteDataSource( @@ -16,21 +16,9 @@ internal class StopTimeRemoteDataSource( suspend fun getAtStop( stopId: String, date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): List { + ): List { return client.get("stoptimes/by_stop/${stopId}") { parameter("date", date) - }.body>() + }.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/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt new file mode 100644 index 0000000..d46affa --- /dev/null +++ b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt @@ -0,0 +1,13 @@ +package moe.lava.banksia.core.data + +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource +import org.koin.core.module.Module +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal expect val platformModule: Module; + +val stopTimeDataDiModule = module { + includes(platformModule) + singleOf(::StopTimeLocalDataSource) +} diff --git a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt new file mode 100644 index 0000000..2de0c10 --- /dev/null +++ b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt @@ -0,0 +1,15 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.flow.Flow +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.core.model.StopTime +import kotlin.time.Clock + +expect class StopTimeRepository { + suspend fun getForStop( + id: String, + date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), + ): Flow> +} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt similarity index 58% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt rename to core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt index 78ca64b..03ebbda 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt +++ b/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt @@ -4,23 +4,19 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext import kotlinx.datetime.LocalDate -import kotlinx.datetime.TimeZone -import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.StopTimeDated +import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.atDate import moe.lava.banksia.core.sqld.StopTimeQueries import moe.lava.banksia.core.sqld.mappers.asModel import moe.lava.banksia.core.util.serialise -import kotlin.time.Clock +import org.koin.core.component.KoinComponent +import org.koin.core.component.get -internal class StopTimeLocalDataSource( - private val queries: StopTimeQueries, -) { - suspend fun getAtStop( - stopId: String, - date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): List { - return withContext(Dispatchers.IO) { +internal class StopTimeLocalDataSource : KoinComponent { + private val queries get() = get() + + suspend fun getAtStop(stopId: String, date: LocalDate): List { + return withContext(context = Dispatchers.IO) { queries .getForStopDated( listOf(date.dayOfWeek).serialise().toLong(), @@ -29,7 +25,7 @@ internal class StopTimeLocalDataSource( ) .executeAsList() .map { it.asModel().atDate(date) } - .sortedBy { it.departureTime } + .sortedBy { it.time.departure } } } } diff --git a/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt new file mode 100644 index 0000000..70ef406 --- /dev/null +++ b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt @@ -0,0 +1,9 @@ +package moe.lava.banksia.core.data + +import moe.lava.banksia.core.data.repositories.StopTimeRepository +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +internal actual val platformModule = module { + singleOf(::StopTimeRepository) +} diff --git a/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt new file mode 100644 index 0000000..b4c37a6 --- /dev/null +++ b/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt @@ -0,0 +1,13 @@ +package moe.lava.banksia.core.data.repositories + +import kotlinx.coroutines.flow.flow +import kotlinx.datetime.LocalDate +import moe.lava.banksia.core.data.sources.stoptime.StopTimeLocalDataSource + +actual class StopTimeRepository internal constructor( + private val local: StopTimeLocalDataSource, +) { + actual suspend fun getForStop(id: String, date: LocalDate) = flow { + emit(local.getAtStop(id, date)) + } +} diff --git a/core/sqld/build.gradle.kts b/core/sqld/build.gradle.kts index d86247a..472a908 100644 --- a/core/sqld/build.gradle.kts +++ b/core/sqld/build.gradle.kts @@ -47,6 +47,7 @@ sqldelight { databases { register("BanksiaDatabase") { packageName.set("moe.lava.banksia.core.sqld") + schemaOutputDirectory.set(file("src/commonMain/sqldelight/schema")) } } } diff --git a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt index b9f2247..c47613c 100644 --- a/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt +++ b/core/sqld/src/androidMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.android.kt @@ -5,10 +5,10 @@ import app.cash.sqldelight.driver.android.AndroidSqliteDriver import org.koin.core.component.KoinComponent import org.koin.core.component.get -actual class DatabaseManager actual constructor() : KoinComponent { +actual class DatabaseManager : KoinComponent { actual val database by lazy { val ctx = get().applicationContext - val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "timetable.db") + val driver = AndroidSqliteDriver(BanksiaDatabase.Schema, ctx, "${DBNAME}.db") BanksiaDatabase(driver) } } diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt index c6b29f1..983eb58 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/DatabaseManager.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.core.sqld -import org.koin.core.component.KoinComponent +internal const val DBNAME = "timetable" -expect class DatabaseManager() : KoinComponent { +expect class DatabaseManager() { val database: BanksiaDatabase } diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt index 24ab9bd..deee453 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/SqldDiModule.kt @@ -11,6 +11,7 @@ val sqldDiModule = module { factory { get().serviceExceptionQueries } factory { get().shapeQueries } factory { get().stopQueries } + factory { get().stoppingPatternQueries } factory { get().stopTimeQueries } factory { get().tripQueries } } diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt index c8b1c44..26d5390 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StopTime.kt @@ -3,23 +3,25 @@ package moe.lava.banksia.core.sqld.mappers import moe.lava.banksia.core.model.FutureTime import moe.lava.banksia.core.model.FutureTime.Companion.asInt import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.TimeType import moe.lava.banksia.core.sqld.StopTime as DbStopTime fun DbStopTime.asModel() = StopTime( - tripId = tripId, + patternId = patternId, stopId = stopId, - arrivalTime = FutureTime.fromInt(arrivalTime.toInt()), - departureTime = FutureTime.fromInt(departureTime.toInt()), - headsign = null, + time = TimeType.Undated( + arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()), + departure = FutureTime.fromInt(departureTime.toInt()), + ), pickupType = pickupType.toInt(), dropOffType = dropOffType.toInt(), ) -fun StopTime.asDb() = DbStopTime( - tripId = tripId, +fun StopTime.Undated.asDb() = DbStopTime( + patternId = patternId, stopId = stopId, - arrivalTime = arrivalTime.asInt().toLong(), - departureTime = departureTime.asInt().toLong(), + arrivalDelta = (time.arrival.asInt() - time.departure.asInt()).toLong(), + departureTime = time.departure.asInt().toLong(), pickupType = pickupType.toLong(), dropOffType = dropOffType.toLong(), ) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt new file mode 100644 index 0000000..e50aa85 --- /dev/null +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt @@ -0,0 +1,22 @@ +package moe.lava.banksia.core.sqld.mappers + +import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.StoppingPattern +import moe.lava.banksia.core.sqld.StoppingPattern as DbStoppingPattern + +fun DbStoppingPattern.asModel(stoptimes: List) = StoppingPattern.Undated( + id = id, + routeId = routeId, + shapeId = shapeId, + headsign = headsign, + wheelchairAccessible = wheelchairAccessible == 1L, + stoptimes = stoptimes, +) + +fun StoppingPattern.Undated.asDb() = DbStoppingPattern( + id = id, + routeId = routeId, + shapeId = shapeId, + headsign = headsign, + wheelchairAccessible = if (wheelchairAccessible) 1L else 0L, +) diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt index 36cdad5..b3443fb 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/Trip.kt @@ -1,32 +1,27 @@ package moe.lava.banksia.core.sqld.mappers import moe.lava.banksia.core.model.Service +import moe.lava.banksia.core.model.StoppingPattern import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.sqld.Trip as DbTrip -fun DbTrip.asModel(service: Service): Trip { +fun DbTrip.asModel(pattern: StoppingPattern.Undated, service: Service): Trip.Undated { if (serviceId != service.id) { throw IllegalArgumentException("trip and service id mismatch (${serviceId} != ${service.id})") } return Trip( - id = id, - routeId = routeId, + id = gtfsId, + pattern = pattern, service = service, - shapeId = shapeId, - tripHeadsign = tripHeadsign, - directionId = directionId, - blockId = blockId, - wheelchairAccessible = wheelchairAccessible == 1L + directionId = directionId.toInt(), + blockId = blockId.toString(), ) } -fun Trip.asDb() = DbTrip( - id = id, - routeId = routeId, +fun Trip.Undated.asDb() = DbTrip( + gtfsId = id, + patternId = pattern.id, serviceId = service.id, - shapeId = shapeId, - tripHeadsign = tripHeadsign, - directionId = directionId, - blockId = blockId, - wheelchairAccessible = if (wheelchairAccessible) 1L else 0L + directionId = directionId.toLong(), + blockId = blockId?.toLong(), ) diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq index 3ac06b9..4af5c50 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Stop.sq @@ -15,7 +15,7 @@ getAll: SELECT * FROM Stop; getAllParentless: -SELECT * FROM Stop WHERE platformCode IS NULL AND parent IS NULL; +SELECT * FROM Stop WHERE platformCode IS NOT NULL AND parent IS NULL; get: SELECT * FROM Stop WHERE id == ?; @@ -32,8 +32,8 @@ UPDATE Stop SET parent = ? WHERE id IN ?; getByRoute: SELECT Stop.* FROM Stop INNER JOIN StopTime ON StopTime.stopId == Stop.id -INNER JOIN Trip ON Trip.id == StopTime.tripId -WHERE Trip.routeId == :id +INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId +WHERE StoppingPattern.routeId == :id GROUP BY Stop.id; -- I vibecoded this, sorry @@ -41,8 +41,8 @@ getParentsByRoute: WITH RECURSIVE Tree AS ( SELECT Stop.* FROM Stop INNER JOIN StopTime ON StopTime.stopId == Stop.id - INNER JOIN Trip ON Trip.id == StopTime.tripId - WHERE Trip.routeId == :id + INNER JOIN StoppingPattern ON StoppingPattern.id == StopTime.patternId + WHERE StoppingPattern.routeId == :id GROUP BY Stop.id UNION ALL diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq index f2dc824..45b3f10 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq @@ -1,11 +1,11 @@ CREATE TABLE StopTime ( - tripId TEXT NOT NULL REFERENCES Trip (id), + patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), stopId TEXT NOT NULL REFERENCES Stop (id), - arrivalTime INTEGER NOT NULL, + arrivalDelta INTEGER NOT NULL, departureTime INTEGER NOT NULL, pickupType INTEGER NOT NULL, dropOffType INTEGER NOT NULL, - PRIMARY KEY (tripId, stopId) + PRIMARY KEY (patternId, stopId) ) WITHOUT ROWID; CREATE INDEX idx_StopTime_stopId ON StopTime (stopId); @@ -16,8 +16,9 @@ INSERT OR REPLACE INTO StopTime VALUES ?; getForStopDated: SELECT DISTINCT StopTime.* FROM StopTime INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` -INNER JOIN Trip ON Trip.serviceId == Service.id LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date -WHERE StopTime.tripId == Trip.id +INNER JOIN Trip ON Trip.serviceId == Service.id +INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId +WHERE StopTime.patternId == StoppingPattern.id AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) AND ServiceException.type IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq new file mode 100644 index 0000000..cc1c5ab --- /dev/null +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq @@ -0,0 +1,10 @@ +CREATE TABLE StoppingPattern ( + id INTEGER PRIMARY KEY NOT NULL, + routeId TEXT NOT NULL REFERENCES Route (id), + shapeId TEXT NOT NULL REFERENCES Shape (id), + headsign TEXT NOT NULL, + wheelchairAccessible INTEGER NOT NULL +); + +insert: +INSERT OR REPLACE INTO StoppingPattern VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq index 0fd3c1f..c53b62a 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Trip.sq @@ -1,14 +1,12 @@ CREATE TABLE Trip ( - id TEXT PRIMARY KEY NOT NULL, - routeId TEXT NOT NULL REFERENCES Route (id), + gtfsId TEXT PRIMARY KEY NOT NULL, + patternId INTEGER NOT NULL REFERENCES StoppingPattern (id), serviceId TEXT NOT NULL REFERENCES Service (id), - shapeId TEXT NOT NULL REFERENCES Shape (id), - tripHeadsign TEXT NOT NULL, - directionId TEXT NOT NULL, - blockId TEXT, - wheelchairAccessible INTEGER NOT NULL + blockId INTEGER, + directionId INTEGER NOT NULL ); +CREATE INDEX idx_Trip_patternId ON Trip (patternId); CREATE INDEX idx_Trip_serviceId ON Trip (serviceId); insert: diff --git a/core/sqld/src/commonMain/sqldelight/schema/1.db b/core/sqld/src/commonMain/sqldelight/schema/1.db new file mode 100644 index 0000000000000000000000000000000000000000..feaacb3260f779a72f844ab265f00a2d091b9bb1 GIT binary patch literal 81920 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCVBlq7U=U$I01%%A!DV1XV&h`+GU)aG z=H>svAk1dRz;D5SmM@W?lQ)V_f#*1{KKCS^d0cv&>|CtuZ`fzBd$GM?o62T~%cxQD z(GVC7fzc2c4S~TE0-?fe;)bG(#hH02sTG+i74acOnFaC1sYPX($*Ge ztAeMWi)(}erWyr*KLwCF1r4}1%}^n1#ug-&l%y8rL5vl~ZY*34!dSSFW{e=4xUno_ zIo!3uCHVy*nYpR)#U=R#o+&V{5Qf<>8K^>#;V>Zu4Txq<9|1OTZBfQ_6w~7i5{pvv zN2105efR1EN7Qh@VYdSDG;!VHzY*<5LnVi-RF-0ZbDi5@1DOBO!bR4UjI) zCJ8=vacynJ=Jb-pq?}ZcqY5(f(gPsjipJ%K8Xn^4J5aj9W7!;}C?HZ}z=O3cr7wY4q16EX&Us{stnW7Nl8W94QQV4Q&a}9F!b9N0@ z2m&ck(8x^DglH(vNG!msAsD0pNkc|zVoGsldLF735W~tdQd4u1GZHh49Fvn%i;FXp za#G=rhB=f=v$2|oU0hU@u{jbPG@t|t!n`Pf01|=vJiR2X80IJ3UWX?=3>%UCj}f+r zkbp!V%p#a`Q8Yu8AcaO!PJVK>Cp=~#!H|+!l$u>N z$0#9Dl3#!n42eZWnPrJNE~z;si5S5T4V2V^#G;bYqEwI{aHuKBOwKMX2&pWjkT3%k5a3dR12xn@;zS2qL1IaUf|HNG6FhvlG&MQc#BIfKHS0A)mI84l;Op}G`JHPq4YDiR!QaCbq%uLMsbO)SBc zevy+6q6*eg0GX!ArD@N`CLXMd)5B=aj5kF07JH*AGrPF7G-Hz@axg%Zv0(Ew)USlo zHKTpxDEpoOekIg z3lZ%hP?+OzbY5w0Qfd)6+99!xnW?!nHJR|GPOw|z4Z##M0|NsGs2vIF)H4WxSfh9} z1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtwau02jz(qy2wSD2(FK5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2cwjnS&|37S_cGP2|Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@S4}sD7e|S)gGDbsSGz3ONU^E0qLtr!nMnhmU1V%$( zGz3ONU^E0qLtxm3!07z{u#MVLkBx@FXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1J zh5$SSM(6+GK{3i04S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=CVH*OY^Z&y( zYDYab8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O@DLcC|Az;~C}T7PMnhmU z1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz5li2#n7E58J36_1I_#jE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb8YVV08W;9u%XD(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7`7oW+W#N6Q9J6f(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R zfQP{7`hR#(j50<;U^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhoOhQR3j|FDhP zQICy=z-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjD`R_1V-oo;XyIV7!85Z5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fngg0qx1j6Hfl#bHW~t>Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0&p( + val patternId: Long, val stopId: String, - val arrivalTime: FutureTime, - val departureTime: FutureTime, - val headsign: String?, + val time: T, val pickupType: Int, val dropOffType: Int, +) { + typealias Dated = StopTime + typealias Undated = StopTime +} + +@Serializable +sealed class TimeType { + @Serializable + data class Undated( + val arrival: FutureTime, + val departure: FutureTime, + ) : TimeType() + + @Serializable + data class Dated( + val arrival: LocalDateTime, + val departure: LocalDateTime, + ) : TimeType() +} + +fun StopTime.atDate(date: LocalDate) = StopTime( + patternId = patternId, + stopId = stopId, + time = TimeType.Dated( + arrival = time.arrival.atDate(date), + departure = time.departure.atDate(date), + ), + pickupType = pickupType, + dropOffType = dropOffType, ) diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt deleted file mode 100644 index 1bd75c6..0000000 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTimeDated.kt +++ /dev/null @@ -1,26 +0,0 @@ -package moe.lava.banksia.core.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/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt new file mode 100644 index 0000000..1374cff --- /dev/null +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StoppingPattern.kt @@ -0,0 +1,16 @@ +package moe.lava.banksia.core.model + +import kotlinx.serialization.Serializable + +@Serializable +data class StoppingPattern( + val id: Long, + val routeId: String, + val shapeId: String, + val headsign: String, + val wheelchairAccessible: Boolean, + val stoptimes: List>, +) { + typealias Dated = StoppingPattern + typealias Undated = StoppingPattern +} diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt index 6edb538..752d6d2 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/Trip.kt @@ -3,13 +3,13 @@ package moe.lava.banksia.core.model import kotlinx.serialization.Serializable @Serializable -data class Trip( +data class Trip( val id: String, - val routeId: String, + val pattern: StoppingPattern, val service: Service, - val shapeId: String, - val tripHeadsign: String, - val directionId: String, + val directionId: Int, val blockId: String?, - val wheelchairAccessible: Boolean, -) +) { + typealias Dated = Trip + typealias Undated = Trip +} diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt index 1a5ec29..c844499 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/GtfsParser.kt @@ -18,6 +18,7 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.serializer import moe.lava.banksia.core.Constants +import moe.lava.banksia.core.model.FutureTime.Companion.asInt import moe.lava.banksia.core.model.Route import moe.lava.banksia.core.model.RouteType import moe.lava.banksia.core.model.Service @@ -25,6 +26,8 @@ import moe.lava.banksia.core.model.ServiceException import moe.lava.banksia.core.model.Shape import moe.lava.banksia.core.model.Stop import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.model.StoppingPattern +import moe.lava.banksia.core.model.TimeType import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.util.Point import moe.lava.banksia.server.gtfs.structures.GtfsRoute @@ -35,6 +38,8 @@ import moe.lava.banksia.server.gtfs.structures.GtfsStop import moe.lava.banksia.server.gtfs.structures.GtfsStopTime import moe.lava.banksia.server.gtfs.structures.GtfsTrip import java.io.File +import java.nio.ByteBuffer +import java.security.MessageDigest import java.util.zip.ZipFile import kotlin.time.ExperimentalTime @@ -46,8 +51,7 @@ sealed class GtfsData { data class ServiceExceptionChunk(val exceptions: List) : GtfsData() data class ShapeChunk(val shapes: List) : GtfsData() data class StopChunk(val stops: List) : GtfsData() - data class StopTimeChunk(val stopTimes: List) : GtfsData() - data class TripChunk(val trips: List) : GtfsData() + data class TripChunk(val trips: List) : GtfsData() } class GtfsParser( @@ -129,7 +133,6 @@ class GtfsParser( .filter { it.name == "trips.txt" } .flatMap { fd -> parseTrips(fd, services) - .also { emit(GtfsData.TripChunk(it)) } } .associateBy { it.id } @@ -137,13 +140,53 @@ class GtfsParser( .filter { it.name == "stop_times.txt" } .forEach { fd -> log.info("parsing stop times for ${fd.parent}...") - parseStopTimes(fd, trips) { seq -> - seq.chunked(1000000) - .forEach { emit(GtfsData.StopTimeChunk(it)) } + parseStopTimes(fd) { seq -> + val times = ArrayList>(1000100) + seq.forEach { pair -> + val (_, stoptime) = pair + if (times.size > 1000000 && stoptime.patternId == 1L) { + emit(GtfsData.TripChunk(processStoptimes(trips, times))) + times.clear() + } + + times.add(pair) + } + emit(GtfsData.TripChunk(processStoptimes(trips, times))) } } } + private fun hashCalc(headsign: String, stops: List): Long { + val inst = MessageDigest.getInstance("SHA-256") + inst.update(headsign.toByteArray()) + stops.forEach { + inst.update(it.stopId.toByteArray()) + val dint = it.time.departure.asInt() + inst.update((dint).toByte()) + inst.update((dint shr 8).toByte()) + val aint = it.time.arrival.asInt() + inst.update((aint).toByte()) + inst.update((aint shr 8).toByte()) + } + + val buf = inst.digest().slice(0..7).toByteArray() + buf[0] = 0 + buf[1] = 0 + return ByteBuffer.wrap(buf).long + } + + private fun processStoptimes(trips: Map, times: ArrayList>) = + times.groupBy { it.first } + .map { (tripId, pairs) -> + val trip = trips[tripId]!! + val stoptimes = pairs.map { it.second } + val hash = hashCalc(trip.pattern.headsign, stoptimes) + trip.copy(pattern = trip.pattern.copy( + id = hash, + stoptimes = stoptimes.map { it.copy(patternId = hash) } + )) + } + private fun parseRoutes(fd: File) = fd.parseCsv() .map { with(it) { @@ -180,16 +223,17 @@ class GtfsParser( ) } } - private inline fun parseStopTimes(fd: File, trips: Map, block: (Sequence) -> Unit) = + private inline fun parseStopTimes(fd: File, block: (Sequence>) -> Unit) = fd.parseCsvSequence { seq -> seq .map { with(it) { - StopTime( - tripId = trip_id, + it.trip_id to StopTime( + patternId = stop_sequence, stopId = stop_id, - arrivalTime = GtfsStopTime.parseGtfsTime(arrival_time), - departureTime = GtfsStopTime.parseGtfsTime(departure_time), - headsign = stop_headsign.ifEmpty { trips[trip_id]!!.tripHeadsign }, + time = TimeType.Undated( + arrival = GtfsStopTime.parseGtfsTime(arrival_time), + departure = GtfsStopTime.parseGtfsTime(departure_time), + ), pickupType = pickup_type, dropOffType = drop_off_type, ) @@ -230,15 +274,19 @@ class GtfsParser( private fun parseTrips(fd: File, services: Map) = fd.parseCsv() .map { with(it) { - Trip( + Trip.Undated( id = trip_id, - routeId = route_id, + pattern = StoppingPattern( + id = 0, + routeId = route_id, + shapeId = shape_id, + headsign = trip_headsign, + wheelchairAccessible = wheelchair_accessible == "1", + stoptimes = listOf() + ), service = services["${fd.parentFile.name}_${service_id}"]!!, - shapeId = shape_id, - tripHeadsign = trip_headsign, - directionId = direction_id, + directionId = direction_id.toInt(), blockId = block_id.ifEmpty { null }, - wheelchairAccessible = wheelchair_accessible == "1", ) } } diff --git a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt index 33da78f..c0bbaf2 100644 --- a/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt +++ b/server/gtfs/src/main/kotlin/moe/lava/banksia/server/gtfs/structures/GtfsStopTime.kt @@ -10,7 +10,7 @@ internal data class GtfsStopTime( val arrival_time: String, val departure_time: String, val stop_id: String, - val stop_sequence: Int, + val stop_sequence: Long, val stop_headsign: String, val pickup_type: Int, val drop_off_type: Int, 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 9ffd03a..17a9002 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -151,7 +151,7 @@ fun Application.module() { ) .executeAsList() .map { it.asModel().atDate(date) } - .sortedBy { it.departureTime } + .sortedBy { it.time.departure } } call.respond(times) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt index 17ab1f5..84fae70 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsImporter.kt @@ -6,7 +6,6 @@ import moe.lava.banksia.core.model.Service import moe.lava.banksia.core.model.ServiceException import moe.lava.banksia.core.model.Shape import moe.lava.banksia.core.model.Stop -import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.Trip import moe.lava.banksia.core.sqld.DatabaseManager import moe.lava.banksia.core.sqld.mappers.asDb @@ -30,7 +29,6 @@ class GtfsImporter( is GtfsData.ServiceExceptionChunk -> database.addServiceExceptions(chunk.exceptions) is GtfsData.ShapeChunk -> database.addShapes(chunk.shapes) is GtfsData.StopChunk -> database.addStops(chunk.stops) - is GtfsData.StopTimeChunk -> database.addStopTimes(chunk.stopTimes) is GtfsData.TripChunk -> database.addTrips(chunk.trips) } } @@ -101,21 +99,15 @@ class GtfsImporter( log.info("done") } - private fun Database.addStopTimes(stopTimes: List) { - log.info("inserting ${stopTimes.size} stoptimes...") - stopTimeQueries.transaction { - stopTimes.forEach { - stopTimeQueries.insert(it.asDb()) - } - } - log.info("done") - } - - private fun Database.addTrips(trips: List) { + private fun Database.addTrips(trips: List) { log.info("inserting ${trips.size} trips...") - tripQueries.transaction { - trips.forEach { - tripQueries.insert(it.asDb()) + transaction { + trips.forEach { trip -> + stoppingPatternQueries.insert(trip.pattern.asDb()) + trip.pattern.stoptimes.forEach { stoptime -> + stopTimeQueries.insert(stoptime.asDb()) + } + tripQueries.insert(trip.asDb()) } } log.info("done") diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml index de5d8bf..6519371 100644 --- a/server/src/main/resources/logback.xml +++ b/server/src/main/resources/logback.xml @@ -14,7 +14,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + diff --git a/settings.gradle.kts b/settings.gradle.kts index 78831ff..28f535e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,6 +39,7 @@ include(":core") include(":core:data") include(":core:data:client") include(":core:data:server") +include(":core:data:stoptime") include(":core:sqld") include(":ui") include(":ui:maps") 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 c4bd768..76fb51e 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 @@ -231,30 +231,36 @@ class MapScreenViewModel( ) } - 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" - } + stopTimeRepository.getForStop(id) + .onEach { stoptimes -> + val departures = stoptimes +// .filter { !it.headsign.isNullOrBlank() } +// .groupBy { it.headsign!! } + .groupBy { it.stopId } // TODO: Placeholder + .map { (headsign, stopTimes) -> + val now = Clock.System.now() + val times = stopTimes + .map { it.time.arrival.toInstant(TimeZone.currentSystemDefault()) } + .filter { it >= (now - 1.minutes) } + .joinToString(" | ") { + val diff = (it - now).inWholeMinutes.coerceAtLeast(0) + if (diff >= 65) { + "${((diff + 30.0) / 60.0).toInt()}hr" + } else { + "${diff}mn" + } + } + StopInfoPanelState.Departure(headsign, times) } - StopInfoPanelState.Departure(headsign, times) + + iInfoState.update { + if (it !is StopInfoPanelState) + it + else + it.copy(departures = departures) + } } - iInfoState.update { - if (it !is StopInfoPanelState) - it - else - it.copy(departures = departures) - } + .launchIn(viewModelScope) } /*private suspend fun buildPolylines(route: PtvRoute) { From b31067992d0cf5fd9f1a17296e862baec865e2b7 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Wed, 27 May 2026 16:35:23 +1000 Subject: [PATCH 32/33] feat(server/gtfsr): compress after every day --- .../moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt index 7b9229b..aaee0a9 100644 --- a/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt +++ b/server/gtfs_rt/src/main/kotlin/moe/lava/banksia/server/gtfsrt/GtfsrtArchiver.kt @@ -41,13 +41,19 @@ internal class GtfsrtArchiver { val time = Instant.fromEpochSeconds(timestamp).toLocalDateTime(TimeZone.currentSystemDefault()) + val (prevWeek, prevDay) = (time.dayOfYear - 1) / 7 to (time.dayOfYear - 1) % 7 + val (nextWeek, nextDay) = time.dayOfYear / 7 to time.dayOfYear % 7 + val base = File(BASE_DIR, type) - val previousParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7).toString().padStart(2, '0')}") - val currentParent = File(base, "${time.year}-${((time.dayOfYear - 1) / 7 + 1).toString().padStart(2, '0')}") + val previousParent = File(base, "${time.year}-${prevWeek.toString().padStart(2, '0')}/${prevDay}") + val currentParent = File(base, "${time.year}-${nextWeek.toString().padStart(2, '0')}/${nextDay}") val target = File(currentParent, "${timestamp}.proto") if (previousParent.isDirectory) { enqueueCompression(previousParent) + if (prevWeek != nextWeek) { + enqueueCompression(previousParent.parentFile) + } } if (!target.exists()) { @@ -90,6 +96,7 @@ internal class GtfsrtArchiver { ).start() val exitCode = proc.waitFor() if (exitCode == 0) { + log("CompressJob", "Compressed ${next.absolutePath} to ${next.absolutePath}.tar.zst") if (next.deleteRecursively()) { cmut.withLock { cqueue.remove(next) } } else { From 8b3016004bdf922a01cd908dbbfd93324e55b784 Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Tue, 23 Jun 2026 00:07:10 +1000 Subject: [PATCH 33/33] feat: basic departures support also a huge refactor to simplify modules --- core/data/build.gradle.kts | 33 +- core/data/client/build.gradle.kts | 54 --- core/data/server/build.gradle.kts | 20 - .../banksia/core/data/DataDiModule.client.kt} | 6 +- .../repositories/ClientRouteRepository.kt | 10 + .../data/repositories/ClientStopRepository.kt | 0 .../sources/route/RouteLocalDataSource.kt | 1 + .../sources/route/RouteRemoteDataSource.kt | 1 + .../data/sources/stop/StopLocalDataSource.kt | 0 .../data/sources/stop/StopRemoteDataSource.kt | 0 .../lava/banksia/core/data/DataDiModule.kt | 13 + .../core/data/repositories/RouteRepository.kt | 3 +- .../banksia/core/data/DataDiModule.jvm.kt | 7 + .../core/sqld/mappers/StoppingPattern.kt | 5 +- .../moe/lava/banksia/core/sqld/Route.sq | 7 +- .../moe/lava/banksia/core/sqld/StopTime.sq | 21 + .../lava/banksia/core/sqld/StoppingPattern.sq | 3 + .../lava/banksia/core/endpoints/Endpoint.kt | 3 + .../moe/lava/banksia/core/model/StopTime.kt | 10 +- core/{data => }/stoptime/build.gradle.kts | 6 +- .../core/data/StopTimeDataDiModule.client.kt | 0 .../repositories/StopTimeRepository.client.kt | 0 .../stoptime/StopTimeRemoteDataSource.kt | 10 +- .../banksia/core/data/StopTimeDataDiModule.kt | 0 .../banksia/core/data/dto/ExtendedStopTime.kt | 34 ++ .../data/repositories/StopTimeRepository.kt | 4 +- .../stoptime/StopTimeLocalDataSource.kt | 11 +- .../core/endpoints/StopTimeEndpoints.kt | 3 + .../core/data/StopTimeDataDiModule.jvm.kt | 0 .../repositories/StopTimeRepository.jvm.kt | 0 .../banksia/server/routes/StopTimeRoute.kt | 27 ++ gradle/libs.versions.toml | 8 +- server/build.gradle.kts | 2 + .../moe/lava/banksia/server/Application.kt | 28 +- .../lava/banksia/server/di/ServerModules.kt | 4 +- settings.gradle.kts | 4 +- ui/build.gradle.kts | 5 +- ui/shared/build.gradle.kts | 5 + .../drawable/arrow_drop_down.xml | 9 + .../drawable/arrow_drop_up.xml | 9 + .../moe/lava/banksia/ui/di/AppModule.kt | 4 +- .../lava/banksia/ui/layout/info/InfoPanel.kt | 9 +- .../banksia/ui/layout/info/StopInfoPanel.kt | 359 ++++++++++++++++-- .../ui/screens/map/MapScreenViewModel.kt | 100 +++-- 44 files changed, 627 insertions(+), 211 deletions(-) delete mode 100644 core/data/client/build.gradle.kts delete mode 100644 core/data/server/build.gradle.kts rename core/data/{client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt => src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt} (93%) rename core/data/{client/src/commonMain => src/clientMain}/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt (71%) rename core/data/{client/src/commonMain => src/clientMain}/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt (100%) rename core/data/{client/src/commonMain => src/clientMain}/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt (92%) rename core/data/{client/src/commonMain => src/clientMain}/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt (78%) rename core/data/{client/src/commonMain => src/clientMain}/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt (100%) rename core/data/{client/src/commonMain => src/clientMain}/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt (100%) create mode 100644 core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt create mode 100644 core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt create mode 100644 core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt rename core/{data => }/stoptime/build.gradle.kts (90%) rename core/{data => }/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt (100%) rename core/{data => }/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt (100%) rename core/{data => }/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt (66%) rename core/{data => }/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt (100%) create mode 100644 core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt rename core/{data => }/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt (80%) rename core/{data => }/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt (78%) create mode 100644 core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt rename core/{data => }/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt (100%) rename core/{data => }/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt (100%) create mode 100644 core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt create mode 100644 ui/shared/src/commonMain/composeResources/drawable/arrow_drop_down.xml create mode 100644 ui/shared/src/commonMain/composeResources/drawable/arrow_drop_up.xml diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 8c89aff..ecdba19 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -25,9 +25,40 @@ kotlin { jvm() sourceSets { + val clientMain by creating { + dependsOn(commonMain.get()) + } + + androidMain.get().dependsOn(clientMain) + iosArm64Main.get().dependsOn(clientMain) + iosSimulatorArm64Main.get().dependsOn(clientMain) + commonMain.dependencies { + implementation(libs.koin.core) implementation(projects.core) - api(projects.core.data.stoptime) + api(projects.core.stoptime) + } + + androidMain.dependencies { + implementation(libs.koin.compose) + implementation(libs.ktor.client.okhttp) + } + commonMain.dependencies { + implementation(libs.okio) + implementation(libs.koin.core) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.serialization.protobuf) + + implementation(projects.core) + implementation(projects.core.sqld) + } + iosMain.dependencies { + implementation(libs.ktor.client.darwin) } } } diff --git a/core/data/client/build.gradle.kts b/core/data/client/build.gradle.kts deleted file mode 100644 index c6d5e5d..0000000 --- a/core/data/client/build.gradle.kts +++ /dev/null @@ -1,54 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -plugins { - alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.androidMultiplatformLibrary) - alias(libs.plugins.ksp) -} - -kotlin { - android { - namespace = "moe.lava.banksia.core.data.client" - compileSdk = libs.versions.android.compileSdk.get().toInt() - - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - } - } - - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - } - - iosArm64() - iosSimulatorArm64() - - jvm() - - sourceSets { - androidMain.dependencies { - implementation(libs.koin.compose) - implementation(libs.ktor.client.okhttp) - } - commonMain.dependencies { - api(projects.core.data) - - implementation(libs.okio) - implementation(libs.koin.core) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.contentnegotiation) - implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.serialization.json) - implementation(libs.kotlinx.serialization.protobuf) - - implementation(projects.core) - implementation(projects.core.sqld) - } - iosMain.dependencies { - implementation(libs.ktor.client.darwin) - } - } -} diff --git a/core/data/server/build.gradle.kts b/core/data/server/build.gradle.kts deleted file mode 100644 index d2296d7..0000000 --- a/core/data/server/build.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - alias(libs.plugins.kotlinJvm) - alias(libs.plugins.kotlinSerialization) -} - -kotlin { - compilerOptions { - freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") - } -} - -dependencies { - implementation(libs.okio) - implementation(libs.koin.core) - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.datetime) - - api(projects.core.data) - implementation(projects.core) -} diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt similarity index 93% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt rename to core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt index 0384f88..104c6bc 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/ClientDataDiModule.kt +++ b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/DataDiModule.client.kt @@ -16,17 +16,13 @@ import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource import moe.lava.banksia.core.data.sources.stop.StopLocalDataSource import moe.lava.banksia.core.data.sources.stop.StopRemoteDataSource -import moe.lava.banksia.core.sqld.sqldDiModule import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind import org.koin.dsl.module -val clientDataDiModule = module { - includes(sqldDiModule) - includes(stopTimeDataDiModule) - +actual val platformModule = module { // HTTP Clients singleOf(::PtvService) single { diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt similarity index 71% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt rename to core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt index 467399a..f46caac 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt +++ b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientRouteRepository.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import moe.lava.banksia.core.data.sources.route.RouteLocalDataSource import moe.lava.banksia.core.data.sources.route.RouteRemoteDataSource +import moe.lava.banksia.core.model.Route import moe.lava.banksia.core.sqld.mappers.asModel internal class ClientRouteRepository internal constructor( @@ -22,5 +23,14 @@ internal class ClientRouteRepository internal constructor( } } + private val tripRouteMap = mutableMapOf() + override suspend fun get(id: String) = mutex.withLock { local.get(id)?.asModel() ?: remote.get(id) } + override suspend fun getByPattern(patternId: Long) = mutex.withLock { + tripRouteMap[patternId] + ?: remote.getByPattern(patternId).also { + local.save(it) + tripRouteMap[patternId] = it + } + } } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt similarity index 100% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt rename to core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/ClientStopRepository.kt diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt similarity index 92% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt rename to core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt index 8e6af0e..8286b1f 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt +++ b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteLocalDataSource.kt @@ -10,6 +10,7 @@ import moe.lava.banksia.core.sqld.mappers.asDb internal class RouteLocalDataSource(private val queries: RouteQueries) { suspend fun get(id: String) = withContext(Dispatchers.IO) { queries.get(id).executeAsOneOrNull() } suspend fun getAll() = withContext(Dispatchers.IO) { queries.getAll().executeAsList() } +// suspend fun getByTrip(tripId: String) = dao.getByTrip(tripId) suspend fun save(vararg routes: Route) { withContext(Dispatchers.IO) { queries.transaction { diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt similarity index 78% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt rename to core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt index bdcbfc1..15088fb 100644 --- a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt +++ b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/route/RouteRemoteDataSource.kt @@ -7,5 +7,6 @@ import moe.lava.banksia.core.model.Route internal class RouteRemoteDataSource(val client: HttpClient) { suspend fun get(id: String) = client.get("routes/${id}").body() + suspend fun getByPattern(patternId: Long) = client.get("routes/by_pattern/${patternId}").body() suspend fun getAll() = client.get("routes").body>() } diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt similarity index 100% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt rename to core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopLocalDataSource.kt diff --git a/core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt b/core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt similarity index 100% rename from core/data/client/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt rename to core/data/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stop/StopRemoteDataSource.kt diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt new file mode 100644 index 0000000..eea6a0e --- /dev/null +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/DataDiModule.kt @@ -0,0 +1,13 @@ +package moe.lava.banksia.core.data + +import moe.lava.banksia.core.sqld.sqldDiModule +import org.koin.core.module.Module +import org.koin.dsl.module + +internal expect val platformModule: Module + +val dataDiModule = module { + includes(platformModule) + includes(sqldDiModule) + includes(stopTimeDataDiModule) +} diff --git a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt index fbb663f..ef3d6f1 100644 --- a/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt +++ b/core/data/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/RouteRepository.kt @@ -3,6 +3,7 @@ package moe.lava.banksia.core.data.repositories import moe.lava.banksia.core.model.Route interface RouteRepository { - suspend fun get(id: String): Route + suspend fun get(id: String): Route? + suspend fun getByPattern(patternId: Long): Route? suspend fun getAll(): List } diff --git a/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt b/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt new file mode 100644 index 0000000..78a44d1 --- /dev/null +++ b/core/data/src/jvmMain/kotlin/moe/lava/banksia/core/data/DataDiModule.jvm.kt @@ -0,0 +1,7 @@ +package moe.lava.banksia.core.data + +import org.koin.dsl.module + +internal actual val platformModule = module { + +} diff --git a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt index e50aa85..d1409a2 100644 --- a/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt +++ b/core/sqld/src/commonMain/kotlin/moe/lava/banksia/core/sqld/mappers/StoppingPattern.kt @@ -2,9 +2,10 @@ package moe.lava.banksia.core.sqld.mappers import moe.lava.banksia.core.model.StopTime import moe.lava.banksia.core.model.StoppingPattern +import moe.lava.banksia.core.model.TimeType import moe.lava.banksia.core.sqld.StoppingPattern as DbStoppingPattern -fun DbStoppingPattern.asModel(stoptimes: List) = StoppingPattern.Undated( +fun DbStoppingPattern.asModel(stoptimes: List>) = StoppingPattern( id = id, routeId = routeId, shapeId = shapeId, @@ -13,7 +14,7 @@ fun DbStoppingPattern.asModel(stoptimes: List) = StoppingPatte stoptimes = stoptimes, ) -fun StoppingPattern.Undated.asDb() = DbStoppingPattern( +fun StoppingPattern<*>.asDb() = DbStoppingPattern( id = id, routeId = routeId, shapeId = shapeId, diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq index f1617f7..e607975 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/Route.sq @@ -11,5 +11,10 @@ SELECT * FROM Route; get: SELECT * FROM Route WHERE id == ?; +getByPattern: +SELECT Route.* FROM Route +INNER JOIN StoppingPattern ON Route.id == StoppingPattern.routeId +WHERE StoppingPattern.id == :patternId; + insert: -INSERT INTO Route VALUES ?; +INSERT OR REPLACE INTO Route VALUES ?; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq index 45b3f10..06bd76b 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StopTime.sq @@ -22,3 +22,24 @@ INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId WHERE StopTime.patternId == StoppingPattern.id AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) AND ServiceException.type IS NULL; + +getExtendedForStop: +SELECT DISTINCT + StopTime.patternId, + StopTime.arrivalDelta, + StopTime.departureTime, + StoppingPattern.headsign, + Route.type AS routeType, + Route.number AS routeNumber, + Route.name AS routeName, + Stop.platformCode AS stopPlatformCode +FROM StopTime +INNER JOIN Service ON Service.days & :days = :days AND :date BETWEEN Service.start AND Service.`end` +LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date +INNER JOIN Trip ON Trip.serviceId == Service.id +INNER JOIN StoppingPattern ON StoppingPattern.id == Trip.patternId +INNER JOIN Route ON Route.id == StoppingPattern.routeId +INNER JOIN Stop ON Stop.id == StopTime.stopId +WHERE StopTime.patternId == StoppingPattern.id + AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) + AND ServiceException.type IS NULL; diff --git a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq index cc1c5ab..9a09e69 100644 --- a/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq +++ b/core/sqld/src/commonMain/sqldelight/moe/lava/banksia/core/sqld/StoppingPattern.sq @@ -8,3 +8,6 @@ CREATE TABLE StoppingPattern ( insert: INSERT OR REPLACE INTO StoppingPattern VALUES ?; + +get: +SELECT * FROM StoppingPattern WHERE id == :id; diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt new file mode 100644 index 0000000..7e23b5d --- /dev/null +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/Endpoint.kt @@ -0,0 +1,3 @@ +package moe.lava.banksia.core.endpoints + +object Endpoint diff --git a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt index da92819..edd7c51 100644 --- a/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt +++ b/core/src/commonMain/kotlin/moe/lava/banksia/core/model/StopTime.kt @@ -31,13 +31,15 @@ sealed class TimeType { ) : TimeType() } +fun TimeType.Undated.atDate(date: LocalDate) = TimeType.Dated( + arrival = arrival.atDate(date), + departure = departure.atDate(date), +) + fun StopTime.atDate(date: LocalDate) = StopTime( patternId = patternId, stopId = stopId, - time = TimeType.Dated( - arrival = time.arrival.atDate(date), - departure = time.departure.atDate(date), - ), + time = time.atDate(date), pickupType = pickupType, dropOffType = dropOffType, ) diff --git a/core/data/stoptime/build.gradle.kts b/core/stoptime/build.gradle.kts similarity index 90% rename from core/data/stoptime/build.gradle.kts rename to core/stoptime/build.gradle.kts index 086e749..44cf072 100644 --- a/core/data/stoptime/build.gradle.kts +++ b/core/stoptime/build.gradle.kts @@ -9,7 +9,7 @@ plugins { kotlin { android { - namespace = "moe.lava.banksia.core.data.stoptime" + namespace = "moe.lava.banksia.core.stoptime" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { @@ -56,5 +56,9 @@ kotlin { iosMain.dependencies { implementation(libs.ktor.client.darwin) } + jvmMain.dependencies { + implementation(libs.koin.ktor) + implementation(libs.ktor.server.core) + } } } diff --git a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt similarity index 100% rename from core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt rename to core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.client.kt diff --git a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt similarity index 100% rename from core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt rename to core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.client.kt diff --git a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt similarity index 66% rename from core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt rename to core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt index 1d338ce..0c38f64 100644 --- a/core/data/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt +++ b/core/stoptime/src/clientMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeRemoteDataSource.kt @@ -7,7 +7,9 @@ import io.ktor.client.request.parameter import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.data.dto.ExtendedStopTime +import moe.lava.banksia.core.endpoints.Endpoint +import moe.lava.banksia.core.endpoints.stopTimeByStop import kotlin.time.Clock internal class StopTimeRemoteDataSource( @@ -16,9 +18,9 @@ internal class StopTimeRemoteDataSource( suspend fun getAtStop( stopId: String, date: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): List { - return client.get("stoptimes/by_stop/${stopId}") { + ): List { + return client.get(Endpoint.stopTimeByStop(stopId)) { parameter("date", date) - }.body>() + }.body>() } } diff --git a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt similarity index 100% rename from core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt rename to core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.kt diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt new file mode 100644 index 0000000..38de29d --- /dev/null +++ b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/dto/ExtendedStopTime.kt @@ -0,0 +1,34 @@ +package moe.lava.banksia.core.data.dto + +import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable +import moe.lava.banksia.core.model.FutureTime +import moe.lava.banksia.core.model.RouteType +import moe.lava.banksia.core.model.TimeType +import moe.lava.banksia.core.model.atDate +import moe.lava.banksia.core.sqld.GetExtendedForStop + +@Serializable +data class ExtendedStopTime( + val patternId: Long, + val stopPlatformCode: String?, + val time: TimeType.Dated, + val headsign: String?, + val routeType: RouteType, + val routeNumber: String?, + val routeName: String, +) + +// TODO: This probably doesn't belong here +fun GetExtendedForStop.asModel(date: LocalDate) = ExtendedStopTime( + patternId = patternId, + stopPlatformCode = stopPlatformCode, + time = TimeType.Undated( + arrival = FutureTime.fromInt((departureTime + arrivalDelta).toInt()), + departure = FutureTime.fromInt(departureTime.toInt()), + ).atDate(date), + headsign = headsign, + routeType = RouteType.from(routeType.toInt()), + routeNumber = routeNumber, + routeName = routeName, +) diff --git a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt similarity index 80% rename from core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt rename to core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt index 2de0c10..6a81c09 100644 --- a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt +++ b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.kt @@ -4,12 +4,12 @@ import kotlinx.coroutines.flow.Flow import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn -import moe.lava.banksia.core.model.StopTime +import moe.lava.banksia.core.data.dto.ExtendedStopTime import kotlin.time.Clock expect class StopTimeRepository { suspend fun getForStop( id: String, date: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault()), - ): Flow> + ): Flow> } diff --git a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt similarity index 78% rename from core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt rename to core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt index 03ebbda..f22dc09 100644 --- a/core/data/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt +++ b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/data/sources/stoptime/StopTimeLocalDataSource.kt @@ -4,10 +4,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext import kotlinx.datetime.LocalDate -import moe.lava.banksia.core.model.StopTime -import moe.lava.banksia.core.model.atDate +import moe.lava.banksia.core.data.dto.ExtendedStopTime +import moe.lava.banksia.core.data.dto.asModel import moe.lava.banksia.core.sqld.StopTimeQueries -import moe.lava.banksia.core.sqld.mappers.asModel import moe.lava.banksia.core.util.serialise import org.koin.core.component.KoinComponent import org.koin.core.component.get @@ -15,16 +14,16 @@ import org.koin.core.component.get internal class StopTimeLocalDataSource : KoinComponent { private val queries get() = get() - suspend fun getAtStop(stopId: String, date: LocalDate): List { + suspend fun getAtStop(stopId: String, date: LocalDate): List { return withContext(context = Dispatchers.IO) { queries - .getForStopDated( + .getExtendedForStop( listOf(date.dayOfWeek).serialise().toLong(), date.toEpochDays(), stopId, ) .executeAsList() - .map { it.asModel().atDate(date) } + .map { it.asModel(date) } .sortedBy { it.time.departure } } } diff --git a/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt new file mode 100644 index 0000000..f689b2d --- /dev/null +++ b/core/stoptime/src/commonMain/kotlin/moe/lava/banksia/core/endpoints/StopTimeEndpoints.kt @@ -0,0 +1,3 @@ +package moe.lava.banksia.core.endpoints + +fun Endpoint.stopTimeByStop(stopId: String) = "stoptimes/by_stop/${stopId}" diff --git a/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt similarity index 100% rename from core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt rename to core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/StopTimeDataDiModule.jvm.kt diff --git a/core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt similarity index 100% rename from core/data/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt rename to core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/core/data/repositories/StopTimeRepository.jvm.kt diff --git a/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt new file mode 100644 index 0000000..5791855 --- /dev/null +++ b/core/stoptime/src/jvmMain/kotlin/moe/lava/banksia/server/routes/StopTimeRoute.kt @@ -0,0 +1,27 @@ +package moe.lava.banksia.server.routes + +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import kotlinx.coroutines.flow.first +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.todayIn +import moe.lava.banksia.core.data.repositories.StopTimeRepository +import moe.lava.banksia.core.endpoints.Endpoint +import moe.lava.banksia.core.endpoints.stopTimeByStop +import org.koin.ktor.ext.inject +import kotlin.time.Clock + +fun Route.stopTimeRoutes() { + val repo by inject() + + get(Endpoint.stopTimeByStop("{stop_id}")) { + val stopId = call.parameters["stop_id"]!! + val date = call.queryParameters["date"] + ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) } + ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) + val data = repo.getForStop(stopId, date).first() + call.respond(data) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 02ca8cc..483c5d5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] agp = "9.1.0" -android-compileSdk = "36" +android-compileSdk = "37" android-minSdk = "24" -android-targetSdk = "36" +android-targetSdk = "37" androidx-activity= "1.13.0" androidx-lifecycle = "2.10.0" -compose-multiplatform = "1.11.0-alpha04" +compose-multiplatform = "1.12.0-alpha02" composeunstyled = "1.49.6" coroutines = "1.10.2" geo = "0.8.0" @@ -19,7 +19,7 @@ ktor = "3.4.1" logback = "1.5.32" maplibre = "0.12.1" material = "1.7.3" -material3 = "1.11.0-alpha04" +material3 = "1.11.0-alpha07" okio = "3.17.0" playServicesLocation = "21.3.0" secretsGradlePlugin = "2.0.1" diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 93da532..9d2cb78 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -20,7 +20,9 @@ kotlin { dependencies { implementation(projects.core) + implementation(projects.core.data) implementation(projects.core.sqld) + implementation(projects.core.stoptime) implementation(projects.server.gtfs) implementation(projects.server.gtfsRt) 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 17a9002..dedffe5 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -15,22 +15,16 @@ 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.core.Constants -import moe.lava.banksia.core.model.atDate import moe.lava.banksia.core.sqld.RouteQueries import moe.lava.banksia.core.sqld.StopQueries -import moe.lava.banksia.core.sqld.StopTimeQueries import moe.lava.banksia.core.sqld.mappers.asModel -import moe.lava.banksia.core.util.serialise import moe.lava.banksia.server.di.ServerModules import moe.lava.banksia.server.gtfsrt.GtfsrtService +import moe.lava.banksia.server.routes.stopTimeRoutes import org.koin.dsl.module import org.koin.ktor.ext.get import org.koin.ktor.plugin.Koin -import kotlin.time.Clock fun main() { if (System.getenv("BANKSIA_PRODUCTION") == "1") Constants.devMode = false @@ -53,6 +47,8 @@ fun Application.module() { launch { get().start(this, !Constants.devMode) } routing { + stopTimeRoutes() + if (Constants.devMode) { get("/fixup") { call.respondText("received") @@ -137,23 +133,5 @@ fun Application.module() { } call.respond(stops.map { it.asModel() }) } - get("/stoptimes/by_stop/{stop_id}") { - val stopId = call.parameters["stop_id"]!! - val date = call.queryParameters["date"] - ?.let { LocalDate.parse(it, LocalDate.Formats.ISO) } - ?: Clock.System.todayIn(TimeZone.currentSystemDefault()) - val times = withContext(context = Dispatchers.IO) { - get() - .getForStopDated( - listOf(date.dayOfWeek).serialise().toLong(), - date.toEpochDays(), - stopId, - ) - .executeAsList() - .map { it.asModel().atDate(date) } - .sortedBy { it.time.departure } - } - call.respond(times) - } } } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index 881becf..b2593b3 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,7 +1,7 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient -import moe.lava.banksia.core.sqld.sqldDiModule +import moe.lava.banksia.core.data.dataDiModule import moe.lava.banksia.server.GtfsDataFixer import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser @@ -11,7 +11,7 @@ import org.koin.core.module.dsl.singleOf import org.koin.dsl.module val ServerModules = module { - includes(sqldDiModule) + includes(dataDiModule) single { HttpClient() } singleOf(::GtfsParser) diff --git a/settings.gradle.kts b/settings.gradle.kts index 28f535e..bdb499e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,9 +37,7 @@ include(":server:gtfs") include(":server:gtfs_rt") include(":core") include(":core:data") -include(":core:data:client") -include(":core:data:server") -include(":core:data:stoptime") +include(":core:stoptime") include(":core:sqld") include(":ui") include(":ui:maps") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 9c5c7bd..b599bc6 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -41,7 +41,9 @@ kotlin { sourceSets { androidMain.dependencies { + implementation(libs.compose.ui.tooling.preview) implementation(libs.play.services.location) + implementation(projects.ui.shared) } commonMain.dependencies { implementation(libs.compose.components.resources) @@ -68,7 +70,8 @@ kotlin { implementation(libs.ui.backhandler) implementation(projects.core) - implementation(projects.core.data.client) + implementation(projects.core.data) + implementation(projects.core.stoptime) implementation(projects.ui.maps) implementation(projects.ui.shared) } diff --git a/ui/shared/build.gradle.kts b/ui/shared/build.gradle.kts index e379840..2a78572 100644 --- a/ui/shared/build.gradle.kts +++ b/ui/shared/build.gradle.kts @@ -16,6 +16,10 @@ kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } + + androidResources { + enable = true + } } compilerOptions { @@ -47,4 +51,5 @@ dependencies { compose.resources { publicResClass = true packageOfResClass = "moe.lava.banksia.resources" + generateResClass = always } diff --git a/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_down.xml b/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_down.xml new file mode 100644 index 0000000..ac49572 --- /dev/null +++ b/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_up.xml b/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_up.xml new file mode 100644 index 0000000..322fa56 --- /dev/null +++ b/ui/shared/src/commonMain/composeResources/drawable/arrow_drop_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt index a2b4d7e..cff36fb 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/di/AppModule.kt @@ -1,12 +1,12 @@ package moe.lava.banksia.ui.di -import moe.lava.banksia.core.data.clientDataDiModule +import moe.lava.banksia.core.data.dataDiModule import moe.lava.banksia.ui.screens.map.MapScreenViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val AppModule = module { - includes(clientDataDiModule) + includes(dataDiModule) // ViewModel viewModelOf(::MapScreenViewModel) diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt index 55eac69..9fb37d7 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/InfoPanel.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeContent import androidx.compose.foundation.layout.size @@ -27,7 +28,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import kotlin.time.Duration.Companion.milliseconds @@ -45,6 +45,7 @@ sealed class InfoPanelState { @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun InfoPanel( + modifier: Modifier = Modifier, state: InfoPanelState, onEvent: (InfoPanelEvent) -> Unit, onPeekHeightChange: (Dp) -> Unit, @@ -65,11 +66,13 @@ fun InfoPanel( } Column( - Modifier + modifier = modifier .fillMaxWidth() .padding(horizontal = 24.dp) + .heightIn(min = 350.dp) .onSizeChanged { - onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) }) +// onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) }) + onPeekHeightChange(350.dp) } ) { Box { diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt index dbe3b29..369721c 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/layout/info/StopInfoPanel.kt @@ -1,75 +1,358 @@ package moe.lava.banksia.ui.layout.info +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SegmentedListItem +import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import moe.lava.banksia.resources.Res +import moe.lava.banksia.resources.arrow_drop_down +import moe.lava.banksia.resources.arrow_drop_up +import moe.lava.banksia.ui.extensions.BUS_ORANGE +import moe.lava.banksia.ui.extensions.TRAIN_BLUE +import moe.lava.banksia.ui.platform.BanksiaTheme +import org.jetbrains.compose.resources.painterResource +import kotlin.time.Clock +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Instant -sealed class StopInfoPanelEvent : InfoPanelEvent() +sealed class StopInfoPanelEvent : InfoPanelEvent() { + data object ToggleGrouping : StopInfoPanelEvent() +} data class StopInfoPanelState( val id: String, val name: String, val subname: String? = null, - val departures: List? = null, + val departures: List? = null, ) : InfoPanelState() { override val loading: Boolean - get() = departures == null + get() = departures.isNullOrEmpty() - data class Departure(val directionName: String, val formattedTimes: String) + data class DeparturePlatforms( + val platform: String, + val departures: List, + ) + + data class DepartureInfo( + val routeName: String, + val routeColour: Color?, + val headsign: String, + val description: String?, + val time: Instant, + ) } @Composable -internal fun StopInfoPanel( - state: StopInfoPanelState, - onEvent: (StopInfoPanelEvent) -> Unit, +private fun listColors() = ListItemDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + selectedContainerColor = MaterialTheme.colorScheme.primary, + selectedContentColor = MaterialTheme.colorScheme.onPrimary, +) + +@Composable +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +private fun MonoPlatform( + state: StopInfoPanelState.DeparturePlatforms ) { - 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) { + val departures = state.departures + val lazyState = LazyListState(firstVisibleItemIndex = + departures.indexOfFirst { + it.time > Clock.System.now() + }.coerceAtLeast(0) + ) + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap), + state = lazyState, + ) { + itemsIndexed(departures) { idx, dep -> + SegmentedListItem( + onClick = {}, + colors = listColors(), + shapes = ListItemDefaults.segmentedShapes( + idx, + departures.size, + ), + supportingContent = { + dep.description?.let { Text(dep.description) } + }, + trailingContent = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy((-4).dp) + ) { + Text( + text = (dep.time - Clock.System.now()).inWholeMinutes.toString(), + style = MaterialTheme.typography.headlineSmallEmphasized, + ) + Text( + text = "mn", + style = MaterialTheme.typography.labelSmallEmphasized, + ) + } + }, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Box( + Modifier + .clip(ShapeDefaults.ExtraSmall) + .background(dep.routeColour ?: MaterialTheme.colorScheme.surface) + .padding(vertical = 2.dp, horizontal = 4.dp) + ) { + Text( + text = dep.routeName, + style = MaterialTheme.typography.labelSmallEmphasized, + color = MaterialTheme.colorScheme.surface, + ) + } Text( - name, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.SemiBold - ) - Text( - formatted, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(horizontal = 5.dp) + text = dep.headsign, + style = MaterialTheme.typography.labelLargeEmphasized, ) } } } } } + +@Composable +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +private fun ManyPlatforms( + state: List, +) { + val expandedList = remember { mutableStateListOf(*Array(state.size) { true }) } + LazyColumn( + modifier = Modifier.fillMaxSize(), + ) { + state.forEachIndexed { idx, depInfo -> + val (platform, departures) = depInfo + val expanded = expandedList[idx] + stickyHeader(key = "header_${depInfo.hashCode()}") { + val base = ListItemDefaults.segmentedShapes(0, 2) + val large = MaterialTheme.shapes.large + + Box( + Modifier + .animateItem() + .background(MaterialTheme.colorScheme.surfaceContainerLow) + .padding(bottom = ListItemDefaults.SegmentedGap) + ) { + SegmentedListItem( + onClick = { expandedList[idx] = !expandedList[idx] }, + colors = listColors(), + shapes = if (expanded) base else base.copy(shape = large), + trailingContent = { + Icon( + painterResource(if (expanded) Res.drawable.arrow_drop_up else Res.drawable.arrow_drop_down), + contentDescription = null, + modifier = Modifier + .background( + if (expanded) MaterialTheme.colorScheme.surface else Color.Transparent, + shape = RoundedCornerShape(100) + ) + .padding(6.dp), + tint = MaterialTheme.colorScheme.onSurface, + ) + }, + ) { + Text( + text = platform, + style = MaterialTheme.typography.labelLarge, + ) + } + } + } + + if (expanded) { + item(key = "items_${depInfo.hashCode()}") { + Column( + modifier = Modifier.animateItem(), + verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap), + ) { + departures.filter { it.time > Clock.System.now() }.take(5) + .forEachIndexed { idx, dep -> + SegmentedListItem( + onClick = {}, + colors = listColors(), + shapes = ListItemDefaults.segmentedShapes( + idx + 1, + (departures.size + 1).coerceAtMost(6), + ), + supportingContent = { + dep.description?.let { Text(dep.description) } + }, + trailingContent = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy((-4).dp) + ) { + Text( + text = (dep.time - Clock.System.now()).inWholeMinutes.toString(), + style = MaterialTheme.typography.headlineSmallEmphasized, + ) + Text( + text = "mn", + style = MaterialTheme.typography.labelSmallEmphasized, + ) + } + }, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Box( + Modifier + .clip(ShapeDefaults.ExtraSmall) + .background( + dep.routeColour + ?: MaterialTheme.colorScheme.surface + ) + .padding(vertical = 2.dp, horizontal = 4.dp) + ) { + Text( + text = dep.routeName, + style = MaterialTheme.typography.labelSmallEmphasized, + color = MaterialTheme.colorScheme.surface, + ) + } + Text( + text = dep.headsign, + style = MaterialTheme.typography.labelLargeEmphasized, + ) + } + } + } + } + } + } + item(key = "spacer_${depInfo.hashCode()}") { + Spacer( + modifier = Modifier.animateItem().height(10.dp) + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +internal fun StopInfoPanel( + state: StopInfoPanelState, + onEvent: (StopInfoPanelEvent) -> Unit, +) { + val spec = fadeIn(tween(300, 300)) togetherWith fadeOut(tween(300)) + + AnimatedContent( + targetState = state, + contentKey = { it.id }, + transitionSpec = { spec }, + ) { state -> + Column(Modifier.fillMaxWidth().fillMaxHeight()) { + Row { + Column { + Text( + 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 + ) + } + } + IconButton( + onClick = { onEvent(StopInfoPanelEvent.ToggleGrouping) }, + ) { Icon(Icons.Default.Edit, null) } + } + Spacer(Modifier.height(10.dp)) + AnimatedContent( + targetState = state.departures, + transitionSpec = { spec }, + ) { departures -> + departures?.let { departurePlatforms -> + if (departurePlatforms.size > 1) { + ManyPlatforms(departurePlatforms) + } else if (departurePlatforms.size == 1) { + MonoPlatform(departurePlatforms[0]) + } + } + } + } + } +} + +@Preview +@Composable +internal fun StopInfoPanelPreview() { + fun dateIn(dur: Duration) = (Clock.System.now() + dur) + + InfoPanel( + modifier = Modifier.background(BanksiaTheme.colors.background), + state = StopInfoPanelState( + id = "id", + name = "name", + subname = "sub", + departures = listOf( + StopInfoPanelState.DeparturePlatforms("Platform 1", listOf( + StopInfoPanelState.DepartureInfo("Sunbury", Color(TRAIN_BLUE), "Sunbury", "··· Malvern -> Anzac ··· Sunbury", dateIn(2.minutes)), + StopInfoPanelState.DepartureInfo("Sunbury", Color(TRAIN_BLUE), "West Footscray", "Express via Metro Tunnel", dateIn(8.minutes)), + )), + StopInfoPanelState.DeparturePlatforms("Platform 2", listOf( + StopInfoPanelState.DepartureInfo("237", Color(BUS_ORANGE), "Westall", null, dateIn(7.minutes)), + StopInfoPanelState.DepartureInfo("442", Color(BUS_ORANGE), "Dandenong", null, dateIn(8.minutes)), + )), + ), + ), + onEvent = {}, + onPeekHeightChange = {}, + ) +} diff --git a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt index 76fb51e..de06381 100644 --- a/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt +++ b/ui/src/commonMain/kotlin/moe/lava/banksia/ui/screens/map/MapScreenViewModel.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant +import moe.lava.banksia.core.data.dto.ExtendedStopTime import moe.lava.banksia.core.data.repositories.RouteRepository import moe.lava.banksia.core.data.repositories.StopRepository import moe.lava.banksia.core.data.repositories.StopTimeRepository @@ -26,9 +27,11 @@ import moe.lava.banksia.core.util.LoopFlow.Companion.waitUntilSubscribed import moe.lava.banksia.core.util.Point import moe.lava.banksia.core.util.log import moe.lava.banksia.data.ptv.PtvService +import moe.lava.banksia.ui.extensions.getUIProperties import moe.lava.banksia.ui.layout.info.InfoPanelEvent import moe.lava.banksia.ui.layout.info.InfoPanelState import moe.lava.banksia.ui.layout.info.RouteInfoPanelState +import moe.lava.banksia.ui.layout.info.StopInfoPanelEvent import moe.lava.banksia.ui.layout.info.StopInfoPanelState import moe.lava.banksia.ui.layout.info.TripInfoPanelState import moe.lava.banksia.ui.map.util.CameraPosition @@ -36,8 +39,6 @@ import moe.lava.banksia.ui.map.util.CameraPositionBounds import moe.lava.banksia.ui.map.util.Marker import moe.lava.banksia.ui.state.MapState import moe.lava.banksia.ui.state.SearchState -import kotlin.time.Clock -import kotlin.time.Duration.Companion.minutes sealed class MapScreenEvent { data object DismissState : MapScreenEvent() @@ -53,6 +54,9 @@ private data class InternalState( val route: String? = null, val stop: String? = null, val run: String? = null, + + val lastStopDepartures: List? = null, + val stopsGrouped: Boolean = true, ) class MapScreenViewModel( @@ -69,6 +73,10 @@ class MapScreenViewModel( viewModelScope.launch { switchRoute(value.route) } if (value.stop != last.stop) viewModelScope.launch { switchStop(value.stop) } + if (value.lastStopDepartures != last.lastStopDepartures) + viewModelScope.launch { buildDepartures() } + if (value.stopsGrouped != last.stopsGrouped) + viewModelScope.launch { buildDepartures() } if (value.run != last.run) switchRun(value.run) } @@ -105,7 +113,9 @@ class MapScreenViewModel( fun handleEvent(event: InfoPanelEvent) { viewModelScope.launch { -// when (event) { } + when (event) { + StopInfoPanelEvent.ToggleGrouping -> state = state.copy(stopsGrouped = !state.stopsGrouped) + } } } @@ -165,7 +175,7 @@ class MapScreenViewModel( } val route = routeRepository.get(routeId) -// val gtfsRoute = ptvService.route(routeId) + ?: return iInfoState.update { RouteInfoPanelState( name = route.name, @@ -215,11 +225,11 @@ class MapScreenViewModel( private suspend fun switchStop(id: String?) { if (id == null) { iInfoState.update { InfoPanelState.None } + state = state.copy(lastStopDepartures = null) return } val stop = stopRepository.get(id) -// val stop = ptvService.stop(routeType, stopId) val split = stop.name.split("/") val name = split[0] val subname = split.getOrNull(1) @@ -232,37 +242,63 @@ class MapScreenViewModel( } stopTimeRepository.getForStop(id) - .onEach { stoptimes -> - val departures = stoptimes -// .filter { !it.headsign.isNullOrBlank() } -// .groupBy { it.headsign!! } - .groupBy { it.stopId } // TODO: Placeholder - .map { (headsign, stopTimes) -> - val now = Clock.System.now() - val times = stopTimes - .map { it.time.arrival.toInstant(TimeZone.currentSystemDefault()) } - .filter { it >= (now - 1.minutes) } - .joinToString(" | ") { - val diff = (it - now).inWholeMinutes.coerceAtLeast(0) - if (diff >= 65) { - "${((diff + 30.0) / 60.0).toInt()}hr" - } else { - "${diff}mn" - } - } - StopInfoPanelState.Departure(headsign, times) - } - - iInfoState.update { - if (it !is StopInfoPanelState) - it - else - it.copy(departures = departures) - } + .onEach { departures -> + state = state.copy( + lastStopDepartures = departures + ) } .launchIn(viewModelScope) } + private fun friendlyPlatform(platform: String) = + platform.takeUnless { it.firstOrNull()?.isDigit() == true } + ?: "Platform $platform" + private fun buildDepartures() { + val rawDepartures = state.lastStopDepartures ?: return + val departures = if (state.stopsGrouped) { + rawDepartures + .groupBy { it.stopPlatformCode } + .mapKeys { (platform) -> platform?.let { friendlyPlatform(it) } } + .entries + .sortedBy { (platform) -> platform } + .map { (platform, deps) -> + StopInfoPanelState.DeparturePlatforms( + platform = platform ?: "", + departures = deps.map { + StopInfoPanelState.DepartureInfo( + routeName = it.routeNumber ?: it.routeName, + routeColour = it.routeType.getUIProperties().colour, + headsign = it.headsign ?: it.routeName, + description = null, + time = it.time.departure.toInstant(TimeZone.currentSystemDefault()), + ) + } + ) + } + } else if (rawDepartures.isEmpty()) { + listOf() + } else { + listOf(StopInfoPanelState.DeparturePlatforms(platform = "", departures = rawDepartures.map { dep -> + StopInfoPanelState.DepartureInfo( + routeName = dep.routeNumber ?: dep.routeName, + routeColour = dep.routeType.getUIProperties().colour, + headsign = dep.headsign ?: dep.routeName, + description = dep.stopPlatformCode?.let { friendlyPlatform(it) }, + time = dep.time.departure.toInstant(TimeZone.currentSystemDefault()), + ) + })) + } + + departures.let { departures -> + iInfoState.update { + if (it !is StopInfoPanelState) + it + else + it.copy(departures = departures) + } + } + } + /*private suspend fun buildPolylines(route: PtvRoute) { val routeWithGeo = if (route.geopath.isEmpty()) ptvService.route(route.routeId, true)