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

View file

@ -1,17 +1,25 @@
package moe.lava.banksia.ui package moe.lava.banksia.ui
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons 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.Clear
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -21,26 +29,34 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier 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.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import moe.lava.banksia.api.ptv.PtvService import moe.lava.banksia.api.ptv.PtvService
import moe.lava.banksia.api.ptv.structures.ComposableIcon import moe.lava.banksia.api.ptv.structures.ComposableIcon
import moe.lava.banksia.api.ptv.structures.PtvRoute 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 @Composable
fun Searcher( fun Searcher(
ptvService: PtvService, ptvService: PtvService,
expanded: Boolean, expanded: Boolean,
onExpandedChange: (Boolean) -> Unit, onExpandedChange: (Boolean) -> Unit,
route: PtvRoute?,
text: String, text: String,
onTextChange: (String) -> Unit, onTextChange: (String) -> Unit,
onRouteChange: (PtvRoute) -> Unit, onRouteChange: (PtvRoute?) -> Unit,
) { ) {
val animatedPadding by animateDpAsState( val animatedPadding by animateDpAsState(
if (expanded) { if (expanded) {
@ -63,13 +79,41 @@ fun Searcher(
) )
} }
SearchBar( SearchBar(
colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
modifier = Modifier modifier = Modifier
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = animatedPadding), .padding(horizontal = animatedPadding),
shadowElevation = 6.dp, // Elevation level 3
inputField = { 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( SearchBarDefaults.InputField(
enabled = route == null,
modifier = Modifier
.alpha(1f - boxOpacityState)
.padding(horizontal = 20.dp - animatedPadding),
query = text, query = text,
onQueryChange = onTextChange, onQueryChange = onTextChange,
onSearch = {}, 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, expanded = expanded,
onExpandedChange = onExpandedChange, onExpandedChange = onExpandedChange,
@ -108,7 +191,7 @@ fun Searcher(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp) .padding(horizontal = 16.dp, vertical = 4.dp)
.clickable { .clickable {
onTextChange(route.getShortFullName()) onTextChange("")
onExpandedChange(false) onExpandedChange(false)
onRouteChange(route) onRouteChange(route)
} }