Довел pull-анимацию реквестов: моментальный показ первым элементом
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user