From 36c91a95e2df66af3a2b0a912ebdcbc595c3c0d7 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 2 May 2024 16:31:58 -0300 Subject: [PATCH] Check spannable intersect when moving between stories. --- .../viewer/page/StoryViewerPageFragment.kt | 56 ++++++++++++++++++- .../util/LongClickMovementMethod.java | 1 - 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index b74bedfe31..f15a2615df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -10,12 +10,15 @@ import android.graphics.drawable.Drawable import android.media.AudioManager import android.os.Bundle import android.text.SpannableString +import android.text.Spanned import android.text.method.LinkMovementMethod import android.text.method.ScrollingMovementMethod +import android.text.style.ClickableSpan import android.view.GestureDetector import android.view.MotionEvent import android.view.ScaleGestureDetector import android.view.View +import android.view.ViewGroup import android.view.animation.Interpolator import android.widget.FrameLayout import android.widget.TextView @@ -25,6 +28,7 @@ import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.view.GestureDetectorCompat import androidx.core.view.animation.PathInterpolatorCompat +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -47,6 +51,7 @@ import org.thoughtcrime.securesms.components.AvatarImageView import org.thoughtcrime.securesms.components.emoji.EmojiTextView import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBar import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBarListener +import org.thoughtcrime.securesms.components.spoiler.SpoilerAnnotation import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto20dp import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto @@ -67,6 +72,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.stories.StorySlateView +import org.thoughtcrime.securesms.stories.StoryTextPostView import org.thoughtcrime.securesms.stories.StoryVolumeOverlayView import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs @@ -84,6 +90,7 @@ import org.thoughtcrime.securesms.util.AvatarUtil import org.thoughtcrime.securesms.util.BottomSheetUtil import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.Debouncer +import org.thoughtcrime.securesms.util.Projection import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.fragments.requireListener @@ -262,7 +269,10 @@ class StoryViewerPageFragment : scaleListener ) - cardWrapper.setOnInterceptTouchEventListener { !storySlate.state.hasClickableContent && viewModel.getPost()?.content?.isText() != true } + cardWrapper.setOnInterceptTouchEventListener { + !storySlate.state.hasClickableContent && !checkEventIntersectsClickableSpan(cardWrapper, it) + } + cardWrapper.setOnTouchListener { _, event -> scaleDetector.onTouchEvent(event) val result = if (scaleDetector.isInProgress || scaleListener.isPerformingEndAnimation) { @@ -533,6 +543,50 @@ class StoryViewerPageFragment : viewModel.setIsDisplayingForwardDialog(false) } + private fun checkEventIntersectsClickableSpan(cardWrapper: ViewGroup, event: MotionEvent): Boolean { + if (viewModel.getPost()?.content?.isText() != true) { + return false + } + + val action = event.action + if (action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_UP) { + return false + } + + val storyTextPostView = cardWrapper.findViewById(R.id.text) + val textView = storyTextPostView.findViewById(R.id.text_story_post_text) + val spanned = textView.text as? Spanned ?: return false + + val textViewProjection = Projection.relativeToParent(cardWrapper, textView, null) + var x = event.x - textViewProjection.x + var y = event.y - textViewProjection.y + + textViewProjection.release() + + x -= textView.totalPaddingLeft + y -= textView.totalPaddingTop + + x += textView.scrollX + y += textView.scrollY + + val layout = textView.layout + val line = layout.getLineForVertical(y.toInt()) + val off = layout.getOffsetForHorizontal(line, x) + + val spoilers = spanned.getSpans(off, off, SpoilerAnnotation.SpoilerClickableSpan::class.java) + if (spoilers.isNotEmpty()) { + return true + } + + val clickables = spanned.getSpans(off, off, ClickableSpan::class.java) + if (clickables.isNotEmpty()) { + return true + } + + val linkPreview = storyTextPostView.findViewById(R.id.text_story_post_link_preview) + return linkPreview.isVisible + } + private fun calculateDurationForText(textContent: StoryPost.Content.TextContent): Long { return calculateDurationForContentLength(textContent.length) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java b/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java index 9a8f8cd4f0..1cfe455bd7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/LongClickMovementMethod.java @@ -12,7 +12,6 @@ import android.view.MotionEvent; import android.view.View; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import org.thoughtcrime.securesms.R;