Довел pull-анимацию реквестов: моментальный показ первым элементом

This commit is contained in:
2026-03-19 22:22:01 +05:00
parent d41674ff78
commit 1ba173be54

View File

@@ -2118,7 +2118,9 @@ fun ChatsListScreen(
val atTop = !chatListState.canScrollBackward
val nearTop =
chatListState.firstVisibleItemIndex == 0
chatListState.firstVisibleItemIndex == 0 &&
chatListState.firstVisibleItemScrollOffset <=
2
if (available.y < 0f &&
isRequestsVisible &&
@@ -2132,32 +2134,63 @@ fun ChatsListScreen(
isRequestsVisible = false
accumulatedPullUp = 0f
}
return androidx.compose.ui.geometry.Offset.Zero
return androidx.compose.ui.geometry.Offset(
0f,
available.y
)
}
if (available.y >= 0f) {
accumulatedPullUp = 0f
}
accumulatedPullUp = 0f
if (available.y > 0f && atTop && !isRequestsVisible) {
accumulatedPullDown =
(accumulatedPullDown + available.y)
.coerceAtMost(
pullDownLimit
if (!isRequestsVisible && atTop) {
if (available.y > 0f) {
accumulatedPullDown =
(accumulatedPullDown + available.y)
.coerceAtMost(
pullDownLimit
)
requestsPullProgress =
(accumulatedPullDown / requestsRevealThresholdPx)
.coerceIn(
0f,
1.15f
)
if (accumulatedPullDown >= requestsRevealThresholdPx) {
isRequestsVisible = true
accumulatedPullDown = 0f
accumulatedPullUp = 0f
requestsPullProgress = 0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
requestsPullProgress =
(accumulatedPullDown / requestsRevealThresholdPx)
.coerceIn(
0f,
1.15f
)
if (accumulatedPullDown >= requestsRevealThresholdPx) {
isRequestsVisible = true
accumulatedPullDown = 0f
accumulatedPullUp = 0f
requestsPullProgress = 0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
}
return androidx.compose.ui.geometry.Offset(
0f,
available.y
)
}
} else if (available.y <= 0f || !atTop) {
if (available.y < 0f &&
accumulatedPullDown > 0f
) {
accumulatedPullDown =
(accumulatedPullDown + available.y)
.coerceAtLeast(
0f
)
requestsPullProgress =
(accumulatedPullDown / requestsRevealThresholdPx)
.coerceIn(
0f,
1.15f
)
return androidx.compose.ui.geometry.Offset(
0f,
available.y
)
}
} else if (!isRequestsVisible && !atTop) {
accumulatedPullDown = 0f
requestsPullProgress = 0f
}
@@ -2165,6 +2198,16 @@ fun ChatsListScreen(
return androidx.compose.ui.geometry.Offset.Zero
}
override suspend fun onPreFling(
available: androidx.compose.ui.unit.Velocity
): androidx.compose.ui.unit.Velocity {
if (!isRequestsVisible) {
accumulatedPullDown = 0f
requestsPullProgress = 0f
}
return androidx.compose.ui.unit.Velocity.Zero
}
override suspend fun onPostFling(
consumed: androidx.compose.ui.unit.Velocity,
available: androidx.compose.ui.unit.Velocity
@@ -2177,18 +2220,6 @@ fun ChatsListScreen(
}
}
LaunchedEffect(
chatListState.isScrollInProgress,
isRequestsVisible
) {
if (!chatListState.isScrollInProgress &&
!isRequestsVisible &&
requestsPullProgress != 0f
) {
requestsPullProgress = 0f
}
}
LazyColumn(
state = chatListState,
modifier =
@@ -2227,56 +2258,70 @@ fun ChatsListScreen(
}
if (requestsCount > 0) {
if (!isRequestsVisible) {
item(key = "requests_reopen_handle") {
val animatedPullProgress by
animateFloatAsState(
targetValue =
requestsPullProgress,
animationSpec =
spring(
dampingRatio =
Spring.DampingRatioNoBouncy,
stiffness =
Spring.StiffnessMediumLow
),
label =
"requestsPullProgress"
)
RequestsRevealHandle(
isDarkTheme = isDarkTheme,
pullProgress =
animatedPullProgress,
onClick = {
isRequestsVisible = true
requestsPullProgress =
0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
}
item(key = "requests_section") {
val requestsSectionProgress by
animateFloatAsState(
targetValue =
if (isRequestsVisible) 1f
else requestsPullProgress,
animationSpec =
spring(
dampingRatio =
Spring.DampingRatioNoBouncy,
stiffness =
Spring.StiffnessMediumLow
),
label =
"requestsSectionProgress"
)
val revealProgress =
FastOutSlowInEasing
.transform(
animatedPullProgress
.coerceIn(
0f,
1f
)
)
if (revealProgress > 0.001f) {
val clampedProgress =
requestsSectionProgress
.coerceIn(
0f,
1.15f
)
val revealProgress =
FastOutSlowInEasing
.transform(
clampedProgress
.coerceIn(
0f,
1f
)
)
val stretchOvershoot =
(clampedProgress - 1f)
.coerceAtLeast(
0f
)
val sectionHeight =
76.dp *
revealProgress +
10.dp *
stretchOvershoot
val sectionAlpha =
(0.55f + revealProgress * 0.45f)
.coerceIn(
0f,
1f
)
if (isRequestsVisible ||
sectionHeight > 0.5.dp
) {
Column {
Box(
modifier =
Modifier.fillMaxWidth()
.height(
76.dp *
revealProgress
sectionHeight
)
.clipToBounds()
.graphicsLayer {
alpha =
revealProgress
if (isRequestsVisible)
1f
else sectionAlpha
}
) {
RequestsSection(
@@ -2291,83 +2336,10 @@ fun ChatsListScreen(
true
requestsPullProgress =
0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
openRequestsRouteSafely()
}
)
}
}
Divider(
color = dividerColor,
thickness = 0.5.dp
)
}
}
item(key = "requests_section") {
AnimatedVisibility(
visible =
isRequestsVisible,
enter =
expandVertically(
expandFrom =
Alignment
.Top,
animationSpec =
tween(
durationMillis =
260,
easing =
FastOutSlowInEasing
)
) +
fadeIn(
animationSpec =
tween(
durationMillis =
180,
easing =
LinearOutSlowInEasing
),
initialAlpha =
0.7f
),
exit =
shrinkVertically(
shrinkTowards =
Alignment
.Top,
animationSpec =
tween(
durationMillis =
220,
easing =
FastOutSlowInEasing
)
) +
fadeOut(
animationSpec =
tween(
durationMillis =
140,
easing =
FastOutLinearInEasing
)
)
) {
Column {
RequestsSection(
count =
requestsCount,
requests =
requests,
isDarkTheme =
isDarkTheme,
onClick = {
openRequestsRouteSafely()
}
)
Divider(
color =
dividerColor,
@@ -4635,61 +4607,6 @@ fun TypingIndicatorSmall() {
}
}
@Composable
private fun RequestsRevealHandle(
isDarkTheme: Boolean,
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(
modifier =
Modifier.fillMaxWidth()
.graphicsLayer {
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,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = TablerIcons.ChevronDown,
contentDescription = "Show requests",
tint = iconColor,
modifier =
Modifier.size(18.dp + (2.dp * revealProgress)).graphicsLayer {
rotationZ = 180f * revealProgress
}
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text =
if (clampedProgress >= 0.92f) "Release to open requests"
else "Pull down to show requests",
color = textColor,
fontSize = 13.sp,
fontWeight = FontWeight.Medium
)
}
}
/** 📬 Секция Requests — Telegram Archived Chats style */
@Composable
fun RequestsSection(