Add support for shade and arbitrary overlay drawables to CIV2 Media items.
This commit is contained in:
parent
21b0a4d370
commit
bc1c8032c1
10 changed files with 284 additions and 160 deletions
|
@ -96,7 +96,7 @@ import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectCollection;
|
|||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
|
||||
import org.thoughtcrime.securesms.conversation.ui.payment.PaymentMessageView;
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.InteractiveConversationElement;
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationBodyUtil;
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemUtils;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.MediaTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
|
@ -1529,7 +1529,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
private void linkifyMessageBody(@NonNull Spannable messageBody,
|
||||
boolean shouldLinkifyAllLinks)
|
||||
{
|
||||
V2ConversationBodyUtil.linkifyUrlLinks(messageBody, shouldLinkifyAllLinks, urlClickListener);
|
||||
V2ConversationItemUtils.linkifyUrlLinks(messageBody, shouldLinkifyAllLinks, urlClickListener);
|
||||
|
||||
if (conversationMessage.hasStyleLinks()) {
|
||||
for (PlaceholderURLSpan placeholder : messageBody.getSpans(0, messageBody.length(), PlaceholderURLSpan.class)) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.Space
|
||||
import org.thoughtcrime.securesms.components.QuoteView
|
||||
import org.thoughtcrime.securesms.databinding.V2ConversationItemMediaIncomingBinding
|
||||
|
@ -20,7 +19,7 @@ import org.thoughtcrime.securesms.util.views.Stub
|
|||
*/
|
||||
data class V2ConversationItemMediaBindingBridge(
|
||||
val textBridge: V2ConversationItemTextOnlyBindingBridge,
|
||||
val thumbnailStub: Stub<ImageView>,
|
||||
val thumbnailStub: Stub<V2ConversationItemThumbnail>,
|
||||
val quoteStub: Stub<QuoteView>,
|
||||
val bodyContentSpacer: Space
|
||||
)
|
||||
|
|
|
@ -5,20 +5,12 @@
|
|||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.bumptech.glide.request.target.CustomViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.QuoteView
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationMessageElement
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import org.thoughtcrime.securesms.util.changeConstraints
|
||||
|
@ -37,10 +29,6 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
|
|||
V2FooterPositionDelegate(binding)
|
||||
) {
|
||||
|
||||
private var thumbnailSlide: Slide? = null
|
||||
private var placeholderTarget: PlaceholderTarget? = null
|
||||
private val thumbnailSize = intArrayOf(0, 0)
|
||||
|
||||
init {
|
||||
binding.textBridge.conversationItemBodyWrapper.clipToOutline = true
|
||||
}
|
||||
|
@ -64,7 +52,7 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
|
|||
|
||||
binding.textBridge.root.changeConstraints {
|
||||
val maxBodyWidth = if (hasThumbnail()) {
|
||||
thumbnailSize[0]
|
||||
binding.thumbnailStub.get().thumbWidth
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
@ -128,125 +116,12 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
|
|||
}
|
||||
|
||||
private fun presentThumbnail() {
|
||||
val slideDeck = requireMediaMessage().slideDeck
|
||||
if (slideDeck.thumbnailSlides.isEmpty() || slideDeck.thumbnailSlides.size > 1) {
|
||||
binding.thumbnailStub.visibility = View.GONE
|
||||
thumbnailSize[0] = -1
|
||||
thumbnailSize[1] = -1
|
||||
|
||||
return
|
||||
if (binding.thumbnailStub.resolved() || requireMediaMessage().slideDeck.thumbnailSlides.size == 1) {
|
||||
binding.thumbnailStub.get().presentThumbnail(
|
||||
mediaMessage = requireMediaMessage(),
|
||||
conversationContext = conversationContext
|
||||
)
|
||||
}
|
||||
|
||||
binding.thumbnailStub.visibility = View.VISIBLE
|
||||
|
||||
val thumbnail = slideDeck.thumbnailSlides.first()
|
||||
|
||||
// TODO [alex] -- Is this correct?
|
||||
if (thumbnail == thumbnailSlide) {
|
||||
return
|
||||
}
|
||||
|
||||
thumbnailSlide = thumbnail
|
||||
|
||||
conversationContext.glideRequests.clear(binding.thumbnailStub.get())
|
||||
|
||||
if (placeholderTarget != null) {
|
||||
conversationContext.glideRequests.clear(placeholderTarget)
|
||||
}
|
||||
// endif
|
||||
|
||||
val thumbnailUri = thumbnail.uri
|
||||
val thumbnailBlur = thumbnail.placeholderBlur
|
||||
|
||||
val thumbnailAttachment = thumbnail.asAttachment()
|
||||
val thumbnailWidth = thumbnailAttachment.width
|
||||
val thumbnailHeight = thumbnailAttachment.height
|
||||
|
||||
val maxWidth = context.resources.getDimensionPixelSize(R.dimen.media_bubble_max_width)
|
||||
val maxHeight = context.resources.getDimensionPixelSize(R.dimen.media_bubble_max_height)
|
||||
|
||||
setThumbnailSize(
|
||||
thumbnailWidth,
|
||||
thumbnailHeight,
|
||||
maxWidth,
|
||||
maxHeight
|
||||
)
|
||||
|
||||
binding.thumbnailStub.get().updateLayoutParams {
|
||||
width = thumbnailSize[0]
|
||||
height = thumbnailSize[1]
|
||||
}
|
||||
|
||||
if (thumbnailBlur != null) {
|
||||
val placeholderTarget = PlaceholderTarget(binding.thumbnailStub.get())
|
||||
conversationContext
|
||||
.glideRequests
|
||||
.load(thumbnailBlur)
|
||||
.centerInside()
|
||||
.dontAnimate()
|
||||
.override(thumbnailSize[0], thumbnailSize[1])
|
||||
.into(placeholderTarget)
|
||||
|
||||
this.placeholderTarget = placeholderTarget
|
||||
}
|
||||
|
||||
if (thumbnailUri != null) {
|
||||
conversationContext
|
||||
.glideRequests
|
||||
.load(DecryptableStreamUriLoader.DecryptableUri(thumbnailUri))
|
||||
.centerInside()
|
||||
.dontAnimate()
|
||||
.override(thumbnailSize[0], thumbnailSize[1])
|
||||
.into(binding.thumbnailStub.get())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setThumbnailSize(
|
||||
thumbnailWidth: Int,
|
||||
thumbnailHeight: Int,
|
||||
maxWidth: Int,
|
||||
maxHeight: Int
|
||||
) {
|
||||
if (thumbnailWidth == 0 || thumbnailHeight == 0) {
|
||||
thumbnailSize[0] = maxWidth
|
||||
thumbnailSize[1] = maxHeight
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailWidth <= maxWidth && thumbnailHeight <= maxHeight) {
|
||||
thumbnailSize[0] = thumbnailWidth
|
||||
thumbnailSize[1] = thumbnailHeight
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailWidth > maxWidth) {
|
||||
val thumbnailScale = 1 - ((thumbnailWidth - maxWidth) / thumbnailWidth.toFloat())
|
||||
|
||||
thumbnailSize[0] = (thumbnailWidth * thumbnailScale).toInt()
|
||||
thumbnailSize[1] = (thumbnailHeight * thumbnailScale).toInt()
|
||||
}
|
||||
|
||||
if (isThumbnailMetricsSatisfied(maxWidth, maxHeight)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailHeight > maxHeight) {
|
||||
val thumbnailScale = 1 - ((thumbnailHeight - maxHeight) / thumbnailHeight.toFloat())
|
||||
|
||||
thumbnailSize[0] = (thumbnailWidth * thumbnailScale).toInt()
|
||||
thumbnailSize[1] = (thumbnailHeight * thumbnailScale).toInt()
|
||||
}
|
||||
|
||||
if (isThumbnailMetricsSatisfied(maxWidth, maxHeight)) {
|
||||
return
|
||||
}
|
||||
|
||||
setThumbnailSize(
|
||||
thumbnailSize[0],
|
||||
thumbnailSize[1],
|
||||
maxWidth,
|
||||
maxHeight
|
||||
)
|
||||
}
|
||||
|
||||
private fun hasGroupSenderName(): Boolean {
|
||||
|
@ -265,25 +140,7 @@ class V2ConversationItemMediaViewHolder<Model : MappingModel<Model>>(
|
|||
return hasThumbnail() || hasQuote()
|
||||
}
|
||||
|
||||
private fun isThumbnailMetricsSatisfied(maxWidth: Int, maxHeight: Int): Boolean {
|
||||
return thumbnailSize[0] in 1..maxWidth && thumbnailSize[1] in 1..maxHeight
|
||||
}
|
||||
|
||||
private fun requireMediaMessage(): MediaMmsMessageRecord {
|
||||
return conversationMessage.messageRecord as MediaMmsMessageRecord
|
||||
}
|
||||
|
||||
private inner class PlaceholderTarget(view: ImageView) : CustomViewTarget<ImageView, Drawable>(view) {
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
view.background = errorDrawable
|
||||
}
|
||||
|
||||
override fun onResourceCleared(placeholder: Drawable?) {
|
||||
view.background = placeholder
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
view.background = resource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -355,7 +355,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
|||
}
|
||||
|
||||
private fun linkifyMessageBody(messageBody: Spannable) {
|
||||
V2ConversationBodyUtil.linkifyUrlLinks(messageBody, conversationContext.selectedItems.isEmpty(), conversationContext.clickListener::onUrlClicked)
|
||||
V2ConversationItemUtils.linkifyUrlLinks(messageBody, conversationContext.selectedItems.isEmpty(), conversationContext.clickListener::onUrlClicked)
|
||||
|
||||
if (conversationMessage.hasStyleLinks()) {
|
||||
messageBody.getSpans(0, messageBody.length, PlaceholderURLSpan::class.java).forEach { placeholder ->
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.annotation.ColorInt
|
|||
import androidx.core.content.ContextCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemUtils.isThumbnailAtBottomOfBubble
|
||||
import org.thoughtcrime.securesms.util.hasNoBubble
|
||||
|
||||
/**
|
||||
|
@ -34,6 +35,10 @@ class V2ConversationItemTheme(
|
|||
fun getFooterIconColor(
|
||||
conversationMessage: ConversationMessage
|
||||
): Int {
|
||||
if (conversationMessage.messageRecord.isThumbnailAtBottomOfBubble(context)) {
|
||||
return ContextCompat.getColor(context, R.color.signal_colorOnCustom)
|
||||
}
|
||||
|
||||
return getColor(
|
||||
conversationMessage,
|
||||
conversationContext.getColorizer()::getOutgoingFooterIconColor,
|
||||
|
@ -45,6 +50,10 @@ class V2ConversationItemTheme(
|
|||
fun getFooterTextColor(
|
||||
conversationMessage: ConversationMessage
|
||||
): Int {
|
||||
if (conversationMessage.messageRecord.isThumbnailAtBottomOfBubble(context)) {
|
||||
return ContextCompat.getColor(context, R.color.signal_colorOnCustom)
|
||||
}
|
||||
|
||||
return getColor(
|
||||
conversationMessage,
|
||||
conversationContext.getColorizer()::getOutgoingFooterTextColor,
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.LinearGradient
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Shader
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.util.Size
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.graphics.withTranslation
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.bumptech.glide.request.target.CustomViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import org.signal.core.util.dp
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemUtils.isThumbnailAtBottomOfBubble
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
|
||||
/**
|
||||
* ImageView subclass that adds support for a foreground drawable and
|
||||
* gradient at the bottom, rendered in onDrawForeground.
|
||||
*
|
||||
* This is lighter weight then adding a bunch of extra, possibly unnecessary views
|
||||
* to the relevant layouts.
|
||||
*
|
||||
* Also encapsulates the logic around presenting thumbnails to the user.
|
||||
*/
|
||||
class V2ConversationItemThumbnail @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : AppCompatImageView(context, attrs) {
|
||||
|
||||
companion object {
|
||||
private val FOREGROUND_SHADE_HEIGHT = 32.dp
|
||||
private val UNSET_SIZE = Size(-1, -1)
|
||||
private const val MAX_SIZE_RECURSION_DEPTH = 5
|
||||
}
|
||||
|
||||
private val gradientPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val rect = Rect()
|
||||
private var drawForegroundShade: Boolean = true
|
||||
private var compatForegroundDrawable: Drawable? = null
|
||||
|
||||
private var thumbnailSlide: Slide? = null
|
||||
private var fastPreflightId: String? = null
|
||||
|
||||
private var placeholderTarget: PlaceholderTarget? = null
|
||||
private var thumbnailSize = UNSET_SIZE
|
||||
|
||||
val thumbWidth: Int get() = thumbnailSize.width
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
gradientPaint.shader = LinearGradient(
|
||||
w / 2f,
|
||||
0f,
|
||||
w / 2f,
|
||||
FOREGROUND_SHADE_HEIGHT.toFloat(),
|
||||
intArrayOf(0x00000000, 0x88000000.toInt()),
|
||||
floatArrayOf(0f, 1f),
|
||||
Shader.TileMode.CLAMP
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDrawForeground(canvas: Canvas) {
|
||||
super.onDrawForeground(canvas)
|
||||
|
||||
canvas.getClipBounds(rect)
|
||||
|
||||
if (drawForegroundShade) {
|
||||
canvas.withTranslation(y = rect.height() - FOREGROUND_SHADE_HEIGHT.toFloat()) {
|
||||
canvas.drawPaint(gradientPaint)
|
||||
}
|
||||
}
|
||||
|
||||
// Sizing?
|
||||
compatForegroundDrawable?.draw(canvas)
|
||||
}
|
||||
|
||||
fun presentThumbnail(
|
||||
mediaMessage: MediaMmsMessageRecord,
|
||||
conversationContext: V2ConversationContext
|
||||
) {
|
||||
val slideDeck = mediaMessage.slideDeck
|
||||
if (slideDeck.thumbnailSlides.isEmpty() || slideDeck.thumbnailSlides.size > 1) {
|
||||
visibility = View.GONE
|
||||
thumbnailSize = UNSET_SIZE
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
visibility = View.VISIBLE
|
||||
|
||||
val thumbnail = slideDeck.thumbnailSlides.first()
|
||||
val fastPreflightId = slideDeck.thumbnailSlides.first().fastPreflightId
|
||||
|
||||
if (thumbnail == thumbnailSlide && fastPreflightId == this.fastPreflightId) {
|
||||
return
|
||||
}
|
||||
|
||||
thumbnailSlide = thumbnail
|
||||
this.fastPreflightId = fastPreflightId
|
||||
|
||||
conversationContext.glideRequests.clear(this)
|
||||
|
||||
if (placeholderTarget != null) {
|
||||
conversationContext.glideRequests.clear(placeholderTarget)
|
||||
}
|
||||
|
||||
val thumbnailUri = thumbnail.uri
|
||||
val thumbnailBlur = thumbnail.placeholderBlur
|
||||
|
||||
val thumbnailAttachment = thumbnail.asAttachment()
|
||||
val thumbnailWidth = thumbnailAttachment.width
|
||||
val thumbnailHeight = thumbnailAttachment.height
|
||||
|
||||
val maxWidth = context.resources.getDimensionPixelSize(R.dimen.media_bubble_max_width)
|
||||
val maxHeight = context.resources.getDimensionPixelSize(R.dimen.media_bubble_max_height)
|
||||
|
||||
setThumbnailSize(
|
||||
thumbnailWidth,
|
||||
thumbnailHeight,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
0
|
||||
)
|
||||
|
||||
updateLayoutParams {
|
||||
width = thumbnailSize.width
|
||||
height = thumbnailSize.height
|
||||
}
|
||||
|
||||
if (thumbnailBlur != null) {
|
||||
val placeholderTarget = PlaceholderTarget(this)
|
||||
conversationContext
|
||||
.glideRequests
|
||||
.load(thumbnailBlur)
|
||||
.centerInside()
|
||||
.dontAnimate()
|
||||
.override(thumbnailSize.width, thumbnailSize.height)
|
||||
.into(placeholderTarget)
|
||||
|
||||
this.placeholderTarget = placeholderTarget
|
||||
}
|
||||
|
||||
if (thumbnailUri != null) {
|
||||
conversationContext
|
||||
.glideRequests
|
||||
.load(DecryptableStreamUriLoader.DecryptableUri(thumbnailUri))
|
||||
.centerInside()
|
||||
.dontAnimate()
|
||||
.override(thumbnailSize.width, thumbnailSize.height)
|
||||
.into(this)
|
||||
}
|
||||
|
||||
setDrawForegroundShade(mediaMessage.isThumbnailAtBottomOfBubble(context))
|
||||
}
|
||||
|
||||
private fun setDrawForegroundShade(drawForegroundShade: Boolean) {
|
||||
this.drawForegroundShade = drawForegroundShade
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun setCompatForegroundDrawable(drawable: Drawable?) {
|
||||
this.compatForegroundDrawable = drawable
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun setThumbnailSize(
|
||||
thumbnailWidth: Int,
|
||||
thumbnailHeight: Int,
|
||||
maxWidth: Int,
|
||||
maxHeight: Int,
|
||||
depth: Int
|
||||
) {
|
||||
if (thumbnailWidth == 0 || thumbnailHeight == 0 || depth >= MAX_SIZE_RECURSION_DEPTH) {
|
||||
thumbnailSize = Size(maxWidth, maxHeight)
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailWidth <= maxWidth && thumbnailHeight <= maxHeight) {
|
||||
thumbnailSize = Size(thumbnailWidth, thumbnailHeight)
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailWidth > maxWidth) {
|
||||
val thumbnailScale = 1 - ((thumbnailWidth - maxWidth) / thumbnailWidth.toFloat())
|
||||
|
||||
thumbnailSize = Size(
|
||||
(thumbnailWidth * thumbnailScale).toInt(),
|
||||
(thumbnailHeight * thumbnailScale).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (isThumbnailMetricsSatisfied(maxWidth, maxHeight)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (thumbnailHeight > maxHeight) {
|
||||
val thumbnailScale = 1 - ((thumbnailHeight - maxHeight) / thumbnailHeight.toFloat())
|
||||
|
||||
thumbnailSize = Size(
|
||||
(thumbnailWidth * thumbnailScale).toInt(),
|
||||
(thumbnailHeight * thumbnailScale).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
if (isThumbnailMetricsSatisfied(maxWidth, maxHeight)) {
|
||||
return
|
||||
}
|
||||
|
||||
setThumbnailSize(
|
||||
thumbnailSize.width,
|
||||
thumbnailSize.height,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
depth + 1
|
||||
)
|
||||
}
|
||||
|
||||
private fun isThumbnailMetricsSatisfied(maxWidth: Int, maxHeight: Int): Boolean {
|
||||
return thumbnailSize.width in 1..maxWidth && thumbnailSize.height in 1..maxHeight
|
||||
}
|
||||
|
||||
private class PlaceholderTarget(view: ImageView) : CustomViewTarget<ImageView, Drawable>(view) {
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
view.background = errorDrawable
|
||||
}
|
||||
|
||||
override fun onResourceCleared(placeholder: Drawable?) {
|
||||
view.background = placeholder
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
view.background = resource
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,19 +5,26 @@
|
|||
|
||||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Spannable
|
||||
import android.text.Spanned
|
||||
import android.text.style.URLSpan
|
||||
import android.text.util.Linkify
|
||||
import androidx.core.text.util.LinkifyCompat
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan
|
||||
import org.thoughtcrime.securesms.util.LinkUtil
|
||||
import org.thoughtcrime.securesms.util.UrlClickHandler
|
||||
import org.thoughtcrime.securesms.util.hasOnlyThumbnail
|
||||
|
||||
/**
|
||||
* Utilities for presenting the body of a conversation message.
|
||||
*/
|
||||
object V2ConversationBodyUtil {
|
||||
object V2ConversationItemUtils {
|
||||
|
||||
fun MessageRecord.isThumbnailAtBottomOfBubble(context: Context): Boolean {
|
||||
return hasOnlyThumbnail(context) && isDisplayBodyEmpty(context)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun linkifyUrlLinks(messageBody: Spannable, shouldLinkifyAllLinks: Boolean, urlClickHandler: UrlClickHandler) {
|
|
@ -6,7 +6,6 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2.items
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import org.signal.core.util.dp
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
|
@ -24,7 +23,7 @@ class V2FooterPositionDelegate private constructor(
|
|||
private val footerViews: List<View>,
|
||||
private val bodyContainer: View,
|
||||
private val body: EmojiTextView,
|
||||
private val thumbnailView: Stub<ImageView>?
|
||||
private val thumbnailView: Stub<V2ConversationItemThumbnail>?
|
||||
) : V2ConversationItemLayout.OnMeasureListener {
|
||||
|
||||
constructor(binding: V2ConversationItemTextOnlyBindingBridge) : this(
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
~ Copyright 2023 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemThumbnail
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -8,7 +8,7 @@ import org.junit.Test
|
|||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationBodyUtil
|
||||
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemUtils
|
||||
import org.thoughtcrime.securesms.util.UrlClickHandler
|
||||
|
||||
@Suppress("ClassName")
|
||||
|
@ -20,7 +20,7 @@ class ConversationItemTest_linkifyUrlLinks(private val input: String, private va
|
|||
fun test1() {
|
||||
val spannableStringBuilder = SpannableStringBuilder(input)
|
||||
|
||||
V2ConversationBodyUtil.linkifyUrlLinks(spannableStringBuilder, true, UrlHandler)
|
||||
V2ConversationItemUtils.linkifyUrlLinks(spannableStringBuilder, true, UrlHandler)
|
||||
|
||||
val spans = spannableStringBuilder.getSpans(0, expectedUrl.length, URLSpan::class.java)
|
||||
assertEquals(1, spans.size)
|
||||
|
|
Loading…
Add table
Reference in a new issue