Сделал плавную вытягивающуюся анимацию реквестов в чат-листе

This commit is contained in:
2026-03-19 20:00:02 +05:00
parent bd6e033ed3
commit d41674ff78

View File

@@ -1794,6 +1794,7 @@ fun ChatsListScreen(
val showSkeleton = isLoading val showSkeleton = isLoading
val chatListState = rememberLazyListState() val chatListState = rememberLazyListState()
var isRequestsVisible by remember { mutableStateOf(true) } var isRequestsVisible by remember { mutableStateOf(true) }
var requestsPullProgress by remember { mutableStateOf(0f) }
var lastAutoScrolledVerificationId by remember { var lastAutoScrolledVerificationId by remember {
mutableStateOf<String?>(null) mutableStateOf<String?>(null)
} }
@@ -2099,6 +2100,8 @@ fun ChatsListScreen(
) { ) {
var accumulatedPullDown = 0f var accumulatedPullDown = 0f
var accumulatedPullUp = 0f var accumulatedPullUp = 0f
val pullDownLimit =
requestsRevealThresholdPx * 1.25f
object : androidx.compose.ui.input.nestedscroll.NestedScrollConnection { object : androidx.compose.ui.input.nestedscroll.NestedScrollConnection {
override fun onPreScroll( override fun onPreScroll(
available: androidx.compose.ui.geometry.Offset, available: androidx.compose.ui.geometry.Offset,
@@ -2109,6 +2112,7 @@ fun ChatsListScreen(
) { ) {
accumulatedPullDown = 0f accumulatedPullDown = 0f
accumulatedPullUp = 0f accumulatedPullUp = 0f
requestsPullProgress = 0f
return androidx.compose.ui.geometry.Offset.Zero return androidx.compose.ui.geometry.Offset.Zero
} }
@@ -2123,6 +2127,7 @@ fun ChatsListScreen(
) { ) {
accumulatedPullUp += -available.y accumulatedPullUp += -available.y
accumulatedPullDown = 0f accumulatedPullDown = 0f
requestsPullProgress = 0f
if (accumulatedPullUp >= requestsHideThresholdPx) { if (accumulatedPullUp >= requestsHideThresholdPx) {
isRequestsVisible = false isRequestsVisible = false
accumulatedPullUp = 0f accumulatedPullUp = 0f
@@ -2135,18 +2140,26 @@ fun ChatsListScreen(
accumulatedPullDown = accumulatedPullDown =
(accumulatedPullDown + available.y) (accumulatedPullDown + available.y)
.coerceAtMost( .coerceAtMost(
requestsRevealThresholdPx pullDownLimit
)
requestsPullProgress =
(accumulatedPullDown / requestsRevealThresholdPx)
.coerceIn(
0f,
1.15f
) )
if (accumulatedPullDown >= requestsRevealThresholdPx) { if (accumulatedPullDown >= requestsRevealThresholdPx) {
isRequestsVisible = true isRequestsVisible = true
accumulatedPullDown = 0f accumulatedPullDown = 0f
accumulatedPullUp = 0f accumulatedPullUp = 0f
requestsPullProgress = 0f
hapticFeedback.performHapticFeedback( hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress HapticFeedbackType.LongPress
) )
} }
} else if (available.y <= 0f || !atTop) { } else if (available.y <= 0f || !atTop) {
accumulatedPullDown = 0f accumulatedPullDown = 0f
requestsPullProgress = 0f
} }
return androidx.compose.ui.geometry.Offset.Zero return androidx.compose.ui.geometry.Offset.Zero
@@ -2158,11 +2171,24 @@ fun ChatsListScreen(
): androidx.compose.ui.unit.Velocity { ): androidx.compose.ui.unit.Velocity {
accumulatedPullDown = 0f accumulatedPullDown = 0f
accumulatedPullUp = 0f accumulatedPullUp = 0f
requestsPullProgress = 0f
return androidx.compose.ui.unit.Velocity.Zero return androidx.compose.ui.unit.Velocity.Zero
} }
} }
} }
LaunchedEffect(
chatListState.isScrollInProgress,
isRequestsVisible
) {
if (!chatListState.isScrollInProgress &&
!isRequestsVisible &&
requestsPullProgress != 0f
) {
requestsPullProgress = 0f
}
}
LazyColumn( LazyColumn(
state = chatListState, state = chatListState,
modifier = modifier =
@@ -2203,15 +2229,75 @@ fun ChatsListScreen(
if (requestsCount > 0) { if (requestsCount > 0) {
if (!isRequestsVisible) { if (!isRequestsVisible) {
item(key = "requests_reopen_handle") { item(key = "requests_reopen_handle") {
val animatedPullProgress by
animateFloatAsState(
targetValue =
requestsPullProgress,
animationSpec =
spring(
dampingRatio =
Spring.DampingRatioNoBouncy,
stiffness =
Spring.StiffnessMediumLow
),
label =
"requestsPullProgress"
)
RequestsRevealHandle( RequestsRevealHandle(
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
pullProgress =
animatedPullProgress,
onClick = { onClick = {
isRequestsVisible = true isRequestsVisible = true
requestsPullProgress =
0f
hapticFeedback.performHapticFeedback( hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress HapticFeedbackType.LongPress
) )
} }
) )
val revealProgress =
FastOutSlowInEasing
.transform(
animatedPullProgress
.coerceIn(
0f,
1f
)
)
if (revealProgress > 0.001f) {
Box(
modifier =
Modifier.fillMaxWidth()
.height(
76.dp *
revealProgress
)
.clipToBounds()
.graphicsLayer {
alpha =
revealProgress
}
) {
RequestsSection(
count =
requestsCount,
requests =
requests,
isDarkTheme =
isDarkTheme,
onClick = {
isRequestsVisible =
true
requestsPullProgress =
0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
}
)
}
}
Divider( Divider(
color = dividerColor, color = dividerColor,
thickness = 0.5.dp thickness = 0.5.dp
@@ -4550,16 +4636,36 @@ fun TypingIndicatorSmall() {
} }
@Composable @Composable
private fun RequestsRevealHandle(isDarkTheme: Boolean, onClick: () -> Unit) { private fun RequestsRevealHandle(
val textColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF6C6C70) isDarkTheme: Boolean,
val iconColor = if (isDarkTheme) Color(0xFF7E7E84) else Color(0xFF8A8A90) pullProgress: Float,
onClick: () -> Unit
) {
val clampedProgress = pullProgress.coerceIn(0f, 1.15f)
val revealProgress = FastOutSlowInEasing.transform(clampedProgress.coerceIn(0f, 1f))
val stretchOvershoot = (clampedProgress - 1f).coerceAtLeast(0f)
val textColor =
if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.84f + revealProgress * 0.16f)
else Color(0xFF6C6C70).copy(alpha = 0.84f + revealProgress * 0.16f)
val iconColor =
if (isDarkTheme) Color(0xFF7E7E84).copy(alpha = 0.88f + revealProgress * 0.12f)
else Color(0xFF8A8A90).copy(alpha = 0.88f + revealProgress * 0.12f)
val verticalPadding = 10.dp + (6.dp * revealProgress) + (4.dp * stretchOvershoot)
Row( Row(
modifier = modifier =
Modifier.fillMaxWidth().clickable(onClick = onClick).padding( Modifier.fillMaxWidth()
horizontal = TELEGRAM_DIALOG_AVATAR_START, .graphicsLayer {
vertical = 10.dp val scaleBoost =
), revealProgress * 0.015f + stretchOvershoot * 0.06f
scaleY = 1f + scaleBoost
}
.clickable(onClick = onClick)
.padding(
horizontal = TELEGRAM_DIALOG_AVATAR_START,
vertical = verticalPadding
),
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -4567,11 +4673,16 @@ private fun RequestsRevealHandle(isDarkTheme: Boolean, onClick: () -> Unit) {
imageVector = TablerIcons.ChevronDown, imageVector = TablerIcons.ChevronDown,
contentDescription = "Show requests", contentDescription = "Show requests",
tint = iconColor, tint = iconColor,
modifier = Modifier.size(18.dp) modifier =
Modifier.size(18.dp + (2.dp * revealProgress)).graphicsLayer {
rotationZ = 180f * revealProgress
}
) )
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
Text( Text(
text = "Pull down to show requests", text =
if (clampedProgress >= 0.92f) "Release to open requests"
else "Pull down to show requests",
color = textColor, color = textColor,
fontSize = 13.sp, fontSize = 13.sp,
fontWeight = FontWeight.Medium fontWeight = FontWeight.Medium