fix: links work properly now and the album navigates to open group messages now

This commit is contained in:
jubb 2021-06-30 14:29:32 +10:00
parent 7ce124118f
commit b59b8b650d
8 changed files with 88 additions and 27 deletions

View File

@ -118,13 +118,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
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;
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
previewIntent = new Intent(context, MediaPreviewActivity.class);
previewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setDataAndType(slide.getUri(), slide.getContentType())
.putExtra(ADDRESS_EXTRA, mms.getRecipient().getAddress())
.putExtra(ADDRESS_EXTRA, threadRecipient.getAddress())
.putExtra(OUTGOING_EXTRA, mms.isOutgoing())
.putExtra(DATE_EXTRA, mms.getTimestamp())
.putExtra(SIZE_EXTRA, slide.asAttachment().getSize())

View File

@ -136,8 +136,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val adapter = ConversationAdapter(
this,
cursor,
onItemPress = { message, position, view, rawRect ->
handlePress(message, position, view, rawRect)
onItemPress = { message, position, view, event ->
handlePress(message, position, view, event)
},
onItemSwipeToReply = { message, position ->
handleSwipeToReply(message, position)
@ -611,7 +611,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
// `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
if (actionMode != null) {
adapter.toggleSelection(message, position)
@ -627,7 +627,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(rawRect)
view.onContentClick(event)
}
}

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
import android.content.Context
import android.database.Cursor
import android.graphics.Rect
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView.ViewHolder
@ -14,7 +15,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, 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 glide: GlideRequests)
: CursorRecyclerViewAdapter<ViewHolder>(context, cursor) {
@ -69,7 +70,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 = { 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.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
}

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.MotionEvent
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
@ -32,6 +33,7 @@ import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog
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.MmsMessageRecord
import org.thoughtcrime.securesms.loki.utilities.*
@ -39,7 +41,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests
import kotlin.math.roundToInt
class VisibleMessageContentView : LinearLayout {
var onContentClick: ((rawRect: Rect) -> Unit)? = null
var onContentClick: ((event: MotionEvent) -> Unit)? = null
var onContentDoubleTap: (() -> Unit)? = null
// region Lifecycle
@ -70,9 +72,7 @@ class VisibleMessageContentView : LinearLayout {
val linkPreviewView = LinkPreviewView(context)
linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster)
mainContainer.addView(linkPreviewView)
onContentClick = { rect ->
linkPreviewView.calculateHit(rect)
}
onContentClick = { event -> linkPreviewView.calculateHit(event) }
// Body text view is inside the link preview for layout convenience
} else if (message is MmsMessageRecord && message.quote != null) {
val quote = message.quote!!
@ -110,7 +110,9 @@ class VisibleMessageContentView : LinearLayout {
isStart = isStartOfMessageCluster,
isEnd = isEndOfMessageCluster
)
onContentClick = { albumThumbnailView.calculateHitObject(it, message) }
onContentClick = { event ->
albumThumbnailView.calculateHitObject(event, message, thread)
}
} else if (message.isOpenGroupInvitation) {
val openGroupInvitationView = OpenGroupInvitationView(context)
openGroupInvitationView.bind(message, VisibleMessageContentView.getTextColor(context, message))
@ -119,6 +121,12 @@ class VisibleMessageContentView : LinearLayout {
} else {
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
mainContainer.addView(bodyTextView)
onContentClick = { event ->
// intersectedModalSpans should only be a list of one item
bodyTextView.getIntersectedModalSpans(event).forEach { span ->
span.onClick(bodyTextView)
}
}
}
}
@ -170,9 +178,8 @@ class VisibleMessageContentView : LinearLayout {
body.setSpan(replacementSpan, start, end, flags)
}
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context);
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context)
result.text = body
result.movementMethod = LinkMovementMethod.getInstance()
return result
}

View File

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

View File

@ -1,8 +1,13 @@
package org.thoughtcrime.securesms.conversation.v2.utilities
import android.graphics.Rect
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.view.MotionEvent
import android.widget.TextView
import androidx.core.text.getSpans
import androidx.core.text.toSpannable
object TextUtilities {
@ -14,4 +19,31 @@ object TextUtilities {
val layout = builder.build()
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()
}
}
}
}