Merge pull request #2040 from element-hq/feature/bma/fixLinkify
fix linkify
This commit is contained in:
commit
d6ada81bcd
9 changed files with 127 additions and 53 deletions
|
|
@ -21,6 +21,7 @@ import android.text.style.URLSpan
|
|||
import android.text.util.Linkify
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.getSpans
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.text.util.LinkifyCompat
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.messages.api.timeline.HtmlConverterProvider
|
||||
|
|
@ -68,12 +69,15 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
|
||||
suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent {
|
||||
return when (val messageType = content.type) {
|
||||
is EmoteMessageType -> TimelineItemEmoteContent(
|
||||
body = "* $senderDisplayName ${messageType.body}",
|
||||
htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"),
|
||||
formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisplayName"),
|
||||
isEdited = content.isEdited,
|
||||
)
|
||||
is EmoteMessageType -> {
|
||||
val emoteBody = "* $senderDisplayName ${messageType.body}"
|
||||
TimelineItemEmoteContent(
|
||||
body = emoteBody,
|
||||
htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"),
|
||||
formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisplayName") ?: emoteBody.withLinks(),
|
||||
isEdited = content.isEdited,
|
||||
)
|
||||
}
|
||||
is ImageMessageType -> {
|
||||
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
||||
TimelineItemImageContent(
|
||||
|
|
@ -171,21 +175,21 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
is NoticeMessageType -> TimelineItemNoticeContent(
|
||||
body = messageType.body,
|
||||
htmlDocument = messageType.formatted?.toHtmlDocument(),
|
||||
formattedBody = parseHtml(messageType.formatted),
|
||||
formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(),
|
||||
isEdited = content.isEdited,
|
||||
)
|
||||
is TextMessageType -> {
|
||||
TimelineItemTextContent(
|
||||
body = messageType.body,
|
||||
htmlDocument = messageType.formatted?.toHtmlDocument(),
|
||||
formattedBody = parseHtml(messageType.formatted),
|
||||
formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(),
|
||||
isEdited = content.isEdited,
|
||||
)
|
||||
}
|
||||
is OtherMessageType -> TimelineItemTextContent(
|
||||
body = messageType.body,
|
||||
htmlDocument = null,
|
||||
formattedBody = null,
|
||||
formattedBody = messageType.body.withLinks(),
|
||||
isEdited = content.isEdited,
|
||||
)
|
||||
}
|
||||
|
|
@ -226,7 +230,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
Pair(start, end)
|
||||
}
|
||||
// Find and set as URLSpans any links present in the text
|
||||
LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS)
|
||||
LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
|
||||
// Restore old spans if they don't conflict with the new ones
|
||||
for ((urlSpan, location) in oldURLSpans) {
|
||||
val (start, end) = location
|
||||
|
|
@ -237,3 +241,11 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
return this
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("USELESS_ELVIS")
|
||||
private fun String.withLinks(): CharSequence? {
|
||||
/* Note: toSpannable() can return null when running unit tests */
|
||||
val spannable = toSpannable() ?: return null
|
||||
val addedLinks = LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
|
||||
return spannable.takeIf { addedLinks }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@
|
|||
package io.element.android.features.messages.impl.timeline.factories.event
|
||||
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.style.URLSpan
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.location.api.Location
|
||||
|
|
@ -144,9 +145,40 @@ class TimelineItemContentMessageFactoryTest {
|
|||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create TextMessageType with simple link`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = TextMessageType("https://www.example.org", null)),
|
||||
senderDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
) as TimelineItemTextContent
|
||||
val expected = TimelineItemTextContent(
|
||||
body = "https://www.example.org",
|
||||
htmlDocument = null,
|
||||
plainText = "https://www.example.org",
|
||||
isEdited = false,
|
||||
formattedBody = buildSpannedString {
|
||||
inSpans(URLSpan("https://www.example.org")) {
|
||||
append("https://www.example.org")
|
||||
}
|
||||
}
|
||||
)
|
||||
assertThat(result.body).isEqualTo(expected.body)
|
||||
assertThat(result.htmlDocument).isEqualTo(expected.htmlDocument)
|
||||
assertThat(result.plainText).isEqualTo(expected.plainText)
|
||||
assertThat(result.isEdited).isEqualTo(expected.isEdited)
|
||||
assertThat(result.formattedBody).isInstanceOf(Spanned::class.java)
|
||||
val spanned = result.formattedBody as Spanned
|
||||
assertThat(spanned.toString()).isEqualTo("https://www.example.org")
|
||||
val urlSpans = spanned.getSpans(0, spanned.length, URLSpan::class.java)
|
||||
assertThat(urlSpans).hasLength(1)
|
||||
assertThat(urlSpans[0].url).isEqualTo("https://www.example.org")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test create TextMessageType with HTML formatted body`() = runTest {
|
||||
val expected = SpannableStringBuilder().apply {
|
||||
val expected = buildSpannedString {
|
||||
append("link to ")
|
||||
inSpans(URLSpan("https://matrix.org")) {
|
||||
append("https://matrix.org")
|
||||
|
|
@ -160,10 +192,12 @@ class TimelineItemContentMessageFactoryTest {
|
|||
htmlConverterTransform = { expected }
|
||||
)
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = TextMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.HTML, expected.toString())
|
||||
)),
|
||||
content = createMessageContent(
|
||||
type = TextMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.HTML, expected.toString())
|
||||
)
|
||||
),
|
||||
senderDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
|
|
@ -176,10 +210,12 @@ class TimelineItemContentMessageFactoryTest {
|
|||
htmlConverterTransform = { it }
|
||||
)
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = TextMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.UNKNOWN, "formatted")
|
||||
)),
|
||||
content = createMessageContent(
|
||||
type = TextMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.UNKNOWN, "formatted")
|
||||
)
|
||||
),
|
||||
senderDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
|
|
@ -520,10 +556,12 @@ class TimelineItemContentMessageFactoryTest {
|
|||
fun `test create NoticeMessageType with HTML formatted body`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = NoticeMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted")
|
||||
)),
|
||||
content = createMessageContent(
|
||||
type = NoticeMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted")
|
||||
)
|
||||
),
|
||||
senderDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
|
|
@ -552,10 +590,12 @@ class TimelineItemContentMessageFactoryTest {
|
|||
fun `test create EmoteMessageType with HTML formatted body`() = runTest {
|
||||
val sut = createTimelineItemContentMessageFactory()
|
||||
val result = sut.create(
|
||||
content = createMessageContent(type = EmoteMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted")
|
||||
)),
|
||||
content = createMessageContent(
|
||||
type = EmoteMessageType(
|
||||
body = "body",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "formatted")
|
||||
)
|
||||
),
|
||||
senderDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue