diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/TextSelectionHelper.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/TextSelectionHelper.kt new file mode 100644 index 0000000..e798cf2 --- /dev/null +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/TextSelectionHelper.kt @@ -0,0 +1,132 @@ +package com.rosetta.messenger.ui.chats.components + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.text.Layout +import android.view.HapticFeedbackConstants +import android.view.View +import android.widget.Toast +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +data class LayoutInfo( + val layout: Layout, + val windowX: Int, + val windowY: Int, + val text: CharSequence +) + +class TextSelectionHelper { + + var selectionStart by mutableIntStateOf(-1) + private set + var selectionEnd by mutableIntStateOf(-1) + private set + var selectedMessageId by mutableStateOf(null) + private set + var layoutInfo by mutableStateOf(null) + private set + var isActive by mutableStateOf(false) + private set + var handleViewProgress by mutableFloatStateOf(0f) + private set + + val isInSelectionMode: Boolean get() = isActive && selectionStart >= 0 && selectionEnd > selectionStart + + fun startSelection( + messageId: String, + info: LayoutInfo, + touchX: Int, + touchY: Int, + view: View? + ) { + val layout = info.layout + val localX = touchX - info.windowX + val localY = touchY - info.windowY + + val line = layout.getLineForVertical(localY) + val hx = localX.toFloat().coerceIn(layout.getLineLeft(line), layout.getLineRight(line)) + val offset = layout.getOffsetForHorizontal(line, hx) + + val text = info.text + var start = offset + var end = offset + + while (start > 0 && Character.isLetterOrDigit(text[start - 1])) start-- + while (end < text.length && Character.isLetterOrDigit(text[end])) end++ + + if (start == end && end < text.length) end++ + + selectedMessageId = messageId + layoutInfo = info + selectionStart = start + selectionEnd = end + isActive = true + handleViewProgress = 1f + + view?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } + + fun updateSelectionStart(charOffset: Int) { + if (!isActive) return + val text = layoutInfo?.text ?: return + var newStart = charOffset.coerceIn(0, text.length) + while (newStart > 0 && Character.isLetterOrDigit(text[newStart - 1])) newStart-- + if (newStart >= selectionEnd) return + selectionStart = newStart + } + + fun updateSelectionEnd(charOffset: Int) { + if (!isActive) return + val text = layoutInfo?.text ?: return + var newEnd = charOffset.coerceIn(0, text.length) + while (newEnd < text.length && Character.isLetterOrDigit(text[newEnd])) newEnd++ + if (newEnd <= selectionStart) return + selectionEnd = newEnd + } + + fun getCharOffsetFromCoords(x: Int, y: Int): Int { + val info = layoutInfo ?: return -1 + val localX = x - info.windowX + val localY = y - info.windowY + val layout = info.layout + val line = layout.getLineForVertical(localY.coerceIn(0, layout.height)) + val hx = localX.toFloat().coerceIn(layout.getLineLeft(line), layout.getLineRight(line)) + return layout.getOffsetForHorizontal(line, hx) + } + + fun getSelectedText(): CharSequence? { + if (!isInSelectionMode) return null + val text = layoutInfo?.text ?: return null + val start = selectionStart.coerceIn(0, text.length) + val end = selectionEnd.coerceIn(start, text.length) + return text.subSequence(start, end) + } + + fun copySelectedText(context: Context) { + val selectedText = getSelectedText() ?: return + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setPrimaryClip(ClipData.newPlainText("selected_text", selectedText)) + Toast.makeText(context, "Copied", Toast.LENGTH_SHORT).show() + clear() + } + + fun selectAll() { + val text = layoutInfo?.text ?: return + selectionStart = 0 + selectionEnd = text.length + } + + fun clear() { + selectionStart = -1 + selectionEnd = -1 + selectedMessageId = null + layoutInfo = null + isActive = false + handleViewProgress = 0f + } +}