feat: preliminary run info panel, and heavy refactoring
This commit is contained in:
parent
e52274a6ef
commit
ce8425d6a7
9 changed files with 226 additions and 97 deletions
|
|
@ -43,6 +43,8 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import moe.lava.banksia.R
|
import moe.lava.banksia.R
|
||||||
import moe.lava.banksia.api.ptv.structures.ComposableRouteIcon
|
import moe.lava.banksia.api.ptv.structures.ComposableRouteIcon
|
||||||
import moe.lava.banksia.native.BanksiaTheme
|
import moe.lava.banksia.native.BanksiaTheme
|
||||||
|
import moe.lava.banksia.ui.BanksiaEvent
|
||||||
|
import moe.lava.banksia.ui.state.MapState
|
||||||
import moe.lava.banksia.util.BoxedValue
|
import moe.lava.banksia.util.BoxedValue
|
||||||
import com.google.android.gms.maps.model.CameraPosition as GoogleCameraPosition
|
import com.google.android.gms.maps.model.CameraPosition as GoogleCameraPosition
|
||||||
|
|
||||||
|
|
@ -64,8 +66,8 @@ actual fun getScreenHeight(): Int {
|
||||||
@Composable
|
@Composable
|
||||||
actual fun Maps(
|
actual fun Maps(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
markers: List<Marker>,
|
state: MapState,
|
||||||
polylines: List<Polyline>,
|
onEvent: (BanksiaEvent) -> Unit,
|
||||||
cameraPositionFlow: Flow<BoxedValue<CameraPosition>>,
|
cameraPositionFlow: Flow<BoxedValue<CameraPosition>>,
|
||||||
setLastKnownLocation: (Point) -> Unit,
|
setLastKnownLocation: (Point) -> Unit,
|
||||||
extInsets: WindowInsets,
|
extInsets: WindowInsets,
|
||||||
|
|
@ -115,33 +117,46 @@ 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
|
// [TODO]: Slight lag when routes with many stops such as the 901 bus is set
|
||||||
for (marker in markers) {
|
for (marker in state.stops) {
|
||||||
val state = rememberMarkerState()
|
val state = rememberMarkerState()
|
||||||
state.position = marker.point.toLatLng()
|
state.position = marker.point.toLatLng()
|
||||||
MarkerComposable(
|
MarkerComposable(
|
||||||
keys = arrayOf(marker.data),
|
keys = arrayOf(marker),
|
||||||
zIndex = if (marker.data is Marker.Data.Vehicle) 1f else 0f,
|
zIndex = 0f,
|
||||||
state = state,
|
state = state,
|
||||||
onClick = { marker.onClick() }
|
onClick = {
|
||||||
|
onEvent(BanksiaEvent.SelectStop(marker.type to marker.id))
|
||||||
|
false
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
when (marker.data) {
|
|
||||||
is Marker.Data.Stop ->
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(12.dp)
|
.size(12.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(BanksiaTheme.colors.surface)
|
.background(BanksiaTheme.colors.surface)
|
||||||
.border(2.dp, marker.data.colour, CircleShape)
|
.border(2.dp, marker.colour, CircleShape)
|
||||||
)
|
)
|
||||||
is Marker.Data.Vehicle ->
|
}
|
||||||
|
}
|
||||||
|
for (marker in state.vehicles) {
|
||||||
|
val state = rememberMarkerState()
|
||||||
|
state.position = marker.point.toLatLng()
|
||||||
|
MarkerComposable(
|
||||||
|
keys = arrayOf(marker),
|
||||||
|
zIndex = 1f,
|
||||||
|
state = state,
|
||||||
|
onClick = {
|
||||||
|
onEvent(BanksiaEvent.SelectRun(marker.ref))
|
||||||
|
false
|
||||||
|
}
|
||||||
|
) {
|
||||||
ComposableRouteIcon(
|
ComposableRouteIcon(
|
||||||
size = 30.dp,
|
size = 30.dp,
|
||||||
routeType = marker.data.type,
|
routeType = marker.type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
for (polyline in state.polylines) {
|
||||||
for (polyline in polylines) {
|
|
||||||
Polyline(
|
Polyline(
|
||||||
points = polyline.points.map { it.toLatLng() },
|
points = polyline.points.map { it.toLatLng() },
|
||||||
color = polyline.colour
|
color = polyline.colour
|
||||||
|
|
|
||||||
|
|
@ -136,13 +136,13 @@ fun App(
|
||||||
) {
|
) {
|
||||||
Maps(
|
Maps(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
state = mapState,
|
||||||
|
onEvent = viewModel::handleEvent,
|
||||||
cameraPositionFlow = viewModel.cameraChangeEmitter,
|
cameraPositionFlow = viewModel.cameraChangeEmitter,
|
||||||
extInsets = WindowInsets(top = with(LocalDensity.current) {
|
extInsets = WindowInsets(top = with(LocalDensity.current) {
|
||||||
SearchBarDefaults.InputFieldHeight.roundToPx()
|
SearchBarDefaults.InputFieldHeight.roundToPx()
|
||||||
}, bottom = extInsets),
|
}, bottom = extInsets),
|
||||||
markers = mapState.stops + mapState.vehicles,
|
|
||||||
setLastKnownLocation = viewModel::setLastKnownLocation,
|
setLastKnownLocation = viewModel::setLastKnownLocation,
|
||||||
polylines = mapState.polylines,
|
|
||||||
)
|
)
|
||||||
Searcher(
|
Searcher(
|
||||||
state = searchState,
|
state = searchState,
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,25 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import moe.lava.banksia.api.ptv.structures.PtvRouteType
|
import moe.lava.banksia.api.ptv.structures.PtvRouteType
|
||||||
|
import moe.lava.banksia.ui.BanksiaEvent
|
||||||
|
import moe.lava.banksia.ui.state.MapState
|
||||||
import moe.lava.banksia.util.BoxedValue
|
import moe.lava.banksia.util.BoxedValue
|
||||||
|
|
||||||
data class Marker(
|
sealed class Marker {
|
||||||
val point: Point,
|
abstract val point: Point
|
||||||
val data: Data,
|
|
||||||
val onClick: () -> Boolean,
|
data class Stop(
|
||||||
) {
|
override val point: Point,
|
||||||
sealed class Data {
|
val id: Int,
|
||||||
data class Stop(val colour: Color) : Data()
|
val type: PtvRouteType,
|
||||||
data class Vehicle(val type: PtvRouteType) : Data()
|
val colour: Color,
|
||||||
}
|
) : Marker()
|
||||||
|
|
||||||
|
data class Vehicle(
|
||||||
|
override val point: Point,
|
||||||
|
val ref: String,
|
||||||
|
val type: PtvRouteType,
|
||||||
|
) : Marker()
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
@ -35,8 +43,8 @@ expect fun getScreenHeight(): Int
|
||||||
@Composable
|
@Composable
|
||||||
expect fun Maps(
|
expect fun Maps(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
markers: List<Marker> = listOf(),
|
state: MapState,
|
||||||
polylines: List<Polyline> = listOf(),
|
onEvent: (BanksiaEvent) -> Unit,
|
||||||
cameraPositionFlow: Flow<BoxedValue<CameraPosition>>,
|
cameraPositionFlow: Flow<BoxedValue<CameraPosition>>,
|
||||||
setLastKnownLocation: (Point) -> Unit,
|
setLastKnownLocation: (Point) -> Unit,
|
||||||
extInsets: WindowInsets,
|
extInsets: WindowInsets,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.flow.updateAndGet
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
|
|
@ -34,12 +36,31 @@ sealed class BanksiaEvent {
|
||||||
data object DismissState : BanksiaEvent()
|
data object DismissState : BanksiaEvent()
|
||||||
|
|
||||||
data class SelectRoute(val id: Int?) : BanksiaEvent()
|
data class SelectRoute(val id: Int?) : BanksiaEvent()
|
||||||
data class SelectStop(val routeType: PtvRouteType, val stopId: Int?) : BanksiaEvent()
|
data class SelectRun(val ref: String?) : BanksiaEvent()
|
||||||
|
data class SelectStop(val typeAndId: Pair<PtvRouteType, Int>) : BanksiaEvent()
|
||||||
|
|
||||||
data class SearchUpdate(val text: String) : BanksiaEvent()
|
data class SearchUpdate(val text: String) : BanksiaEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class InternalState(
|
||||||
|
val route: Int? = null,
|
||||||
|
val stop: Pair<PtvRouteType, Int>? = null,
|
||||||
|
val run: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
class BanksiaViewModel : ViewModel() {
|
class BanksiaViewModel : ViewModel() {
|
||||||
|
private var state = InternalState()
|
||||||
|
set(value) {
|
||||||
|
val last = field
|
||||||
|
field = value
|
||||||
|
if (value.route != last.route)
|
||||||
|
viewModelScope.launch { switchRoute(value.route) }
|
||||||
|
if (value.stop != last.stop)
|
||||||
|
viewModelScope.launch { switchStop(value.stop) }
|
||||||
|
if (value.run != last.run)
|
||||||
|
switchRun(value.run)
|
||||||
|
}
|
||||||
|
|
||||||
private val iInfoState = MutableStateFlow<InfoPanelState>(InfoPanelState.None)
|
private val iInfoState = MutableStateFlow<InfoPanelState>(InfoPanelState.None)
|
||||||
val infoState = iInfoState.asStateFlow()
|
val infoState = iInfoState.asStateFlow()
|
||||||
|
|
||||||
|
|
@ -64,7 +85,8 @@ class BanksiaViewModel : ViewModel() {
|
||||||
when (event) {
|
when (event) {
|
||||||
is BanksiaEvent.DismissState -> dismissState()
|
is BanksiaEvent.DismissState -> dismissState()
|
||||||
is BanksiaEvent.SelectRoute -> switchRoute(event.id)
|
is BanksiaEvent.SelectRoute -> switchRoute(event.id)
|
||||||
is BanksiaEvent.SelectStop -> switchStop(event.routeType, event.stopId)
|
is BanksiaEvent.SelectRun -> switchRun(event.ref)
|
||||||
|
is BanksiaEvent.SelectStop -> switchStop(event.typeAndId)
|
||||||
is BanksiaEvent.SearchUpdate -> searchUpdate(event.text)
|
is BanksiaEvent.SearchUpdate -> searchUpdate(event.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,18 +157,49 @@ class BanksiaViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch { buildPolylines(route) }
|
viewModelScope.launch { buildPolylines(route) }
|
||||||
viewModelScope.launch { buildRuns(route) }
|
|
||||||
viewModelScope.launch { buildStops(route) }
|
viewModelScope.launch { buildStops(route) }
|
||||||
// viewModelScope.launch { buildDepartures() }
|
buildRuns(route)
|
||||||
// viewModelScope.launch { buildRuns() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// [TODO]: Cleanup
|
private fun switchRun(ref: String?) {
|
||||||
private suspend fun switchStop(routeType: PtvRouteType, stopId: Int?) {
|
if (ref == null) {
|
||||||
if (stopId == null) {
|
|
||||||
iInfoState.update { InfoPanelState.None }
|
iInfoState.update { InfoPanelState.None }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lastState = iInfoState.value
|
||||||
|
var routeName: String? = null
|
||||||
|
ptvService.runFlow(ref, firstWithCache = true)
|
||||||
|
.takeWhile { lastState == iInfoState.value }
|
||||||
|
.onEach { run ->
|
||||||
|
if (routeName == null) {
|
||||||
|
lastState = iInfoState.updateAndGet {
|
||||||
|
InfoPanelState.Run(
|
||||||
|
direction = run.destinationName,
|
||||||
|
type = run.routeType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
routeName = ptvService.route(run.routeId).routeName
|
||||||
|
}
|
||||||
|
|
||||||
|
lastState = iInfoState.updateAndGet {
|
||||||
|
InfoPanelState.Run(
|
||||||
|
direction = run.destinationName,
|
||||||
|
type = run.routeType,
|
||||||
|
routeName = routeName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [TODO]: Cleanup
|
||||||
|
private suspend fun switchStop(typeAndId: Pair<PtvRouteType, Int>?) {
|
||||||
|
if (typeAndId == null) {
|
||||||
|
iInfoState.update { InfoPanelState.None }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val (routeType, stopId) = typeAndId
|
||||||
val stop = ptvService.stop(routeType, stopId)
|
val stop = ptvService.stop(routeType, stopId)
|
||||||
val split = stop.stopName.split("/")
|
val split = stop.stopName.split("/")
|
||||||
val name = split[0]
|
val name = split[0]
|
||||||
|
|
@ -229,39 +282,30 @@ class BanksiaViewModel : ViewModel() {
|
||||||
newCameraPosition?.let { iCameraChangeEmitter.emit(it.box()) }
|
newCameraPosition?.let { iCameraChangeEmitter.emit(it.box()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildRuns(route: PtvRoute) {
|
var runsRouteKey: Int? = null
|
||||||
val runs = ptvService.runs(route.routeId)
|
private fun buildRuns(route: PtvRoute) {
|
||||||
|
runsRouteKey = route.routeId
|
||||||
val markers = runs.mapNotNull { it.vehiclePosition }
|
ptvService
|
||||||
.distinctBy { it.latitude to it.longitude }
|
.runsFlow(route.routeId)
|
||||||
.map {
|
.takeWhile { route.routeId == runsRouteKey }
|
||||||
Marker(
|
.onEach { runs ->
|
||||||
Point(it.latitude, it.longitude),
|
val markers = runs
|
||||||
onClick = { false },
|
.filter { it.vehiclePosition != null }
|
||||||
data = Marker.Data.Vehicle(route.routeType)
|
.map { it to it.vehiclePosition!! }
|
||||||
|
.distinctBy { (_, pos) -> pos.latitude to pos.longitude }
|
||||||
|
.map { (run, pos) ->
|
||||||
|
Marker.Vehicle(
|
||||||
|
Point(pos.latitude, pos.longitude),
|
||||||
|
ref = run.runRef,
|
||||||
|
type = route.routeType,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
iMapState.update { it.copy(vehicles = markers) }
|
iMapState.update { it.copy(vehicles = markers) }
|
||||||
}
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
// private suspend fun buildDepartures(route: PtvRoute) {
|
}
|
||||||
// val directions = ptvService.directionsByRoute(route.routeId)
|
|
||||||
//
|
|
||||||
// iState.update {
|
|
||||||
// it.copy(routeState = it.routeState?.copy(directions = directions))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private suspend fun buildRuns() {
|
|
||||||
// val route = iState.value.routeState?.route ?: return
|
|
||||||
//
|
|
||||||
// val directions = ptvService.directionsByRoute(route.routeId)
|
|
||||||
//
|
|
||||||
// iState.update {
|
|
||||||
// it.copy(routeState = it.routeState?.copy(directions = directions))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
private suspend fun buildStops(route: PtvRoute) {
|
private suspend fun buildStops(route: PtvRoute) {
|
||||||
val stops = ptvService.stopsByRoute(route.routeId, route.routeType)
|
val stops = ptvService.stopsByRoute(route.routeId, route.routeType)
|
||||||
|
|
@ -270,13 +314,11 @@ class BanksiaViewModel : ViewModel() {
|
||||||
val markers = stops
|
val markers = stops
|
||||||
.filter { it.stopLatitude != null && it.stopLongitude != null }
|
.filter { it.stopLatitude != null && it.stopLongitude != null }
|
||||||
.map { stop ->
|
.map { stop ->
|
||||||
Marker(
|
Marker.Stop(
|
||||||
point = Point(stop.stopLatitude!!, stop.stopLongitude!!),
|
point = Point(stop.stopLatitude!!, stop.stopLongitude!!),
|
||||||
data = Marker.Data.Stop(colour),
|
id = stop.stopId,
|
||||||
onClick = {
|
colour = colour,
|
||||||
viewModelScope.launch { switchStop(route.routeType, stop.stopId) }
|
type = route.routeType,
|
||||||
false
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ fun InfoPanel(
|
||||||
when (state) {
|
when (state) {
|
||||||
is InfoPanelState.Route -> RouteInfoPanel(state, onEvent)
|
is InfoPanelState.Route -> RouteInfoPanel(state, onEvent)
|
||||||
is InfoPanelState.Stop -> StopInfoPanel(state, onEvent)
|
is InfoPanelState.Stop -> StopInfoPanel(state, onEvent)
|
||||||
else -> throw UnsupportedOperationException()
|
is InfoPanelState.Run -> RunInfoPanel(state, onEvent)
|
||||||
|
is InfoPanelState.None -> throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.loading)
|
if (state.loading)
|
||||||
|
|
@ -87,6 +88,24 @@ private inline fun RouteInfoPanel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private inline fun RunInfoPanel(
|
||||||
|
state: InfoPanelState.Run,
|
||||||
|
onEvent: (BanksiaEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
Row {
|
||||||
|
ComposableRouteIcon(routeType = state.type)
|
||||||
|
Text(
|
||||||
|
"${state.direction} via ${state.routeName ?: "..."}",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private inline fun StopInfoPanel(
|
private inline fun StopInfoPanel(
|
||||||
state: InfoPanelState.Stop,
|
state: InfoPanelState.Stop,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,14 @@ sealed class InfoPanelState {
|
||||||
override val loading = false
|
override val loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Run(
|
||||||
|
val direction: String,
|
||||||
|
val type: PtvRouteType,
|
||||||
|
val routeName: String? = null,
|
||||||
|
) : InfoPanelState() {
|
||||||
|
override val loading = routeName == null
|
||||||
|
}
|
||||||
|
|
||||||
data class Stop(
|
data class Stop(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import moe.lava.banksia.native.maps.Marker
|
||||||
import moe.lava.banksia.native.maps.Polyline
|
import moe.lava.banksia.native.maps.Polyline
|
||||||
|
|
||||||
data class MapState(
|
data class MapState(
|
||||||
val stops: List<Marker> = listOf(),
|
val stops: List<Marker.Stop> = listOf(),
|
||||||
val vehicles: List<Marker> = listOf(),
|
val vehicles: List<Marker.Vehicle> = listOf(),
|
||||||
val polylines: List<Polyline> = listOf(),
|
val polylines: List<Polyline> = listOf(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalWindowInfo
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import moe.lava.banksia.ui.BanksiaEvent
|
||||||
|
import moe.lava.banksia.ui.state.MapState
|
||||||
import moe.lava.banksia.util.BoxedValue
|
import moe.lava.banksia.util.BoxedValue
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
|
@ -19,8 +21,8 @@ actual fun getScreenHeight(): Int {
|
||||||
@Composable
|
@Composable
|
||||||
actual fun Maps(
|
actual fun Maps(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
markers: List<Marker>,
|
state: MapState,
|
||||||
polylines: List<Polyline>,
|
onEvent: (BanksiaEvent) -> Unit,
|
||||||
cameraPositionFlow: Flow<BoxedValue<CameraPosition>>,
|
cameraPositionFlow: Flow<BoxedValue<CameraPosition>>,
|
||||||
setLastKnownLocation: (Point) -> Unit,
|
setLastKnownLocation: (Point) -> Unit,
|
||||||
extInsets: WindowInsets,
|
extInsets: WindowInsets,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ import io.ktor.client.request.get
|
||||||
import io.ktor.client.request.parameter
|
import io.ktor.client.request.parameter
|
||||||
import io.ktor.http.appendPathSegments
|
import io.ktor.http.appendPathSegments
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import moe.lava.banksia.Constants
|
import moe.lava.banksia.Constants
|
||||||
|
|
@ -44,12 +47,12 @@ object Responses {
|
||||||
data class PtvDirectionsResponse(val directions: List<PtvDirection>)
|
data class PtvDirectionsResponse(val directions: List<PtvDirection>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PtvService {
|
class PtvService {
|
||||||
class PtvCache(
|
class PtvCache(
|
||||||
private val service: PtvService,
|
private val service: PtvService,
|
||||||
private val directions: HashMap<Pair<Int, Int>, PtvDirection> = HashMap(),
|
private val directions: HashMap<Pair<Int, Int>, PtvDirection> = HashMap(),
|
||||||
private val routes: HashMap<Int, PtvRoute> = HashMap(),
|
private val routes: HashMap<Int, PtvRoute> = HashMap(),
|
||||||
|
private val runs: HashMap<String, PtvRun> = HashMap(),
|
||||||
private val stops: HashMap<Int, PtvStop> = HashMap(),
|
private val stops: HashMap<Int, PtvStop> = HashMap(),
|
||||||
) {
|
) {
|
||||||
suspend fun direction(directionID: Int, routeID: Int): PtvDirection? {
|
suspend fun direction(directionID: Int, routeID: Int): PtvDirection? {
|
||||||
|
|
@ -79,6 +82,14 @@ class PtvService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStop(stopId: Int) = stops[stopId]
|
fun getStop(stopId: Int) = stops[stopId]
|
||||||
|
|
||||||
|
fun addRuns(runs: Iterable<PtvRun>) {
|
||||||
|
runs.forEach {
|
||||||
|
this.runs[it.runRef] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRun(runRef: String) = runs[runRef]
|
||||||
}
|
}
|
||||||
|
|
||||||
val cache = PtvCache(this)
|
val cache = PtvCache(this)
|
||||||
|
|
@ -133,8 +144,29 @@ class PtvService {
|
||||||
return response.routes
|
return response.routes
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun runs(routeId: Int): List<PtvRun> {
|
fun runFlow(ref: String, firstWithCache: Boolean = false, intervalMillis: Long = 5000): Flow<PtvRun> = flow {
|
||||||
val response: Responses.PtvRunsResponse = client.get() {
|
val cached = cache.getRun(ref)
|
||||||
|
if (firstWithCache && cached != null)
|
||||||
|
emit(cached)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val response: Responses.PtvRunsResponse = client.get {
|
||||||
|
url {
|
||||||
|
appendPathSegments(
|
||||||
|
"runs",
|
||||||
|
ref,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.body()
|
||||||
|
cache.addRuns(response.runs)
|
||||||
|
emit(response.runs[0])
|
||||||
|
delay(intervalMillis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runsFlow(routeId: Int, intervalMillis: Long = 5000): Flow<List<PtvRun>> = flow {
|
||||||
|
while (true) {
|
||||||
|
val response: Responses.PtvRunsResponse = client.get {
|
||||||
url {
|
url {
|
||||||
appendPathSegments(
|
appendPathSegments(
|
||||||
"runs",
|
"runs",
|
||||||
|
|
@ -144,7 +176,10 @@ class PtvService {
|
||||||
parameter("expand", "VehiclePosition")
|
parameter("expand", "VehiclePosition")
|
||||||
}
|
}
|
||||||
}.body()
|
}.body()
|
||||||
return response.runs
|
cache.addRuns(response.runs)
|
||||||
|
emit(response.runs)
|
||||||
|
delay(intervalMillis)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun stopsByRoute(routeId: Int, routeType: PtvRouteType): List<PtvStop> {
|
suspend fun stopsByRoute(routeId: Int, routeType: PtvRouteType): List<PtvStop> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue