@@ -100,4 +100,74 @@ object EnhancedMovementMethod : ArrowKeyMovementMethod() {
100100
101101 return super .onTouchEvent(widget, text, event)
102102 }
103+
104+ fun handleLinkTouchEvent (widget : TextView , text : Spannable , event : MotionEvent ): LinkTouchEventResult {
105+ var x = event.x.toInt()
106+ var y = event.y.toInt()
107+
108+ x - = widget.totalPaddingLeft
109+ y - = widget.totalPaddingTop
110+
111+ x + = widget.scrollX
112+ y + = widget.scrollY
113+
114+ val layout = widget.layout
115+ val line = layout.getLineForVertical(y)
116+ val off = layout.getOffsetForHorizontal(line, x.toFloat())
117+
118+ // get the character's position. This may be the left or the right edge of the character so, find the
119+ // other edge by inspecting nearby characters (if they exist)
120+ val charX = layout.getPrimaryHorizontal(off)
121+ val charPrevX = if (off > 0 ) layout.getPrimaryHorizontal(off - 1 ) else charX
122+ val charNextX = if (off < text.length) layout.getPrimaryHorizontal(off + 1 ) else charX
123+
124+ val lineRect = Rect ()
125+ layout.getLineBounds(line, lineRect)
126+
127+ val clickedWithinLineHeight = y >= lineRect.top && y <= lineRect.bottom
128+ val clickedOnSpanToTheLeftOfCursor = x.toFloat() in charPrevX.. charX
129+ val clickedOnSpanToTheRightOfCursor = x.toFloat() in charX.. charNextX
130+
131+ val clickedOnSpan = clickedWithinLineHeight &&
132+ (clickedOnSpanToTheLeftOfCursor || clickedOnSpanToTheRightOfCursor)
133+
134+ val clickedSpanBordersAnotherOne = (text.getSpans(off, off, ClickableSpan ::class .java).size == 1 &&
135+ text.getSpans(off + 1 , off + 1 , ClickableSpan ::class .java).isNotEmpty())
136+
137+ val isClickedSpanAmbiguous = text.getSpans(off, off, ClickableSpan ::class .java).size > 1
138+
139+ val failedToPinpointClickedSpan = (isClickedSpanAmbiguous || clickedSpanBordersAnotherOne)
140+ && ! clickedOnSpanToTheLeftOfCursor && ! clickedOnSpanToTheRightOfCursor
141+
142+ var link: ClickableSpan ? = null
143+
144+ if (clickedOnSpan) {
145+ link = if (isClickedSpanAmbiguous) {
146+ if (clickedOnSpanToTheLeftOfCursor) {
147+ text.getSpans(off, off, ClickableSpan ::class .java)[0 ]
148+ } else {
149+ text.getSpans(off, off, ClickableSpan ::class .java)[1 ]
150+ }
151+ } else {
152+ text.getSpans(off, off, ClickableSpan ::class .java).firstOrNull()
153+ }
154+ } else if (failedToPinpointClickedSpan) {
155+ link = text.getSpans(off, off, ClickableSpan ::class .java).firstOrNull { text.getSpanStart(it) == off }
156+ }
157+
158+ if (link != null ) {
159+ if (link is AztecMediaClickableSpan || link is UnknownClickableSpan ) {
160+ link.onClick(widget)
161+ return LinkTouchEventResult (true )
162+ } else if (link is AztecURLSpan && isLinkTapEnabled) {
163+ linkTappedListenerRef.get()?.onLinkTapped(widget, link.url) ? : link.onClick(widget)
164+ return LinkTouchEventResult (true )
165+ }
166+ }
167+
168+ return LinkTouchEventResult (false )
169+ }
103170}
171+
172+ @JvmInline
173+ value class LinkTouchEventResult (val handled : Boolean )
0 commit comments