feat: display selected route more prominently

This commit is contained in:
LavaDesu 2025-05-01 19:32:28 +10:00
parent 81cdfcc9c5
commit 7281f6e1ba
Signed by: cilly
GPG key ID: 6500251E087653C9
2 changed files with 94 additions and 6 deletions

View file

@ -15,6 +15,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberStandardBottomSheetState
@ -126,13 +127,13 @@ fun App() {
LaunchedEffect(route) {
val route = route
polylines.clear()
if (route == null)
return@LaunchedEffect
val geoRoute = ptvService.route(route.routeId, true)
val colour = route.routeType.getProperties().colour
val allPoints = mutableListOf<Point>()
polylines.clear()
geoRoute.geopath.forEach { pp ->
// TODO: use gtfs colours
pp.paths.forEach { sp ->
@ -159,6 +160,7 @@ fun App() {
var stop by remember { mutableStateOf<PtvStop?>(null) }
var markers by remember { mutableStateOf(listOf<Marker>()) }
LaunchedEffect(route) {
markers = listOf()
route?.let { route ->
markers = buildStops(ptvService, route) {
stop = it
@ -196,7 +198,9 @@ fun App() {
modifier = Modifier.fillMaxSize(),
newCameraPosition = newCameraPosition,
cameraPositionUpdated = { newCameraPosition = null },
extInsets = WindowInsets(top = with(LocalDensity.current) { 56.dp.roundToPx() }, bottom = extInsets),
extInsets = WindowInsets(top = with(LocalDensity.current) {
SearchBarDefaults.InputFieldHeight.roundToPx()
}, bottom = extInsets),
markers = markers,
polylines = polylines,
)
@ -208,6 +212,7 @@ fun App() {
if (it)
scope.launch { scaffoldState.bottomSheetState.hide() }
},
route = route,
text = searchTextState,
onTextChange = { searchTextState = it },
onRouteChange = { route = it }

View file

@ -1,17 +1,25 @@
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.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
@ -21,26 +29,34 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.backhandler.PredictiveBackHandler
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import moe.lava.banksia.api.ptv.PtvService
import moe.lava.banksia.api.ptv.structures.ComposableIcon
import moe.lava.banksia.api.ptv.structures.PtvRoute
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.pow
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun Searcher(
ptvService: PtvService,
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
route: PtvRoute?,
text: String,
onTextChange: (String) -> Unit,
onRouteChange: (PtvRoute) -> Unit,
onRouteChange: (PtvRoute?) -> Unit,
) {
val animatedPadding by animateDpAsState(
if (expanded) {
@ -63,13 +79,41 @@ fun Searcher(
)
}
SearchBar(
colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
modifier = Modifier
.align(Alignment.TopCenter)
.fillMaxWidth()
.padding(horizontal = animatedPadding),
shadowElevation = 6.dp, // Elevation level 3
inputField = {
var backProgress by remember { mutableFloatStateOf(1f) }
var backEdgeIsLeft by remember { mutableStateOf<Boolean?>(null) }
val boxOpacityState by animateFloatAsState((1f - backProgress).pow(3))
val slideState by animateDpAsState((50 * backProgress).dp)
val slidePadding = if (backEdgeIsLeft == true)
PaddingValues(start = slideState)
else if (backEdgeIsLeft == false)
PaddingValues(end = slideState)
else
PaddingValues()
PredictiveBackHandler(enabled = route != null) { progress ->
try {
progress.collect { backEvent ->
backProgress = backEvent.progress
backEdgeIsLeft = backEvent.swipeEdge == 0
}
backProgress = 1f
onRouteChange(null)
} catch (_: CancellationException) {
backProgress = 0f
}
backEdgeIsLeft = null
}
SearchBarDefaults.InputField(
enabled = route == null,
modifier = Modifier
.alpha(1f - boxOpacityState)
.padding(horizontal = 20.dp - animatedPadding),
query = text,
onQueryChange = onTextChange,
onSearch = {},
@ -85,6 +129,45 @@ fun Searcher(
)
}
)
LaunchedEffect(route) {
backProgress = if (route != null) 0f else 1f;
}
if (route != null) {
Box(
modifier = Modifier
.alpha(boxOpacityState)
.sizeIn(
minHeight = SearchBarDefaults.InputFieldHeight,
maxHeight = SearchBarDefaults.InputFieldHeight,
)
.fillMaxWidth()
.padding(slidePadding)
) {
IconButton(
modifier = Modifier.align(Alignment.CenterStart),
onClick = {
onRouteChange(null)
}
) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Clear route")
}
Row(
modifier = Modifier
.padding(horizontal = 50.dp)
.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically,
) {
route.routeType.ComposableIcon()
Spacer(Modifier.width(15.dp))
Text(
route.routeNumber.ifEmpty { route.routeName },
style = MaterialTheme.typography.headlineSmall,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
}
}
},
expanded = expanded,
onExpandedChange = onExpandedChange,
@ -108,7 +191,7 @@ fun Searcher(
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp)
.clickable {
onTextChange(route.getShortFullName())
onTextChange("")
onExpandedChange(false)
onRouteChange(route)
}