From bd5a324ad8c8b29b3389cdbcec9137536106a965 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 18 Jan 2022 14:33:04 +1100 Subject: [PATCH] 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 --- .../v2/components/AlbumThumbnailView.kt | 42 +--- .../v2/messages/LinkPreviewView.kt | 10 +- .../conversation/v2/messages/QuoteView.kt | 19 +- .../v2/messages/VisibleMessageContentView.kt | 196 ++++++++++-------- .../v2/messages/VisibleMessageView.kt | 18 +- .../v2/utilities/ThumbnailView.java | 29 ++- .../main/res/layout/album_thumbnail_view.xml | 56 ----- app/src/main/res/layout/view_link_preview.xml | 3 +- app/src/main/res/layout/view_quote.xml | 29 +-- .../main/res/layout/view_visible_message.xml | 11 +- .../layout/view_visible_message_content.xml | 103 ++++++++- 11 files changed, 275 insertions(+), 241 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index abf2eb4b1..46387d9c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -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(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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 9d751d3fb..0f8431c64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -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]) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index fa9f31044..7c23ca143 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index fd3d47400..af0e6578a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index fc858bb25..bcc9e31ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java index f40a57924..6a6ead67d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java @@ -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); } diff --git a/app/src/main/res/layout/album_thumbnail_view.xml b/app/src/main/res/layout/album_thumbnail_view.xml index 706964a20..9e5cc0c53 100644 --- a/app/src/main/res/layout/album_thumbnail_view.xml +++ b/app/src/main/res/layout/album_thumbnail_view.xml @@ -1,7 +1,5 @@ @@ -22,58 +20,4 @@ android:layout_gravity="center" android:layout="@layout/transfer_controls_stub" /> - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_link_preview.xml b/app/src/main/res/layout/view_link_preview.xml index 78262d817..aa5c5cca8 100644 --- a/app/src/main/res/layout/view_link_preview.xml +++ b/app/src/main/res/layout/view_link_preview.xml @@ -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" diff --git a/app/src/main/res/layout/view_quote.xml b/app/src/main/res/layout/view_quote.xml index 32112435a..6956c4011 100644 --- a/app/src/main/res/layout/view_quote.xml +++ b/app/src/main/res/layout/view_quote.xml @@ -1,12 +1,12 @@ - + android:paddingHorizontal="@dimen/medium_spacing" + xmlns:tools="http://schemas.android.com/tools"> @@ -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"> + tools:text="Spiderman" + android:textColor="@color/text" + android:textSize="@dimen/small_font_size" + android:textStyle="bold" /> diff --git a/app/src/main/res/layout/view_visible_message.xml b/app/src/main/res/layout/view_visible_message.xml index 195a81d02..2ed6af3a0 100644 --- a/app/src/main/res/layout/view_visible_message.xml +++ b/app/src/main/res/layout/view_visible_message.xml @@ -75,8 +75,9 @@ android:maxLines="1" android:ellipsize="end" /> - @@ -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" /> - + + android:textSize="11sp" /> - \ No newline at end of file + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/mainContainerConstraint" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + + + + + + + + + + + + + + \ No newline at end of file