Довел 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 atTop = !chatListState.canScrollBackward
val nearTop = val nearTop =
chatListState.firstVisibleItemIndex == 0 chatListState.firstVisibleItemIndex == 0 &&
chatListState.firstVisibleItemScrollOffset <=
2
if (available.y < 0f && if (available.y < 0f &&
isRequestsVisible && isRequestsVisible &&
@@ -2132,11 +2134,17 @@ fun ChatsListScreen(
isRequestsVisible = false isRequestsVisible = false
accumulatedPullUp = 0f 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) { if (!isRequestsVisible && atTop) {
if (available.y > 0f) {
accumulatedPullDown = accumulatedPullDown =
(accumulatedPullDown + available.y) (accumulatedPullDown + available.y)
.coerceAtMost( .coerceAtMost(
@@ -2157,7 +2165,32 @@ fun ChatsListScreen(
HapticFeedbackType.LongPress HapticFeedbackType.LongPress
) )
} }
} else if (available.y <= 0f || !atTop) { return androidx.compose.ui.geometry.Offset(
0f,
available.y
)
}
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 accumulatedPullDown = 0f
requestsPullProgress = 0f requestsPullProgress = 0f
} }
@@ -2165,6 +2198,16 @@ fun ChatsListScreen(
return androidx.compose.ui.geometry.Offset.Zero 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( override suspend fun onPostFling(
consumed: androidx.compose.ui.unit.Velocity, consumed: androidx.compose.ui.unit.Velocity,
available: 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( LazyColumn(
state = chatListState, state = chatListState,
modifier = modifier =
@@ -2227,12 +2258,12 @@ fun ChatsListScreen(
} }
if (requestsCount > 0) { if (requestsCount > 0) {
if (!isRequestsVisible) { item(key = "requests_section") {
item(key = "requests_reopen_handle") { val requestsSectionProgress by
val animatedPullProgress by
animateFloatAsState( animateFloatAsState(
targetValue = targetValue =
requestsPullProgress, if (isRequestsVisible) 1f
else requestsPullProgress,
animationSpec = animationSpec =
spring( spring(
dampingRatio = dampingRatio =
@@ -2241,42 +2272,56 @@ fun ChatsListScreen(
Spring.StiffnessMediumLow Spring.StiffnessMediumLow
), ),
label = label =
"requestsPullProgress" "requestsSectionProgress"
) )
RequestsRevealHandle( val clampedProgress =
isDarkTheme = isDarkTheme, requestsSectionProgress
pullProgress = .coerceIn(
animatedPullProgress, 0f,
onClick = { 1.15f
isRequestsVisible = true
requestsPullProgress =
0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
}
) )
val revealProgress = val revealProgress =
FastOutSlowInEasing FastOutSlowInEasing
.transform( .transform(
animatedPullProgress clampedProgress
.coerceIn( .coerceIn(
0f, 0f,
1f 1f
) )
) )
if (revealProgress > 0.001f) { 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( Box(
modifier = modifier =
Modifier.fillMaxWidth() Modifier.fillMaxWidth()
.height( .height(
76.dp * sectionHeight
revealProgress
) )
.clipToBounds() .clipToBounds()
.graphicsLayer { .graphicsLayer {
alpha = alpha =
revealProgress if (isRequestsVisible)
1f
else sectionAlpha
} }
) { ) {
RequestsSection( RequestsSection(
@@ -2291,83 +2336,10 @@ fun ChatsListScreen(
true true
requestsPullProgress = requestsPullProgress =
0f 0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
}
)
}
}
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() openRequestsRouteSafely()
} }
) )
}
Divider( Divider(
color = color =
dividerColor, 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 */ /** 📬 Секция Requests — Telegram Archived Chats style */
@Composable @Composable
fun RequestsSection( fun RequestsSection(