Переделал механику реквестов: отдельный pull-gesture и ручка раскрытия

This commit is contained in:
2026-03-19 19:41:59 +05:00
parent 2cf64e80eb
commit 72a2cf1b70

View File

@@ -1799,9 +1799,7 @@ fun ChatsListScreen(
}
val localDensity = LocalDensity.current
val requestsRevealThresholdPx =
remember(localDensity) { with(localDensity) { 24.dp.toPx() } }
val requestsRevealInstantPx =
remember(localDensity) { with(localDensity) { 8.dp.toPx() } }
remember(localDensity) { with(localDensity) { 28.dp.toPx() } }
val requestsHideThresholdPx =
remember(localDensity) { with(localDensity) { 16.dp.toPx() } }
@@ -2091,89 +2089,79 @@ fun ChatsListScreen(
lastAutoScrolledVerificationId = verificationId
}
// Pull-to-show/hide для реквестов: работаем только когда
// список реально дотянут до верха и пользователь тянет вниз.
val requestsNestedScroll =
remember(
chatListState,
requestsCount,
requestsRevealThresholdPx,
requestsRevealInstantPx,
requestsHideThresholdPx,
hapticFeedback
) {
var accumulatedPullDown = 0f
var hapticSent = false
object : androidx.compose.ui.input.nestedscroll.NestedScrollConnection {
fun isAtTop(): Boolean {
return !chatListState.canScrollBackward
}
override fun onPreScroll(
available: androidx.compose.ui.geometry.Offset,
source: androidx.compose.ui.input.nestedscroll.NestedScrollSource
): androidx.compose.ui.geometry.Offset {
if (source != androidx.compose.ui.input.nestedscroll.NestedScrollSource.Drag ||
requestsCount <= 0
) {
accumulatedPullDown = 0f
hapticSent = false
return androidx.compose.ui.geometry.Offset.Zero
}
if (available.y < -requestsHideThresholdPx) {
// Свайп вверх — быстро прячем блок реквестов.
accumulatedPullDown = 0f
hapticSent = false
if (isRequestsVisible) {
isRequestsVisible = false
}
} else if (available.y > 0f && !isRequestsVisible && isAtTop()) {
// Pull down from top should always reopen requests.
val shouldRevealInstantly =
available.y >= requestsRevealInstantPx
accumulatedPullDown =
(accumulatedPullDown + available.y).coerceAtMost(
requestsRevealThresholdPx
)
if (shouldRevealInstantly ||
accumulatedPullDown >= requestsRevealThresholdPx
) {
isRequestsVisible = true
accumulatedPullDown = 0f
if (!hapticSent) {
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
hapticSent = true
}
}
} else if (available.y <= 0f || !isAtTop()) {
accumulatedPullDown = 0f
hapticSent = false
}
return androidx.compose.ui.geometry.Offset.Zero
}
override suspend fun onPostFling(
consumed: androidx.compose.ui.unit.Velocity,
available: androidx.compose.ui.unit.Velocity
): androidx.compose.ui.unit.Velocity {
accumulatedPullDown = 0f
hapticSent = false
return androidx.compose.ui.unit.Velocity.Zero
}
}
}
LazyColumn(
state = chatListState,
modifier =
Modifier.fillMaxSize()
.then(
if (requestsCount > 0) Modifier.nestedScroll(requestsNestedScroll)
else Modifier
)
.pointerInput(
requestsCount,
isRequestsVisible,
chatListState,
requestsRevealThresholdPx,
requestsHideThresholdPx
) {
if (requestsCount <= 0) return@pointerInput
awaitEachGesture {
val down =
awaitFirstDown(
requireUnconsumed =
false
)
var accumulatedPullDown = 0f
while (true) {
val event =
awaitPointerEvent()
val change =
event.changes
.firstOrNull {
it.id ==
down
.id
}
?: break
if (change.changedToUpIgnoreConsumed()) {
break
}
val deltaY =
change.positionChange()
.y
val atTop =
!chatListState.canScrollBackward
if (isRequestsVisible &&
atTop &&
deltaY <
-requestsHideThresholdPx
) {
isRequestsVisible = false
accumulatedPullDown = 0f
break
}
if (!isRequestsVisible &&
atTop &&
deltaY > 0f
) {
accumulatedPullDown +=
deltaY
if (accumulatedPullDown >=
requestsRevealThresholdPx
) {
isRequestsVisible = true
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
break
}
} else if (deltaY < 0f || !atTop) {
accumulatedPullDown = 0f
}
}
}
}
.background(
listBackgroundColor
)
@@ -2204,6 +2192,24 @@ fun ChatsListScreen(
}
if (requestsCount > 0) {
if (!isRequestsVisible) {
item(key = "requests_reopen_handle") {
RequestsRevealHandle(
isDarkTheme = isDarkTheme,
onClick = {
isRequestsVisible = true
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
}
)
Divider(
color = dividerColor,
thickness = 0.5.dp
)
}
}
item(key = "requests_section") {
AnimatedVisibility(
visible =
@@ -4534,6 +4540,36 @@ fun TypingIndicatorSmall() {
}
}
@Composable
private fun RequestsRevealHandle(isDarkTheme: Boolean, onClick: () -> Unit) {
val textColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF6C6C70)
val iconColor = if (isDarkTheme) Color(0xFF7E7E84) else Color(0xFF8A8A90)
Row(
modifier =
Modifier.fillMaxWidth().clickable(onClick = onClick).padding(
horizontal = TELEGRAM_DIALOG_AVATAR_START,
vertical = 10.dp
),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = TablerIcons.ChevronDown,
contentDescription = "Show requests",
tint = iconColor,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text = "Pull down to show requests",
color = textColor,
fontSize = 13.sp,
fontWeight = FontWeight.Medium
)
}
}
/** 📬 Секция Requests — Telegram Archived Chats style */
@Composable
fun RequestsSection(