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
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.spring
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@ -42,31 +43,27 @@ class CalendarViewModel(private val coroutineScope: CoroutineScope) {
selectedDate = date
}
fun collapse() {
if (isCollapsed) return
fun onDrag(delta: Float) {
coroutineScope.launch {
_collapseAnimatable.animateTo(
targetValue = 1f,
animationSpec = androidx.compose.animation.core.spring(
dampingRatio = 0.8f,
stiffness = 400f
)
)
isCollapsed = true
val new = (_collapseAnimatable.value + delta).coerceIn(0f, 1f)
_collapseAnimatable.snapTo(new)
}
}
fun expand() {
if (!isCollapsed) return
isCollapsed = false
fun onDragEnd() {
coroutineScope.launch {
_collapseAnimatable.animateTo(
targetValue = 0f,
animationSpec = androidx.compose.animation.core.spring(
dampingRatio = 0.8f,
stiffness = 400f
if (_collapseAnimatable.value > 0.5f) {
_collapseAnimatable.animateTo(
targetValue = 1f,
animationSpec = spring(dampingRatio = 0.8f, 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
import androidx.compose.animation.core.animate
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectVerticalDragGestures
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.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import plus.rua.project.CalendarViewModel
@Composable
@ -27,16 +25,20 @@ fun BottomCard(
viewModel: CalendarViewModel,
modifier: Modifier = Modifier
) {
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current
val dragRange = with(density) { 200.dp.toPx() }
Surface(
modifier = modifier
.fillMaxWidth()
.pointerInput(Unit) {
detectVerticalDragGestures { _, dragAmount ->
if (dragAmount < 0 && !viewModel.isCollapsed) {
viewModel.collapse()
}
.pointerInput(viewModel.isCollapsed) {
if (viewModel.isCollapsed) return@pointerInput
detectVerticalDragGestures(
onDragEnd = { viewModel.onDragEnd() },
onDragCancel = { viewModel.onDragEnd() }
) { _, dragAmount ->
val delta = -dragAmount / dragRange
viewModel.onDrag(delta)
}
},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),