@@ -70,6 +70,11 @@ import nz.aryan.akllive.data.Nz
7070import nz.aryan.akllive.gtfs.Timetable
7171import nz.aryan.akllive.gtfs.TripInfo
7272import nz.aryan.akllive.gtfs.tripInfo
73+ import androidx.compose.animation.animateContentSize
74+ import androidx.compose.foundation.gestures.detectVerticalDragGestures
75+ import androidx.compose.material.icons.filled.KeyboardArrowUp
76+ import androidx.compose.runtime.mutableFloatStateOf
77+ import androidx.compose.ui.input.pointer.pointerInput
7378import nz.aryan.akllive.AppViewModel
7479import nz.aryan.akllive.data.BusTrip
7580import nz.aryan.akllive.data.Fleet
@@ -296,9 +301,27 @@ private fun FilterPill(text: String, count: Int, dot: Color?, on: Boolean, click
296301private fun LiveBusCard (b : LiveBus , trip : BusTrip ? , ahead : List <Ahead >, now : Long , follow : Boolean ,
297302 onFollow : () -> Unit , onZoom : () -> Unit , onStop : (String ) -> Unit , close : () -> Unit ) {
298303 val color = operatorColor(b.info.code)
299- Surface (Modifier .fillMaxWidth(), shape = RoundedCornerShape (28 .dp), color = MaterialTheme .colorScheme.surfaceContainer,
300- shadowElevation = 6 .dp) {
301- Column (Modifier .padding(start = 16 .dp, end = 16 .dp, top = 14 .dp, bottom = 12 .dp)) {
304+ // small by default so the map stays in view; drag up (or tap the handle) for the rest
305+ var expanded by rememberSaveable(b.v.id) { mutableStateOf(false ) }
306+ var drag by remember { mutableFloatStateOf(0f ) }
307+ Surface (Modifier .fillMaxWidth().pointerInput(b.v.id) {
308+ detectVerticalDragGestures(
309+ onDragStart = { drag = 0f },
310+ onDragEnd = {
311+ when {
312+ drag < - 40 -> expanded = true
313+ drag > 40 && expanded -> expanded = false
314+ drag > 90 -> close()
315+ }
316+ },
317+ ) { _, dy -> drag + = dy }
318+ }, shape = RoundedCornerShape (28 .dp), color = MaterialTheme .colorScheme.surfaceContainer, shadowElevation = 6 .dp) {
319+ Column (Modifier .animateContentSize().padding(start = 16 .dp, end = 16 .dp, top = 6 .dp, bottom = 10 .dp)) {
320+ Box (Modifier .fillMaxWidth().clickable(interactionSource = null , indication = null ) { expanded = ! expanded }
321+ .padding(vertical = 6 .dp), contentAlignment = Alignment .Center ) {
322+ Box (Modifier .size(width = 36 .dp, height = 4 .dp).background(MaterialTheme .colorScheme.onSurfaceVariant.copy(alpha = 0.4f ),
323+ RoundedCornerShape (50 )))
324+ }
302325 Row (verticalAlignment = Alignment .CenterVertically ) {
303326 Box (Modifier .background(color, RoundedCornerShape (10 .dp)).padding(horizontal = 10 .dp, vertical = 3 .dp)) {
304327 Text (b.route ? : " —" , color = Color .White , fontWeight = FontWeight .Black , fontSize = 17 .sp)
@@ -316,6 +339,21 @@ private fun LiveBusCard(b: LiveBus, trip: BusTrip?, ahead: List<Ahead>, now: Lon
316339 }
317340 IconButton (onClick = close) { Icon (Icons .Filled .Close , " Close" , tint = MaterialTheme .colorScheme.onSurfaceVariant) }
318341 }
342+ if (! expanded) {
343+ // one line: how it's running and what's next
344+ Row (Modifier .fillMaxWidth().clickable { expanded = true }.padding(top = 4 .dp), verticalAlignment = Alignment .CenterVertically ) {
345+ if (b.route != null ) {
346+ val (text, c) = punctuality(trip?.delay)
347+ Chip (if (trip?.delay == null ) " Timing unknown" else text, if (trip?.delay == null ) Pal .Ink else c)
348+ Spacer (Modifier .width(8 .dp))
349+ }
350+ Text (ahead.firstOrNull()?.let { " Next: ${it.stop.name} · ${countdown(it.eta - now)} " }
351+ ? : (b.info.model?.short ? : b.info.fleetNo),
352+ style = MaterialTheme .typography.bodyMedium, maxLines = 1 , overflow = TextOverflow .Ellipsis , modifier = Modifier .weight(1f ))
353+ Icon (Icons .Filled .KeyboardArrowUp , " More" , tint = MaterialTheme .colorScheme.onSurfaceVariant)
354+ }
355+ return @Column
356+ }
319357 Spacer (Modifier .height(8 .dp))
320358 VehicleInfo (b.v, big = true )
321359 Spacer (Modifier .height(8 .dp))
0 commit comments