From 752fc8787d409c367c782a963def5f4b27f6d95b Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 10 Dec 2024 13:29:09 +0100 Subject: [PATCH] Fixed link scrolling behaviour. --- .../others/PlatformLinkMovementMethod.kt | 127 +++++++++++------ .../views/behavior/NonScrollingTextView.kt | 134 ++++++++++++------ 2 files changed, 170 insertions(+), 91 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt index c36a1942..9faf9433 100644 --- a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt +++ b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt @@ -12,70 +12,109 @@ import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.receivers.MediaControlReceiver import com.futo.platformplayer.timestampRegex -import com.futo.platformplayer.views.behavior.NonScrollingTextView -import com.futo.platformplayer.views.behavior.NonScrollingTextView.Companion import kotlinx.coroutines.runBlocking -class PlatformLinkMovementMethod : LinkMovementMethod { - private val _context: Context; +class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMethod() { - constructor(context: Context) : super() { - _context = context; - } + private var pressedLinks: Array? = null + private var linkPressed = false + private var downX = 0f + private var downY = 0f + private val touchSlop = 20 override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { - val action = event.action; - Logger.i(TAG, "onTouchEvent (action = $action)") - if (action == MotionEvent.ACTION_UP) { - val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX; - val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY; + val action = event.actionMasked - val layout = widget.layout; - val line = layout.getLineForVertical(y); - val off = layout.getOffsetForHorizontal(line, x.toFloat()); - val links = buffer.getSpans(off, off, URLSpan::class.java); + when (action) { + MotionEvent.ACTION_DOWN -> { + val links = findLinksAtTouchPosition(widget, buffer, event) + if (links.isNotEmpty()) { + pressedLinks = links + linkPressed = true + downX = event.x + downY = event.y + widget.parent?.requestDisallowInterceptTouchEvent(true) + return true + } else { + linkPressed = false + pressedLinks = null + } + } - if (links.isNotEmpty()) { - runBlocking { - for (link in links) { - Logger.i(TAG) { "Link clicked '${link.url}'." }; + MotionEvent.ACTION_MOVE -> { + if (linkPressed) { + val dx = event.x - downX + val dy = event.y - downY + if (Math.abs(dx) > touchSlop || Math.abs(dy) > touchSlop) { + linkPressed = false + pressedLinks = null + widget.parent?.requestDisallowInterceptTouchEvent(false) + return false + } + return true + } + } - if (_context is MainActivity) { - if (_context.handleUrl(link.url)) { - continue; - } + MotionEvent.ACTION_UP -> { + if (linkPressed && pressedLinks != null) { + val dx = event.x - downX + val dy = event.y - downY + if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop) { + runBlocking { + for (link in pressedLinks!!) { + Logger.i(TAG) { "Link clicked '${link.url}'." } - if (timestampRegex.matches(link.url)) { - val tokens = link.url.split(':'); + if (_context is MainActivity) { + if (_context.handleUrl(link.url)) continue + if (timestampRegex.matches(link.url)) { + val tokens = link.url.split(':') + var time_s = -1L + when (tokens.size) { + 2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong() + 3 -> time_s = tokens[0].toLong() * 3600 + + tokens[1].toLong() * 60 + + tokens[2].toLong() + } - var time_s = -1L; - if (tokens.size == 2) { - time_s = tokens[0].toLong() * 60 + tokens[1].toLong(); - } else if (tokens.size == 3) { - time_s = - tokens[0].toLong() * 60 * 60 + tokens[1].toLong() * 60 + tokens[2].toLong(); - } - - if (time_s != -1L) { - MediaControlReceiver.onSeekToReceived.emit(time_s * 1000); - continue; + if (time_s != -1L) { + MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) + continue + } + } } + _context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) } } - - - _context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))); + pressedLinks = null + linkPressed = false + return true + } else { + pressedLinks = null + linkPressed = false } } + } - return true; + MotionEvent.ACTION_CANCEL -> { + linkPressed = false + pressedLinks = null } } - return super.onTouchEvent(widget, buffer, event); + return super.onTouchEvent(widget, buffer, event) + } + + private fun findLinksAtTouchPosition(widget: TextView, buffer: Spannable, event: MotionEvent): Array { + val x = (event.x - widget.totalPaddingLeft + widget.scrollX).toInt() + val y = (event.y - widget.totalPaddingTop + widget.scrollY).toInt() + + val layout = widget.layout ?: return emptyArray() + val line = layout.getLineForVertical(y) + val off = layout.getOffsetForHorizontal(line, x.toFloat()) + return buffer.getSpans(off, off, URLSpan::class.java) } companion object { - val TAG = "PlatformLinkMovementMethod"; + const val TAG = "PlatformLinkMovementMethod" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt index cb5e8638..402940ad 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt @@ -16,73 +16,113 @@ import com.futo.platformplayer.timestampRegex import kotlinx.coroutines.runBlocking class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { + private var _lastTouchedLinks: Array? = null + private var downX = 0f + private var downY = 0f + private var linkPressed = false + private val touchSlop = 20 + constructor(context: Context) : super(context) {} constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} override fun scrollTo(x: Int, y: Int) { - //do nothing + // do nothing } override fun onTouchEvent(event: MotionEvent?): Boolean { - val action = event?.action - Logger.i(TAG, "onTouchEvent (action = $action)"); + val action = event?.actionMasked + if (event == null) return super.onTouchEvent(event) - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { - val x = event.x.toInt() - val y = event.y.toInt() + when (action) { + MotionEvent.ACTION_DOWN -> { + val x = event.x.toInt() + val y = event.y.toInt() - val layout: Layout? = this.layout - if (layout != null) { - val line = layout.getLineForVertical(y) - val offset = layout.getOffsetForHorizontal(line, x.toFloat()) - - val text = this.text - if (text is Spannable) { + val layout: Layout? = this.layout + if (layout != null && this.text is Spannable) { + val offset = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x.toFloat()) + val text = this.text as Spannable val links = text.getSpans(offset, offset, URLSpan::class.java) if (links.isNotEmpty()) { - runBlocking { - for (link in links) { - Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." }; - - val c = context; - if (c is MainActivity) { - if (c.handleUrl(link.url)) { - continue; - } - - if (timestampRegex.matches(link.url)) { - val tokens = link.url.split(':'); - - var time_s = -1L; - if (tokens.size == 2) { - time_s = tokens[0].toLong() * 60 + tokens[1].toLong(); - } else if (tokens.size == 3) { - time_s = tokens[0].toLong() * 60 * 60 + tokens[1].toLong() * 60 + tokens[2].toLong(); - } - - if (time_s != -1L) { - MediaControlReceiver.onSeekToReceived.emit(time_s * 1000); - continue; - } - } - - c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))); - } - } - } - + parent?.requestDisallowInterceptTouchEvent(true) + _lastTouchedLinks = links + downX = event.x + downY = event.y + linkPressed = true return true + } else { + linkPressed = false + _lastTouchedLinks = null } } } + + MotionEvent.ACTION_MOVE -> { + if (linkPressed) { + val dx = event.x - downX + val dy = event.y - downY + if (Math.abs(dx) > touchSlop || Math.abs(dy) > touchSlop) { + linkPressed = false + _lastTouchedLinks = null + parent?.requestDisallowInterceptTouchEvent(false) + return false + } + return true + } + } + + MotionEvent.ACTION_UP -> { + if (linkPressed && _lastTouchedLinks != null) { + val dx = event.x - downX + val dy = event.y - downY + if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop) { + runBlocking { + for (link in _lastTouchedLinks!!) { + Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." } + val c = context + if (c is MainActivity) { + if (c.handleUrl(link.url)) continue + if (timestampRegex.matches(link.url)) { + val tokens = link.url.split(':') + var time_s = -1L + when (tokens.size) { + 2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong() + 3 -> time_s = tokens[0].toLong() * 3600 + + tokens[1].toLong() * 60 + + tokens[2].toLong() + } + if (time_s != -1L) { + MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) + continue + } + } + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } else { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } + } + } + _lastTouchedLinks = null + linkPressed = false + return true + } else { + linkPressed = false + _lastTouchedLinks = null + } + } + } + + MotionEvent.ACTION_CANCEL -> { + linkPressed = false + _lastTouchedLinks = null + } } - super.onTouchEvent(event) - return false + return super.onTouchEvent(event) } companion object { private const val TAG = "NonScrollingTextView" } -} \ No newline at end of file +}