|
|
|
|
@ -6,6 +6,7 @@ import android.icu.text.BreakIterator
|
|
|
|
|
import androidx.compose.foundation.layout.Box |
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize |
|
|
|
|
import androidx.compose.foundation.clickable |
|
|
|
|
import androidx.compose.foundation.gestures.detectTapGestures |
|
|
|
|
import androidx.compose.foundation.text.InlineTextContent |
|
|
|
|
import androidx.compose.foundation.text.appendInlineContent |
|
|
|
|
import androidx.compose.material3.Text |
|
|
|
|
@ -19,6 +20,10 @@ import androidx.compose.ui.semantics.semantics
|
|
|
|
|
import androidx.compose.ui.text.Placeholder |
|
|
|
|
import androidx.compose.ui.text.PlaceholderVerticalAlign |
|
|
|
|
import androidx.compose.ui.text.buildAnnotatedString |
|
|
|
|
import androidx.compose.ui.text.TextLayoutResult |
|
|
|
|
import androidx.compose.ui.text.SpanStyle |
|
|
|
|
import androidx.compose.ui.text.style.TextDecoration |
|
|
|
|
import androidx.compose.ui.input.pointer.pointerInput |
|
|
|
|
import androidx.compose.ui.unit.TextUnit |
|
|
|
|
import androidx.lifecycle.Lifecycle |
|
|
|
|
import androidx.lifecycle.LifecycleEventObserver |
|
|
|
|
@ -103,9 +108,10 @@ fun AnimatedEmoji(emoji: String, fontSize: TextUnit, modifier: Modifier = Modifi
|
|
|
|
|
|
|
|
|
|
/** Полные графемы сохраняют модификаторы кожи и ZWJ-последовательности обычными Unicode-смайликами. */ |
|
|
|
|
@Composable |
|
|
|
|
fun EmojiText(text: String, fontSize: TextUnit, color: Color, modifier: Modifier = Modifier) { |
|
|
|
|
fun EmojiText(text: String, fontSize: TextUnit, color: Color, modifier: Modifier = Modifier, |
|
|
|
|
onInviteLink: ((String) -> Unit)? = null) { |
|
|
|
|
val annotated = remember(text) { |
|
|
|
|
buildAnnotatedString { |
|
|
|
|
val emojiText = buildAnnotatedString { |
|
|
|
|
val iterator = BreakIterator.getCharacterInstance(Locale.US) |
|
|
|
|
iterator.setText(text) |
|
|
|
|
var start = iterator.first() |
|
|
|
|
@ -118,6 +124,14 @@ fun EmojiText(text: String, fontSize: TextUnit, color: Color, modifier: Modifier
|
|
|
|
|
end = iterator.next() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
buildAnnotatedString { |
|
|
|
|
append(emojiText) |
|
|
|
|
Regex("utun://[A-Za-z0-9+/]+={0,2}").findAll(emojiText.text).forEach { match -> |
|
|
|
|
addStringAnnotation("invite", match.value, match.range.first, match.range.last + 1) |
|
|
|
|
addStyle(SpanStyle(color = Color(0xFF3390EC), textDecoration = TextDecoration.Underline), |
|
|
|
|
match.range.first, match.range.last + 1) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
val inline = remember(fontSize, color) { |
|
|
|
|
animatedEmojiAssets.values.associateWith { |
|
|
|
|
@ -126,5 +140,19 @@ fun EmojiText(text: String, fontSize: TextUnit, color: Color, modifier: Modifier
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Text(annotated, inlineContent = inline, fontSize = fontSize, color = color, modifier = modifier) |
|
|
|
|
var layout by remember { mutableStateOf<TextLayoutResult?>(null) } |
|
|
|
|
val callback by rememberUpdatedState(onInviteLink) |
|
|
|
|
val links = annotated.getStringAnnotations("invite", 0, annotated.length) |
|
|
|
|
val linkModifier = if (onInviteLink == null || links.isEmpty()) modifier else modifier.pointerInput(annotated) { |
|
|
|
|
detectTapGestures { point -> |
|
|
|
|
val result = layout ?: return@detectTapGestures |
|
|
|
|
val line = result.getLineForVerticalPosition(point.y) |
|
|
|
|
if (point.y < 0 || point.y >= result.size.height || point.x < result.getLineLeft(line) || |
|
|
|
|
point.x >= result.getLineRight(line)) return@detectTapGestures |
|
|
|
|
val offset = result.getOffsetForPosition(point) |
|
|
|
|
annotated.getStringAnnotations("invite", offset, offset).firstOrNull()?.let { callback?.invoke(it.item) } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Text(annotated, inlineContent = inline, fontSize = fontSize, color = color, modifier = linkModifier, |
|
|
|
|
onTextLayout = { layout = it }) |
|
|
|
|
} |
|
|
|
|
|