diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 2fc3d8292c..6e5a097846 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -54,6 +54,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.DimenRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.core.text.util.LinkifyCompat; @@ -1444,6 +1445,29 @@ public final class ConversationItem extends RelativeLayout implements BindableCo private void linkifyMessageBody(@NonNull Spannable messageBody, boolean shouldLinkifyAllLinks) { + linkifyUrlLinks(messageBody, shouldLinkifyAllLinks, urlClickListener); + + if (conversationMessage.hasStyleLinks()) { + for (PlaceholderURLSpan placeholder : messageBody.getSpans(0, messageBody.length(), PlaceholderURLSpan.class)) { + int start = messageBody.getSpanStart(placeholder); + int end = messageBody.getSpanEnd(placeholder); + URLSpan span = new InterceptableLongClickCopyLinkSpan(placeholder.getValue(), + urlClickListener, + ContextCompat.getColor(getContext(), R.color.signal_accent_primary), + false); + + messageBody.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + List mentionAnnotations = MentionAnnotation.getMentionAnnotations(messageBody); + for (Annotation annotation : mentionAnnotations) { + messageBody.setSpan(new MentionClickableSpan(RecipientId.from(annotation.getValue())), messageBody.getSpanStart(annotation), messageBody.getSpanEnd(annotation), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + @VisibleForTesting + static void linkifyUrlLinks(@NonNull Spannable messageBody, boolean shouldLinkifyAllLinks, @NonNull UrlClickHandler urlClickHandler) { int linkPattern = Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS; boolean hasLinks = LinkifyCompat.addLinks(messageBody, shouldLinkifyAllLinks ? linkPattern : 0); @@ -1471,28 +1495,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo for (URLSpan urlSpan : urlSpans) { int start = messageBody.getSpanStart(urlSpan); int end = messageBody.getSpanEnd(urlSpan); - URLSpan span = new InterceptableLongClickCopyLinkSpan(urlSpan.getURL(), urlClickListener); + URLSpan span = new InterceptableLongClickCopyLinkSpan(urlSpan.getURL(), urlClickHandler); messageBody.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } - - if (conversationMessage.hasStyleLinks()) { - for (PlaceholderURLSpan placeholder : messageBody.getSpans(0, messageBody.length(), PlaceholderURLSpan.class)) { - int start = messageBody.getSpanStart(placeholder); - int end = messageBody.getSpanEnd(placeholder); - URLSpan span = new InterceptableLongClickCopyLinkSpan(placeholder.getValue(), - urlClickListener, - ContextCompat.getColor(getContext(), R.color.signal_accent_primary), - false); - - messageBody.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - - List mentionAnnotations = MentionAnnotation.getMentionAnnotations(messageBody); - for (Annotation annotation : mentionAnnotations) { - messageBody.setSpan(new MentionClickableSpan(RecipientId.from(annotation.getValue())), messageBody.getSpanStart(annotation), messageBody.getSpanEnd(annotation), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } } private void setStatusIcons(MessageRecord messageRecord, boolean hasWallpaper) { diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/ConversationItemTest_linkifyUrlLinks.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/ConversationItemTest_linkifyUrlLinks.kt new file mode 100644 index 0000000000..add1cf7b0d --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/ConversationItemTest_linkifyUrlLinks.kt @@ -0,0 +1,44 @@ +package org.thoughtcrime.securesms.conversation + +import android.app.Application +import android.text.SpannableStringBuilder +import android.text.style.URLSpan +import android.text.util.Linkify +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.ParameterizedRobolectricTestRunner +import org.robolectric.annotation.Config +import org.thoughtcrime.securesms.util.UrlClickHandler + +@Suppress("ClassName") +@RunWith(ParameterizedRobolectricTestRunner::class) +@Config(application = Application::class) +class ConversationItemTest_linkifyUrlLinks(private val input: String, private val expectedUrl: String) { + + @Test + fun test1() { + val spannableStringBuilder = SpannableStringBuilder(input) + Linkify.addLinks(spannableStringBuilder, Linkify.WEB_URLS) + ConversationItem.linkifyUrlLinks(spannableStringBuilder, true, UrlHandler) + val spans = spannableStringBuilder.getSpans(0, expectedUrl.length, URLSpan::class.java) + assertEquals(2, spans.size) + } + + private object UrlHandler : UrlClickHandler { + override fun handleOnClick(url: String): Boolean = true + } + + companion object { + @JvmStatic + @ParameterizedRobolectricTestRunner.Parameters(name = "Input: {0}, {1}") + fun params() = listOf( + arrayOf("https://www.google.com", "https://www.google.com"), + arrayOf("https://www.google.com%d332", "https://www.google.com"), + arrayOf("https://www.instagram.com/tv/CfImYdngccQ/?igshid=YmMyMTA2M2Y= ", "https://www.instagram.com/tv/CfImYdngccQ/?igshid=YmMyMTA2M2Y="), + arrayOf("https://www.instagram.com/tv/CfImYdngccQ/?igshid=YmMyMTA2M2Y=\n", "https://www.instagram.com/tv/CfImYdngccQ/?igshid=YmMyMTA2M2Y="), + arrayOf("https://fr.ulule.com/sapins-barbus-la-bd-/ ", "https://fr.ulule.com/sapins-barbus-la-bd-/"), + arrayOf("https://fr.ulule.com/sapins-barbus-la-bd-/\n", "https://fr.ulule.com/sapins-barbus-la-bd-/") + ) + } +}