mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-09 08:21:48 +00:00
Split image from replies (#779)
* refactor: VisibleMessageContentView.kt re-using layouts instead of instantiating every bind to fix alignment and constraint issues for splitting thumbnails and body * refactor: constraint works for sms only records, adjust other components of the visible message content accordingly * feat: link previews and quotes now wrap content or align according to media type * refactor: move back to emojitextview for body * fix: add some padding at the bottom of the quote * fix: voice message view not rendering properly * fix: set visibility to false for each message content view on recycle event * fix: untrusted attachments * fix: compile issues and small UI improvement
This commit is contained in:
@@ -17,22 +17,17 @@ import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
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.messages.VisibleMessageContentView
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.longmessage.LongMessageActivity
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class AlbumThumbnailView : FrameLayout {
|
||||
|
||||
|
||||
private lateinit var binding: AlbumThumbnailViewBinding
|
||||
|
||||
companion object {
|
||||
@@ -72,24 +67,7 @@ class AlbumThumbnailView : FrameLayout {
|
||||
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"
|
||||
binding.albumCellBodyTextReadMore.getGlobalVisibleRect(testRect)
|
||||
if (testRect.contains(eventRect)) {
|
||||
// dispatch to activity view
|
||||
ActivityDispatcher.get(context)?.dispatchIntent { context ->
|
||||
LongMessageActivity.getIntent(context, mms.recipient.address, mms.getId(), true)
|
||||
}
|
||||
return
|
||||
}
|
||||
val intersectedSpans = binding.albumCellBodyText.getIntersectedModalSpans(eventRect)
|
||||
if (intersectedSpans.isNotEmpty()) {
|
||||
intersectedSpans.forEach { span ->
|
||||
span.onClick(binding.albumCellBodyText)
|
||||
}
|
||||
return
|
||||
}
|
||||
// test each album child
|
||||
binding.albumCellContainer.findViewById<ViewGroup>(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child ->
|
||||
child.getGlobalVisibleRect(testRect)
|
||||
@@ -113,6 +91,11 @@ class AlbumThumbnailView : FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
fun clearViews() {
|
||||
binding.albumCellContainer.removeAllViews()
|
||||
slideSize = -1
|
||||
}
|
||||
|
||||
fun bind(glideRequests: GlideRequests, message: MmsMessageRecord,
|
||||
isStart: Boolean, isEnd: Boolean) {
|
||||
slides = message.slideDeck.thumbnailSlides
|
||||
@@ -139,19 +122,6 @@ class AlbumThumbnailView : FrameLayout {
|
||||
val thumbnailView = getThumbnailView(position)
|
||||
thumbnailView.setImageResource(glideRequests, slide, isPreview = false, mms = message)
|
||||
}
|
||||
binding.albumCellBodyParent.isVisible = message.body.isNotEmpty()
|
||||
val body = VisibleMessageContentView.getBodySpans(context, message, null)
|
||||
binding.albumCellBodyText.text = body
|
||||
post {
|
||||
// post to await layout of text
|
||||
binding.albumCellBodyText.layout?.let { layout ->
|
||||
val maxEllipsis = (0 until layout.lineCount).maxByOrNull { lineNum -> layout.getEllipsisCount(lineNum) }
|
||||
?: 0
|
||||
// show read more text if at least one line is ellipsized
|
||||
ViewUtil.setPaddingTop(binding.albumCellBodyTextParent, if (maxEllipsis > 0) resources.getDimension(R.dimen.small_spacing).roundToInt() else resources.getDimension(R.dimen.medium_spacing).roundToInt())
|
||||
binding.albumCellBodyTextReadMore.isVisible = maxEllipsis > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -39,7 +39,12 @@ class LinkPreviewView : LinearLayout {
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
fun bind(message: MmsMessageRecord, glide: GlideRequests, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, searchQuery: String?) {
|
||||
fun bind(
|
||||
message: MmsMessageRecord,
|
||||
glide: GlideRequests,
|
||||
isStartOfMessageCluster: Boolean,
|
||||
isEndOfMessageCluster: Boolean
|
||||
) {
|
||||
val linkPreview = message.linkPreviews.first()
|
||||
url = linkPreview.url
|
||||
// Thumbnail
|
||||
@@ -57,8 +62,7 @@ class LinkPreviewView : LinearLayout {
|
||||
}
|
||||
binding.titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
|
||||
// Body
|
||||
bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
||||
binding.mainLinkPreviewContainer.addView(bodyTextView)
|
||||
binding.titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
|
||||
// Corner radii
|
||||
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
|
||||
cornerMask.setTopLeftRadius(cornerRadii[0])
|
||||
|
||||
@@ -47,11 +47,10 @@ class QuoteView : LinearLayout {
|
||||
enum class Mode { Regular, Draft }
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { throw IllegalAccessError("Use QuoteView(context:mode:) instead.") }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { throw IllegalAccessError("Use QuoteView(context:mode:) instead.") }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { throw IllegalAccessError("Use QuoteView(context:mode:) instead.") }
|
||||
constructor(context: Context) : this(context, Mode.Regular)
|
||||
constructor(context: Context, attrs: AttributeSet) : this(context, Mode.Regular, attrs)
|
||||
|
||||
constructor(context: Context, mode: Mode) : super(context) {
|
||||
constructor(context: Context, mode: Mode, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||
this.mode = mode
|
||||
binding = ViewQuoteBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
// Add padding here (not on binding.mainQuoteViewContainer) to get a bit of a top inset while avoiding
|
||||
@@ -65,7 +64,7 @@ class QuoteView : LinearLayout {
|
||||
val quoteViewMainContentContainerLayoutParams = binding.quoteViewMainContentContainer.layoutParams as RelativeLayout.LayoutParams
|
||||
// Since we're not showing the cancel button we can shorten the end margin
|
||||
quoteViewMainContentContainerLayoutParams.marginEnd = resources.getDimension(R.dimen.medium_spacing).roundToInt()
|
||||
binding.quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
|
||||
// binding.quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +134,7 @@ class QuoteView : LinearLayout {
|
||||
if (!hasAttachments) {
|
||||
val accentLineLayoutParams = binding.quoteViewAccentLine.layoutParams as RelativeLayout.LayoutParams
|
||||
accentLineLayoutParams.height = getIntrinsicContentHeight(maxContentWidth) // Match the intrinsic * content * height
|
||||
binding.quoteViewAccentLine.layoutParams = accentLineLayoutParams
|
||||
// binding.quoteViewAccentLine.layoutParams = accentLineLayoutParams
|
||||
binding.quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
|
||||
} else if (attachments != null) {
|
||||
binding.quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(ResourcesCompat.getColor(resources, R.color.white, context.theme))
|
||||
@@ -165,11 +164,11 @@ class QuoteView : LinearLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.mainQuoteViewContainer.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, getIntrinsicHeight(maxContentWidth))
|
||||
val quoteViewMainContentContainerLayoutParams = binding.quoteViewMainContentContainer.layoutParams as RelativeLayout.LayoutParams
|
||||
//mainQuoteViewContainer.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, getIntrinsicHeight(maxContentWidth))
|
||||
// val quoteViewMainContentContainerLayoutParams = quoteViewMainContentContainer.layoutParams as RelativeLayout.LayoutParams
|
||||
// The start margin is different if we just show the accent line vs if we show an attachment thumbnail
|
||||
quoteViewMainContentContainerLayoutParams.marginStart = if (!hasAttachments) toPx(16, resources) else toPx(48, resources)
|
||||
binding.quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
|
||||
// quoteViewMainContentContainerLayoutParams.marginStart = if (!hasAttachments) toPx(16, resources) else toPx(48, resources)
|
||||
// quoteViewMainContentContainer.layoutParams = quoteViewMainContentContainerLayoutParams
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
||||
@@ -10,11 +10,10 @@ import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.URLSpan
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -23,22 +22,20 @@ import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import androidx.core.text.getSpans
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
|
||||
import okhttp3.HttpUrl
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet
|
||||
import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
|
||||
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.database.model.SmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.SearchUtil
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
@@ -49,7 +46,7 @@ import kotlin.math.roundToInt
|
||||
|
||||
class VisibleMessageContentView : LinearLayout {
|
||||
private lateinit var binding: ViewVisibleMessageContentBinding
|
||||
var onContentClick: ((event: MotionEvent) -> Unit)? = null
|
||||
var onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf()
|
||||
var onContentDoubleTap: (() -> Unit)? = null
|
||||
var delegate: VisibleMessageContentViewDelegate? = null
|
||||
var indexInAdapter: Int = -1
|
||||
@@ -74,23 +71,45 @@ class VisibleMessageContentView : LinearLayout {
|
||||
val filter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN)
|
||||
background.colorFilter = filter
|
||||
setBackground(background)
|
||||
// Body
|
||||
binding.mainContainer.removeAllViews()
|
||||
onContentClick = null
|
||||
|
||||
val onlyBodyMessage = message is SmsMessageRecord
|
||||
val mediaThumbnailMessage = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.thumbnailSlide != null
|
||||
|
||||
// reset visibilities / containers
|
||||
onContentClick.clear()
|
||||
binding.albumThumbnailView.clearViews()
|
||||
onContentDoubleTap = null
|
||||
|
||||
if (message.isDeleted) {
|
||||
val deletedMessageView = DeletedMessageView(context)
|
||||
deletedMessageView.bind(message, getTextColor(context,message))
|
||||
binding.mainContainer.addView(deletedMessageView)
|
||||
} else if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) {
|
||||
val linkPreviewView = LinkPreviewView(context)
|
||||
linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery)
|
||||
binding.mainContainer.addView(linkPreviewView)
|
||||
onContentClick = { event -> linkPreviewView.calculateHit(event) }
|
||||
// Body text view is inside the link preview for layout convenience
|
||||
} else if (message is MmsMessageRecord && message.quote != null) {
|
||||
binding.deletedMessageView.isVisible = true
|
||||
binding.deletedMessageView.bind(message, VisibleMessageContentView.getTextColor(context,message))
|
||||
return
|
||||
} else {
|
||||
binding.deletedMessageView.isVisible = false
|
||||
}
|
||||
|
||||
binding.quoteView.isVisible = message is MmsMessageRecord && message.quote != null
|
||||
val quoteLayoutParams = binding.quoteView.layoutParams
|
||||
quoteLayoutParams.width = if (mediaThumbnailMessage) 0 else ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
binding.quoteView.layoutParams = quoteLayoutParams
|
||||
|
||||
binding.linkPreviewView.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty()
|
||||
|
||||
val linkPreviewLayout = binding.linkPreviewView.layoutParams
|
||||
linkPreviewLayout.width = if (mediaThumbnailMessage) 0 else ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
binding.linkPreviewView.layoutParams = linkPreviewLayout
|
||||
|
||||
binding.untrustedView.isVisible = !contactIsTrusted && message is MmsMessageRecord && message.quote == null
|
||||
binding.voiceMessageView.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.audioSlide != null
|
||||
binding.documentView.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.documentSlide != null
|
||||
binding.albumThumbnailView.isVisible = mediaThumbnailMessage
|
||||
binding.openGroupInvitationView.isVisible = message.isOpenGroupInvitation
|
||||
|
||||
var hideBody = false
|
||||
|
||||
if (message is MmsMessageRecord && message.quote != null) {
|
||||
binding.quoteView.isVisible = true
|
||||
val quote = message.quote!!
|
||||
val quoteView = QuoteView(context, QuoteView.Mode.Regular)
|
||||
// The max content width is the max message bubble size - 2 times the horizontal padding - 2
|
||||
// times the horizontal margin. This unfortunately has to be calculated manually
|
||||
// here to get the layout right.
|
||||
@@ -100,88 +119,91 @@ class VisibleMessageContentView : LinearLayout {
|
||||
} else {
|
||||
quote.text
|
||||
}
|
||||
quoteView.bind(quote.author.toString(), quoteText, quote.attachment, thread,
|
||||
binding.quoteView.bind(quote.author.toString(), quoteText, quote.attachment, thread,
|
||||
message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId,
|
||||
quote.isOriginalMissing, glide)
|
||||
binding.mainContainer.addView(quoteView)
|
||||
val bodyTextView = getBodyTextView(context, message, searchQuery)
|
||||
ViewUtil.setPaddingTop(bodyTextView, 0)
|
||||
binding.mainContainer.addView(bodyTextView)
|
||||
onContentClick = { event ->
|
||||
onContentClick.add { event ->
|
||||
val r = Rect()
|
||||
quoteView.getGlobalVisibleRect(r)
|
||||
binding.quoteView.getGlobalVisibleRect(r)
|
||||
if (r.contains(event.rawX.roundToInt(), event.rawY.roundToInt())) {
|
||||
delegate?.scrollToMessageIfPossible(quote.id)
|
||||
} else {
|
||||
bodyTextView.getIntersectedModalSpans(event).forEach { span ->
|
||||
span.onClick(bodyTextView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) {
|
||||
binding.linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||
onContentClick.add { event -> binding.linkPreviewView.calculateHit(event) }
|
||||
// Body text view is inside the link preview for layout convenience
|
||||
} else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) {
|
||||
hideBody = true
|
||||
// Audio attachment
|
||||
if (contactIsTrusted || message.isOutgoing) {
|
||||
val voiceMessageView = VoiceMessageView(context)
|
||||
voiceMessageView.indexInAdapter = indexInAdapter
|
||||
voiceMessageView.delegate = context as? ConversationActivityV2
|
||||
voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||
binding.mainContainer.addView(voiceMessageView)
|
||||
binding.voiceMessageView.indexInAdapter = indexInAdapter
|
||||
binding.voiceMessageView.delegate = context as? ConversationActivityV2
|
||||
binding.voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||
// We have to use onContentClick (rather than a click listener directly on the voice
|
||||
// message view) so as to not interfere with all the other gestures.
|
||||
onContentClick = { voiceMessageView.togglePlayback() }
|
||||
onContentDoubleTap = { voiceMessageView.handleDoubleTap() }
|
||||
onContentClick.add { binding.voiceMessageView.togglePlayback() }
|
||||
onContentDoubleTap = { binding.voiceMessageView.handleDoubleTap() }
|
||||
} else {
|
||||
val untrustedView = UntrustedAttachmentView(context)
|
||||
untrustedView.bind(UntrustedAttachmentView.AttachmentType.AUDIO, getTextColor(context,message))
|
||||
binding.mainContainer.addView(untrustedView)
|
||||
onContentClick = { untrustedView.showTrustDialog(message.individualRecipient) }
|
||||
// TODO: move this out to its own area
|
||||
binding.untrustedView.bind(UntrustedAttachmentView.AttachmentType.AUDIO, VisibleMessageContentView.getTextColor(context,message))
|
||||
onContentClick.add { binding.untrustedView.showTrustDialog(message.individualRecipient) }
|
||||
}
|
||||
} else if (message is MmsMessageRecord && message.slideDeck.documentSlide != null) {
|
||||
hideBody = true
|
||||
// Document attachment
|
||||
if (contactIsTrusted || message.isOutgoing) {
|
||||
val documentView = DocumentView(context)
|
||||
documentView.bind(message, getTextColor(context, message))
|
||||
binding.mainContainer.addView(documentView)
|
||||
binding.documentView.bind(message, VisibleMessageContentView.getTextColor(context, message))
|
||||
} else {
|
||||
val untrustedView = UntrustedAttachmentView(context)
|
||||
untrustedView.bind(UntrustedAttachmentView.AttachmentType.DOCUMENT, getTextColor(context,message))
|
||||
binding.mainContainer.addView(untrustedView)
|
||||
onContentClick = { untrustedView.showTrustDialog(message.individualRecipient) }
|
||||
binding.untrustedView.bind(UntrustedAttachmentView.AttachmentType.DOCUMENT, VisibleMessageContentView.getTextColor(context,message))
|
||||
onContentClick.add { binding.untrustedView.showTrustDialog(message.individualRecipient) }
|
||||
}
|
||||
} else if (message is MmsMessageRecord && message.slideDeck.asAttachments().isNotEmpty()) {
|
||||
// Images/Video attachment
|
||||
/*
|
||||
* Images / Video attachment
|
||||
*/
|
||||
if (contactIsTrusted || message.isOutgoing) {
|
||||
val albumThumbnailView = AlbumThumbnailView(context)
|
||||
binding.mainContainer.addView(albumThumbnailView)
|
||||
// isStart and isEnd of cluster needed for calculating the mask for full bubble image groups
|
||||
// bind after add view because views are inflated and calculated during bind
|
||||
albumThumbnailView.bind(
|
||||
binding.albumThumbnailView.bind(
|
||||
glideRequests = glide,
|
||||
message = message,
|
||||
isStart = isStartOfMessageCluster,
|
||||
isEnd = isEndOfMessageCluster
|
||||
)
|
||||
onContentClick = { event ->
|
||||
albumThumbnailView.calculateHitObject(event, message, thread)
|
||||
onContentClick.add { event ->
|
||||
binding.albumThumbnailView.calculateHitObject(event, message, thread)
|
||||
}
|
||||
} else {
|
||||
val untrustedView = UntrustedAttachmentView(context)
|
||||
untrustedView.bind(UntrustedAttachmentView.AttachmentType.MEDIA, getTextColor(context,message))
|
||||
binding.mainContainer.addView(untrustedView)
|
||||
onContentClick = { untrustedView.showTrustDialog(message.individualRecipient) }
|
||||
hideBody = true
|
||||
binding.albumThumbnailView.clearViews()
|
||||
binding.untrustedView.bind(UntrustedAttachmentView.AttachmentType.MEDIA, VisibleMessageContentView.getTextColor(context,message))
|
||||
onContentClick.add { binding.untrustedView.showTrustDialog(message.individualRecipient) }
|
||||
}
|
||||
} else if (message.isOpenGroupInvitation) {
|
||||
val openGroupInvitationView = OpenGroupInvitationView(context)
|
||||
openGroupInvitationView.bind(message, getTextColor(context, message))
|
||||
binding.mainContainer.addView(openGroupInvitationView)
|
||||
onContentClick = { openGroupInvitationView.joinOpenGroup() }
|
||||
} else {
|
||||
val bodyTextView = getBodyTextView(context, message, searchQuery)
|
||||
binding.mainContainer.addView(bodyTextView)
|
||||
onContentClick = { event ->
|
||||
// intersectedModalSpans should only be a list of one item
|
||||
bodyTextView.getIntersectedModalSpans(event).forEach { span ->
|
||||
span.onClick(bodyTextView)
|
||||
hideBody = true
|
||||
binding.openGroupInvitationView.bind(message, VisibleMessageContentView.getTextColor(context, message))
|
||||
onContentClick.add { binding.openGroupInvitationView.joinOpenGroup() }
|
||||
}
|
||||
|
||||
binding.bodyTextView.isVisible = message.body.isNotEmpty() && !hideBody
|
||||
|
||||
// set it to use constraints if not only a text message, otherwise wrap content to whatever width it wants
|
||||
val params = binding.bodyTextView.layoutParams
|
||||
params.width = if (onlyBodyMessage) ViewGroup.LayoutParams.WRAP_CONTENT else 0
|
||||
binding.bodyTextView.layoutParams = params
|
||||
|
||||
if (message.body.isNotEmpty() && !hideBody) {
|
||||
val color = getTextColor(context, message)
|
||||
binding.bodyTextView.setTextColor(color)
|
||||
binding.bodyTextView.setLinkTextColor(color)
|
||||
val body = getBodySpans(context, message, searchQuery)
|
||||
binding.bodyTextView.text = body
|
||||
onContentClick.add { e: MotionEvent ->
|
||||
binding.bodyTextView.getIntersectedModalSpans(e).forEach { span ->
|
||||
span.onClick(binding.bodyTextView)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,35 +229,27 @@ class VisibleMessageContentView : LinearLayout {
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
binding.mainContainer.removeAllViews()
|
||||
arrayOf(
|
||||
binding.deletedMessageView,
|
||||
binding.untrustedView,
|
||||
binding.voiceMessageView,
|
||||
binding.openGroupInvitationView,
|
||||
binding.documentView,
|
||||
binding.quoteView,
|
||||
binding.linkPreviewView,
|
||||
binding.albumThumbnailView,
|
||||
binding.bodyTextView
|
||||
).forEach { view -> view.isVisible = false }
|
||||
}
|
||||
|
||||
fun playVoiceMessage() {
|
||||
binding.mainContainer.children.forEach { view ->
|
||||
if (view is VoiceMessageView) {
|
||||
return@forEach view.togglePlayback()
|
||||
}
|
||||
}
|
||||
binding.voiceMessageView.togglePlayback()
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Convenience
|
||||
companion object {
|
||||
|
||||
fun getBodyTextView(context: Context, message: MessageRecord, searchQuery: String?): TextView {
|
||||
val result = EmojiTextView(context)
|
||||
val vPadding = context.resources.getDimension(R.dimen.small_spacing).toInt()
|
||||
val hPadding = toPx(12, context.resources)
|
||||
result.setPadding(hPadding, vPadding, hPadding, vPadding)
|
||||
result.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.resources.getDimension(R.dimen.small_font_size))
|
||||
val color = getTextColor(context, message)
|
||||
result.setTextColor(color)
|
||||
result.setLinkTextColor(color)
|
||||
val body = getBodySpans(context, message, searchQuery)
|
||||
result.text = body
|
||||
return result
|
||||
}
|
||||
|
||||
fun getBodySpans(context: Context, message: MessageRecord, searchQuery: String?): Spannable {
|
||||
var body = message.body.toSpannable()
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
@@ -181,7 +180,7 @@ class VisibleMessageView : LinearLayout {
|
||||
if (binding.profilePictureContainer.visibility != View.GONE) { maxWidth -= binding.profilePictureContainer.width }
|
||||
// Populate content view
|
||||
binding.messageContentView.indexInAdapter = indexInAdapter
|
||||
binding.messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery, isGroupThread || (contact?.isTrusted ?: false))
|
||||
binding.messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery, message.isOutgoing || isGroupThread || (contact?.isTrusted ?: false))
|
||||
binding.messageContentView.delegate = contentViewDelegate
|
||||
onDoubleTap = { binding.messageContentView.onContentDoubleTap?.invoke() }
|
||||
}
|
||||
@@ -224,11 +223,13 @@ class VisibleMessageView : LinearLayout {
|
||||
}
|
||||
|
||||
private fun updateExpirationTimer(message: MessageRecord) {
|
||||
val expirationTimerViewLayoutParams = binding.expirationTimerView.layoutParams as RelativeLayout.LayoutParams
|
||||
val ruleToAdd = if (message.isOutgoing) RelativeLayout.ALIGN_START else RelativeLayout.ALIGN_END
|
||||
val ruleToRemove = if (message.isOutgoing) RelativeLayout.ALIGN_END else RelativeLayout.ALIGN_START
|
||||
expirationTimerViewLayoutParams.removeRule(ruleToRemove)
|
||||
expirationTimerViewLayoutParams.addRule(ruleToAdd, R.id.messageContentView)
|
||||
val expirationTimerViewLayoutParams = binding.expirationTimerView.layoutParams as MarginLayoutParams
|
||||
val container = binding.expirationTimerViewContainer
|
||||
val content = binding.messageContentView
|
||||
val expiration = binding.expirationTimerView
|
||||
container.removeAllViewsInLayout()
|
||||
container.addView(if (message.isOutgoing) expiration else content)
|
||||
container.addView(if (message.isOutgoing) content else expiration)
|
||||
val expirationTimerViewSize = toPx(12, resources)
|
||||
val smallSpacing = resources.getDimension(R.dimen.small_spacing).roundToInt()
|
||||
expirationTimerViewLayoutParams.marginStart = if (message.isOutgoing) -(smallSpacing + expirationTimerViewSize) else 0
|
||||
@@ -261,6 +262,7 @@ class VisibleMessageView : LinearLayout {
|
||||
} else {
|
||||
binding.expirationTimerView.isVisible = false
|
||||
}
|
||||
container.requestLayout()
|
||||
}
|
||||
|
||||
private fun handleIsSelectedChanged() {
|
||||
@@ -388,7 +390,7 @@ class VisibleMessageView : LinearLayout {
|
||||
}
|
||||
|
||||
fun onContentClick(event: MotionEvent) {
|
||||
binding.messageContentView.onContentClick?.invoke(event)
|
||||
binding.messageContentView.onContentClick.forEach { clickHandler -> clickHandler.invoke(event) }
|
||||
}
|
||||
|
||||
private fun onPress(event: MotionEvent) {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.utilities;
|
||||
|
||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
@@ -22,8 +22,13 @@ import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.ViewUtil;
|
||||
import org.session.libsignal.utilities.ListenableFuture;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.SettableFuture;
|
||||
import org.session.libsignal.utilities.guava.Optional;
|
||||
import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget;
|
||||
import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget;
|
||||
import org.thoughtcrime.securesms.components.TransferControlView;
|
||||
@@ -33,17 +38,11 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||
import org.session.libsignal.utilities.guava.Optional;
|
||||
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.ViewUtil;
|
||||
import org.session.libsignal.utilities.ListenableFuture;
|
||||
import org.session.libsignal.utilities.SettableFuture;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public class ThumbnailView extends FrameLayout {
|
||||
|
||||
@@ -287,7 +286,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
} else if (slide.hasPlaceholder()) {
|
||||
buildPlaceholderGlideRequest(glideRequests, slide).into(new GlideBitmapListeningTarget(image, result));
|
||||
} else {
|
||||
glideRequests.clear(image);
|
||||
glideRequests.load(R.drawable.ic_image_white_24dp).centerInside().into(image);
|
||||
result.set(false);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user