diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index 228a300..49029a5 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -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(