refactor(ui): split into shared, maps, and main modules
This commit is contained in:
parent
aab03ced07
commit
a79c95829e
43 changed files with 539 additions and 378 deletions
56
ui/maps/build.gradle.kts
Normal file
56
ui/maps/build.gradle.kts
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Geometry, JsonObject?>) -> 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<T>(feature.properties!!)
|
||||
onStopClicked(features[0])
|
||||
ClickResult.Consume
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Marker.Stop> = listOf(),
|
||||
// vehicles: List<Marker.Vehicle> = 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 = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -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<Point>
|
||||
field = MutableSharedFlow()
|
||||
|
||||
fun update(position: Point) {
|
||||
scope.launch { updates.emit(position) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberMapsPositionState(): MapsPositionState {
|
||||
val scope = rememberCoroutineScope()
|
||||
return remember { MapsPositionState(scope) }
|
||||
}
|
||||
|
|
@ -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<Marker>.asFeatures() = GeoJsonData.Features(asFeatureCollection())
|
||||
|
||||
internal fun Iterable<Marker>.asFeatureCollection(): FeatureCollection<MLPoint, MarkerProps> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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),
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package moe.lava.banksia.ui.map.util
|
||||
|
||||
import moe.lava.banksia.util.Point
|
||||
|
||||
data class CameraPosition(
|
||||
val centre: Point = Point(-37.8136, 144.9631),
|
||||
val bounds: CameraPositionBounds? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package moe.lava.banksia.ui.map.util
|
||||
|
||||
import moe.lava.banksia.util.Point
|
||||
|
||||
data class CameraPositionBounds(val northeast: Point, val southwest: Point)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package moe.lava.banksia.ui.map.util
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import moe.lava.banksia.util.Point
|
||||
|
||||
data class Polyline(val points: List<Point>, val colour: Color)
|
||||
Loading…
Add table
Add a link
Reference in a new issue