This commit is contained in:
ryanzhao 2021-06-30 14:55:30 +10:00
commit 79fd74a157
11 changed files with 95 additions and 32 deletions

View File

@ -118,13 +118,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
private int restartItem = -1; private int restartItem = -1;
public static Intent getPreviewIntent(Context context, Slide slide, MmsMessageRecord mms) { public static Intent getPreviewIntent(Context context, Slide slide, MmsMessageRecord mms, Recipient threadRecipient) {
Intent previewIntent = null; Intent previewIntent = null;
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
previewIntent = new Intent(context, MediaPreviewActivity.class); previewIntent = new Intent(context, MediaPreviewActivity.class);
previewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) previewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setDataAndType(slide.getUri(), slide.getContentType()) .setDataAndType(slide.getUri(), slide.getContentType())
.putExtra(ADDRESS_EXTRA, mms.getRecipient().getAddress()) .putExtra(ADDRESS_EXTRA, threadRecipient.getAddress())
.putExtra(OUTGOING_EXTRA, mms.isOutgoing()) .putExtra(OUTGOING_EXTRA, mms.isOutgoing())
.putExtra(DATE_EXTRA, mms.getTimestamp()) .putExtra(DATE_EXTRA, mms.getTimestamp())
.putExtra(SIZE_EXTRA, slide.asAttachment().getSize()) .putExtra(SIZE_EXTRA, slide.asAttachment().getSize())

View File

@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;

View File

@ -153,8 +153,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val adapter = ConversationAdapter( val adapter = ConversationAdapter(
this, this,
cursor, cursor,
onItemPress = { message, position, view, rawRect -> onItemPress = { message, position, view, event ->
handlePress(message, position, view, rawRect) handlePress(message, position, view, event)
}, },
onItemSwipeToReply = { message, position -> onItemSwipeToReply = { message, position ->
handleSwipeToReply(message, position) handleSwipeToReply(message, position)
@ -667,7 +667,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
// `position` is the adapter position; not the visual position // `position` is the adapter position; not the visual position
private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView, rawRect: Rect) { private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView, event: MotionEvent) {
val actionMode = this.actionMode val actionMode = this.actionMode
if (actionMode != null) { if (actionMode != null) {
adapter.toggleSelection(message, position) adapter.toggleSelection(message, position)
@ -683,7 +683,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// We have to use onContentClick (rather than a click listener directly on // We have to use onContentClick (rather than a click listener directly on
// the view) so as to not interfere with all the other gestures. Do not add // the view) so as to not interfere with all the other gestures. Do not add
// onClickListeners directly to message content views. // onClickListeners directly to message content views.
view.onContentClick(rawRect) view.onContentClick(event)
} }
} }

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.graphics.Rect import android.graphics.Rect
import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
@ -15,7 +16,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView, Rect) -> Unit, class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit,
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit, private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit,
private val glide: GlideRequests) private val glide: GlideRequests)
: CursorRecyclerViewAdapter<ViewHolder>(context, cursor) { : CursorRecyclerViewAdapter<ViewHolder>(context, cursor) {
@ -72,7 +73,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
view.messageTimestampTextView.isVisible = isSelected view.messageTimestampTextView.isVisible = isSelected
val position = viewHolder.adapterPosition val position = viewHolder.adapterPosition
view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide, searchQuery) view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide, searchQuery)
view.onPress = { rawX, rawY -> onItemPress(message, viewHolder.adapterPosition, view, Rect(rawX, rawY, rawX, rawY)) } view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) }
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
view.contentViewDelegate = visibleMessageContentViewDelegate view.contentViewDelegate = visibleMessageContentViewDelegate

View File

@ -5,6 +5,7 @@ import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
@ -13,6 +14,7 @@ import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.album_thumbnail_view.view.* import kotlinx.android.synthetic.main.album_thumbnail_view.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.MediaPreviewActivity
import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.components.CornerMask
import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
@ -58,12 +60,15 @@ class AlbumThumbnailView : FrameLayout {
// region Interaction // region Interaction
fun calculateHitObject(rawRect: Rect, mms: MmsMessageRecord) { fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient) {
val rawXInt = event.rawX.toInt()
val rawYInt = event.rawY.toInt()
val eventRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt)
// Z-check in specific order // Z-check in specific order
val testRect = Rect() val testRect = Rect()
// test "Read More" // test "Read More"
albumCellBodyTextReadMore.getGlobalVisibleRect(testRect) albumCellBodyTextReadMore.getGlobalVisibleRect(testRect)
if (Rect.intersects(rawRect, testRect)) { if (testRect.contains(eventRect)) {
// dispatch to activity view // dispatch to activity view
ActivityDispatcher.get(context)?.dispatchIntent { context -> ActivityDispatcher.get(context)?.dispatchIntent { context ->
LongMessageActivity.getIntent(context, mms.recipient.address, mms.getId(), true) LongMessageActivity.getIntent(context, mms.recipient.address, mms.getId(), true)
@ -73,14 +78,14 @@ class AlbumThumbnailView : FrameLayout {
// test each album child // test each album child
albumCellContainer.findViewById<ViewGroup>(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child -> albumCellContainer.findViewById<ViewGroup>(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child ->
child.getGlobalVisibleRect(testRect) child.getGlobalVisibleRect(testRect)
if (Rect.intersects(rawRect, testRect)) { if (testRect.contains(eventRect)) {
// hit intersects with this particular child // hit intersects with this particular child
val slide = slides.getOrNull(index) ?: return val slide = slides.getOrNull(index) ?: return
// only open to downloaded images // only open to downloaded images
if (slide.isInProgress) return if (slide.isInProgress) return
ActivityDispatcher.get(context)?.dispatchIntent { context -> ActivityDispatcher.get(context)?.dispatchIntent { context ->
MediaPreviewActivity.getPreviewIntent(context, slide, mms) MediaPreviewActivity.getPreviewIntent(context, slide, mms, threadRecipient)
} }
} }
} }

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.components; package org.thoughtcrime.securesms.conversation.v2.components;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -118,5 +118,4 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag
Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn)); Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn));
} }
} }
} }

View File

@ -6,15 +6,21 @@ import android.graphics.Rect
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.text.getSpans
import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.view_link_preview.view.* import kotlinx.android.synthetic.main.view_link_preview.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.components.CornerMask
import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
@ -23,6 +29,7 @@ import org.thoughtcrime.securesms.mms.ImageSlide
class LinkPreviewView : LinearLayout { class LinkPreviewView : LinearLayout {
private val cornerMask by lazy { CornerMask(this) } private val cornerMask by lazy { CornerMask(this) }
private var url: String? = null private var url: String? = null
lateinit var bodyTextView: TextView
// region Lifecycle // region Lifecycle
constructor(context: Context) : super(context) { initialize() } constructor(context: Context) : super(context) { initialize() }
@ -53,7 +60,7 @@ class LinkPreviewView : LinearLayout {
} }
titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme)) titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
// Body // Body
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
mainLinkPreviewContainer.addView(bodyTextView) mainLinkPreviewContainer.addView(bodyTextView)
// Corner radii // Corner radii
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing) val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
@ -70,11 +77,20 @@ class LinkPreviewView : LinearLayout {
// endregion // endregion
// region Interaction // region Interaction
fun calculateHit(hitRect: Rect) { fun calculateHit(event: MotionEvent) {
val rawXInt = event.rawX.toInt()
val rawYInt = event.rawY.toInt()
val hitRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt)
val previewRect = Rect() val previewRect = Rect()
mainLinkPreviewParent.getGlobalVisibleRect(previewRect) mainLinkPreviewParent.getGlobalVisibleRect(previewRect)
if (previewRect.contains(hitRect)) { if (previewRect.contains(hitRect)) {
openURL() openURL()
return
}
// intersectedModalSpans should only be a list of one item
val hitSpans = bodyTextView.getIntersectedModalSpans(hitRect)
hitSpans.forEach { span ->
span.onClick(bodyTextView)
} }
} }

View File

