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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 275 additions and 241 deletions

View File

@ -17,19 +17,14 @@ 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 {
@ -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);
}

View File

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -22,58 +20,4 @@
android:layout_gravity="center"
android:layout="@layout/transfer_controls_stub" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_alignTop="@+id/albumCellContainer"
android:layout_alignStart="@+id/albumCellContainer"
android:layout_alignEnd="@+id/albumCellContainer"
android:layout_alignBottom="@+id/albumCellContainer"
tools:visibility="visible"
android:visibility="gone"
android:layout_gravity="bottom"
android:id="@+id/albumCellBodyParent"
android:layout_width="0dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/albumCellShade"
android:src="@drawable/image_shade"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@+id/albumCellBodyTextParent"
app:layout_constraintBottom_toBottomOf="parent"
/>
<LinearLayout
android:id="@+id/albumCellBodyTextParent"
android:padding="@dimen/medium_spacing"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/albumCellBodyTextReadMore"
android:orientation="horizontal">
<View
android:layout_width="@dimen/accent_line_thickness"
android:layout_height="match_parent"
android:background="@color/accent"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:maxLines="4"
android:ellipsize="end"
android:id="@+id/albumCellBodyText"
android:textColor="@color/core_white"
android:layout_marginStart="@dimen/small_spacing"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
tools:visibility="visible"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:id="@+id/albumCellBodyTextReadMore"
android:textColor="@color/core_white"
android:paddingHorizontal="@dimen/medium_spacing"
android:paddingBottom="@dimen/medium_spacing"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ConversationItem_read_more"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

View File

@ -41,7 +41,8 @@
android:gravity="center_vertical"
android:textSize="@dimen/small_font_size"
android:textStyle="bold"
tools:text="The Day The Dinosaurs Died - Minute by Minute"
tools:text="Some Text here"
android:minWidth="@dimen/media_bubble_min_width"
android:maxLines="3"
android:ellipsize="end"
android:textColor="@color/text"

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/mainQuoteViewContainer"
<RelativeLayout android:id="@+id/mainQuoteViewContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/input_bar_background"
android:paddingHorizontal="@dimen/medium_spacing">
android:paddingHorizontal="@dimen/medium_spacing"
xmlns:tools="http://schemas.android.com/tools">
<View
android:id="@+id/quoteViewAccentLine"
@ -21,6 +21,7 @@
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_marginVertical="@dimen/small_spacing"
android:layout_centerVertical="true"
android:background="@drawable/view_quote_attachment_preview_background">
@ -45,23 +46,25 @@
android:id="@+id/quoteViewMainContentContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="30dp"
android:gravity="center_vertical"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginVertical="@dimen/small_spacing"
android:layout_marginStart="@dimen/medium_spacing"
android:layout_marginEnd="@dimen/medium_spacing"
android:layout_toEndOf="@+id/quoteViewAttachmentPreviewContainer"
android:layout_toStartOf="@+id/quoteViewCancelButton"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/quoteViewAuthorTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Spiderman"
android:textSize="@dimen/small_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:ellipsize="end"
android:maxLines="1"
android:ellipsize="end" />
tools:text="Spiderman"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size"
android:textStyle="bold" />
<TextView
android:id="@+id/quoteViewBodyTextView"
@ -69,7 +72,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:text="Yo, I need your help here!"
tools:text="Yo, I need your help here!"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size" />

View File

@ -75,8 +75,9 @@
android:maxLines="1"
android:ellipsize="end" />
<RelativeLayout
<LinearLayout
android:id="@+id/expirationTimerViewContainer"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
@ -89,9 +90,9 @@
android:id="@+id/expirationTimerView"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_centerVertical="true" />
android:layout_gravity="center_vertical" />
</RelativeLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
@ -100,12 +101,12 @@
<TextView
android:id="@+id/messageTimestampTextView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="2dp"
android:maxLines="1"
android:textSize="10dp" />
android:textSize="11sp" />
<ImageView
android:id="@+id/messageStatusImageView"

View File

@ -1,7 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mainContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mainContainerConstraint"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Content that will only show on its own -->
<org.thoughtcrime.securesms.conversation.v2.messages.DeletedMessageView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
android:id="@+id/deletedMessageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<org.thoughtcrime.securesms.conversation.v2.messages.UntrustedAttachmentView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
android:id="@+id/untrustedView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<org.thoughtcrime.securesms.conversation.v2.messages.VoiceMessageView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
android:id="@+id/voiceMessageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<org.thoughtcrime.securesms.conversation.v2.messages.OpenGroupInvitationView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
android:id="@+id/openGroupInvitationView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<org.thoughtcrime.securesms.conversation.v2.messages.DocumentView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:visibility="gone"
android:id="@+id/documentView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- Content that will show with other elements -->
<org.thoughtcrime.securesms.conversation.v2.messages.QuoteView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="@+id/albumThumbnailView"
app:layout_constraintHorizontal_bias="0"
tools:visibility="visible"
android:visibility="gone"
android:id="@+id/quoteView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<org.thoughtcrime.securesms.conversation.v2.messages.LinkPreviewView
app:layout_constraintTop_toBottomOf="@+id/quoteView"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible"
android:visibility="gone"
android:id="@+id/linkPreviewView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bodyBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="albumThumbnailView,linkPreviewView,quoteView,voiceMessageView"/>
<org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
app:layout_constraintTop_toBottomOf="@+id/linkPreviewView"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
android:id="@+id/albumThumbnailView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
app:layout_constraintHorizontal_bias="0"
tools:visibility="visible"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/albumThumbnailView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="@+id/bodyBarrier"
android:paddingHorizontal="12dp"
android:paddingVertical="@dimen/small_spacing"
android:id="@+id/bodyTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>