From ce098fe91815dbcf2d8a5172987721b617ca7d8b Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 25 Jun 2021 14:43:22 +1000 Subject: [PATCH] fix: change the content click to be hit-rect based to determine child object intersection for views with multiple content objects --- .../conversation/v2/AlbumThumbnailView.kt | 78 ++++++++++--------- .../conversation/v2/ConversationActivityV2.kt | 8 +- .../conversation/v2/ConversationAdapter.kt | 5 +- .../v2/messages/VisibleMessageContentView.kt | 25 +++--- .../v2/messages/VisibleMessageView.kt | 8 +- .../v2/utilities/KThumbnailView.kt | 10 +-- 6 files changed, 66 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AlbumThumbnailView.kt index 013f21fe59..d8136c9f12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AlbumThumbnailView.kt @@ -2,11 +2,12 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Context import android.graphics.Canvas +import android.graphics.Rect import android.util.AttributeSet import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.core.view.children import androidx.core.view.isVisible import kotlinx.android.synthetic.main.album_thumbnail_view.view.* import network.loki.messenger.R @@ -15,24 +16,33 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.Slide -import org.thoughtcrime.securesms.mms.SlideClickListener -import org.thoughtcrime.securesms.mms.SlidesClickedListener -class AlbumThumbnailView: FrameLayout, SlideClickListener, SlidesClickedListener, View.OnClickListener { +class AlbumThumbnailView : FrameLayout { // region Lifecycle - constructor(context: Context) : super(context) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + constructor(context: Context) : super(context) { + initialize() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + initialize() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + initialize() + } - private var slideClickListener: ((Slide) -> Unit)? = null - private var downloadClickListener: ((Slide) -> Unit)? = null - private var readMoreListener: (() -> Unit)? = null private val cornerMask by lazy { CornerMask(this) } + private var slides: List = listOf() + + sealed class Hit { + object ReadMoreHit : Hit() + data class SlideHit(val slide: Slide) : Hit() + data class DownloadHit(val slide: Slide) : Hit() + } private fun initialize() { LayoutInflater.from(context).inflate(R.layout.album_thumbnail_view, this) - albumCellBodyTextReadMore.setOnClickListener(this) } override fun dispatchDraw(canvas: Canvas?) { @@ -44,36 +54,31 @@ class AlbumThumbnailView: FrameLayout, SlideClickListener, SlidesClickedListener // region Interaction - override fun onClick(v: View?) { - // clicked the view or one of its children - if (v === albumCellBodyTextReadMore) { - readMoreListener?.invoke() + fun calculateHitObject(hitRect: Rect): Hit? { + // Z-check in specific order + val testRect = Rect() + // test "Read More" + albumCellBodyTextReadMore.getHitRect(testRect) + if (Rect.intersects(hitRect, testRect)) { + return Hit.ReadMoreHit } - } - - override fun onClick(v: View?, slide: Slide?) { - // slide thumbnail clicked - if (slide==null) return - slideClickListener?.invoke(slide) - } - - override fun onClick(v: View?, slides: MutableList?) { - // slide download clicked - if (slides.isNullOrEmpty()) return - slides.firstOrNull().let { slide -> - if (slide == null) return@let - downloadClickListener?.invoke(slide) + // test each album child + albumCellContainer.findViewById(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child -> + child.getHitRect(testRect) + if (Rect.intersects(hitRect, testRect)) { + // hit intersects with this particular child + slides.getOrNull(index)?.let { slide -> + return Hit.SlideHit(slide) + } + } } + return null } fun bind(glideRequests: GlideRequests, message: MmsMessageRecord, - clickListener: (Slide)->Unit, downloadClickListener: (Slide)->Unit, readMoreListener: ()->Unit, isStart: Boolean, isEnd: Boolean) { - this.slideClickListener = clickListener - this.downloadClickListener = downloadClickListener - this.readMoreListener = readMoreListener // TODO: optimize for same size - val slides = message.slideDeck.thumbnailSlides + slides = message.slideDeck.thumbnailSlides if (slides.isEmpty()) { // this should never be encountered because it's checked by parent return @@ -84,16 +89,15 @@ class AlbumThumbnailView: FrameLayout, SlideClickListener, SlidesClickedListener // iterate slides.take(5).forEachIndexed { position, slide -> val thumbnailView = getThumbnailView(position) - thumbnailView.thumbnailClickListener = this thumbnailView.setImageResource(glideRequests, slide, showControls = false, isPreview = false) - thumbnailView.setDownloadClickListener(this) } albumCellBodyParent.isVisible = message.body.isNotEmpty() albumCellBodyText.text = message.body post { // post to await layout of text albumCellBodyText.layout?.let { layout -> - val maxEllipsis = (0 until layout.lineCount).maxByOrNull { lineNum -> layout.getEllipsisCount(lineNum) } ?: 0 + val maxEllipsis = (0 until layout.lineCount).maxByOrNull { lineNum -> layout.getEllipsisCount(lineNum) } + ?: 0 // show read more text if at least one line is ellipsized albumCellBodyTextReadMore.isVisible = maxEllipsis > 0 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 6bebd88c2b..a424a5d5d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -76,8 +76,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val adapter = ConversationAdapter( this, cursor, - onItemPress = { message, position, view -> - handlePress(message, position, view) + onItemPress = { message, position, view, rect -> + handlePress(message, position, view, rect) }, onItemSwipeToReply = { message, position -> handleSwipeToReply(message, position) @@ -452,7 +452,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } // `position` is the adapter position; not the visual position - private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView) { + private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView, hitRect: Rect) { val actionMode = this.actionMode if (actionMode != null) { adapter.toggleSelection(message, position) @@ -467,7 +467,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // 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 // onClickListeners directly to message content views. - view.onContentClick() + view.onContentClick(hitRect) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 63306b0c4f..2a409ab0e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Context import android.database.Cursor +import android.graphics.Rect import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView.ViewHolder @@ -13,7 +14,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.mms.GlideRequests -class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView) -> Unit, +class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView, Rect) -> Unit, private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit, private val glide: GlideRequests) : CursorRecyclerViewAdapter(context, cursor) { @@ -68,7 +69,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr view.messageTimestampTextView.isVisible = isSelected val position = viewHolder.adapterPosition view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide) - view.onPress = { onItemPress(message, viewHolder.adapterPosition, view) } + view.onPress = { x, y -> onItemPress(message, viewHolder.adapterPosition, view, Rect(x,y,x,y)) } view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 3ab7ad065f..b32be1a2e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context +import android.graphics.Rect import android.graphics.drawable.Drawable import android.text.util.Linkify import android.util.AttributeSet @@ -30,7 +31,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests import kotlin.math.roundToInt class VisibleMessageContentView : LinearLayout { - var onContentClick: (() -> Unit)? = null + var onContentClick: ((Rect) -> Unit)? = null // region Lifecycle constructor(context: Context) : super(context) { initialize() } @@ -93,18 +94,18 @@ class VisibleMessageContentView : LinearLayout { glideRequests = glide, message = message, isStart = isStartOfMessageCluster, - isEnd = isEndOfMessageCluster, - clickListener = { slide -> - Log.d("Loki-UI","clicked to display the slide $slide") - }, - downloadClickListener = { slide -> - // trigger download of content? - Log.d("Loki-UI","clicked to download the slide $slide") - }, - readMoreListener = { - Log.d("Loki-UI", "clicked to read more the message $message") - } + isEnd = isEndOfMessageCluster ) + onContentClick = { + when (val hitObject = albumThumbnailView.calculateHitObject(it)) { + is AlbumThumbnailView.Hit.SlideHit -> Log.d("Loki-UI", "clicked display slide ${hitObject.slide}")// open the slide preview + is AlbumThumbnailView.Hit.DownloadHit -> Log.d("Loki-UI", "clicked display download") + AlbumThumbnailView.Hit.ReadMoreHit -> Log.d("Loki-UI", "clicked the read more display") + else -> { + Log.d("Loki-UI", "DIDN'T click anything important") + } + } + } } else if (message.isOpenGroupInvitation) { val openGroupInvitationView = OpenGroupInvitationView(context) openGroupInvitationView.bind(message, VisibleMessageContentView.getTextColor(context, message)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 3d665c5cbd..4e4add768c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -50,7 +50,7 @@ class VisibleMessageView : LinearLayout { private var onDownTimestamp = 0L var snIsSelected = false set(value) { field = value; handleIsSelectedChanged()} - var onPress: (() -> Unit)? = null + var onPress: ((x: Int, y: Int) -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null @@ -272,7 +272,7 @@ class VisibleMessageView : LinearLayout { onSwipeToReply?.invoke() } else if ((Date().time - onDownTimestamp) < VisibleMessageView.longPressDurationThreshold) { longPressCallback?.let { gestureHandler.removeCallbacks(it) } - onPress?.invoke() + onPress?.invoke(event.x.toInt(), event.y.toInt()) } resetPosition() } @@ -297,8 +297,8 @@ class VisibleMessageView : LinearLayout { onLongPress?.invoke() } - fun onContentClick() { - messageContentView.onContentClick?.invoke() + fun onContentClick(hitRect: Rect) { + messageContentView.onContentClick?.invoke(hitRect) } // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt index e0320c234e..df6f4d600d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt @@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.components.TransferControlView import org.thoughtcrime.securesms.mms.* import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri -open class KThumbnailView: FrameLayout, View.OnClickListener { +open class KThumbnailView: FrameLayout { companion object { private const val WIDTH = 0 @@ -66,7 +66,6 @@ open class KThumbnailView: FrameLayout, View.OnClickListener { typedArray.recycle() } - setOnClickListener(this) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { @@ -89,13 +88,6 @@ open class KThumbnailView: FrameLayout, View.OnClickListener { // endregion // region Interaction - - override fun onClick(v: View?) { - if (v === this) { - thumbnailClickListener?.onClick(v, slide) - } - } - fun setImageResource(glide: GlideRequests, slide: Slide, showControls: Boolean, isPreview: Boolean): ListenableFuture { return setImageResource(glide, slide, showControls, isPreview, 0, 0) }