diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt index ff225951a..7e23e45d4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt @@ -11,7 +11,8 @@ * Public License for more details. * * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ + * see . + */ @file:JvmName("LinkHelper") package com.keylesspalace.tusky.util @@ -21,12 +22,15 @@ import android.content.Context import android.content.Intent import android.graphics.Color import android.net.Uri +import android.text.Spannable import android.text.SpannableStringBuilder import android.text.Spanned import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.text.style.URLSpan import android.util.Log +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_UP import android.view.View import android.widget.TextView import androidx.annotation.VisibleForTesting @@ -68,7 +72,7 @@ fun setClickableText(view: TextView, content: CharSequence, mentions: List= length || subSequence(end, end + 1).toString() == "\n") { - insert(end, "\u200B") - } } @VisibleForTesting @@ -199,12 +196,10 @@ fun setClickableMentions(view: TextView, mentions: List?, listener: Lin append("@") append(mention.localUsername) setSpan(customSpan, start, end, flags) - append("\u200B") // same reasoning as in setClickableText - end += 1 // shift position to take the previous character into account start = end } } - view.movementMethod = LinkMovementMethod.getInstance() + view.movementMethod = NoTrailingSpaceLinkMovementMethod.getInstance() } fun createClickableText(text: String, link: String): CharSequence { @@ -322,3 +317,35 @@ fun looksLikeMastodonUrl(urlString: String): Boolean { } private const val TAG = "LinkHelper" + +/** + * [LinkMovementMethod] that doesn't add a leading/trailing clickable area. + * + * [LinkMovementMethod] has a bug in its calculation of the clickable width of a span on a line. If + * the span is the last thing on the line the clickable area extends to the end of the view. So the + * user can tap what appears to be whitespace and open a link. + * + * Fix this by overriding ACTION_UP touch events and calculating the true start and end of the + * content on the line that was tapped. Then ignore clicks that are outside this area. + * + * See https://github.com/tuskyapp/Tusky/issues/1567. + */ +object NoTrailingSpaceLinkMovementMethod : LinkMovementMethod() { + override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { + val action = event.action + if (action != ACTION_UP) return super.onTouchEvent(widget, buffer, event) + + val x = event.x.toInt() + val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY + val line = widget.layout.getLineForVertical(y) + val lineLeft = widget.layout.getLineLeft(line) + val lineRight = widget.layout.getLineRight(line) + if (x > lineRight || x >= 0 && x < lineLeft) { + return true + } + + return super.onTouchEvent(widget, buffer, event) + } + + fun getInstance() = NoTrailingSpaceLinkMovementMethod +}