@ -12,6 +12,7 @@ import android.text.util.Linkify
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
@ -33,6 +34,7 @@ import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
import org.thoughtcrime.securesms.components.emoji.EmojiTextView import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.loki.utilities.*
@ -43,7 +45,7 @@ import java.util.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
class VisibleMessageContentView : LinearLayout { class VisibleMessageContentView : LinearLayout {
var onContentClick: ((rawRect: Rect) -> Unit)? = null var onContentClick: ((event: MotionEvent) -> Unit)? = null
var onContentDoubleTap: (() -> Unit)? = null var onContentDoubleTap: (() -> Unit)? = null
var delegate: VisibleMessageContentViewDelegate? = null var delegate: VisibleMessageContentViewDelegate? = null
@ -75,9 +77,7 @@ class VisibleMessageContentView : LinearLayout {
val linkPreviewView = LinkPreviewView(context) val linkPreviewView = LinkPreviewView(context)
linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery) linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery)
mainContainer.addView(linkPreviewView) mainContainer.addView(linkPreviewView)
onContentClick = { rect -> onContentClick = { event -> linkPreviewView.calculateHit(event) }
linkPreviewView.calculateHit(rect)
}
// Body text view is inside the link preview for layout convenience // Body text view is inside the link preview for layout convenience
} else if (message is MmsMessageRecord && message.quote != null) { } else if (message is MmsMessageRecord && message.quote != null) {
val quote = message.quote!! val quote = message.quote!!
@ -92,10 +92,10 @@ class VisibleMessageContentView : LinearLayout {
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
ViewUtil.setPaddingTop(bodyTextView, 0) ViewUtil.setPaddingTop(bodyTextView, 0)
mainContainer.addView(bodyTextView) mainContainer.addView(bodyTextView)
onContentClick = { rect -> onContentClick = { event ->
val r = Rect() val r = Rect()
quoteView.getGlobalVisibleRect(r) quoteView.getGlobalVisibleRect(r)
if (r.contains(rect)) { if (r.contains(event.rawX.roundToInt(), event.rawY.roundToInt())) {
delegate?.scrollToMessageIfPossible(quote.id) delegate?.scrollToMessageIfPossible(quote.id)
} }
} }
@ -122,7 +122,9 @@ class VisibleMessageContentView : LinearLayout {
isStart = isStartOfMessageCluster, isStart = isStartOfMessageCluster,
isEnd = isEndOfMessageCluster isEnd = isEndOfMessageCluster
) )
onContentClick = { albumThumbnailView.calculateHitObject(it, message) } onContentClick = { event ->
albumThumbnailView.calculateHitObject(event, message, thread)
}
} else if (message.isOpenGroupInvitation) { } else if (message.isOpenGroupInvitation) {
val openGroupInvitationView = OpenGroupInvitationView(context) val openGroupInvitationView = OpenGroupInvitationView(context)
openGroupInvitationView.bind(message, VisibleMessageContentView.getTextColor(context, message)) openGroupInvitationView.bind(message, VisibleMessageContentView.getTextColor(context, message))
@ -131,6 +133,12 @@ class VisibleMessageContentView : LinearLayout {
} else { } else {
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
mainContainer.addView(bodyTextView) mainContainer.addView(bodyTextView)
onContentClick = { event ->
// intersectedModalSpans should only be a list of one item
bodyTextView.getIntersectedModalSpans(event).forEach { span ->
span.onClick(bodyTextView)
}
}
} }
} }
@ -181,10 +189,11 @@ class VisibleMessageContentView : LinearLayout {
body.removeSpan(urlSpan) body.removeSpan(urlSpan)
body.setSpan(replacementSpan, start, end, flags) body.setSpan(replacementSpan, start, end, flags)
} }
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context); body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context)
body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { BackgroundColorSpan(Color.WHITE) }, body, searchQuery) body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { BackgroundColorSpan(Color.WHITE) }, body, searchQuery)
body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { ForegroundColorSpan(Color.BLACK) }, body, searchQuery) body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { ForegroundColorSpan(Color.BLACK) }, body, searchQuery)
result.text = body result.text = body
return result return result
} }

View File

