Ignore clicks outside the start/end of a line (#3380)
* Ignore clicks outside the start/end of a line `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. Previous code tried to fix this by adding a zero-width space after the link so that `LinkMovementMethod` wouldn't consider it empty. However the ZWS was selected by copy/paste operations, resulting in junk results if users tried to copy the link. Fix this by subclassing `LinkMovementMethod` and fixing the click detection code to ignore clicks that are outside the bounds of the line that was clicked on. Remove the code that adds the ZWS. Fixes https://github.com/tuskyapp/Tusky/issues/1567 * Assume arguments are all non-null * Use `object` for singleton * getInstance as a one-liner
This commit is contained in:
parent
9340e7a6f4
commit
10f983c953
|
@ -11,7 +11,8 @@
|
||||||
* Public License for more details.
|
* Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
* see <http://www.gnu.org/licenses>. */
|
* see <http://www.gnu.org/licenses>.
|
||||||
|
*/
|
||||||
@file:JvmName("LinkHelper")
|
@file:JvmName("LinkHelper")
|
||||||
|
|
||||||
package com.keylesspalace.tusky.util
|
package com.keylesspalace.tusky.util
|
||||||
|
@ -21,12 +22,15 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.Spannable
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.MotionEvent.ACTION_UP
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
@ -68,7 +72,7 @@ fun setClickableText(view: TextView, content: CharSequence, mentions: List<Menti
|
||||||
setClickableText(it, this, mentions, tags, listener)
|
setClickableText(it, this, mentions, tags, listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.movementMethod = LinkMovementMethod.getInstance()
|
view.movementMethod = NoTrailingSpaceLinkMovementMethod.getInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -125,13 +129,6 @@ fun setClickableText(
|
||||||
|
|
||||||
removeSpan(span)
|
removeSpan(span)
|
||||||
setSpan(customSpan, start, end, flags)
|
setSpan(customSpan, start, end, flags)
|
||||||
|
|
||||||
/* Add zero-width space after links in end of line to fix its too large hitbox.
|
|
||||||
* See also : https://github.com/tuskyapp/Tusky/issues/846
|
|
||||||
* https://github.com/tuskyapp/Tusky/pull/916 */
|
|
||||||
if (end >= length || subSequence(end, end + 1).toString() == "\n") {
|
|
||||||
insert(end, "\u200B")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -199,12 +196,10 @@ fun setClickableMentions(view: TextView, mentions: List<Mention>?, listener: Lin
|
||||||
append("@")
|
append("@")
|
||||||
append(mention.localUsername)
|
append(mention.localUsername)
|
||||||
setSpan(customSpan, start, end, flags)
|
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
|
start = end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.movementMethod = LinkMovementMethod.getInstance()
|
view.movementMethod = NoTrailingSpaceLinkMovementMethod.getInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createClickableText(text: String, link: String): CharSequence {
|
fun createClickableText(text: String, link: String): CharSequence {
|
||||||
|
@ -322,3 +317,35 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "LinkHelper"
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue