feat: интегрировать TextSelectionHelper в ChatDetailScreen и MessageBubble
- TextSelectionHelper инстанс в ChatDetailScreen - TextSelectionOverlay поверх LazyColumn - Clear selection при scroll и при message selection mode - onTextLongPress + onViewCreated проброшены через MessageBubble к AppleEmojiText Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -394,6 +394,9 @@ fun ChatDetailScreen(
|
|||||||
var longPressSuppressedMessageId by remember { mutableStateOf<String?>(null) }
|
var longPressSuppressedMessageId by remember { mutableStateOf<String?>(null) }
|
||||||
var longPressSuppressUntilMs by remember { mutableLongStateOf(0L) }
|
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
|
// 💬 MESSAGE CONTEXT MENU STATE
|
||||||
var contextMenuMessage by remember { mutableStateOf<ChatMessage?>(null) }
|
var contextMenuMessage by remember { mutableStateOf<ChatMessage?>(null) }
|
||||||
var showContextMenu by remember { mutableStateOf(false) }
|
var showContextMenu by remember { mutableStateOf(false) }
|
||||||
@@ -838,6 +841,7 @@ fun ChatDetailScreen(
|
|||||||
// иначе при двойном колбэке (text + bubble) сообщение мгновенно "откатывается".
|
// иначе при двойном колбэке (text + bubble) сообщение мгновенно "откатывается".
|
||||||
val selectMessageOnLongPress: (messageId: String, canSelect: Boolean) -> Unit =
|
val selectMessageOnLongPress: (messageId: String, canSelect: Boolean) -> Unit =
|
||||||
{ messageId, canSelect ->
|
{ messageId, canSelect ->
|
||||||
|
textSelectionHelper.clear()
|
||||||
if (canSelect && !selectedMessages.contains(messageId)) {
|
if (canSelect && !selectedMessages.contains(messageId)) {
|
||||||
selectedMessages = selectedMessages + messageId
|
selectedMessages = selectedMessages + messageId
|
||||||
}
|
}
|
||||||
@@ -886,6 +890,13 @@ fun ChatDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔤 Сброс текстового выделения при скролле
|
||||||
|
LaunchedEffect(listState.isScrollInProgress) {
|
||||||
|
if (listState.isScrollInProgress && textSelectionHelper.isActive) {
|
||||||
|
textSelectionHelper.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🔥 Display reply messages - получаем полную информацию о сообщениях для reply
|
// 🔥 Display reply messages - получаем полную информацию о сообщениях для reply
|
||||||
val displayReplyMessages =
|
val displayReplyMessages =
|
||||||
remember(replyMessages, messages) {
|
remember(replyMessages, messages) {
|
||||||
@@ -3164,6 +3175,8 @@ fun ChatDetailScreen(
|
|||||||
MessageBubble(
|
MessageBubble(
|
||||||
message =
|
message =
|
||||||
message,
|
message,
|
||||||
|
textSelectionHelper =
|
||||||
|
textSelectionHelper,
|
||||||
isDarkTheme =
|
isDarkTheme =
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
hasWallpaper =
|
hasWallpaper =
|
||||||
@@ -3644,6 +3657,11 @@ fun ChatDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 🔤 Text selection overlay
|
||||||
|
com.rosetta.messenger.ui.chats.components.TextSelectionOverlay(
|
||||||
|
helper = textSelectionHelper,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,6 +320,7 @@ fun TypingIndicator(
|
|||||||
@Composable
|
@Composable
|
||||||
fun MessageBubble(
|
fun MessageBubble(
|
||||||
message: ChatMessage,
|
message: ChatMessage,
|
||||||
|
textSelectionHelper: com.rosetta.messenger.ui.chats.components.TextSelectionHelper? = null,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
hasWallpaper: Boolean = false,
|
hasWallpaper: Boolean = false,
|
||||||
isSystemSafeChat: Boolean = false,
|
isSystemSafeChat: Boolean = false,
|
||||||
@@ -400,6 +401,7 @@ fun MessageBubble(
|
|||||||
if (message.isOutgoing) Color(0xFFB3E5FC) // Светло-голубой на синем фоне
|
if (message.isOutgoing) Color(0xFFB3E5FC) // Светло-голубой на синем фоне
|
||||||
else Color(0xFF2196F3) // Стандартный Material Blue для входящих
|
else Color(0xFF2196F3) // Стандартный Material Blue для входящих
|
||||||
}
|
}
|
||||||
|
var textViewRef by remember { mutableStateOf<com.rosetta.messenger.ui.components.AppleEmojiTextView?>(null) }
|
||||||
val linksEnabled = !isSelectionMode
|
val linksEnabled = !isSelectionMode
|
||||||
val textClickHandler: (() -> Unit)? = onClick
|
val textClickHandler: (() -> Unit)? = onClick
|
||||||
val mentionClickHandler: ((String) -> Unit)? =
|
val mentionClickHandler: ((String) -> Unit)? =
|
||||||
@@ -1066,7 +1068,20 @@ fun MessageBubble(
|
|||||||
onClick =
|
onClick =
|
||||||
textClickHandler,
|
textClickHandler,
|
||||||
onLongClick =
|
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 = {
|
timeContent = {
|
||||||
@@ -1157,12 +1172,21 @@ fun MessageBubble(
|
|||||||
suppressBubbleTapFromSpan,
|
suppressBubbleTapFromSpan,
|
||||||
onClick = textClickHandler,
|
onClick = textClickHandler,
|
||||||
onLongClick =
|
onLongClick =
|
||||||
onLongClick // 🔥
|
onLongClick, // 🔥 Long press для selection
|
||||||
// Long
|
onViewCreated = { textViewRef = it },
|
||||||
// press
|
onTextLongPress = if (textSelectionHelper != null && !isSelectionMode) { touchX, touchY ->
|
||||||
// для
|
val info = textViewRef?.getLayoutInfo()
|
||||||
// selection
|
if (info != null) {
|
||||||
)
|
textSelectionHelper.startSelection(
|
||||||
|
messageId = message.id,
|
||||||
|
info = info,
|
||||||
|
touchX = touchX,
|
||||||
|
touchY = touchY,
|
||||||
|
view = textViewRef
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
)
|
||||||
},
|
},
|
||||||
timeContent = {
|
timeContent = {
|
||||||
Row(
|
Row(
|
||||||
@@ -1261,11 +1285,20 @@ fun MessageBubble(
|
|||||||
suppressBubbleTapFromSpan,
|
suppressBubbleTapFromSpan,
|
||||||
onClick = textClickHandler,
|
onClick = textClickHandler,
|
||||||
onLongClick =
|
onLongClick =
|
||||||
onLongClick // 🔥
|
onLongClick, // 🔥 Long press для selection
|
||||||
// Long
|
onViewCreated = { textViewRef = it },
|
||||||
// press
|
onTextLongPress = if (textSelectionHelper != null && !isSelectionMode) { touchX, touchY ->
|
||||||
// для
|
val info = textViewRef?.getLayoutInfo()
|
||||||
// selection
|
if (info != null) {
|
||||||
|
textSelectionHelper.startSelection(
|
||||||
|
messageId = message.id,
|
||||||
|
info = info,
|
||||||
|
touchX = touchX,
|
||||||
|
touchY = touchY,
|
||||||
|
view = textViewRef
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
timeContent = {
|
timeContent = {
|
||||||
|
|||||||
Reference in New Issue
Block a user