From a79c95829e3dc74d43266c1f0461e62082454d2f Mon Sep 17 00:00:00 2001 From: Cilly Leang Date: Thu, 26 Mar 2026 02:55:46 +1100 Subject: [PATCH] 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() -}