Replace button-triggered collapse with vertical drag gesture

ViewModel now exposes onDrag/onDragEnd so BottomCard drives the
collapse animation via detectVerticalDragGestures instead of a
discrete collapse() call. Drag maps to 0–1 progress over 200dp;
on release the animatable snaps to the nearest end with spring.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-14 14:39:50 +08:00
parent 35cbcaf430
commit b734e26645
2 changed files with 27 additions and 28 deletions

View File

@ -1,6 +1,7 @@
package plus.rua.project package plus.rua.project
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.spring
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -42,31 +43,27 @@ class CalendarViewModel(private val coroutineScope: CoroutineScope) {
selectedDate = date selectedDate = date
} }
fun collapse() { fun onDrag(delta: Float) {
if (isCollapsed) return
coroutineScope.launch { coroutineScope.launch {
_collapseAnimatable.animateTo( val new = (_collapseAnimatable.value + delta).coerceIn(0f, 1f)
targetValue = 1f, _collapseAnimatable.snapTo(new)
animationSpec = androidx.compose.animation.core.spring(
dampingRatio = 0.8f,
stiffness = 400f
)
)
isCollapsed = true
} }
} }
fun expand() { fun onDragEnd() {
if (!isCollapsed) return
isCollapsed = false
coroutineScope.launch { coroutineScope.launch {
_collapseAnimatable.animateTo( if (_collapseAnimatable.value > 0.5f) {
targetValue = 0f, _collapseAnimatable.animateTo(
animationSpec = androidx.compose.animation.core.spring( targetValue = 1f,
dampingRatio = 0.8f, animationSpec = spring(dampingRatio = 0.8f, stiffness = 400f)
stiffness = 400f
) )
) isCollapsed = true
} else {
_collapseAnimatable.animateTo(
targetValue = 0f,
animationSpec = spring(dampingRatio = 0.8f, stiffness = 400f)
)
}
} }
} }

View File

@ -1,6 +1,5 @@
package plus.rua.project.ui package plus.rua.project.ui
import androidx.compose.animation.core.animate
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectVerticalDragGestures import androidx.compose.foundation.gestures.detectVerticalDragGestures
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -12,14 +11,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import plus.rua.project.CalendarViewModel import plus.rua.project.CalendarViewModel
@Composable @Composable
@ -27,16 +25,20 @@ fun BottomCard(
viewModel: CalendarViewModel, viewModel: CalendarViewModel,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val coroutineScope = rememberCoroutineScope() val density = LocalDensity.current
val dragRange = with(density) { 200.dp.toPx() }
Surface( Surface(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.pointerInput(Unit) { .pointerInput(viewModel.isCollapsed) {
detectVerticalDragGestures { _, dragAmount -> if (viewModel.isCollapsed) return@pointerInput
if (dragAmount < 0 && !viewModel.isCollapsed) { detectVerticalDragGestures(
viewModel.collapse() onDragEnd = { viewModel.onDragEnd() },
} onDragCancel = { viewModel.onDragEnd() }
) { _, dragAmount ->
val delta = -dragAmount / dragRange
viewModel.onDrag(delta)
} }
}, },
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),