Compare commits
2 Commits
429025537f
...
c5737e51b0
| Author | SHA1 | Date | |
|---|---|---|---|
| c5737e51b0 | |||
| b62ff7d7c4 |
@@ -8,6 +8,7 @@ import androidx.activity.compose.BackHandler
|
|||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.Spring
|
import androidx.compose.animation.core.Spring
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
@@ -721,6 +722,15 @@ fun ChatDetailScreen(
|
|||||||
.maxWithOrNull(compareBy<ChatMessage>({ it.timestamp.time }, { it.id }))
|
.maxWithOrNull(compareBy<ChatMessage>({ it.timestamp.time }, { it.id }))
|
||||||
?.id
|
?.id
|
||||||
var lastNewestMessageId by remember { mutableStateOf<String?>(null) }
|
var lastNewestMessageId by remember { mutableStateOf<String?>(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: Прокрутка ТОЛЬКО при новых сообщениях (не при пагинации)
|
// Telegram-style: Прокрутка ТОЛЬКО при новых сообщениях (не при пагинации)
|
||||||
// 🔥 Скроллим только если изменился ID самого нового сообщения
|
// 🔥 Скроллим только если изменился ID самого нового сообщения
|
||||||
@@ -730,10 +740,15 @@ fun ChatDetailScreen(
|
|||||||
lastNewestMessageId != null &&
|
lastNewestMessageId != null &&
|
||||||
newestMessageId != lastNewestMessageId
|
newestMessageId != lastNewestMessageId
|
||||||
) {
|
) {
|
||||||
// Новое сообщение пришло - скроллим вниз
|
val newestMessage = messages.firstOrNull { it.id == newestMessageId }
|
||||||
delay(50) // Debounce - ждём стабилизации
|
val isOwnOutgoingMessage = newestMessage?.isOutgoing == true
|
||||||
listState.animateScrollToItem(0)
|
val shouldAutoScroll = isAtBottom || isOwnOutgoingMessage
|
||||||
wasManualScroll = false
|
|
||||||
|
if (shouldAutoScroll) {
|
||||||
|
delay(50) // Debounce - ждём стабилизации
|
||||||
|
listState.animateScrollToItem(0)
|
||||||
|
wasManualScroll = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lastNewestMessageId = newestMessageId
|
lastNewestMessageId = newestMessageId
|
||||||
}
|
}
|
||||||
@@ -1141,7 +1156,8 @@ fun ChatDetailScreen(
|
|||||||
color = Color.White,
|
color = Color.White,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = android.text.TextUtils.TruncateAt.END,
|
overflow = android.text.TextUtils.TruncateAt.END,
|
||||||
enableLinks = false
|
enableLinks = false,
|
||||||
|
minHeightMultiplier = 1.1f
|
||||||
)
|
)
|
||||||
if (!isSavedMessages &&
|
if (!isSavedMessages &&
|
||||||
!isGroupChat &&
|
!isGroupChat &&
|
||||||
@@ -2465,6 +2481,50 @@ fun ChatDetailScreen(
|
|||||||
}
|
}
|
||||||
} // Конец Column внутри Scaffold content
|
} // Конец 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)
|
// 📎 Media Picker — new tab-based ChatAttachAlert (Telegram-style)
|
||||||
// Feature flag: set USE_NEW_ATTACH_ALERT to false to use old MediaPickerBottomSheet
|
// Feature flag: set USE_NEW_ATTACH_ALERT to false to use old MediaPickerBottomSheet
|
||||||
val USE_NEW_ATTACH_ALERT = true
|
val USE_NEW_ATTACH_ALERT = true
|
||||||
|
|||||||
@@ -358,13 +358,14 @@ fun AppleEmojiText(
|
|||||||
enableMentions: Boolean = false,
|
enableMentions: Boolean = false,
|
||||||
onMentionClick: ((String) -> Unit)? = null,
|
onMentionClick: ((String) -> Unit)? = null,
|
||||||
onClick: (() -> Unit)? = null, // 🔥 Обычный tap (selection mode в MessageBubble)
|
onClick: (() -> Unit)? = null, // 🔥 Обычный tap (selection mode в MessageBubble)
|
||||||
onLongClick: (() -> Unit)? = null // 🔥 Callback для long press (selection в MessageBubble)
|
onLongClick: (() -> Unit)? = null, // 🔥 Callback для long press (selection в MessageBubble)
|
||||||
|
minHeightMultiplier: Float = 1.5f
|
||||||
) {
|
) {
|
||||||
val fontSizeValue = if (fontSize == androidx.compose.ui.unit.TextUnit.Unspecified) 15f
|
val fontSizeValue = if (fontSize == androidx.compose.ui.unit.TextUnit.Unspecified) 15f
|
||||||
else fontSize.value
|
else fontSize.value
|
||||||
|
|
||||||
// Минимальная высота для корректного отображения emoji
|
// Минимальная высота для корректного отображения emoji
|
||||||
val minHeight = (fontSizeValue * 1.5).toInt()
|
val minHeight = (fontSizeValue * minHeightMultiplier).toInt()
|
||||||
|
|
||||||
// Преобразуем FontWeight в Android typeface style
|
// Преобразуем FontWeight в Android typeface style
|
||||||
val typefaceStyle = when (fontWeight) {
|
val typefaceStyle = when (fontWeight) {
|
||||||
|
|||||||
Reference in New Issue
Block a user