From 2225a14e13077259cf306a7483402627c19f424d Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 10 Nov 2025 11:36:54 -0400 Subject: [PATCH] Allow users to confirm link preview on text story before it loads. --- .../securesms/linkpreview/Link.java | 20 ------------------- .../securesms/linkpreview/Link.kt | 7 +++++++ .../securesms/linkpreview/LinkPreviewState.kt | 19 ++++++++++++------ .../linkpreview/LinkPreviewUtil.java | 4 ++-- .../linkpreview/LinkPreviewViewModel.java | 10 +++++----- .../linkpreview/LinkPreviewViewModelV2.kt | 4 ++-- .../v2/text/TextStoryPostLinkEntryFragment.kt | 5 ++--- 7 files changed, 31 insertions(+), 38 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.java deleted file mode 100644 index d58b370475..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.thoughtcrime.securesms.linkpreview; - -public class Link { - - private final String url; - private final int position; - - public Link(String url, int position) { - this.url = url; - this.position = position; - } - - public String getUrl() { - return url; - } - - public int getPosition() { - return position; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.kt b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.kt new file mode 100644 index 0000000000..6221912d29 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/Link.kt @@ -0,0 +1,7 @@ +package org.thoughtcrime.securesms.linkpreview + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +class Link(@JvmField val url: String, @JvmField val position: Int) : Parcelable diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewState.kt b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewState.kt index 9d475d9ff4..b23fd9430e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewState.kt @@ -15,7 +15,8 @@ class LinkPreviewState private constructor( @JvmField val isLoading: Boolean, private val hasLinks: Boolean, private val preview: LinkPreview?, - @JvmField val error: LinkPreviewRepository.Error? + @JvmField val error: LinkPreviewRepository.Error?, + @JvmField val link: Link? ) : Parcelable { @IgnoredOnParcel @@ -30,15 +31,18 @@ class LinkPreviewState private constructor( return isLoading || hasLinks } + val url: String? = link?.url ?: preview?.url ?: activeUrlForError + companion object { @JvmStatic - fun forLoading(): LinkPreviewState { + fun forLoading(link: Link): LinkPreviewState { return LinkPreviewState( activeUrlForError = null, isLoading = true, hasLinks = false, preview = null, - error = null + error = null, + link = link ) } @@ -49,7 +53,8 @@ class LinkPreviewState private constructor( isLoading = false, hasLinks = true, preview = linkPreview, - error = null + error = null, + link = null ) } @@ -60,7 +65,8 @@ class LinkPreviewState private constructor( isLoading = false, hasLinks = true, preview = null, - error = error + error = error, + link = null ) } @@ -71,7 +77,8 @@ class LinkPreviewState private constructor( isLoading = false, hasLinks = false, preview = null, - error = null + error = null, + link = null ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java index 9a50d21def..c04f8b5766 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java @@ -62,7 +62,7 @@ public final class LinkPreviewUtil { return new Links(Stream.of(spannable.getSpans(0, spannable.length(), URLSpan.class)) .map(span -> new Link(span.getURL(), spannable.getSpanStart(span))) - .filter(link -> LinkUtil.isValidPreviewUrl(link.getUrl())) + .filter(link -> LinkUtil.isValidPreviewUrl(link.url)) .toList()); } @@ -180,7 +180,7 @@ public final class LinkPreviewUtil { private Links(@NonNull List links) { this.links = links; this.urlSet = Stream.of(links) - .map(link -> trimTrailingSlash(link.getUrl())) + .map(link -> trimTrailingSlash(link.url)) .collect(Collectors.toSet()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java index 1b29a9df8f..3e8005b605 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java @@ -124,7 +124,7 @@ public class LinkPreviewViewModel extends ViewModel { Optional link = LinkPreviewUtil.findValidPreviewUrls(text) .findFirst(); - if (link.isPresent() && link.get().getUrl().equals(activeUrl)) { + if (link.isPresent() && link.get().url.equals(activeUrl)) { return; } @@ -139,9 +139,9 @@ public class LinkPreviewViewModel extends ViewModel { return; } - linkPreviewState.setValue(LinkPreviewState.forLoading()); + linkPreviewState.setValue(LinkPreviewState.forLoading(link.get())); - activeUrl = link.get().getUrl(); + activeUrl = link.get().url; activeRequest = enabled ? performRequest(activeUrl) : createPlaceholder(activeUrl); }); } @@ -186,11 +186,11 @@ public class LinkPreviewViewModel extends ViewModel { return true; } - if (text.endsWith(link.getUrl()) && cursorStart == link.getPosition() + link.getUrl().length()) { + if (text.endsWith(link.url) && cursorStart == link.position + link.url.length()) { return true; } - return cursorStart < link.getPosition() || cursorStart > link.getPosition() + link.getUrl().length(); + return cursorStart < link.position || cursorStart > link.position + link.url.length(); } private @Nullable RequestController createPlaceholder(String url) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModelV2.kt b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModelV2.kt index 767751806e..dfa279a945 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModelV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModelV2.kt @@ -88,7 +88,7 @@ class LinkPreviewViewModelV2( } val link: Optional = LinkPreviewUtil.findValidPreviewUrls(text).findFirst() - if (link.isPresent && link.get().url.equals(activeUrl)) { + if (link.isPresent && link.get().url == activeUrl) { return@publish } @@ -100,7 +100,7 @@ class LinkPreviewViewModelV2( return@publish } - setLinkPreviewState(LinkPreviewState.forLoading()) + setLinkPreviewState(LinkPreviewState.forLoading(link.get())) val activeUrl = link.get().url this.activeUrl = activeUrl diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostLinkEntryFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostLinkEntryFragment.kt index 57ccdabb6a..e8a6c81fdb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostLinkEntryFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostLinkEntryFragment.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.mediasend.v2.text import android.content.DialogInterface import android.os.Bundle -import android.text.TextUtils import android.view.View import android.widget.EditText import androidx.constraintlayout.widget.Group @@ -64,7 +63,7 @@ class TextStoryPostLinkEntryFragment(private val shouldPreset: Boolean = false) confirmButton.setOnClickListener { val linkPreviewState = linkPreviewViewModel.linkPreviewState.value if (linkPreviewState != null) { - val url = linkPreviewState.linkPreview.map { it.url }.orElseGet { linkPreviewState.activeUrlForError } + val url = linkPreviewState.url ?: "" if (LinkUtil.isValidTextStoryPostPreview(url)) { viewModel.setLinkPreview(url) @@ -82,7 +81,7 @@ class TextStoryPostLinkEntryFragment(private val shouldPreset: Boolean = false) linkPreviewViewModel.linkPreviewState.observe(viewLifecycleOwner) { state -> linkPreview.bind(state, useLargeThumbnail = false) shareALinkGroup.visible = !state.isLoading && !state.linkPreview.isPresent && (state.error == null && state.activeUrlForError == null) - confirmButton.isEnabled = state.linkPreview.isPresent || !TextUtils.isEmpty(state.activeUrlForError) + confirmButton.isEnabled = state.url != null } }