@ -45,7 +45,7 @@ class VisibleMessageView : LinearLayout {
private var onDoubleTap: (() -> Unit)? = null private var onDoubleTap: (() -> Unit)? = null
var snIsSelected = false var snIsSelected = false
set(value) { field = value; handleIsSelectedChanged()} set(value) { field = value; handleIsSelectedChanged()}
var onPress: ((rawX: Int, rawY: Int) -> Unit)? = null var onPress: ((event: MotionEvent) -> Unit)? = null
var onSwipeToReply: (() -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null
var onLongPress: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null
var contentViewDelegate: VisibleMessageContentViewDelegate? = null var contentViewDelegate: VisibleMessageContentViewDelegate? = null
@ -280,7 +280,7 @@ class VisibleMessageView : LinearLayout {
this.pressCallback = null this.pressCallback = null
onDoubleTap?.invoke() onDoubleTap?.invoke()
} else { } else {
val newPressCallback = Runnable { onPress(event.rawX.toInt(), event.rawY.toInt()) } val newPressCallback = Runnable { onPress(event) }
this.pressCallback = newPressCallback this.pressCallback = newPressCallback
gestureHandler.postDelayed(newPressCallback, VisibleMessageView.maxDoubleTapInterval) gestureHandler.postDelayed(newPressCallback, VisibleMessageView.maxDoubleTapInterval)
} }
@ -308,12 +308,12 @@ class VisibleMessageView : LinearLayout {
onLongPress?.invoke() onLongPress?.invoke()
} }
fun onContentClick(rawRect: Rect) { fun onContentClick(event: MotionEvent) {
messageContentView.onContentClick?.invoke(rawRect) messageContentView.onContentClick?.invoke(event)
} }
private fun onPress(rawX: Int, rawY: Int) { private fun onPress(event: MotionEvent) {
onPress?.invoke(rawX, rawY) onPress?.invoke(event)
pressCallback = null pressCallback = null
} }
// endregion // endregion

View File

@ -1,8 +1,13 @@
package org.thoughtcrime.securesms.conversation.v2.utilities package org.thoughtcrime.securesms.conversation.v2.utilities
import android.graphics.Rect
import android.text.Layout import android.text.Layout
import android.text.StaticLayout import android.text.StaticLayout
import android.text.TextPaint import android.text.TextPaint
import android.view.MotionEvent
import android.widget.TextView
import androidx.core.text.getSpans
import androidx.core.text.toSpannable
object TextUtilities { object TextUtilities {
@ -14,4 +19,31 @@ object TextUtilities {
val layout = builder.build() val layout = builder.build()
return layout.height return layout.height
} }
fun TextView.getIntersectedModalSpans(event: MotionEvent): List<ModalURLSpan> {
val xInt = event.rawX.toInt()
val yInt = event.rawY.toInt()
val hitRect = Rect(xInt, yInt, xInt, yInt)
return getIntersectedModalSpans(hitRect)
}
fun TextView.getIntersectedModalSpans(hitRect: Rect): List<ModalURLSpan> {
val textLayout = layout ?: return emptyList()
val lineRect = Rect()
val bodyTextRect = Rect()
getGlobalVisibleRect(bodyTextRect)
val textSpannable = text.toSpannable()
return (0 until textLayout.lineCount).flatMap { line ->
textLayout.getLineBounds(line, lineRect)
lineRect.offset(bodyTextRect.left + totalPaddingLeft, bodyTextRect.top + totalPaddingTop)
if ((Rect(lineRect)).contains(hitRect)) {
// calculate the url span intersected with (if any)
val off = textLayout.getOffsetForHorizontal(line, hitRect.left.toFloat()) // left and right will be the same
textSpannable.getSpans<ModalURLSpan>(off, off).toList()
} else {
emptyList()
}
}
}
} }

View File

@ -26,7 +26,7 @@
android:textAllCaps="true" android:textAllCaps="true"
tools:text="30 mins"/> tools:text="30 mins"/>
<org.thoughtcrime.securesms.components.ExpirationTimerView <org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
android:id="@+id/footer_expiration_timer" android:id="@+id/footer_expiration_timer"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"