diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts
new file mode 100644
index 0000000..b8b100b
--- /dev/null
+++ b/androidApp/build.gradle.kts
@@ -0,0 +1,57 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(libs.plugins.androidApplication)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+}
+
+kotlin {
+ target {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_11)
+ }
+ }
+
+ compilerOptions {
+ freeCompilerArgs.add("-Xexplicit-backing-fields")
+ }
+
+ dependencies {
+ implementation(projects.ui)
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.compose.ui.tooling.preview)
+ }
+}
+
+dependencies {
+ debugImplementation(libs.compose.ui.tooling)
+}
+
+android {
+ namespace = "moe.lava.banksia"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
+ defaultConfig {
+ applicationId = "moe.lava.banksia"
+ minSdk = libs.versions.android.minSdk.get().toInt()
+ targetSdk = libs.versions.android.targetSdk.get().toInt()
+ versionCode = 1
+ versionName = "1.0"
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+}
diff --git a/ui/src/androidMain/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml
similarity index 91%
rename from ui/src/androidMain/AndroidManifest.xml
rename to androidApp/src/main/AndroidManifest.xml
index 928349e..16435e6 100644
--- a/ui/src/androidMain/AndroidManifest.xml
+++ b/androidApp/src/main/AndroidManifest.xml
@@ -13,9 +13,6 @@
android:enableOnBackInvokedCallback="true"
android:usesCleartextTraffic="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
-
{
+ return 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/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties
new file mode 100644
index 0000000..9b7b12a
--- /dev/null
+++ b/gradle/gradle-daemon-jvm.properties
@@ -0,0 +1,13 @@
+#This file is generated by updateDaemonJvm
+toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
+toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
+toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
+toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
+toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect
+toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
+toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect
+toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect
+toolchainVendor=JETBRAINS
+toolchainVersion=21
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 638a098..70676f5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.13.1"
+agp = "9.1.0"
android-compileSdk = "36"
android-minSdk = "24"
android-targetSdk = "36"
@@ -85,7 +85,7 @@ ui-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.r
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
-androidLibrary = { id = "com.android.library", version.ref = "agp" }
+androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 37f853b..37f78a6 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a33c5ec..4688423 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,6 +14,9 @@ pluginManagement {
gradlePluginPortal()
}
}
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
+}
dependencyResolutionManagement {
repositories {
@@ -28,7 +31,10 @@ dependencyResolutionManagement {
}
}
+include(":androidApp")
include(":client")
include(":server")
include(":shared")
include(":ui")
+include(":ui:maps")
+include(":ui:shared")
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 1f26a53..953d790 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -1,10 +1,9 @@
-import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
- alias(libs.plugins.androidLibrary)
+ alias(libs.plugins.androidMultiplatformLibrary)
alias(libs.plugins.ksp)
alias(libs.plugins.room)
alias(libs.plugins.wire)
@@ -15,8 +14,10 @@ room {
}
kotlin {
- androidTarget {
- @OptIn(ExperimentalKotlinGradlePluginApi::class)
+ android {
+ namespace = "moe.lava.banksia.shared"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
@@ -26,7 +27,6 @@ kotlin {
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
}
- iosX64()
iosArm64()
iosSimulatorArm64()
@@ -58,27 +58,14 @@ kotlin {
dependencies {
add("kspAndroid", libs.room.compiler)
- add("kspIosX64", libs.room.compiler)
add("kspIosArm64", libs.room.compiler)
add("kspIosSimulatorArm64", libs.room.compiler)
add("kspJvm", libs.room.compiler)
}
-android {
- namespace = "moe.lava.banksia.shared"
- compileSdk = libs.versions.android.compileSdk.get().toInt()
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
- defaultConfig {
- minSdk = libs.versions.android.minSdk.get().toInt()
- }
-}
-
wire {
sourcePath {
srcDir("src/commonMain/proto")
}
kotlin {}
-}
\ No newline at end of file
+}
diff --git a/shared/schemas/moe.lava.banksia.room.Database/4.json b/shared/schemas/moe.lava.banksia.room.Database/4.json
new file mode 100644
index 0000000..783b3ee
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/4.json
@@ -0,0 +1,368 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 4,
+ "identityHash": "4426fd2ccc826d9d9d9021546b105850",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": true,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": true,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/schemas/moe.lava.banksia.room.Database/5.json b/shared/schemas/moe.lava.banksia.room.Database/5.json
new file mode 100644
index 0000000..c4a786d
--- /dev/null
+++ b/shared/schemas/moe.lava.banksia.room.Database/5.json
@@ -0,0 +1,368 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 5,
+ "identityHash": "4426fd2ccc826d9d9d9021546b105850",
+ "entities": [
+ {
+ "tableName": "Route",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "number",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Shape",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "path",
+ "columnName": "path",
+ "affinity": "BLOB",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ }
+ },
+ {
+ "tableName": "Stop",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT NOT NULL, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lat",
+ "columnName": "lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lng",
+ "columnName": "lng",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "parent",
+ "columnName": "parent",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasWheelChairBoarding",
+ "columnName": "hasWheelChairBoarding",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "platformCode",
+ "columnName": "platformCode",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Stop_parent",
+ "unique": false,
+ "columnNames": [
+ "parent"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)"
+ }
+ ]
+ },
+ {
+ "tableName": "StopTime",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripId",
+ "columnName": "tripId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopId",
+ "columnName": "stopId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrivalTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departureTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "headsign",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "pickupType",
+ "columnName": "pickupType",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dropOffType",
+ "columnName": "dropOffType",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "tripId",
+ "stopId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_StopTime_tripId",
+ "unique": true,
+ "columnNames": [
+ "tripId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)"
+ },
+ {
+ "name": "index_StopTime_stopId",
+ "unique": true,
+ "columnNames": [
+ "stopId"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Trip",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "tripId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Stop",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stopId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Trip",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeId",
+ "columnName": "routeId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceId",
+ "columnName": "serviceId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeId",
+ "columnName": "shapeId",
+ "affinity": "TEXT"
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "tripHeadsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "directionId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockId",
+ "columnName": "blockId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchairAccessible",
+ "columnName": "wheelchairAccessible",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_Trip_shapeId",
+ "unique": false,
+ "columnNames": [
+ "shapeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)"
+ },
+ {
+ "name": "index_Trip_routeId",
+ "unique": false,
+ "columnNames": [
+ "routeId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Route",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "routeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "Shape",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "shapeId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "VersionMetadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))",
+ "fields": [
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "lastUpdated",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "type"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4426fd2ccc826d9d9d9021546b105850')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/shared/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,
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 100228c..201a10f 100644
--- a/ui/build.gradle.kts
+++ b/ui/build.gradle.kts
@@ -1,25 +1,31 @@
-import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
- alias(libs.plugins.androidApplication)
+ alias(libs.plugins.androidMultiplatformLibrary)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.secretsGradle)
}
kotlin {
- androidTarget {
- @OptIn(ExperimentalKotlinGradlePluginApi::class)
+ android {
+ namespace = "moe.lava.banksia.ui"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
+
+ androidResources {
+ enable = true
+ }
}
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
+ freeCompilerArgs.add("-Xexplicit-backing-fields")
}
listOf(
@@ -35,9 +41,6 @@ kotlin {
sourceSets {
androidMain.dependencies {
- implementation(libs.compose.ui.tooling.preview)
- implementation(libs.androidx.activity.compose)
- implementation(libs.kotlinx.coroutines.android)
implementation(libs.play.services.location)
}
commonMain.dependencies {
@@ -66,47 +69,16 @@ kotlin {
implementation(projects.client)
implementation(projects.shared)
+ implementation(projects.ui.maps)
+ implementation(projects.ui.shared)
}
}
}
-android {
- namespace = "moe.lava.banksia"
- compileSdk = libs.versions.android.compileSdk.get().toInt()
-
- defaultConfig {
- applicationId = "moe.lava.banksia"
- minSdk = libs.versions.android.minSdk.get().toInt()
- targetSdk = libs.versions.android.targetSdk.get().toInt()
- versionCode = 1
- versionName = "1.0"
- }
- packaging {
- resources {
- excludes += "/META-INF/{AL2.0,LGPL2.1}"
- }
- }
- buildTypes {
- getByName("release") {
- isMinifyEnabled = false
- signingConfig = signingConfigs.getByName("debug")
- }
- }
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
-}
-
dependencies {
- debugImplementation(compose.uiTooling)
+ androidRuntimeClasspath(libs.compose.ui.tooling)
}
secrets {
propertiesFileName = "secrets.properties"
}
-
-compose.resources {
- publicResClass = true
- packageOfResClass = "moe.lava.banksia.resources"
-}
diff --git a/ui/maps/build.gradle.kts b/ui/maps/build.gradle.kts
new file mode 100644
index 0000000..324b0b3
--- /dev/null
+++ b/ui/maps/build.gradle.kts
@@ -0,0 +1,56 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.kotlinSerialization)
+ alias(libs.plugins.androidMultiplatformLibrary)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.composeCompiler)
+}
+
+kotlin {
+ android {
+ namespace = "moe.lava.banksia.ui.map"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_11)
+ }
+ }
+
+ compilerOptions {
+ freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
+ freeCompilerArgs.add("-Xexplicit-backing-fields")
+ }
+
+ iosArm64()
+ iosSimulatorArm64()
+
+ sourceSets {
+ androidMain.dependencies {
+ implementation(libs.compose.ui.tooling.preview)
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.kotlinx.coroutines.android)
+ implementation(libs.play.services.location)
+ }
+ commonMain.dependencies {
+ implementation(libs.koin.core)
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.kotlinx.datetime)
+ implementation(libs.ktor.serialization.kotlinx.json)
+
+ implementation(libs.maplibre.compose)
+ implementation(libs.moko.geo)
+ implementation(libs.moko.geo.compose)
+
+ implementation(libs.compose.components.resources)
+ implementation(libs.compose.runtime)
+ implementation(libs.compose.foundation)
+ implementation(libs.compose.material3)
+ implementation(libs.compose.ui)
+
+ implementation(projects.shared)
+ implementation(projects.ui.shared)
+ }
+ }
+}
diff --git a/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt b/ui/maps/src/commonMain/kotlin/moe/lava/banksia/ui/map/MapLibreMaps.kt
new file mode 100644
index 0000000..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()
-}