Browse Source
* Restore intentional mentions in the markdown/plain text editor --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>pull/3200/head
Jorge Martin Espinosa
2 months ago
committed by
GitHub
31 changed files with 715 additions and 286 deletions
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.messages.impl.utils |
||||
|
||||
import android.text.Spannable |
||||
import android.text.SpannableStringBuilder |
||||
import androidx.core.text.getSpans |
||||
import io.element.android.libraries.matrix.api.core.MatrixPatternType |
||||
import io.element.android.libraries.matrix.api.core.MatrixPatterns |
||||
import io.element.android.libraries.matrix.api.core.RoomAlias |
||||
import io.element.android.libraries.matrix.api.core.UserId |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser |
||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache |
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpan |
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider |
||||
import javax.inject.Inject |
||||
|
||||
class TextPillificationHelper @Inject constructor( |
||||
private val mentionSpanProvider: MentionSpanProvider, |
||||
private val permalinkBuilder: PermalinkBuilder, |
||||
private val permalinkParser: PermalinkParser, |
||||
private val roomMemberProfilesCache: RoomMemberProfilesCache, |
||||
) { |
||||
@Suppress("LoopWithTooManyJumpStatements") |
||||
fun pillify(text: CharSequence): CharSequence { |
||||
val matches = MatrixPatterns.findPatterns(text, permalinkParser).sortedByDescending { it.end } |
||||
if (matches.isEmpty()) return text |
||||
|
||||
val spannable = SpannableStringBuilder(text) |
||||
for (match in matches) { |
||||
when (match.type) { |
||||
MatrixPatternType.USER_ID -> { |
||||
val mentionSpanExists = spannable.getSpans<MentionSpan>(match.start, match.end).isNotEmpty() |
||||
if (!mentionSpanExists) { |
||||
val userId = UserId(match.value) |
||||
val permalink = permalinkBuilder.permalinkForUser(userId).getOrNull() ?: continue |
||||
val mentionSpan = mentionSpanProvider.getMentionSpanFor(match.value, permalink) |
||||
roomMemberProfilesCache.getDisplayName(userId)?.let { mentionSpan.text = it } |
||||
spannable.replace(match.start, match.end, "@ ") |
||||
spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) |
||||
} |
||||
} |
||||
MatrixPatternType.ROOM_ALIAS -> { |
||||
val mentionSpanExists = spannable.getSpans<MentionSpan>(match.start, match.end).isNotEmpty() |
||||
if (!mentionSpanExists) { |
||||
val permalink = permalinkBuilder.permalinkForRoomAlias(RoomAlias(match.value)).getOrNull() ?: continue |
||||
val mentionSpan = mentionSpanProvider.getMentionSpanFor(match.value, permalink) |
||||
spannable.replace(match.start, match.end, "@ ") |
||||
spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) |
||||
} |
||||
} |
||||
MatrixPatternType.AT_ROOM -> { |
||||
val mentionSpanExists = spannable.getSpans<MentionSpan>(match.start, match.end).isNotEmpty() |
||||
if (!mentionSpanExists) { |
||||
val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "") |
||||
spannable.replace(match.start, match.end, "@ ") |
||||
spannable.setSpan(mentionSpan, match.start, match.start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) |
||||
} |
||||
} |
||||
else -> Unit |
||||
} |
||||
} |
||||
return spannable |
||||
} |
||||
} |
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.messages.impl.utils |
||||
|
||||
import android.net.Uri |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import com.google.common.truth.Truth.assertThat |
||||
import io.element.android.libraries.matrix.api.core.RoomAlias |
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias |
||||
import io.element.android.libraries.matrix.api.core.UserId |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser |
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder |
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser |
||||
import io.element.android.libraries.matrix.test.room.aRoomMember |
||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache |
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpan |
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider |
||||
import io.element.android.libraries.textcomposer.mentions.getMentionSpans |
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class TextPillificationHelperTest { |
||||
@Test |
||||
fun `pillify - adds pills for user ids`() { |
||||
val text = "A @user:server.com" |
||||
val helper = aTextPillificationHelper( |
||||
permalinkparser = FakePermalinkParser(result = { |
||||
PermalinkData.UserLink(UserId("@user:server.com")) |
||||
}), |
||||
permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { |
||||
Result.success("https://matrix.to/#/@user:server.com") |
||||
}), |
||||
) |
||||
val pillified = helper.pillify(text) |
||||
val mentionSpans = pillified.getMentionSpans() |
||||
assertThat(mentionSpans).hasSize(1) |
||||
val mentionSpan = mentionSpans.firstOrNull() |
||||
assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.USER) |
||||
assertThat(mentionSpan?.rawValue).isEqualTo("@user:server.com") |
||||
assertThat(mentionSpan?.text).isEqualTo("@user:server.com") |
||||
} |
||||
|
||||
@Test |
||||
fun `pillify - uses the cached display name for user mentions`() { |
||||
val text = "A @user:server.com" |
||||
val helper = aTextPillificationHelper( |
||||
permalinkparser = FakePermalinkParser(result = { |
||||
PermalinkData.UserLink(UserId("@user:server.com")) |
||||
}), |
||||
permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { |
||||
Result.success("https://matrix.to/#/@user:server.com") |
||||
}), |
||||
roomMemberProfilesCache = RoomMemberProfilesCache().apply { |
||||
replace(listOf(aRoomMember(userId = UserId("@user:server.com"), displayName = "Alice"))) |
||||
}, |
||||
) |
||||
val pillified = helper.pillify(text) |
||||
val mentionSpans = pillified.getMentionSpans() |
||||
assertThat(mentionSpans).hasSize(1) |
||||
val mentionSpan = mentionSpans.firstOrNull() |
||||
assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.USER) |
||||
assertThat(mentionSpan?.rawValue).isEqualTo("@user:server.com") |
||||
assertThat(mentionSpan?.text).isEqualTo("Alice") |
||||
} |
||||
|
||||
@Test |
||||
fun `pillify - adds pills for room aliases`() { |
||||
val text = "A #room:server.com" |
||||
val helper = aTextPillificationHelper( |
||||
permalinkparser = FakePermalinkParser(result = { |
||||
PermalinkData.RoomLink(RoomIdOrAlias.Alias(RoomAlias("#room:server.com"))) |
||||
}), |
||||
permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { |
||||
Result.success("https://matrix.to/#/#room:server.com") |
||||
}), |
||||
) |
||||
val pillified = helper.pillify(text) |
||||
val mentionSpans = pillified.getMentionSpans() |
||||
assertThat(mentionSpans).hasSize(1) |
||||
val mentionSpan = mentionSpans.firstOrNull() |
||||
assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.ROOM) |
||||
assertThat(mentionSpan?.rawValue).isEqualTo("#room:server.com") |
||||
assertThat(mentionSpan?.text).isEqualTo("#room:server.com") |
||||
} |
||||
|
||||
@Test |
||||
fun `pillify - adds pills for @room mentions`() { |
||||
val text = "An @room mention" |
||||
val helper = aTextPillificationHelper(permalinkparser = FakePermalinkParser(result = { |
||||
PermalinkData.FallbackLink(Uri.EMPTY) |
||||
})) |
||||
val pillified = helper.pillify(text) |
||||
val mentionSpans = pillified.getMentionSpans() |
||||
assertThat(mentionSpans).hasSize(1) |
||||
val mentionSpan = mentionSpans.firstOrNull() |
||||
assertThat(mentionSpan?.type).isEqualTo(MentionSpan.Type.EVERYONE) |
||||
assertThat(mentionSpan?.rawValue).isEqualTo("@room") |
||||
assertThat(mentionSpan?.text).isEqualTo("@room") |
||||
} |
||||
|
||||
private fun aTextPillificationHelper( |
||||
permalinkparser: PermalinkParser = FakePermalinkParser(), |
||||
permalinkBuilder: FakePermalinkBuilder = FakePermalinkBuilder(), |
||||
mentionSpanProvider: MentionSpanProvider = MentionSpanProvider(permalinkparser), |
||||
roomMemberProfilesCache: RoomMemberProfilesCache = RoomMemberProfilesCache(), |
||||
) = TextPillificationHelper( |
||||
mentionSpanProvider = mentionSpanProvider, |
||||
permalinkBuilder = permalinkBuilder, |
||||
permalinkParser = permalinkparser, |
||||
roomMemberProfilesCache = roomMemberProfilesCache, |
||||
) |
||||
} |
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.matrix.api.core |
||||
|
||||
import android.net.Uri |
||||
import com.google.common.truth.Truth.assertThat |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser |
||||
import org.junit.Test |
||||
|
||||
class MatrixPatternsTest { |
||||
@Test |
||||
fun `findPatterns - returns raw user ids`() { |
||||
val text = "A @user:server.com and @user2:server.com" |
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) |
||||
assertThat(patterns).containsExactly( |
||||
MatrixPatternResult(MatrixPatternType.USER_ID, "@user:server.com", 2, 18), |
||||
MatrixPatternResult(MatrixPatternType.USER_ID, "@user2:server.com", 23, 40) |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun `findPatterns - returns raw room ids`() { |
||||
val text = "A !room:server.com and !room2:server.com" |
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) |
||||
assertThat(patterns).containsExactly( |
||||
MatrixPatternResult(MatrixPatternType.ROOM_ID, "!room:server.com", 2, 18), |
||||
MatrixPatternResult(MatrixPatternType.ROOM_ID, "!room2:server.com", 23, 40) |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun `findPatterns - returns raw room aliases`() { |
||||
val text = "A #room:server.com and #room2:server.com" |
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) |
||||
assertThat(patterns).containsExactly( |
||||
MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 18), |
||||
MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room2:server.com", 23, 40) |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun `findPatterns - returns raw room event ids`() { |
||||
val text = "A \$event:server.com and \$event2:server.com" |
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) |
||||
assertThat(patterns).containsExactly( |
||||
MatrixPatternResult(MatrixPatternType.EVENT_ID, "\$event:server.com", 2, 19), |
||||
MatrixPatternResult(MatrixPatternType.EVENT_ID, "\$event2:server.com", 24, 42) |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun `findPatterns - returns @room mention`() { |
||||
val text = "A @room mention" |
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser()) |
||||
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.AT_ROOM, "@room", 2, 7)) |
||||
} |
||||
|
||||
@Test |
||||
fun `findPatterns - returns user ids in permalinks`() { |
||||
val text = "A [User](https://matrix.to/#/@user:server.com)" |
||||
val permalinkParser = aPermalinkParser { _ -> |
||||
PermalinkData.UserLink(UserId("@user:server.com")) |
||||
} |
||||
val patterns = MatrixPatterns.findPatterns(text, permalinkParser) |
||||
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.USER_ID, "@user:server.com", 2, 46)) |
||||
} |
||||
|
||||
@Test |
||||
fun `findPatterns - returns room aliases in permalinks`() { |
||||
val text = "A [Room](https://matrix.to/#/#room:server.com)" |
||||
val permalinkParser = aPermalinkParser { _ -> |
||||
PermalinkData.RoomLink(RoomIdOrAlias.Alias(RoomAlias("#room:server.com"))) |
||||
} |
||||
val patterns = MatrixPatterns.findPatterns(text, permalinkParser) |
||||
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 46)) |
||||
} |
||||
|
||||
private fun aPermalinkParser(block: (String) -> PermalinkData = { PermalinkData.FallbackLink(Uri.EMPTY) }) = object : PermalinkParser { |
||||
override fun parse(uriString: String): PermalinkData { |
||||
return block(uriString) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,172 @@
@@ -0,0 +1,172 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.textcomposer.mentions |
||||
|
||||
import android.graphics.Color |
||||
import android.graphics.Typeface |
||||
import android.text.Spanned |
||||
import android.view.ViewGroup |
||||
import android.widget.TextView |
||||
import androidx.compose.foundation.layout.PaddingValues |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.CompositionLocalProvider |
||||
import androidx.compose.runtime.Stable |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.staticCompositionLocalOf |
||||
import androidx.compose.ui.graphics.toArgb |
||||
import androidx.compose.ui.platform.LocalDensity |
||||
import androidx.compose.ui.platform.LocalLayoutDirection |
||||
import androidx.compose.ui.unit.dp |
||||
import androidx.compose.ui.viewinterop.AndroidView |
||||
import androidx.core.text.buildSpannedString |
||||
import io.element.android.compound.theme.ElementTheme |
||||
import io.element.android.libraries.designsystem.preview.ElementPreview |
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight |
||||
import io.element.android.libraries.designsystem.text.rememberTypeface |
||||
import io.element.android.libraries.designsystem.theme.currentUserMentionPillBackground |
||||
import io.element.android.libraries.designsystem.theme.currentUserMentionPillText |
||||
import io.element.android.libraries.designsystem.theme.mentionPillBackground |
||||
import io.element.android.libraries.designsystem.theme.mentionPillText |
||||
import io.element.android.libraries.matrix.api.core.RoomAlias |
||||
import io.element.android.libraries.matrix.api.core.UserId |
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData |
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser |
||||
import kotlinx.collections.immutable.persistentListOf |
||||
import javax.inject.Inject |
||||
|
||||
/** |
||||
* Theme used for mention spans. |
||||
* To make this work, you need to: |
||||
* 1. Provide [LocalMentionSpanTheme] in a composable that wraps the ones where you want to use mentions. |
||||
* 2. Call [MentionSpanTheme.updateStyles] with the current [UserId] so the colors and sizes are computed. |
||||
* 3. Use either [MentionSpanTheme.updateMentionStyles] or [MentionSpan.update] to update the styles of the mention spans. |
||||
*/ |
||||
@Stable |
||||
class MentionSpanTheme @Inject constructor() { |
||||
internal var currentUserId: UserId? = null |
||||
internal var currentUserTextColor: Int = 0 |
||||
internal var currentUserBackgroundColor: Int = Color.WHITE |
||||
internal var otherTextColor: Int = 0 |
||||
internal var otherBackgroundColor: Int = Color.WHITE |
||||
|
||||
private val paddingValues = PaddingValues(start = 4.dp, end = 6.dp) |
||||
internal val paddingValuesPx = mutableStateOf(0 to 0) |
||||
internal val typeface = mutableStateOf(Typeface.DEFAULT) |
||||
|
||||
/** |
||||
* Updates the styles of the mention spans based on the [ElementTheme] and [currentUserId]. |
||||
*/ |
||||
@Suppress("ComposableNaming") |
||||
@Composable |
||||
fun updateStyles(currentUserId: UserId) { |
||||
this.currentUserId = currentUserId |
||||
currentUserTextColor = ElementTheme.colors.currentUserMentionPillText.toArgb() |
||||
currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb() |
||||
otherTextColor = ElementTheme.colors.mentionPillText.toArgb() |
||||
otherBackgroundColor = ElementTheme.colors.mentionPillBackground.toArgb() |
||||
|
||||
typeface.value = ElementTheme.typography.fontBodyLgMedium.rememberTypeface().value |
||||
val density = LocalDensity.current |
||||
val layoutDirection = LocalLayoutDirection.current |
||||
paddingValuesPx.value = remember(paddingValues, density, layoutDirection) { |
||||
with(density) { |
||||
val leftPadding = paddingValues.calculateLeftPadding(layoutDirection).roundToPx() |
||||
val rightPadding = paddingValues.calculateRightPadding(layoutDirection).roundToPx() |
||||
leftPadding to rightPadding |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Updates the styles of the mention spans in the given [CharSequence]. |
||||
*/ |
||||
fun MentionSpanTheme.updateMentionStyles(charSequence: CharSequence) { |
||||
val spanned = charSequence as? Spanned ?: return |
||||
val mentionSpans = spanned.getMentionSpans() |
||||
for (span in mentionSpans) { |
||||
span.update(this) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Composition local containing the current [MentionSpanTheme]. |
||||
*/ |
||||
val LocalMentionSpanTheme = staticCompositionLocalOf { |
||||
MentionSpanTheme() |
||||
} |
||||
|
||||
@PreviewsDayNight |
||||
@Composable |
||||
internal fun MentionSpanThemePreview() { |
||||
ElementPreview { |
||||
val mentionSpanTheme = remember { MentionSpanTheme() } |
||||
val provider = remember { |
||||
MentionSpanProvider( |
||||
permalinkParser = object : PermalinkParser { |
||||
override fun parse(uriString: String): PermalinkData { |
||||
return when (uriString) { |
||||
"https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink(UserId("@me:matrix.org")) |
||||
"https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink(UserId("@other:matrix.org")) |
||||
"https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink( |
||||
roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(), |
||||
eventId = null, |
||||
viaParameters = persistentListOf(), |
||||
) |
||||
else -> throw AssertionError("Unexpected value $uriString") |
||||
} |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
|
||||
val textColor = ElementTheme.colors.textPrimary.toArgb() |
||||
fun mentionSpanMe() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@me:matrix.org") |
||||
fun mentionSpanOther() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@other:matrix.org") |
||||
fun mentionSpanRoom() = provider.getMentionSpanFor("room", "https://matrix.to/#/#room:matrix.org") |
||||
mentionSpanTheme.updateStyles(currentUserId = UserId("@me:matrix.org")) |
||||
|
||||
CompositionLocalProvider( |
||||
LocalMentionSpanTheme provides mentionSpanTheme |
||||
) { |
||||
AndroidView(factory = { context -> |
||||
TextView(context).apply { |
||||
includeFontPadding = false |
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) |
||||
text = buildSpannedString { |
||||
append("This is a ") |
||||
append("@mention", mentionSpanMe(), 0) |
||||
append(" to the current user and this is a ") |
||||
append("@mention", mentionSpanOther(), 0) |
||||
append(" to other user. This one is for a room: ") |
||||
append("#room:matrix.org", mentionSpanRoom(), 0) |
||||
append("\n\n") |
||||
append("This ") |
||||
append("mention", mentionSpanMe(), 0) |
||||
append(" didn't have an '@' and it was automatically added, same as this ") |
||||
append("room:matrix.org", mentionSpanRoom(), 0) |
||||
append(" one, which had no leading '#'.") |
||||
} |
||||
mentionSpanTheme.updateMentionStyles(text) |
||||
setTextColor(textColor) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue