Suggestion for room alias.
Rename `Mention` to `IntentionalMention` for clarity Remove dead code, there is no intentional mention for Room or RoomAlias. Rename `IntentionalMention.AtRoom` to `IntentionalMention.Room` to match Rust naming
This commit is contained in:
parent
4385a7779c
commit
4b8985e501
34 changed files with 399 additions and 222 deletions
|
|
@ -111,13 +111,13 @@ fun MarkdownTextInput(
|
|||
state.text.update(editable, false)
|
||||
state.lineCount = lineCount
|
||||
|
||||
state.currentMentionSuggestion = editable?.checkSuggestionNeeded()
|
||||
onReceiveSuggestion(state.currentMentionSuggestion)
|
||||
state.currentSuggestion = editable?.checkSuggestionNeeded()
|
||||
onReceiveSuggestion(state.currentSuggestion)
|
||||
}
|
||||
onSelectionChangeListener = { selStart, selEnd ->
|
||||
state.selection = selStart..selEnd
|
||||
state.currentMentionSuggestion = editableText.checkSuggestionNeeded()
|
||||
onReceiveSuggestion(state.currentMentionSuggestion)
|
||||
state.currentSuggestion = editableText.checkSuggestionNeeded()
|
||||
onReceiveSuggestion(state.currentSuggestion)
|
||||
}
|
||||
if (onSelectRichContent != null) {
|
||||
ViewCompat.setOnReceiveContentListener(
|
||||
|
|
|
|||
|
|
@ -17,10 +17,13 @@
|
|||
package io.element.android.libraries.textcomposer.mentions
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
|
||||
@Immutable
|
||||
sealed interface ResolvedMentionSuggestion {
|
||||
data object AtRoom : ResolvedMentionSuggestion
|
||||
data class Member(val roomMember: RoomMember) : ResolvedMentionSuggestion
|
||||
sealed interface ResolvedSuggestion {
|
||||
data object AtRoom : ResolvedSuggestion
|
||||
data class Member(val roomMember: RoomMember) : ResolvedSuggestion
|
||||
data class Alias(val roomAlias: RoomAlias, val roomSummary: RoomSummary) : ResolvedSuggestion
|
||||
}
|
||||
|
|
@ -31,13 +31,14 @@ import androidx.compose.runtime.saveable.SaverScope
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.text.getSpans
|
||||
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.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
import io.element.android.libraries.textcomposer.components.markdown.StableCharSequence
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpan
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.getMentionSpans
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
|
|
@ -51,16 +52,16 @@ class MarkdownTextEditorState(
|
|||
var hasFocus by mutableStateOf(initialFocus)
|
||||
var requestFocusAction by mutableStateOf({})
|
||||
var lineCount by mutableIntStateOf(1)
|
||||
var currentMentionSuggestion by mutableStateOf<Suggestion?>(null)
|
||||
var currentSuggestion by mutableStateOf<Suggestion?>(null)
|
||||
|
||||
fun insertMention(
|
||||
mention: ResolvedMentionSuggestion,
|
||||
fun insertSuggestion(
|
||||
resolvedSuggestion: ResolvedSuggestion,
|
||||
mentionSpanProvider: MentionSpanProvider,
|
||||
permalinkBuilder: PermalinkBuilder,
|
||||
) {
|
||||
val suggestion = currentMentionSuggestion ?: return
|
||||
when (mention) {
|
||||
is ResolvedMentionSuggestion.AtRoom -> {
|
||||
val suggestion = currentSuggestion ?: return
|
||||
when (resolvedSuggestion) {
|
||||
is ResolvedSuggestion.AtRoom -> {
|
||||
val currentText = SpannableStringBuilder(text.value())
|
||||
val replaceText = "@room"
|
||||
val roomPill = mentionSpanProvider.getMentionSpanFor(replaceText, "")
|
||||
|
|
@ -70,10 +71,21 @@ class MarkdownTextEditorState(
|
|||
text.update(currentText, true)
|
||||
selection = IntRange(end + 1, end + 1)
|
||||
}
|
||||
is ResolvedMentionSuggestion.Member -> {
|
||||
is ResolvedSuggestion.Member -> {
|
||||
val currentText = SpannableStringBuilder(text.value())
|
||||
val text = mention.roomMember.displayName?.prependIndent("@") ?: mention.roomMember.userId.value
|
||||
val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return
|
||||
val text = resolvedSuggestion.roomMember.displayName?.prependIndent("@") ?: resolvedSuggestion.roomMember.userId.value
|
||||
val link = permalinkBuilder.permalinkForUser(resolvedSuggestion.roomMember.userId).getOrNull() ?: return
|
||||
val mentionPill = mentionSpanProvider.getMentionSpanFor(text, link)
|
||||
currentText.replace(suggestion.start, suggestion.end, ". ")
|
||||
val end = suggestion.start + 1
|
||||
currentText.setSpan(mentionPill, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
this.text.update(currentText, true)
|
||||
this.selection = IntRange(end + 1, end + 1)
|
||||
}
|
||||
is ResolvedSuggestion.Alias -> {
|
||||
val currentText = SpannableStringBuilder(text.value())
|
||||
val text = resolvedSuggestion.roomAlias.value
|
||||
val link = permalinkBuilder.permalinkForRoomAlias(resolvedSuggestion.roomAlias).getOrNull() ?: return
|
||||
val mentionPill = mentionSpanProvider.getMentionSpanFor(text, link)
|
||||
currentText.replace(suggestion.start, suggestion.end, "@ ")
|
||||
val end = suggestion.start + 1
|
||||
|
|
@ -96,14 +108,18 @@ class MarkdownTextEditorState(
|
|||
val end = charSequence.getSpanEnd(mention)
|
||||
when (mention.type) {
|
||||
MentionSpan.Type.USER -> {
|
||||
val link = permalinkBuilder.permalinkForUser(UserId(mention.rawValue)).getOrNull() ?: continue
|
||||
replace(start, end, "[${mention.rawValue}]($link)")
|
||||
permalinkBuilder.permalinkForUser(UserId(mention.rawValue)).getOrNull()?.let { link ->
|
||||
replace(start, end, "[${mention.rawValue}]($link)")
|
||||
}
|
||||
}
|
||||
MentionSpan.Type.EVERYONE -> {
|
||||
replace(start, end, "@room")
|
||||
}
|
||||
// Nothing to do here yet
|
||||
MentionSpan.Type.ROOM -> Unit
|
||||
MentionSpan.Type.ROOM -> {
|
||||
permalinkBuilder.permalinkForRoomAlias(RoomAlias(mention.rawValue)).getOrNull()?.let { link ->
|
||||
replace(start, end, "[${mention.text}]($link)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -113,13 +129,13 @@ class MarkdownTextEditorState(
|
|||
}
|
||||
}
|
||||
|
||||
fun getMentions(): List<Mention> {
|
||||
fun getMentions(): List<IntentionalMention> {
|
||||
val text = SpannableString(text.value())
|
||||
val mentionSpans = text.getSpans<MentionSpan>(0, text.length)
|
||||
return mentionSpans.mapNotNull { mentionSpan ->
|
||||
when (mentionSpan.type) {
|
||||
MentionSpan.Type.USER -> Mention.User(UserId(mentionSpan.rawValue))
|
||||
MentionSpan.Type.EVERYONE -> Mention.AtRoom
|
||||
MentionSpan.Type.USER -> IntentionalMention.User(UserId(mentionSpan.rawValue))
|
||||
MentionSpan.Type.EVERYONE -> IntentionalMention.Room
|
||||
MentionSpan.Type.ROOM -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
package io.element.android.libraries.textcomposer.model
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
|
||||
data class Message(
|
||||
val html: String?,
|
||||
val markdown: String,
|
||||
val mentions: List<Mention>,
|
||||
val intentionalMentions: List<IntentionalMention>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import io.element.android.libraries.textcomposer.components.markdown.MarkdownTex
|
|||
import io.element.android.libraries.textcomposer.components.markdown.aMarkdownTextEditorState
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpan
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
|
||||
import io.element.android.libraries.textcomposer.model.Suggestion
|
||||
import io.element.android.libraries.textcomposer.model.SuggestionType
|
||||
|
|
@ -157,13 +157,13 @@ class MarkdownTextInputTest {
|
|||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(A_SESSION_ID) })
|
||||
val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/$A_SESSION_ID") })
|
||||
val state = aMarkdownTextEditorState(initialText = "@", initialFocus = true)
|
||||
state.currentMentionSuggestion = Suggestion(0, 1, SuggestionType.Mention, "")
|
||||
state.currentSuggestion = Suggestion(0, 1, SuggestionType.Mention, "")
|
||||
rule.setMarkdownTextInput(state = state)
|
||||
var editor: EditText? = null
|
||||
rule.activityRule.scenario.onActivity {
|
||||
editor = it.findEditor()
|
||||
state.insertMention(
|
||||
ResolvedMentionSuggestion.Member(roomMember = aRoomMember()),
|
||||
state.insertSuggestion(
|
||||
ResolvedSuggestion.Member(roomMember = aRoomMember()),
|
||||
MentionSpanProvider(permalinkParser = permalinkParser),
|
||||
permalinkBuilder,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import org.junit.runner.RunWith
|
|||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class MentionSpanProviderTest {
|
||||
class IntentionalMentionSpanProviderTest {
|
||||
@JvmField @Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
|
|
@ -22,13 +22,13 @@ import androidx.core.text.inSpans
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.room.Mention
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
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.textcomposer.mentions.MentionSpan
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
|
||||
import io.element.android.libraries.textcomposer.model.Suggestion
|
||||
import io.element.android.libraries.textcomposer.model.SuggestionType
|
||||
|
|
@ -41,11 +41,11 @@ class MarkdownTextEditorStateTest {
|
|||
fun `insertMention - with no currentMentionSuggestion does nothing`() {
|
||||
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
|
||||
val member = aRoomMember()
|
||||
val mention = ResolvedMentionSuggestion.Member(member)
|
||||
val mention = ResolvedSuggestion.Member(member)
|
||||
val permalinkBuilder = FakePermalinkBuilder()
|
||||
val mentionSpanProvider = aMentionSpanProvider()
|
||||
|
||||
state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
|
||||
state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
|
||||
|
||||
assertThat(state.getMentions()).isEmpty()
|
||||
}
|
||||
|
|
@ -53,15 +53,15 @@ class MarkdownTextEditorStateTest {
|
|||
@Test
|
||||
fun `insertMention - with member but failed PermalinkBuilder result`() {
|
||||
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
|
||||
currentMentionSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
|
||||
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
|
||||
}
|
||||
val member = aRoomMember()
|
||||
val mention = ResolvedMentionSuggestion.Member(member)
|
||||
val mention = ResolvedSuggestion.Member(member)
|
||||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) })
|
||||
val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.failure(IllegalStateException("Failed")) })
|
||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||
|
||||
state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
|
||||
state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
|
||||
|
||||
val mentions = state.getMentions()
|
||||
assertThat(mentions).isEmpty()
|
||||
|
|
@ -70,36 +70,36 @@ class MarkdownTextEditorStateTest {
|
|||
@Test
|
||||
fun `insertMention - with member`() {
|
||||
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
|
||||
currentMentionSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
|
||||
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
|
||||
}
|
||||
val member = aRoomMember()
|
||||
val mention = ResolvedMentionSuggestion.Member(member)
|
||||
val mention = ResolvedSuggestion.Member(member)
|
||||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) })
|
||||
val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/${member.userId}") })
|
||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||
|
||||
state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
|
||||
state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
|
||||
|
||||
val mentions = state.getMentions()
|
||||
assertThat(mentions).isNotEmpty()
|
||||
assertThat((mentions.firstOrNull() as? Mention.User)?.userId).isEqualTo(member.userId)
|
||||
assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId).isEqualTo(member.userId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insertMention - with @room`() {
|
||||
val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
|
||||
currentMentionSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
|
||||
currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
|
||||
}
|
||||
val mention = ResolvedMentionSuggestion.AtRoom
|
||||
val mention = ResolvedSuggestion.AtRoom
|
||||
val permalinkBuilder = FakePermalinkBuilder()
|
||||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.FallbackLink(Uri.EMPTY, false) })
|
||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||
|
||||
state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
|
||||
state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
|
||||
|
||||
val mentions = state.getMentions()
|
||||
assertThat(mentions).isNotEmpty()
|
||||
assertThat(mentions.firstOrNull()).isInstanceOf(Mention.AtRoom::class.java)
|
||||
assertThat(mentions.firstOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -115,14 +115,18 @@ class MarkdownTextEditorStateTest {
|
|||
@Test
|
||||
fun `getMessageMarkdown - when there are MentionSpans returns the same text with links to the mentions`() {
|
||||
val text = "No mentions here"
|
||||
val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/$it") })
|
||||
val permalinkBuilder = FakePermalinkBuilder(
|
||||
permalinkForUserLambda = { Result.success("https://matrix.to/#/$it") },
|
||||
permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/$it") },
|
||||
)
|
||||
val state = MarkdownTextEditorState(initialText = text, initialFocus = true)
|
||||
state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
|
||||
|
||||
val markdown = state.getMessageMarkdown(permalinkBuilder = permalinkBuilder)
|
||||
|
||||
assertThat(markdown).isEqualTo(
|
||||
"Hello [@alice:matrix.org](https://matrix.to/#/@alice:matrix.org) and everyone in @room"
|
||||
"Hello [@alice:matrix.org](https://matrix.to/#/@alice:matrix.org) and everyone in @room" +
|
||||
" and a room [#room:domain.org](https://matrix.to/#/#room:domain.org)"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -141,8 +145,8 @@ class MarkdownTextEditorStateTest {
|
|||
val mentions = state.getMentions()
|
||||
|
||||
assertThat(mentions).isNotEmpty()
|
||||
assertThat((mentions.firstOrNull() as? Mention.User)?.userId?.value).isEqualTo("@alice:matrix.org")
|
||||
assertThat(mentions.lastOrNull()).isInstanceOf(Mention.AtRoom::class.java)
|
||||
assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId?.value).isEqualTo("@alice:matrix.org")
|
||||
assertThat(mentions.lastOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
|
||||
}
|
||||
|
||||
private fun aMentionSpanProvider(
|
||||
|
|
@ -154,6 +158,7 @@ class MarkdownTextEditorStateTest {
|
|||
private fun aMarkdownTextWithMentions(): CharSequence {
|
||||
val userMentionSpan = MentionSpan("@Alice", "@alice:matrix.org", MentionSpan.Type.USER)
|
||||
val atRoomMentionSpan = MentionSpan("@room", "@room", MentionSpan.Type.EVERYONE)
|
||||
val roomMentionSpan = MentionSpan("#room:domain.org", "#room:domain.org", MentionSpan.Type.ROOM)
|
||||
return buildSpannedString {
|
||||
append("Hello ")
|
||||
inSpans(userMentionSpan) {
|
||||
|
|
@ -163,6 +168,10 @@ class MarkdownTextEditorStateTest {
|
|||
inSpans(atRoomMentionSpan) {
|
||||
append("@")
|
||||
}
|
||||
append(" and a room ")
|
||||
inSpans(roomMentionSpan) {
|
||||
append("#room:domain.org")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue