feat: vehicle positions and state dismissal
This commit is contained in:
parent
c526269e5d
commit
e52274a6ef
10 changed files with 163 additions and 42 deletions
|
|
@ -18,7 +18,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
|
|
@ -42,6 +41,7 @@ import com.google.maps.android.compose.rememberCameraPositionState
|
|||
import com.google.maps.android.compose.rememberMarkerState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import moe.lava.banksia.R
|
||||
import moe.lava.banksia.api.ptv.structures.ComposableRouteIcon
|
||||
import moe.lava.banksia.native.BanksiaTheme
|
||||
import moe.lava.banksia.util.BoxedValue
|
||||
import com.google.android.gms.maps.model.CameraPosition as GoogleCameraPosition
|
||||
|
|
@ -70,7 +70,6 @@ actual fun Maps(
|
|||
setLastKnownLocation: (Point) -> Unit,
|
||||
extInsets: WindowInsets,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val camPos = rememberCameraPositionState()
|
||||
val newCameraPos by cameraPositionFlow.collectAsStateWithLifecycle(null)
|
||||
LaunchedEffect(newCameraPos) {
|
||||
|
|
@ -120,17 +119,26 @@ actual fun Maps(
|
|||
val state = rememberMarkerState()
|
||||
state.position = marker.point.toLatLng()
|
||||
MarkerComposable(
|
||||
keys = arrayOf(marker.colour),
|
||||
keys = arrayOf(marker.data),
|
||||
zIndex = if (marker.data is Marker.Data.Vehicle) 1f else 0f,
|
||||
state = state,
|
||||
onClick = { marker.onClick() }
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(CircleShape)
|
||||
.background(BanksiaTheme.colors.surface)
|
||||
.border(2.dp, marker.colour, CircleShape)
|
||||
)
|
||||
when (marker.data) {
|
||||
is Marker.Data.Stop ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.clip(CircleShape)
|
||||
.background(BanksiaTheme.colors.surface)
|
||||
.border(2.dp, marker.data.colour, CircleShape)
|
||||
)
|
||||
is Marker.Data.Vehicle ->
|
||||
ComposableRouteIcon(
|
||||
size = 30.dp,
|
||||
routeType = marker.data.type,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (polyline in polylines) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import moe.lava.banksia.native.maps.Point
|
|||
import moe.lava.banksia.native.maps.getScreenHeight
|
||||
import moe.lava.banksia.resources.Res
|
||||
import moe.lava.banksia.resources.my_location_24
|
||||
import moe.lava.banksia.ui.BanksiaEvent
|
||||
import moe.lava.banksia.ui.BanksiaViewModel
|
||||
import moe.lava.banksia.ui.InfoPanel
|
||||
import moe.lava.banksia.ui.Searcher
|
||||
|
|
@ -139,7 +140,7 @@ fun App(
|
|||
extInsets = WindowInsets(top = with(LocalDensity.current) {
|
||||
SearchBarDefaults.InputFieldHeight.roundToPx()
|
||||
}, bottom = extInsets),
|
||||
markers = mapState.markers,
|
||||
markers = mapState.stops + mapState.vehicles,
|
||||
setLastKnownLocation = viewModel::setLastKnownLocation,
|
||||
polylines = mapState.polylines,
|
||||
)
|
||||
|
|
@ -168,6 +169,7 @@ fun App(
|
|||
scope.launch {
|
||||
scaffoldState.bottomSheetState.hide()
|
||||
peekHeightMultiplier = 1F
|
||||
viewModel.handleEvent(BanksiaEvent.DismissState)
|
||||
}
|
||||
} catch (_: CancellationException) {
|
||||
peekHeightMultiplier = 1F
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
package moe.lava.banksia.api.ptv.structures
|
||||
|
||||
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.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import moe.lava.banksia.resources.Res
|
||||
import moe.lava.banksia.resources.bus
|
||||
|
|
@ -18,6 +23,7 @@ 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
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
data class RouteTypeProperties(
|
||||
val colour: Color,
|
||||
|
|
@ -43,18 +49,37 @@ fun PtvRouteType.getProperties(): RouteTypeProperties {
|
|||
return RouteTypeProperties(colour, drawable, background, icon)
|
||||
}
|
||||
|
||||
const val ICON_PADDING = 0.25f
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ComposableRouteIcon(routeType: PtvRouteType) {
|
||||
private fun RouteIconPreview() {
|
||||
Row {
|
||||
ComposableRouteIcon(routeType = PtvRouteType.TRAIN)
|
||||
ComposableRouteIcon(routeType = PtvRouteType.TRAM)
|
||||
ComposableRouteIcon(routeType = PtvRouteType.BUS)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ComposableRouteIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 40.dp,
|
||||
routeType: PtvRouteType,
|
||||
) {
|
||||
val properties = routeType.getProperties()
|
||||
Image(
|
||||
painter = painterResource(properties.icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.size(size)
|
||||
.aspectRatio(1f)
|
||||
.padding(size * ICON_PADDING / 2)
|
||||
.drawBehind {
|
||||
drawCircle(properties.colour, radius = (this.size.minDimension + 10.dp.toPx()) / 2f)
|
||||
drawCircle(properties.colour, radius = size.toPx() / 2f)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
inline fun PtvRouteType.ComposableIcon() = ComposableRouteIcon(this)
|
||||
inline fun PtvRouteType.ComposableIcon(modifier: Modifier = Modifier) = ComposableRouteIcon(modifier, routeType = this)
|
||||
|
|
|
|||
|
|
@ -6,17 +6,19 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import moe.lava.banksia.api.ptv.structures.PtvRouteType
|
||||
import moe.lava.banksia.util.BoxedValue
|
||||
|
||||
enum class MarkerType {
|
||||
GENERIC_STOP,
|
||||
}
|
||||
data class Marker(
|
||||
val point: Point,
|
||||
val type: MarkerType,
|
||||
val colour: Color,
|
||||
val onClick: () -> Boolean
|
||||
)
|
||||
val data: Data,
|
||||
val onClick: () -> Boolean,
|
||||
) {
|
||||
sealed class Data {
|
||||
data class Stop(val colour: Color) : Data()
|
||||
data class Vehicle(val type: PtvRouteType) : Data()
|
||||
}
|
||||
}
|
||||
data class Point(val lat: Double, val lng: Double)
|
||||
data class Polyline(val points: List<Point>, val colour: Color)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import moe.lava.banksia.log
|
|||
import moe.lava.banksia.native.maps.CameraPosition
|
||||
import moe.lava.banksia.native.maps.CameraPositionBounds
|
||||
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.Polyline
|
||||
import moe.lava.banksia.ui.state.InfoPanelState
|
||||
|
|
@ -32,6 +31,8 @@ import moe.lava.banksia.util.BoxedValue
|
|||
import moe.lava.banksia.util.BoxedValue.Companion.box
|
||||
|
||||
sealed class BanksiaEvent {
|
||||
data object DismissState : BanksiaEvent()
|
||||
|
||||
data class SelectRoute(val id: Int?) : BanksiaEvent()
|
||||
data class SelectStop(val routeType: PtvRouteType, val stopId: Int?) : BanksiaEvent()
|
||||
|
||||
|
|
@ -61,6 +62,7 @@ class BanksiaViewModel : ViewModel() {
|
|||
fun handleEvent(event: BanksiaEvent) {
|
||||
viewModelScope.launch {
|
||||
when (event) {
|
||||
is BanksiaEvent.DismissState -> dismissState()
|
||||
is BanksiaEvent.SelectRoute -> switchRoute(event.id)
|
||||
is BanksiaEvent.SelectStop -> switchStop(event.routeType, event.stopId)
|
||||
is BanksiaEvent.SearchUpdate -> searchUpdate(event.text)
|
||||
|
|
@ -87,6 +89,13 @@ class BanksiaViewModel : ViewModel() {
|
|||
lastKnownLocation = location
|
||||
}
|
||||
|
||||
private fun dismissState() {
|
||||
viewModelScope.launch {
|
||||
switchRoute(null)
|
||||
searchUpdate("")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun searchUpdate(text: String) {
|
||||
val entries = ptvService.routes()
|
||||
.sortedWith(
|
||||
|
|
@ -126,6 +135,7 @@ class BanksiaViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
viewModelScope.launch { buildPolylines(route) }
|
||||
viewModelScope.launch { buildRuns(route) }
|
||||
viewModelScope.launch { buildStops(route) }
|
||||
// viewModelScope.launch { buildDepartures() }
|
||||
// viewModelScope.launch { buildRuns() }
|
||||
|
|
@ -219,6 +229,22 @@ class BanksiaViewModel : ViewModel() {
|
|||
newCameraPosition?.let { iCameraChangeEmitter.emit(it.box()) }
|
||||
}
|
||||
|
||||
private suspend fun buildRuns(route: PtvRoute) {
|
||||
val runs = ptvService.runs(route.routeId)
|
||||
|
||||
val markers = runs.mapNotNull { it.vehiclePosition }
|
||||
.distinctBy { it.latitude to it.longitude }
|
||||
.map {
|
||||
Marker(
|
||||
Point(it.latitude, it.longitude),
|
||||
onClick = { false },
|
||||
data = Marker.Data.Vehicle(route.routeType)
|
||||
)
|
||||
}
|
||||
|
||||
iMapState.update { it.copy(vehicles = markers) }
|
||||
}
|
||||
|
||||
// private suspend fun buildDepartures(route: PtvRoute) {
|
||||
// val directions = ptvService.directionsByRoute(route.routeId)
|
||||
//
|
||||
|
|
@ -239,27 +265,22 @@ class BanksiaViewModel : ViewModel() {
|
|||
|
||||
private suspend fun buildStops(route: PtvRoute) {
|
||||
val stops = ptvService.stopsByRoute(route.routeId, route.routeType)
|
||||
val markers = 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!!)
|
||||
|
||||
val marker = Marker(
|
||||
point = pos,
|
||||
type = MarkerType.GENERIC_STOP,
|
||||
colour = colour,
|
||||
val markers = stops
|
||||
.filter { it.stopLatitude != null && it.stopLongitude != null }
|
||||
.map { stop ->
|
||||
Marker(
|
||||
point = Point(stop.stopLatitude!!, stop.stopLongitude!!),
|
||||
data = Marker.Data.Stop(colour),
|
||||
onClick = {
|
||||
viewModelScope.launch { switchStop(route.routeType, stop.stopId) }
|
||||
false
|
||||
}
|
||||
)
|
||||
markers.add(marker)
|
||||
}
|
||||
}
|
||||
|
||||
iMapState.update { it.copy(markers = markers) }
|
||||
iMapState.update { it.copy(stops = markers) }
|
||||
}
|
||||
|
||||
private fun buildBounds(points: List<Point>): CameraPositionBounds {
|
||||
|
|
|
|||
|
|
@ -70,13 +70,13 @@ fun InfoPanel(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun RouteInfoPanel(
|
||||
private inline fun RouteInfoPanel(
|
||||
state: InfoPanelState.Route,
|
||||
onEvent: (BanksiaEvent) -> Unit,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row {
|
||||
ComposableRouteIcon(state.type)
|
||||
ComposableRouteIcon(routeType = state.type)
|
||||
Text(
|
||||
state.name,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
|
|
@ -88,7 +88,7 @@ private fun RouteInfoPanel(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun StopInfoPanel(
|
||||
private inline fun StopInfoPanel(
|
||||
state: InfoPanelState.Stop,
|
||||
onEvent: (BanksiaEvent) -> Unit,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ fun Searcher(
|
|||
ListItem(
|
||||
headlineContent = { Text(entry.mainText) },
|
||||
supportingContent = { entry.subText?.let { Text(it) } },
|
||||
leadingContent = { ComposableRouteIcon(entry.routeType) },
|
||||
leadingContent = { ComposableRouteIcon(routeType = entry.routeType) },
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import moe.lava.banksia.native.maps.Marker
|
|||
import moe.lava.banksia.native.maps.Polyline
|
||||
|
||||
data class MapState(
|
||||
val markers: List<Marker> = listOf(),
|
||||
val stops: List<Marker> = listOf(),
|
||||
val vehicles: List<Marker> = listOf(),
|
||||
val polylines: List<Polyline> = listOf(),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue