feat: basic stop markers on route

This commit is contained in:
LavaDesu 2025-04-29 22:58:26 +10:00
parent 6a5a9b4974
commit 67f18afc01
Signed by: cilly
GPG key ID: 6500251E087653C9
5 changed files with 108 additions and 6 deletions

View file

@ -2,17 +2,23 @@ package moe.lava.banksia.native.maps
import android.Manifest import android.Manifest
import android.content.pm.PackageManager import android.content.pm.PackageManager
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.add
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
@ -28,10 +34,12 @@ import com.google.maps.android.compose.ComposeMapColorScheme
import com.google.maps.android.compose.DefaultMapProperties import com.google.maps.android.compose.DefaultMapProperties
import com.google.maps.android.compose.DefaultMapUiSettings import com.google.maps.android.compose.DefaultMapUiSettings
import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MarkerComposable
import com.google.maps.android.compose.Polyline import com.google.maps.android.compose.Polyline
import com.google.maps.android.compose.rememberCameraPositionState import com.google.maps.android.compose.rememberCameraPositionState
import com.google.maps.android.compose.rememberMarkerState
import moe.lava.banksia.R import moe.lava.banksia.R
import moe.lava.banksia.native.BanksiaTheme
fun Point.toLatLng(): LatLng = LatLng(this.lat, this.lng) fun Point.toLatLng(): LatLng = LatLng(this.lat, this.lng)
@ -97,12 +105,25 @@ actual fun Maps(
), ),
contentPadding = WindowInsets.safeDrawing.add(extInsets).asPaddingValues() contentPadding = WindowInsets.safeDrawing.add(extInsets).asPaddingValues()
) { ) {
// [TODO]: Slight lag when routes with many stops such as the 901 bus is set
for (marker in markers) { for (marker in markers) {
Marker( val state = rememberMarkerState()
name = marker.name, state.position = marker.point.toLatLng()
onClick = marker.onClick MarkerComposable(
keys = arrayOf(marker.colour),
state = state,
title = marker.name,
onClick = { marker.onClick() }
) {
Box(
modifier = Modifier
.size(12.dp)
.clip(CircleShape)
.background(BanksiaTheme.colors.surface)
.border(2.dp, marker.colour, CircleShape)
) )
} }
}
for (polyline in polylines) { for (polyline in polylines) {
Polyline( Polyline(
points = polyline.points.map { it.toLatLng() }, points = polyline.points.map { it.toLatLng() },

View file

@ -37,9 +37,12 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import moe.lava.banksia.api.ptv.PtvService import moe.lava.banksia.api.ptv.PtvService
import moe.lava.banksia.api.ptv.structures.PtvRoute import moe.lava.banksia.api.ptv.structures.PtvRoute
import moe.lava.banksia.api.ptv.structures.PtvStop
import moe.lava.banksia.api.ptv.structures.getProperties import moe.lava.banksia.api.ptv.structures.getProperties
import moe.lava.banksia.native.BanksiaTheme import moe.lava.banksia.native.BanksiaTheme
import moe.lava.banksia.native.maps.Maps import moe.lava.banksia.native.maps.Maps
import moe.lava.banksia.native.maps.Marker
import moe.lava.banksia.native.maps.MarkerType
import moe.lava.banksia.native.maps.Point import moe.lava.banksia.native.maps.Point
import moe.lava.banksia.native.maps.Polyline import moe.lava.banksia.native.maps.Polyline
import moe.lava.banksia.native.maps.getScreenHeight import moe.lava.banksia.native.maps.getScreenHeight
@ -147,6 +150,9 @@ fun App() {
var peekHeight by remember { mutableStateOf(128.dp) } var peekHeight by remember { mutableStateOf(128.dp) }
var peekHeightMultiplier by remember { mutableFloatStateOf(1F) } var peekHeightMultiplier by remember { mutableFloatStateOf(1F) }
var markers by remember { mutableStateOf(listOf<Marker>()) }
LaunchedEffect(route) { route?.let { markers = buildStops(ptvService, it) {} } }
BanksiaTheme { BanksiaTheme {
BottomSheetScaffold( BottomSheetScaffold(
scaffoldState = scaffoldState, scaffoldState = scaffoldState,
@ -160,6 +166,7 @@ fun App() {
newCameraPosition = newCameraPosition, newCameraPosition = newCameraPosition,
cameraPositionUpdated = { newCameraPosition = null }, cameraPositionUpdated = { newCameraPosition = null },
extInsets = WindowInsets(top = with(LocalDensity.current) { 56.dp.roundToPx() }, bottom = extInsets), extInsets = WindowInsets(top = with(LocalDensity.current) { 56.dp.roundToPx() }, bottom = extInsets),
markers = markers,
polylines = polylines, polylines = polylines,
) )
Searcher( Searcher(
@ -208,3 +215,37 @@ fun App() {
} }
} }
} }
suspend fun buildStops(
ptvService: PtvService,
route: PtvRoute,
launchInfoPanel: (PtvStop) -> Unit,
): List<Marker> {
var stops = ptvService.stopsByRoute(route.routeId, route.routeType)
var res = mutableListOf<Marker>()
val colour = route.routeType.getProperties().colour
for (stop in stops) {
if (stop.stopLatitude != null && stop.stopLongitude != null) {
val pos = Point(stop.stopLatitude!!, stop.stopLongitude!!)
var name = stop.stopName;
if (name.endsWith(" Station"))
name = name.replace(" Station", "")
val marker = Marker(
name = name,
point = pos,
type = MarkerType.GENERIC_STOP,
colour = colour,
onClick = {
launchInfoPanel(stop)
false
}
)
res.add(marker)
}
}
return res
}

View file

@ -6,7 +6,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
data class Marker(val name: String, val onClick: () -> Boolean) enum class MarkerType {
GENERIC_STOP,
}
data class Marker(
val point: Point,
val name: String,
val type: MarkerType,
val colour: Color,
val onClick: () -> Boolean
)
data class Point(val lat: Double, val lng: Double) data class Point(val lat: Double, val lng: Double)
data class Polyline(val points: List<Point>, val colour: Color) data class Polyline(val points: List<Point>, val colour: Color)

View file

@ -14,6 +14,8 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import moe.lava.banksia.Constants import moe.lava.banksia.Constants
import moe.lava.banksia.api.ptv.structures.PtvRoute import moe.lava.banksia.api.ptv.structures.PtvRoute
import moe.lava.banksia.api.ptv.structures.PtvRouteType
import moe.lava.banksia.api.ptv.structures.PtvStop
import moe.lava.banksia.log import moe.lava.banksia.log
import okio.ByteString.Companion.encodeUtf8 import okio.ByteString.Companion.encodeUtf8
@ -22,6 +24,9 @@ object Responses {
data class PtvRouteResponse(val route: PtvRoute) data class PtvRouteResponse(val route: PtvRoute)
@Serializable @Serializable
data class PtvRoutesResponse(val routes: List<PtvRoute>) data class PtvRoutesResponse(val routes: List<PtvRoute>)
@Serializable
data class PtvStopsResponse(val stops: List<PtvStop>)
} }
class PtvService { class PtvService {
@ -61,4 +66,17 @@ class PtvService {
val response: Responses.PtvRoutesResponse = client.get("routes").body() val response: Responses.PtvRoutesResponse = client.get("routes").body()
return response.routes return response.routes
} }
suspend fun stopsByRoute(routeId: Int, routeType: PtvRouteType): List<PtvStop> {
val response: Responses.PtvStopsResponse = client.get("stops/route") {
url {
appendPathSegments(
routeId.toString(),
"route_type",
routeType.ordinal.toString()
)
}
}.body()
return response.stops
}
} }

View file

@ -0,0 +1,13 @@
package moe.lava.banksia.api.ptv.structures
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class PtvStop(
@SerialName("stop_id") val stopId: Int,
@SerialName("stop_name") val stopName: String,
@SerialName("stop_latitude") val stopLatitude: Double?,
@SerialName("stop_longitude") val stopLongitude: Double?,
@SerialName("route_type") val routeType: PtvRouteType,
)