refactor: split out searchstate
This commit is contained in:
parent
c26e522a2e
commit
c526269e5d
5 changed files with 153 additions and 94 deletions
|
|
@ -72,9 +72,9 @@ fun App(
|
|||
viewModel.bindTracker(locationTracker)
|
||||
scope.launch { locationTracker.startTracking() }
|
||||
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val infoState by viewModel.infoState.collectAsStateWithLifecycle()
|
||||
val mapState by viewModel.mapState.collectAsStateWithLifecycle()
|
||||
val searchState by viewModel.searchState.collectAsStateWithLifecycle()
|
||||
|
||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = rememberStandardBottomSheetState(
|
||||
|
|
@ -100,7 +100,6 @@ fun App(
|
|||
scope.launch { scaffoldState.bottomSheetState.hide() }
|
||||
}
|
||||
|
||||
var searchTextState by rememberSaveable { mutableStateOf("") }
|
||||
var searchExpandedState by rememberSaveable { mutableStateOf(false) }
|
||||
var sheetSwipeEnabled by rememberSaveable { mutableStateOf(true) }
|
||||
var handleHeight by remember { mutableStateOf(0.dp) }
|
||||
|
|
@ -145,16 +144,14 @@ fun App(
|
|||
polylines = mapState.polylines,
|
||||
)
|
||||
Searcher(
|
||||
routes = state.routes,
|
||||
state = searchState,
|
||||
onEvent = viewModel::handleEvent,
|
||||
expanded = searchExpandedState,
|
||||
onExpandedChange = {
|
||||
searchExpandedState = it
|
||||
if (it)
|
||||
scope.launch { scaffoldState.bottomSheetState.hide() }
|
||||
},
|
||||
text = searchTextState,
|
||||
onTextChange = { searchTextState = it },
|
||||
onRouteChange = { viewModel.switchRoute(it) }
|
||||
)
|
||||
|
||||
PredictiveBackHandler(scaffoldState.bottomSheetState.currentValue != SheetValue.Hidden) { progress ->
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import kotlinx.datetime.Clock
|
|||
import kotlinx.datetime.Instant
|
||||
import moe.lava.banksia.api.ptv.PtvService
|
||||
import moe.lava.banksia.api.ptv.structures.PtvRoute
|
||||
import moe.lava.banksia.api.ptv.structures.PtvStop
|
||||
import moe.lava.banksia.api.ptv.structures.PtvRouteType
|
||||
import moe.lava.banksia.api.ptv.structures.getProperties
|
||||
import moe.lava.banksia.log
|
||||
import moe.lava.banksia.native.maps.CameraPosition
|
||||
|
|
@ -27,19 +27,18 @@ import moe.lava.banksia.native.maps.Point
|
|||
import moe.lava.banksia.native.maps.Polyline
|
||||
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.util.BoxedValue
|
||||
import moe.lava.banksia.util.BoxedValue.Companion.box
|
||||
|
||||
sealed class BanksiaEvent {}
|
||||
sealed class BanksiaEvent {
|
||||
data class SelectRoute(val id: Int?) : BanksiaEvent()
|
||||
data class SelectStop(val routeType: PtvRouteType, val stopId: Int?) : BanksiaEvent()
|
||||
|
||||
data class BanksiaViewState(
|
||||
val routes: List<PtvRoute> = listOf(),
|
||||
)
|
||||
data class SearchUpdate(val text: String) : BanksiaEvent()
|
||||
}
|
||||
|
||||
class BanksiaViewModel : ViewModel() {
|
||||
private val iState = MutableStateFlow(BanksiaViewState())
|
||||
val state = iState.asStateFlow()
|
||||
|
||||
private val iInfoState = MutableStateFlow<InfoPanelState>(InfoPanelState.None)
|
||||
val infoState = iInfoState.asStateFlow()
|
||||
|
||||
|
|
@ -48,17 +47,26 @@ class BanksiaViewModel : ViewModel() {
|
|||
private val iCameraChangeEmitter = MutableSharedFlow<BoxedValue<CameraPosition>>()
|
||||
val cameraChangeEmitter = iCameraChangeEmitter.asSharedFlow()
|
||||
|
||||
private val iSearchState = MutableStateFlow(SearchState())
|
||||
val searchState = iSearchState.asStateFlow()
|
||||
|
||||
private val ptvService = PtvService()
|
||||
private var locationTrackerJob: Job? = null
|
||||
private var lastKnownLocation: Point? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
requestRoutes()
|
||||
}
|
||||
viewModelScope.launch { searchUpdate("") }
|
||||
}
|
||||
|
||||
fun handleEvent(event: BanksiaEvent) {}
|
||||
fun handleEvent(event: BanksiaEvent) {
|
||||
viewModelScope.launch {
|
||||
when (event) {
|
||||
is BanksiaEvent.SelectRoute -> switchRoute(event.id)
|
||||
is BanksiaEvent.SelectStop -> switchStop(event.routeType, event.stopId)
|
||||
is BanksiaEvent.SearchUpdate -> searchUpdate(event.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bindTracker(locationTracker: LocationTracker) {
|
||||
locationTrackerJob = locationTracker.getLocationsFlow()
|
||||
|
|
@ -79,43 +87,57 @@ class BanksiaViewModel : ViewModel() {
|
|||
lastKnownLocation = location
|
||||
}
|
||||
|
||||
private suspend fun requestRoutes() {
|
||||
val routes = ptvService.routes().sortedWith(
|
||||
compareBy(
|
||||
{ it.gtfsSubType()?.ordinal },
|
||||
{ it.routeNumber.toIntOrNull() },
|
||||
{ it.routeName }
|
||||
)
|
||||
)
|
||||
iState.update { it.copy(routes = routes) }
|
||||
}
|
||||
|
||||
fun switchRoute(route: PtvRoute?) {
|
||||
iMapState.update { MapState() }
|
||||
iInfoState.update {
|
||||
if (route == null)
|
||||
InfoPanelState.None
|
||||
else
|
||||
InfoPanelState.Route(
|
||||
name = route.routeName,
|
||||
type = route.routeType,
|
||||
private suspend fun searchUpdate(text: String) {
|
||||
val entries = ptvService.routes()
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it.gtfsSubType()?.ordinal },
|
||||
{ it.routeNumber.toIntOrNull() },
|
||||
{ it.routeName }
|
||||
)
|
||||
}
|
||||
)
|
||||
.filter { it.routeNumber.contains(text) || it.routeName.lowercase().contains(text.lowercase()) }
|
||||
.map { route ->
|
||||
val (main, sub) = if (route.routeNumber.isNotEmpty()) {
|
||||
route.routeNumber to route.routeName
|
||||
} else {
|
||||
route.routeName to null
|
||||
}
|
||||
|
||||
if (route != null) {
|
||||
viewModelScope.launch { buildPolylines(route) }
|
||||
viewModelScope.launch { buildStops(route) }
|
||||
// viewModelScope.launch { buildDepartures() }
|
||||
// viewModelScope.launch { buildRuns() }
|
||||
}
|
||||
SearchState.SearchEntry(main, sub, route.routeId, route.routeType)
|
||||
}
|
||||
|
||||
iSearchState.update { SearchState(entries, text) }
|
||||
}
|
||||
|
||||
// [TODO]: Cleanup
|
||||
suspend fun switchStop(stop: PtvStop?) {
|
||||
if (stop == null) {
|
||||
private suspend fun switchRoute(routeId: Int?) {
|
||||
iMapState.update { MapState() }
|
||||
if (routeId == null) {
|
||||
iInfoState.update { InfoPanelState.None }
|
||||
return
|
||||
}
|
||||
|
||||
val route = ptvService.route(routeId)
|
||||
iInfoState.update {
|
||||
InfoPanelState.Route(
|
||||
name = route.routeName,
|
||||
type = route.routeType,
|
||||
)
|
||||
}
|
||||
|
||||
viewModelScope.launch { buildPolylines(route) }
|
||||
viewModelScope.launch { buildStops(route) }
|
||||
// viewModelScope.launch { buildDepartures() }
|
||||
// viewModelScope.launch { buildRuns() }
|
||||
}
|
||||
|
||||
// [TODO]: Cleanup
|
||||
private suspend fun switchStop(routeType: PtvRouteType, stopId: Int?) {
|
||||
if (stopId == null) {
|
||||
iInfoState.update { InfoPanelState.None }
|
||||
return
|
||||
}
|
||||
val stop = ptvService.stop(routeType, stopId)
|
||||
val split = stop.stopName.split("/")
|
||||
val name = split[0]
|
||||
val subname = split.getOrNull(1)
|
||||
|
|
@ -229,7 +251,7 @@ class BanksiaViewModel : ViewModel() {
|
|||
type = MarkerType.GENERIC_STOP,
|
||||
colour = colour,
|
||||
onClick = {
|
||||
viewModelScope.launch { switchStop(stop) }
|
||||
viewModelScope.launch { switchStop(route.routeType, stop.stopId) }
|
||||
false
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package moe.lava.banksia.ui
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -21,28 +19,20 @@ import androidx.compose.material3.SearchBarDefaults
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import moe.lava.banksia.api.ptv.structures.ComposableIcon
|
||||
import moe.lava.banksia.api.ptv.structures.PtvRoute
|
||||
import kotlin.math.pow
|
||||
import moe.lava.banksia.api.ptv.structures.ComposableRouteIcon
|
||||
import moe.lava.banksia.ui.state.SearchState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Searcher(
|
||||
routes: List<PtvRoute>,
|
||||
state: SearchState,
|
||||
onEvent: (BanksiaEvent) -> Unit,
|
||||
expanded: Boolean,
|
||||
onExpandedChange: (Boolean) -> Unit,
|
||||
text: String,
|
||||
onTextChange: (String) -> Unit,
|
||||
onRouteChange: (PtvRoute?) -> Unit,
|
||||
) {
|
||||
val animatedPadding by animateDpAsState(
|
||||
if (expanded) {
|
||||
|
|
@ -61,32 +51,20 @@ fun Searcher(
|
|||
.padding(horizontal = animatedPadding),
|
||||
shadowElevation = 6.dp, // Elevation level 3
|
||||
inputField = {
|
||||
var backProgress by remember { mutableFloatStateOf(1f) }
|
||||
var backEdgeIsLeft by remember { mutableStateOf<Boolean?>(null) }
|
||||
val routeInfoOpacity by animateFloatAsState((1f - backProgress).pow(3))
|
||||
val slideState by animateDpAsState((50 * backProgress).dp)
|
||||
val slidePadding = when (backEdgeIsLeft) {
|
||||
true -> PaddingValues(start = slideState)
|
||||
false -> PaddingValues(end = slideState)
|
||||
null -> PaddingValues()
|
||||
}
|
||||
|
||||
SearchBarDefaults.InputField(
|
||||
modifier = Modifier
|
||||
.alpha(1f - routeInfoOpacity)
|
||||
.padding(horizontal = 20.dp - animatedPadding),
|
||||
query = text,
|
||||
onQueryChange = onTextChange,
|
||||
modifier = Modifier.padding(horizontal = 20.dp - animatedPadding),
|
||||
query = state.text,
|
||||
onQueryChange = { onEvent(BanksiaEvent.SearchUpdate(it)) },
|
||||
onSearch = {},
|
||||
expanded = expanded,
|
||||
onExpandedChange = onExpandedChange,
|
||||
leadingIcon = { Icon(Icons.Default.Search, null) },
|
||||
trailingIcon = {
|
||||
if (expanded && text.isNotEmpty())
|
||||
if (expanded && state.text.isNotEmpty())
|
||||
Icon(
|
||||
imageVector = Icons.Default.Clear,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable { onTextChange("") }
|
||||
modifier = Modifier.clickable { onEvent(BanksiaEvent.SearchUpdate("")) }
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -95,27 +73,20 @@ fun Searcher(
|
|||
onExpandedChange = onExpandedChange,
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxWidth()) {
|
||||
for (route in routes) {
|
||||
if (!route.routeNumber.contains(text) &&
|
||||
!route.routeName.lowercase().contains(text.lowercase()))
|
||||
continue
|
||||
for (entry in state.entries) {
|
||||
item {
|
||||
ListItem(
|
||||
headlineContent = { Text(route.routeNumber.ifEmpty { route.routeName }) },
|
||||
supportingContent = {
|
||||
if (route.routeNumber.isNotEmpty()) {
|
||||
Text(route.routeName)
|
||||
}
|
||||
},
|
||||
leadingContent = { route.routeType.ComposableIcon() },
|
||||
headlineContent = { Text(entry.mainText) },
|
||||
supportingContent = { entry.subText?.let { Text(it) } },
|
||||
leadingContent = { ComposableRouteIcon(entry.routeType) },
|
||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||
.clickable {
|
||||
onTextChange("")
|
||||
onExpandedChange(false)
|
||||
onRouteChange(route)
|
||||
onEvent(BanksiaEvent.SearchUpdate(""))
|
||||
onEvent(BanksiaEvent.SelectRoute(entry.routeId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package moe.lava.banksia.ui.state
|
||||
|
||||
import moe.lava.banksia.api.ptv.structures.PtvRouteType
|
||||
|
||||
data class SearchState(
|
||||
val entries: List<SearchEntry> = listOf(),
|
||||
val text: String = "",
|
||||
) {
|
||||
data class SearchEntry(
|
||||
val mainText: String,
|
||||
val subText: String?,
|
||||
val routeId: Int,
|
||||
val routeType: PtvRouteType,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue