diff --git a/app/src/main/java/io/legado/app/ui/widget/text/BetterLinkMovementMethod.kt b/app/src/main/java/io/legado/app/ui/widget/text/BetterLinkMovementMethod.kt
deleted file mode 100644
index da4ea65eb..000000000
--- a/app/src/main/java/io/legado/app/ui/widget/text/BetterLinkMovementMethod.kt
+++ /dev/null
@@ -1,437 +0,0 @@
-package io.legado.app.ui.widget.text
-
-import android.app.Activity
-import android.graphics.RectF
-import android.text.Selection
-import android.text.Spannable
-import android.text.Spanned
-import android.text.method.LinkMovementMethod
-import android.text.style.BackgroundColorSpan
-import android.text.style.ClickableSpan
-import android.text.style.URLSpan
-import android.text.util.Linkify
-import android.view.*
-import android.widget.TextView
-import io.legado.app.R
-import io.legado.app.ui.widget.text.BetterLinkMovementMethod.LongPressTimer.OnTimerReachedListener
-
-class BetterLinkMovementMethod protected constructor() : LinkMovementMethod() {
- private var onLinkClickListener: OnLinkClickListener? = null
- private var onLinkLongClickListener: OnLinkLongClickListener? = null
- private val touchedLineBounds = RectF()
- private var isUrlHighlighted = false
- private var clickableSpanUnderTouchOnActionDown: ClickableSpan? = null
- private var activeTextViewHashcode = 0
- private var ongoingLongPressTimer: LongPressTimer? = null
- private var wasLongPressRegistered = false
-
- interface OnLinkClickListener {
- /**
- * @param textView The TextView on which a click was registered.
- * @param url The clicked URL.
- * @return True if this click was handled. False to let Android handle the URL.
- */
- fun onClick(textView: TextView?, url: String?): Boolean
- }
-
- interface OnLinkLongClickListener {
- /**
- * @param textView The TextView on which a long-click was registered.
- * @param url The long-clicked URL.
- * @return True if this long-click was handled. False to let Android handle the URL (as a short-click).
- */
- fun onLongClick(textView: TextView?, url: String?): Boolean
- }
-
- /**
- * Set a listener that will get called whenever any link is clicked on the TextView.
- */
- fun setOnLinkClickListener(clickListener: OnLinkClickListener?): BetterLinkMovementMethod {
- if (this === singleInstance) {
- throw UnsupportedOperationException(
- "Setting a click listener on the instance returned by getInstance() is not supported to avoid memory " +
- "leaks. Please use newInstance() or any of the linkify() methods instead."
- )
- }
- onLinkClickListener = clickListener
- return this
- }
-
- /**
- * Set a listener that will get called whenever any link is clicked on the TextView.
- */
- fun setOnLinkLongClickListener(longClickListener: OnLinkLongClickListener?): BetterLinkMovementMethod {
- if (this === singleInstance) {
- throw UnsupportedOperationException(
- "Setting a long-click listener on the instance returned by getInstance() is not supported to avoid " +
- "memory leaks. Please use newInstance() or any of the linkify() methods instead."
- )
- }
- onLinkLongClickListener = longClickListener
- return this
- }
-
- override fun onTouchEvent(textView: TextView, text: Spannable, event: MotionEvent): Boolean {
- if (activeTextViewHashcode != textView.hashCode()) {
- // Bug workaround: TextView stops calling onTouchEvent() once any URL is highlighted.
- // A hacky solution is to reset any "autoLink" property set in XML. But we also want
- // to do this once per TextView.
- activeTextViewHashcode = textView.hashCode()
- textView.autoLinkMask = 0
- }
- val clickableSpanUnderTouch = findClickableSpanUnderTouch(textView, text, event)
- if (event.action == MotionEvent.ACTION_DOWN) {
- clickableSpanUnderTouchOnActionDown = clickableSpanUnderTouch
- }
- val touchStartedOverAClickableSpan = clickableSpanUnderTouchOnActionDown != null
- return when (event.action) {
- MotionEvent.ACTION_DOWN -> {
- clickableSpanUnderTouch?.let { highlightUrl(textView, it, text) }
- if (touchStartedOverAClickableSpan && onLinkLongClickListener != null) {
- val longClickListener: OnTimerReachedListener =
- object : OnTimerReachedListener {
- override fun onTimerReached() {
- wasLongPressRegistered = true
- textView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
- removeUrlHighlightColor(textView)
- dispatchUrlLongClick(textView, clickableSpanUnderTouch)
- }
- }
- startTimerForRegisteringLongClick(textView, longClickListener)
- }
- touchStartedOverAClickableSpan
- }
- MotionEvent.ACTION_UP -> {
- // Register a click only if the touch started and ended on the same URL.
- if (!wasLongPressRegistered && touchStartedOverAClickableSpan && clickableSpanUnderTouch === clickableSpanUnderTouchOnActionDown) {
- dispatchUrlClick(textView, clickableSpanUnderTouch)
- }
- cleanupOnTouchUp(textView)
-
- // Consume this event even if we could not find any spans to avoid letting Android handle this event.
- // Android's TextView implementation has a bug where links get clicked even when there is no more text
- // next to the link and the touch lies outside its bounds in the same direction.
- touchStartedOverAClickableSpan
- }
- MotionEvent.ACTION_CANCEL -> {
- cleanupOnTouchUp(textView)
- false
- }
- MotionEvent.ACTION_MOVE -> {
- // Stop listening for a long-press as soon as the user wanders off to unknown lands.
- if (clickableSpanUnderTouch !== clickableSpanUnderTouchOnActionDown) {
- removeLongPressCallback(textView)
- }
- if (!wasLongPressRegistered) {
- // Toggle highlight.
- if (clickableSpanUnderTouch != null) {
- highlightUrl(textView, clickableSpanUnderTouch, text)
- } else {
- removeUrlHighlightColor(textView)
- }
- }
- touchStartedOverAClickableSpan
- }
- else -> false
- }
- }
-
- private fun cleanupOnTouchUp(textView: TextView) {
- wasLongPressRegistered = false
- clickableSpanUnderTouchOnActionDown = null
- removeUrlHighlightColor(textView)
- removeLongPressCallback(textView)
- }
-
- /**
- * Determines the touched location inside the TextView's text and returns the ClickableSpan found under it (if any).
- *
- * @return The touched ClickableSpan or null.
- */
- protected fun findClickableSpanUnderTouch(
- textView: TextView,
- text: Spannable,
- event: MotionEvent
- ): ClickableSpan? {
- // So we need to find the location in text where touch was made, regardless of whether the TextView
- // has scrollable text. That is, not the entire text is currently visible.
- var touchX = event.x.toInt()
- var touchY = event.y.toInt()
-
- // Ignore padding.
- touchX -= textView.totalPaddingLeft
- touchY -= textView.totalPaddingTop
-
- // Account for scrollable text.
- touchX += textView.scrollX
- touchY += textView.scrollY
- val layout = textView.layout
- val touchedLine = layout.getLineForVertical(touchY)
- val touchOffset = layout.getOffsetForHorizontal(touchedLine, touchX.toFloat())
- touchedLineBounds.left = layout.getLineLeft(touchedLine)
- touchedLineBounds.top = layout.getLineTop(touchedLine).toFloat()
- touchedLineBounds.right = layout.getLineWidth(touchedLine) + touchedLineBounds.left
- touchedLineBounds.bottom = layout.getLineBottom(touchedLine).toFloat()
- return if (touchedLineBounds.contains(touchX.toFloat(), touchY.toFloat())) {
- // Find a ClickableSpan that lies under the touched area.
- val spans = text.getSpans(touchOffset, touchOffset, ClickableSpan::class.java)
- for (span in spans) {
- if (span is ClickableSpan) {
- return span
- }
- }
- // No ClickableSpan found under the touched location.
- null
- } else {
- // Touch lies outside the line's horizontal bounds where no spans should exist.
- null
- }
- }
-
- /**
- * Adds a background color span at clickableSpan's location.
- */
- protected fun highlightUrl(textView: TextView, clickableSpan: ClickableSpan?, text: Spannable) {
- if (isUrlHighlighted) {
- return
- }
- isUrlHighlighted = true
- val spanStart = text.getSpanStart(clickableSpan)
- val spanEnd = text.getSpanEnd(clickableSpan)
- val highlightSpan = BackgroundColorSpan(textView.highlightColor)
- text.setSpan(highlightSpan, spanStart, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
- textView.setTag(R.id.bettermovementmethod_highlight_background_span, highlightSpan)
- Selection.setSelection(text, spanStart, spanEnd)
- }
-
- /**
- * Removes the highlight color under the Url.
- */
- protected fun removeUrlHighlightColor(textView: TextView) {
- if (!isUrlHighlighted) {
- return
- }
- isUrlHighlighted = false
- val text = textView.text as Spannable
- val highlightSpan =
- textView.getTag(R.id.bettermovementmethod_highlight_background_span) as BackgroundColorSpan
- text.removeSpan(highlightSpan)
- Selection.removeSelection(text)
- }
-
- protected fun startTimerForRegisteringLongClick(
- textView: TextView,
- longClickListener: OnTimerReachedListener?
- ) {
- ongoingLongPressTimer = LongPressTimer()
- ongoingLongPressTimer!!.setOnTimerReachedListener(longClickListener)
- textView.postDelayed(
- ongoingLongPressTimer,
- ViewConfiguration.getLongPressTimeout().toLong()
- )
- }
-
- /**
- * Remove the long-press detection timer.
- */
- protected fun removeLongPressCallback(textView: TextView) {
- if (ongoingLongPressTimer != null) {
- textView.removeCallbacks(ongoingLongPressTimer)
- ongoingLongPressTimer = null
- }
- }
-
- protected fun dispatchUrlClick(textView: TextView, clickableSpan: ClickableSpan?) {
- val clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan)
- val handled = onLinkClickListener != null && onLinkClickListener!!.onClick(
- textView,
- clickableSpanWithText.text()
- )
- if (!handled) {
- // Let Android handle this click.
- clickableSpanWithText.span()!!.onClick(textView)
- }
- }
-
- protected fun dispatchUrlLongClick(textView: TextView, clickableSpan: ClickableSpan?) {
- val clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan)
- val handled = onLinkLongClickListener != null && onLinkLongClickListener!!.onLongClick(
- textView,
- clickableSpanWithText.text()
- )
- if (!handled) {
- // Let Android handle this long click as a short-click.
- clickableSpanWithText.span()!!.onClick(textView)
- }
- }
-
- protected class LongPressTimer : Runnable {
- private var onTimerReachedListener: OnTimerReachedListener? = null
-
- interface OnTimerReachedListener {
- fun onTimerReached()
- }
-
- override fun run() {
- onTimerReachedListener!!.onTimerReached()
- }
-
- fun setOnTimerReachedListener(listener: OnTimerReachedListener?) {
- onTimerReachedListener = listener
- }
- }
-
- /**
- * A wrapper to support all [ClickableSpan]s that may or may not provide URLs.
- */
- protected class ClickableSpanWithText protected constructor(
- private val span: ClickableSpan?,
- private val text: String
- ) {
- fun span(): ClickableSpan? {
- return span
- }
-
- fun text(): String {
- return text
- }
-
- companion object {
- fun ofSpan(textView: TextView, span: ClickableSpan?): ClickableSpanWithText {
- val s = textView.text as Spanned
- val text: String
- text = if (span is URLSpan) {
- span.url
- } else {
- val start = s.getSpanStart(span)
- val end = s.getSpanEnd(span)
- s.subSequence(start, end).toString()
- }
- return ClickableSpanWithText(span, text)
- }
- }
- }
-
- companion object {
- private var singleInstance: BetterLinkMovementMethod? = null
- private const val LINKIFY_NONE = -2
-
- /**
- * Return a new instance of BetterLinkMovementMethod.
- */
- fun newInstance(): BetterLinkMovementMethod {
- return BetterLinkMovementMethod()
- }
-
- /**
- * @param linkifyMask One of [Linkify.ALL], [Linkify.PHONE_NUMBERS], [Linkify.MAP_ADDRESSES],
- * [Linkify.WEB_URLS] and [Linkify.EMAIL_ADDRESSES].
- * @param textViews The TextViews on which a [BetterLinkMovementMethod] should be registered.
- * @return The registered [BetterLinkMovementMethod] on the TextViews.
- */
- fun linkify(linkifyMask: Int, vararg textViews: TextView): BetterLinkMovementMethod {
- val movementMethod = newInstance()
- for (textView in textViews) {
- addLinks(linkifyMask, movementMethod, textView)
- }
- return movementMethod
- }
-
- /**
- * Like [.linkify], but can be used for TextViews with HTML links.
- *
- * @param textViews The TextViews on which a [BetterLinkMovementMethod] should be registered.
- * @return The registered [BetterLinkMovementMethod] on the TextViews.
- */
- fun linkifyHtml(vararg textViews: TextView): BetterLinkMovementMethod {
- return linkify(LINKIFY_NONE, *textViews)
- }
-
- /**
- * Recursively register a [BetterLinkMovementMethod] on every TextView inside a layout.
- *
- * @param linkifyMask One of [Linkify.ALL], [Linkify.PHONE_NUMBERS], [Linkify.MAP_ADDRESSES],
- * [Linkify.WEB_URLS] and [Linkify.EMAIL_ADDRESSES].
- * @return The registered [BetterLinkMovementMethod] on the TextViews.
- */
- fun linkify(linkifyMask: Int, viewGroup: ViewGroup): BetterLinkMovementMethod {
- val movementMethod = newInstance()
- rAddLinks(linkifyMask, viewGroup, movementMethod)
- return movementMethod
- }
-
- /**
- * Like [.linkify], but can be used for TextViews with HTML links.
- *
- * @return The registered [BetterLinkMovementMethod] on the TextViews.
- */
- fun linkifyHtml(viewGroup: ViewGroup): BetterLinkMovementMethod {
- return linkify(LINKIFY_NONE, viewGroup)
- }
-
- /**
- * Recursively register a [BetterLinkMovementMethod] on every TextView inside a layout.
- *
- * @param linkifyMask One of [Linkify.ALL], [Linkify.PHONE_NUMBERS], [Linkify.MAP_ADDRESSES],
- * [Linkify.WEB_URLS] and [Linkify.EMAIL_ADDRESSES].
- * @return The registered [BetterLinkMovementMethod] on the TextViews.
- */
- fun linkify(linkifyMask: Int, activity: Activity): BetterLinkMovementMethod {
- // Find the layout passed to setContentView().
- val activityLayout =
- (activity.findViewById(Window.ID_ANDROID_CONTENT) as ViewGroup).getChildAt(0) as ViewGroup
- val movementMethod = newInstance()
- rAddLinks(linkifyMask, activityLayout, movementMethod)
- return movementMethod
- }
-
- /**
- * Like [.linkify], but can be used for TextViews with HTML links.
- *
- * @return The registered [BetterLinkMovementMethod] on the TextViews.
- */
- fun linkifyHtml(activity: Activity): BetterLinkMovementMethod {
- return linkify(LINKIFY_NONE, activity)
- }
-
- /**
- * Get a static instance of BetterLinkMovementMethod. Do note that registering a click listener on the returned
- * instance is not supported because it will potentially be shared on multiple TextViews.
- */
- val instance: BetterLinkMovementMethod?
- get() {
- if (singleInstance == null) {
- singleInstance = BetterLinkMovementMethod()
- }
- return singleInstance
- }
-
- // ======== PUBLIC APIs END ======== //
- private fun rAddLinks(
- linkifyMask: Int,
- viewGroup: ViewGroup,
- movementMethod: BetterLinkMovementMethod
- ) {
- for (i in 0 until viewGroup.childCount) {
- val child = viewGroup.getChildAt(i)
- if (child is ViewGroup) {
- // Recursively find child TextViews.
- rAddLinks(linkifyMask, child, movementMethod)
- } else if (child is TextView) {
- addLinks(linkifyMask, movementMethod, child)
- }
- }
- }
-
- private fun addLinks(
- linkifyMask: Int,
- movementMethod: BetterLinkMovementMethod,
- textView: TextView
- ) {
- textView.movementMethod = movementMethod
- if (linkifyMask != LINKIFY_NONE) {
- Linkify.addLinks(textView, linkifyMask)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/widget/text/InertiaScrollTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/InertiaScrollTextView.kt
index d5a376893..41afecaff 100644
--- a/app/src/main/java/io/legado/app/ui/widget/text/InertiaScrollTextView.kt
+++ b/app/src/main/java/io/legado/app/ui/widget/text/InertiaScrollTextView.kt
@@ -2,6 +2,7 @@ package io.legado.app.ui.widget.text
import android.annotation.SuppressLint
import android.content.Context
+import android.text.method.LinkMovementMethod
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
@@ -48,7 +49,7 @@ open class InertiaScrollTextView @JvmOverloads constructor(
mTouchSlop = vc.scaledTouchSlop
mMinFlingVelocity = vc.scaledMinimumFlingVelocity
mMaxFlingVelocity = vc.scaledMaximumFlingVelocity
- movementMethod = BetterLinkMovementMethod.instance
+ movementMethod = LinkMovementMethod.getInstance()
}
fun atTop(): Boolean {