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:
Harris
2022-01-18 14:33:04 +11:00
committed by GitHub
parent c2657bb785
commit bd5a324ad8
11 changed files with 275 additions and 241 deletions

View File

@@ -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

View File

@@ -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])

View File

@@ -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

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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);
}