From b62ff7d7c4cc30ed06210b5c717dccc9a20be024 Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Sat, 7 Mar 2026 17:15:25 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BC=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D1=81=D0=BA=D0=BE=D0=B5=20=D0=BF=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D1=80=D1=83=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=BC=20=D1=81=D0=BE=D0=BE=D0=B1?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=D0=BC=20=D0=B8=20=D0=BA=D0=BD?= =?UTF-8?q?=D0=BE=D0=BF=D0=BA=D0=B0=20"=D0=9F=D1=80=D0=BE=D0=BA=D1=80?= =?UTF-8?q?=D1=83=D1=82=D0=B8=D1=82=D1=8C=20=D0=B2=D0=BD=D0=B8=D0=B7"=20?= =?UTF-8?q?=D0=B2=20ChatDetailScreen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../messenger/ui/chats/ChatDetailScreen.kt | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index 82c4223..6904e51 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -8,6 +8,7 @@ import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState @@ -721,6 +722,15 @@ fun ChatDetailScreen( .maxWithOrNull(compareBy({ it.timestamp.time }, { it.id })) ?.id var lastNewestMessageId by remember { mutableStateOf(null) } + val isAtBottom by remember(listState) { + derivedStateOf { + listState.firstVisibleItemIndex == 0 && + listState.firstVisibleItemScrollOffset <= 12 + } + } + val showScrollToBottomButton by remember(messagesWithDates, isAtBottom) { + derivedStateOf { messagesWithDates.isNotEmpty() && !isAtBottom } + } // Telegram-style: Прокрутка ТОЛЬКО при новых сообщениях (не при пагинации) // 🔥 Скроллим только если изменился ID самого нового сообщения @@ -730,10 +740,15 @@ fun ChatDetailScreen( lastNewestMessageId != null && newestMessageId != lastNewestMessageId ) { - // Новое сообщение пришло - скроллим вниз - delay(50) // Debounce - ждём стабилизации - listState.animateScrollToItem(0) - wasManualScroll = false + val newestMessage = messages.firstOrNull { it.id == newestMessageId } + val isOwnOutgoingMessage = newestMessage?.isOutgoing == true + val shouldAutoScroll = isAtBottom || isOwnOutgoingMessage + + if (shouldAutoScroll) { + delay(50) // Debounce - ждём стабилизации + listState.animateScrollToItem(0) + wasManualScroll = false + } } lastNewestMessageId = newestMessageId } @@ -2465,6 +2480,50 @@ fun ChatDetailScreen( } } // Конец Column внутри Scaffold content + AnimatedVisibility( + visible = showScrollToBottomButton && !isLoading && !isSelectionMode, + enter = fadeIn(animationSpec = tween(140)) + expandVertically(expandFrom = Alignment.Bottom), + exit = fadeOut(animationSpec = tween(120)) + shrinkVertically(shrinkTowards = Alignment.Bottom), + modifier = + Modifier.align(Alignment.BottomEnd) + .padding( + end = 14.dp, + bottom = if (isSystemAccount) 24.dp else 86.dp + ) + ) { + Box( + modifier = + Modifier.size(38.dp) + .clip(CircleShape) + .background( + if (isDarkTheme) Color(0xFF2D2E31) + else Color.White + ) + .clickable( + indication = null, + interactionSource = + remember { + MutableInteractionSource() + } + ) { + scope.launch { + listState.animateScrollToItem(0) + wasManualScroll = false + } + }, + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = TablerIcons.ChevronDown, + contentDescription = "Scroll to bottom", + tint = + if (isDarkTheme) Color(0xFFF2F2F3) + else Color(0xFF2D3138), + modifier = Modifier.size(21.dp) + ) + } + } + // 📎 Media Picker — new tab-based ChatAttachAlert (Telegram-style) // Feature flag: set USE_NEW_ATTACH_ALERT to false to use old MediaPickerBottomSheet val USE_NEW_ATTACH_ALERT = true