Fixed link scrolling behaviour.

This commit is contained in:
Koen J 2024-12-10 13:29:09 +01:00
commit 752fc8787d
2 changed files with 170 additions and 91 deletions

View file

@ -12,70 +12,109 @@ import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.receivers.MediaControlReceiver import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.timestampRegex import com.futo.platformplayer.timestampRegex
import com.futo.platformplayer.views.behavior.NonScrollingTextView
import com.futo.platformplayer.views.behavior.NonScrollingTextView.Companion
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class PlatformLinkMovementMethod : LinkMovementMethod { class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMethod() {
private val _context: Context;
constructor(context: Context) : super() { private var pressedLinks: Array<URLSpan>? = null
_context = context; 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 { override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
val action = event.action; val action = event.actionMasked
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 layout = widget.layout; when (action) {
val line = layout.getLineForVertical(y); MotionEvent.ACTION_DOWN -> {
val off = layout.getOffsetForHorizontal(line, x.toFloat()); val links = findLinksAtTouchPosition(widget, buffer, event)
val links = buffer.getSpans(off, off, URLSpan::class.java); 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()) { MotionEvent.ACTION_MOVE -> {
runBlocking { if (linkPressed) {
for (link in links) { val dx = event.x - downX
Logger.i(TAG) { "Link clicked '${link.url}'." }; 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) { MotionEvent.ACTION_UP -> {
if (_context.handleUrl(link.url)) { if (linkPressed && pressedLinks != null) {
continue; 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)) { if (_context is MainActivity) {
val tokens = link.url.split(':'); 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 (time_s != -1L) {
if (tokens.size == 2) { MediaControlReceiver.onSeekToReceived.emit(time_s * 1000)
time_s = tokens[0].toLong() * 60 + tokens[1].toLong(); continue
} 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;
} }
_context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
} }
} }
pressedLinks = null
linkPressed = false
_context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))); 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<URLSpan> {
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 { companion object {
val TAG = "PlatformLinkMovementMethod"; const val TAG = "PlatformLinkMovementMethod"
} }
} }

View file

@ -16,70 +16,110 @@ import com.futo.platformplayer.timestampRegex
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView {
private var _lastTouchedLinks: Array<URLSpan>? = 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) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
override fun scrollTo(x: Int, y: Int) { override fun scrollTo(x: Int, y: Int) {
//do nothing // do nothing
} }
override fun onTouchEvent(event: MotionEvent?): Boolean { override fun onTouchEvent(event: MotionEvent?): Boolean {
val action = event?.action val action = event?.actionMasked
Logger.i(TAG, "onTouchEvent (action = $action)"); if (event == null) return super.onTouchEvent(event)
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { when (action) {
val x = event.x.toInt() MotionEvent.ACTION_DOWN -> {
val y = event.y.toInt() val x = event.x.toInt()
val y = event.y.toInt()
val layout: Layout? = this.layout val layout: Layout? = this.layout
if (layout != null) { if (layout != null && this.text is Spannable) {
val line = layout.getLineForVertical(y) val offset = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x.toFloat())
val offset = layout.getOffsetForHorizontal(line, x.toFloat()) val text = this.text as Spannable
val text = this.text
if (text is Spannable) {
val links = text.getSpans(offset, offset, URLSpan::class.java) val links = text.getSpans(offset, offset, URLSpan::class.java)
if (links.isNotEmpty()) { if (links.isNotEmpty()) {
runBlocking { parent?.requestDisallowInterceptTouchEvent(true)
for (link in links) { _lastTouchedLinks = links
Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." }; downX = event.x
downY = event.y
val c = context; linkPressed = true
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)));
}
}
}
return 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 super.onTouchEvent(event)
return false
} }
companion object { companion object {