From edd747327bf0f4494480cca94cced1e27bb3f33b Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 12 Feb 2026 13:12:57 +0100 Subject: [PATCH] When linkifying, adjust the `URLSpan`'s url too (#6188) --- .../androidutils/text/LinkifyHelper.kt | 9 +++---- .../androidutils/text/LinkifierHelperTest.kt | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt index 5115605322..441f819546 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt @@ -16,8 +16,6 @@ import androidx.core.text.toSpannable import androidx.core.text.util.LinkifyCompat import io.element.android.libraries.core.extensions.runCatchingExceptions import timber.log.Timber -import kotlin.collections.component1 -import kotlin.collections.component2 /** * Helper class to linkify text while preserving existing URL spans. @@ -59,7 +57,8 @@ object LinkifyHelper { // Adapt the url in the URL span to the new end index too if needed if (end != newEnd) { - val url = spannable.subSequence(start, newEnd).toString() + val diff = end - newEnd + val url = urlSpan.url.substring(0, urlSpan.url.length - diff) spannable.removeSpan(urlSpan) spannable.setSpan(URLSpan(url), start, newEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } else { @@ -87,12 +86,12 @@ object LinkifyHelper { var end = end // Trailing punctuation found, adjust the end index - while (spannable[end - 1] in sequenceOf('.', ',', ';', ':', '!', '?', '…') && end > start) { + while (end > start && spannable[end - 1] in sequenceOf('.', ',', ';', ':', '!', '?', '…')) { end-- } // If the last character is a closing parenthesis, check if it's part of a pair - if (spannable[end - 1] == ')' && end > start) { + if (end > start && spannable[end - 1] == ')') { val linkifiedTextLastPath = spannable.substring(start, end).substringAfterLast('/') val closingParenthesisCount = linkifiedTextLastPath.count { it == ')' } val openingParenthesisCount = linkifiedTextLastPath.count { it == '(' } diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt index 6fa18be1ac..22928781c9 100644 --- a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt @@ -10,7 +10,9 @@ package io.element.android.libraries.androidutils.text import android.telephony.TelephonyManager import android.text.style.URLSpan +import androidx.core.text.buildSpannedString import androidx.core.text.getSpans +import androidx.core.text.inSpans import androidx.core.text.toSpannable import com.google.common.truth.Truth.assertThat import io.element.android.tests.testutils.WarmUpRule @@ -140,4 +142,27 @@ class LinkifierHelperTest { assertThat(urlSpans.size).isEqualTo(1) assertThat(urlSpans.first().url).isEqualTo("https://github.com/element-hq/element-android/READ(ME)") } + + @Test + fun `linkification handles trailing question marks`() { + val text = "A url: https://github.com/element-hq/element-android?" + val result = LinkifyHelper.linkify(text) + val urlSpans = result.toSpannable().getSpans() + assertThat(urlSpans.size).isEqualTo(1) + assertThat(urlSpans.first().url).isEqualTo("https://github.com/element-hq/element-android") + } + + @Test + fun `linkification doesn't modify existing URLSpan`() { + val text = buildSpannedString { + append("A url: ") + inSpans(URLSpan("https://github.com/element-hq/element-android?")) { + append("here") + } + } + val result = LinkifyHelper.linkify(text) + val urlSpans = result.toSpannable().getSpans() + assertThat(urlSpans.size).isEqualTo(1) + assertThat(urlSpans.first().url).isEqualTo("https://github.com/element-hq/element-android?") + } }