mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 16:22:49 +00:00
Stop rescanning message bodies for emoji only on every recomposition (#7531)
Deciding whether a text message is emoji only compares the formatted body to the plain one and runs a grapheme scan over the whole message. Nothing was remembered, so both ran again on every recomposition of every visible message. Remember the decision against the body and the formatted body. The preview branch moves into the same computation, so the composable wrapper around containsOnlyEmojis, which had a single caller and existed only to read LocalInspectionMode, is no longer needed.
This commit is contained in:
+10
-2
@@ -15,7 +15,9 @@ import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -23,6 +25,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentPreviewParam
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
@@ -47,8 +50,13 @@ fun TimelineItemTextView(
|
||||
// The View <-> Compose interop is not working well with Compose UI tests (it loops indefinitely), so we skip it in the UI test mode.
|
||||
if (LocalUiTestMode.current) return
|
||||
|
||||
val emojiOnly = content.formattedBody.toString() == content.body &&
|
||||
content.body.replace(" ", "").containsOnlyEmojis()
|
||||
val isInPreview = LocalInspectionMode.current
|
||||
val emojiOnly = remember(content.body, content.formattedBody, isInPreview) {
|
||||
content.formattedBody.toString() == content.body &&
|
||||
content.body.replace(" ", "").let { body ->
|
||||
if (isInPreview) body == AN_EMOJI_ONLY_TEXT else body.containsOnlyEmojis()
|
||||
}
|
||||
}
|
||||
val textStyle = when {
|
||||
emojiOnly -> ElementTheme.typography.fontHeadingXlRegular
|
||||
else -> ElementTheme.typography.fontBodyLgRegular
|
||||
|
||||
+1
-11
@@ -8,25 +8,15 @@
|
||||
|
||||
package io.element.android.features.messages.impl.utils
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import com.sigpwned.emoji4j.core.Grapheme.Type.EMOJI
|
||||
import com.sigpwned.emoji4j.core.Grapheme.Type.PICTOGRAPHIC
|
||||
import com.sigpwned.emoji4j.core.GraphemeMatchResult
|
||||
import com.sigpwned.emoji4j.core.GraphemeMatcher
|
||||
import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT
|
||||
|
||||
/**
|
||||
* Returns true if the string consists exclusively of "emoji or pictographic graphemes".
|
||||
*/
|
||||
@Composable
|
||||
fun String.containsOnlyEmojis(): Boolean {
|
||||
if (LocalInspectionMode.current) return this == AN_EMOJI_ONLY_TEXT
|
||||
if (isEmpty()) return false
|
||||
return containsOnlyEmojisInternal()
|
||||
}
|
||||
|
||||
internal fun String.containsOnlyEmojisInternal(): Boolean {
|
||||
internal fun String.containsOnlyEmojis(): Boolean {
|
||||
val matcher = GraphemeMatcher(this)
|
||||
var m: GraphemeMatchResult? = null
|
||||
var contiguous = true
|
||||
|
||||
+15
-15
@@ -16,23 +16,23 @@ class EmojiTest {
|
||||
@Test
|
||||
fun validEmojis() {
|
||||
// Simple single/multiple single-codepoint emojis per string
|
||||
assertTrue("👍".containsOnlyEmojisInternal())
|
||||
assertTrue("😀".containsOnlyEmojisInternal())
|
||||
assertTrue("🙂🙁".containsOnlyEmojisInternal())
|
||||
assertTrue("👁❤️🍝".containsOnlyEmojisInternal()) // 👁 is a pictographic
|
||||
assertTrue("👨👩👦1️⃣🚀👳🏾♂️🪩".containsOnlyEmojisInternal())
|
||||
assertTrue("🌍🌎🌏".containsOnlyEmojisInternal())
|
||||
assertTrue("👍".containsOnlyEmojis())
|
||||
assertTrue("😀".containsOnlyEmojis())
|
||||
assertTrue("🙂🙁".containsOnlyEmojis())
|
||||
assertTrue("👁❤️🍝".containsOnlyEmojis()) // 👁 is a pictographic
|
||||
assertTrue("👨👩👦1️⃣🚀👳🏾♂️🪩".containsOnlyEmojis())
|
||||
assertTrue("🌍🌎🌏".containsOnlyEmojis())
|
||||
|
||||
// Awkward multi-codepoint graphemes
|
||||
assertTrue("🧑🧑🧒🧒".containsOnlyEmojisInternal())
|
||||
assertTrue("🏴☠".containsOnlyEmojisInternal())
|
||||
assertTrue("👩🏿🔧".containsOnlyEmojisInternal())
|
||||
assertTrue("🧑🧑🧒🧒".containsOnlyEmojis())
|
||||
assertTrue("🏴☠".containsOnlyEmojis())
|
||||
assertTrue("👩🏿🔧".containsOnlyEmojis())
|
||||
|
||||
Assert.assertFalse("".containsOnlyEmojisInternal())
|
||||
Assert.assertFalse(" ".containsOnlyEmojisInternal())
|
||||
Assert.assertFalse("🙂 🙁".containsOnlyEmojisInternal())
|
||||
Assert.assertFalse(" 🙂 🙁 ".containsOnlyEmojisInternal())
|
||||
Assert.assertFalse("Hello".containsOnlyEmojisInternal())
|
||||
Assert.assertFalse("Hello 👋".containsOnlyEmojisInternal())
|
||||
Assert.assertFalse("".containsOnlyEmojis())
|
||||
Assert.assertFalse(" ".containsOnlyEmojis())
|
||||
Assert.assertFalse("🙂 🙁".containsOnlyEmojis())
|
||||
Assert.assertFalse(" 🙂 🙁 ".containsOnlyEmojis())
|
||||
Assert.assertFalse("Hello".containsOnlyEmojis())
|
||||
Assert.assertFalse("Hello 👋".containsOnlyEmojis())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user