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 641f636..27b3702 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 @@ -394,6 +394,9 @@ fun ChatDetailScreen( var longPressSuppressedMessageId by remember { mutableStateOf(null) } var longPressSuppressUntilMs by remember { mutableLongStateOf(0L) } + // 🔤 TEXT SELECTION - Telegram-style character-level selection + val textSelectionHelper = remember { com.rosetta.messenger.ui.chats.components.TextSelectionHelper() } + // 💬 MESSAGE CONTEXT MENU STATE var contextMenuMessage by remember { mutableStateOf(null) } var showContextMenu by remember { mutableStateOf(false) } @@ -838,6 +841,7 @@ fun ChatDetailScreen( // иначе при двойном колбэке (text + bubble) сообщение мгновенно "откатывается". val selectMessageOnLongPress: (messageId: String, canSelect: Boolean) -> Unit = { messageId, canSelect -> + textSelectionHelper.clear() if (canSelect && !selectedMessages.contains(messageId)) { selectedMessages = selectedMessages + messageId } @@ -886,6 +890,13 @@ fun ChatDetailScreen( } } + // 🔤 Сброс текстового выделения при скролле + LaunchedEffect(listState.isScrollInProgress) { + if (listState.isScrollInProgress && textSelectionHelper.isActive) { + textSelectionHelper.clear() + } + } + // 🔥 Display reply messages - получаем полную информацию о сообщениях для reply val displayReplyMessages = remember(replyMessages, messages) { @@ -3164,6 +3175,8 @@ fun ChatDetailScreen( MessageBubble( message = message, + textSelectionHelper = + textSelectionHelper, isDarkTheme = isDarkTheme, hasWallpaper = @@ -3644,6 +3657,11 @@ fun ChatDetailScreen( } } } + // 🔤 Text selection overlay + com.rosetta.messenger.ui.chats.components.TextSelectionOverlay( + helper = textSelectionHelper, + modifier = Modifier.fillMaxSize() + ) } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt index f3f6100..4957225 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt @@ -320,6 +320,7 @@ fun TypingIndicator( @Composable fun MessageBubble( message: ChatMessage, + textSelectionHelper: com.rosetta.messenger.ui.chats.components.TextSelectionHelper? = null, isDarkTheme: Boolean, hasWallpaper: Boolean = false, isSystemSafeChat: Boolean = false, @@ -400,6 +401,7 @@ fun MessageBubble( if (message.isOutgoing) Color(0xFFB3E5FC) // Светло-голубой на синем фоне else Color(0xFF2196F3) // Стандартный Material Blue для входящих } + var textViewRef by remember { mutableStateOf(null) } val linksEnabled = !isSelectionMode val textClickHandler: (() -> Unit)? = onClick val mentionClickHandler: ((String) -> Unit)? = @@ -1066,7 +1068,20 @@ fun MessageBubble( onClick = textClickHandler, onLongClick = - onLongClick // 🔥 Long press для selection + onLongClick, // 🔥 Long press для selection + onViewCreated = { textViewRef = it }, + onTextLongPress = if (textSelectionHelper != null && !isSelectionMode) { touchX, touchY -> + val info = textViewRef?.getLayoutInfo() + if (info != null) { + textSelectionHelper.startSelection( + messageId = message.id, + info = info, + touchX = touchX, + touchY = touchY, + view = textViewRef + ) + } + } else null ) }, timeContent = { @@ -1157,12 +1172,21 @@ fun MessageBubble( suppressBubbleTapFromSpan, onClick = textClickHandler, onLongClick = - onLongClick // 🔥 - // Long - // press - // для - // selection - ) + onLongClick, // 🔥 Long press для selection + onViewCreated = { textViewRef = it }, + onTextLongPress = if (textSelectionHelper != null && !isSelectionMode) { touchX, touchY -> + val info = textViewRef?.getLayoutInfo() + if (info != null) { + textSelectionHelper.startSelection( + messageId = message.id, + info = info, + touchX = touchX, + touchY = touchY, + view = textViewRef + ) + } + } else null + ) }, timeContent = { Row( @@ -1261,11 +1285,20 @@ fun MessageBubble( suppressBubbleTapFromSpan, onClick = textClickHandler, onLongClick = - onLongClick // 🔥 - // Long - // press - // для - // selection + onLongClick, // 🔥 Long press для selection + onViewCreated = { textViewRef = it }, + onTextLongPress = if (textSelectionHelper != null && !isSelectionMode) { touchX, touchY -> + val info = textViewRef?.getLayoutInfo() + if (info != null) { + textSelectionHelper.startSelection( + messageId = message.id, + info = info, + touchX = touchX, + touchY = touchY, + view = textViewRef + ) + } + } else null ) }, timeContent = {