Add several performance improvements to ConversationItemV2.

This commit is contained in:
Alex Hart 2023-08-18 14:12:36 -03:00 committed by Cody Henthorne
parent 32ae4393e2
commit 4494d8652d
10 changed files with 342 additions and 128 deletions

View file

@ -95,8 +95,10 @@ class BadgeImageView @JvmOverloads constructor(
}
private fun clearDrawable() {
setImageDrawable(null)
isClickable = false
if (drawable != null) {
setImageDrawable(null)
isClickable = false
}
}
private fun getGlideRequests(): GlideRequests? {

View file

@ -59,73 +59,93 @@ class BadgeSpriteTransformation(
return outBitmap
}
enum class Size(val code: String, val frameMap: Map<Density, FrameSet>) {
enum class Size(val code: String) {
SMALL(
"small",
mapOf(
Density.LDPI to FrameSet(Frame(124, 1, 12, 12), Frame(145, 31, 12, 12)),
Density.MDPI to FrameSet(Frame(163, 1, 16, 16), Frame(189, 39, 16, 16)),
Density.HDPI to FrameSet(Frame(244, 1, 24, 24), Frame(283, 58, 24, 24)),
Density.XHDPI to FrameSet(Frame(323, 1, 32, 32), Frame(373, 75, 32, 32)),
Density.XXHDPI to FrameSet(Frame(483, 1, 48, 48), Frame(557, 111, 48, 48)),
Density.XXXHDPI to FrameSet(Frame(643, 1, 64, 64), Frame(741, 147, 64, 64))
)
),
"small"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf(
Density.LDPI to FrameSet(Frame(124, 1, 12, 12), Frame(145, 31, 12, 12)),
Density.MDPI to FrameSet(Frame(163, 1, 16, 16), Frame(189, 39, 16, 16)),
Density.HDPI to FrameSet(Frame(244, 1, 24, 24), Frame(283, 58, 24, 24)),
Density.XHDPI to FrameSet(Frame(323, 1, 32, 32), Frame(373, 75, 32, 32)),
Density.XXHDPI to FrameSet(Frame(483, 1, 48, 48), Frame(557, 111, 48, 48)),
Density.XXXHDPI to FrameSet(Frame(643, 1, 64, 64), Frame(741, 147, 64, 64))
)
}
},
MEDIUM(
"medium",
mapOf(
Density.LDPI to FrameSet(Frame(124, 16, 18, 18), Frame(160, 31, 18, 18)),
Density.MDPI to FrameSet(Frame(163, 19, 24, 24), Frame(207, 39, 24, 24)),
Density.HDPI to FrameSet(Frame(244, 28, 36, 36), Frame(310, 58, 36, 36)),
Density.XHDPI to FrameSet(Frame(323, 35, 48, 48), Frame(407, 75, 48, 48)),
Density.XXHDPI to FrameSet(Frame(483, 51, 72, 72), Frame(607, 111, 72, 72)),
Density.XXXHDPI to FrameSet(Frame(643, 67, 96, 96), Frame(807, 147, 96, 96))
)
),
"medium"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf(
Density.LDPI to FrameSet(Frame(124, 16, 18, 18), Frame(160, 31, 18, 18)),
Density.MDPI to FrameSet(Frame(163, 19, 24, 24), Frame(207, 39, 24, 24)),
Density.HDPI to FrameSet(Frame(244, 28, 36, 36), Frame(310, 58, 36, 36)),
Density.XHDPI to FrameSet(Frame(323, 35, 48, 48), Frame(407, 75, 48, 48)),
Density.XXHDPI to FrameSet(Frame(483, 51, 72, 72), Frame(607, 111, 72, 72)),
Density.XXXHDPI to FrameSet(Frame(643, 67, 96, 96), Frame(807, 147, 96, 96))
)
}
},
LARGE(
"large",
mapOf(
Density.LDPI to FrameSet(Frame(145, 1, 27, 27), Frame(124, 46, 27, 27)),
Density.MDPI to FrameSet(Frame(189, 1, 36, 36), Frame(163, 57, 36, 36)),
Density.HDPI to FrameSet(Frame(283, 1, 54, 54), Frame(244, 85, 54, 54)),
Density.XHDPI to FrameSet(Frame(373, 1, 72, 72), Frame(323, 109, 72, 72)),
Density.XXHDPI to FrameSet(Frame(557, 1, 108, 108), Frame(483, 161, 108, 108)),
Density.XXXHDPI to FrameSet(Frame(741, 1, 144, 144), Frame(643, 213, 144, 144))
)
),
"large"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf(
Density.LDPI to FrameSet(Frame(145, 1, 27, 27), Frame(124, 46, 27, 27)),
Density.MDPI to FrameSet(Frame(189, 1, 36, 36), Frame(163, 57, 36, 36)),
Density.HDPI to FrameSet(Frame(283, 1, 54, 54), Frame(244, 85, 54, 54)),
Density.XHDPI to FrameSet(Frame(373, 1, 72, 72), Frame(323, 109, 72, 72)),
Density.XXHDPI to FrameSet(Frame(557, 1, 108, 108), Frame(483, 161, 108, 108)),
Density.XXXHDPI to FrameSet(Frame(741, 1, 144, 144), Frame(643, 213, 144, 144))
)
}
},
BADGE_64(
"badge_64",
mapOf(
Density.LDPI to FrameSet(Frame(124, 73, 48, 48), Frame(124, 73, 48, 48)),
Density.MDPI to FrameSet(Frame(163, 97, 64, 64), Frame(163, 97, 64, 64)),
Density.HDPI to FrameSet(Frame(244, 145, 96, 96), Frame(244, 145, 96, 96)),
Density.XHDPI to FrameSet(Frame(323, 193, 128, 128), Frame(323, 193, 128, 128)),
Density.XXHDPI to FrameSet(Frame(483, 289, 192, 192), Frame(483, 289, 192, 192)),
Density.XXXHDPI to FrameSet(Frame(643, 385, 256, 256), Frame(643, 385, 256, 256))
)
),
"badge_64"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf(
Density.LDPI to FrameSet(Frame(124, 73, 48, 48), Frame(124, 73, 48, 48)),
Density.MDPI to FrameSet(Frame(163, 97, 64, 64), Frame(163, 97, 64, 64)),
Density.HDPI to FrameSet(Frame(244, 145, 96, 96), Frame(244, 145, 96, 96)),
Density.XHDPI to FrameSet(Frame(323, 193, 128, 128), Frame(323, 193, 128, 128)),
Density.XXHDPI to FrameSet(Frame(483, 289, 192, 192), Frame(483, 289, 192, 192)),
Density.XXXHDPI to FrameSet(Frame(643, 385, 256, 256), Frame(643, 385, 256, 256))
)
}
},
BADGE_112(
"badge_112",
mapOf(
Density.LDPI to FrameSet(Frame(181, 1, 84, 84), Frame(181, 1, 84, 84)),
Density.MDPI to FrameSet(Frame(233, 1, 112, 112), Frame(233, 1, 112, 112)),
Density.HDPI to FrameSet(Frame(349, 1, 168, 168), Frame(349, 1, 168, 168)),
Density.XHDPI to FrameSet(Frame(457, 1, 224, 224), Frame(457, 1, 224, 224)),
Density.XXHDPI to FrameSet(Frame(681, 1, 336, 336), Frame(681, 1, 336, 336)),
Density.XXXHDPI to FrameSet(Frame(905, 1, 448, 448), Frame(905, 1, 448, 448))
)
),
"badge_112"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf(
Density.LDPI to FrameSet(Frame(181, 1, 84, 84), Frame(181, 1, 84, 84)),
Density.MDPI to FrameSet(Frame(233, 1, 112, 112), Frame(233, 1, 112, 112)),
Density.HDPI to FrameSet(Frame(349, 1, 168, 168), Frame(349, 1, 168, 168)),
Density.XHDPI to FrameSet(Frame(457, 1, 224, 224), Frame(457, 1, 224, 224)),
Density.XXHDPI to FrameSet(Frame(681, 1, 336, 336), Frame(681, 1, 336, 336)),
Density.XXXHDPI to FrameSet(Frame(905, 1, 448, 448), Frame(905, 1, 448, 448))
)
}
},
XLARGE(
"xlarge",
mapOf(
Density.LDPI to FrameSet(Frame(1, 1, 120, 120), Frame(1, 1, 120, 120)),
Density.MDPI to FrameSet(Frame(1, 1, 160, 160), Frame(1, 1, 160, 160)),
Density.HDPI to FrameSet(Frame(1, 1, 240, 240), Frame(1, 1, 240, 240)),
Density.XHDPI to FrameSet(Frame(1, 1, 320, 320), Frame(1, 1, 320, 320)),
Density.XXHDPI to FrameSet(Frame(1, 1, 480, 480), Frame(1, 1, 480, 480)),
Density.XXXHDPI to FrameSet(Frame(1, 1, 640, 640), Frame(1, 1, 640, 640))
)
);
"xlarge"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf(
Density.LDPI to FrameSet(Frame(1, 1, 120, 120), Frame(1, 1, 120, 120)),
Density.MDPI to FrameSet(Frame(1, 1, 160, 160), Frame(1, 1, 160, 160)),
Density.HDPI to FrameSet(Frame(1, 1, 240, 240), Frame(1, 1, 240, 240)),
Density.XHDPI to FrameSet(Frame(1, 1, 320, 320), Frame(1, 1, 320, 320)),
Density.XXHDPI to FrameSet(Frame(1, 1, 480, 480), Frame(1, 1, 480, 480)),
Density.XXXHDPI to FrameSet(Frame(1, 1, 640, 640), Frame(1, 1, 640, 640))
)
}
};
abstract val frameMap: Map<Density, FrameSet>
companion object {
fun fromInteger(integer: Int): Size {

View file

@ -2303,6 +2303,11 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
return bodyBubble;
}
@Override
public void invalidateChatColorsDrawable(@NonNull ViewGroup coordinateRoot) {
// Intentionally left blank.
}
private class SharedContactEventListener implements SharedContactView.EventListener {
@Override
public void onAddToContactsClicked(@NonNull Contact contact) {

View file

@ -185,6 +185,7 @@ import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryResults
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryViewModelV2
import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupCallViewModel
import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupViewModel
import org.thoughtcrime.securesms.conversation.v2.items.ChatColorsDrawable
import org.thoughtcrime.securesms.conversation.v2.items.InteractiveConversationElement
import org.thoughtcrime.securesms.conversation.v2.keyboard.AttachmentKeyboardFragment
import org.thoughtcrime.securesms.database.DraftTable
@ -579,6 +580,8 @@ class ConversationFragment :
registerForResults()
inputPanel.setMediaListener(InputPanelMediaListener())
ChatColorsDrawable.attach(binding.conversationItemRecycler)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {

View file

@ -0,0 +1,165 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversation.v2.items
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.view.ViewGroup
import androidx.core.graphics.withClip
import androidx.core.graphics.withTranslation
import androidx.core.view.children
import androidx.core.view.doOnDetach
import androidx.recyclerview.widget.RecyclerView
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.util.Projection
import org.thoughtcrime.securesms.util.Projection.Corners
/**
* Drawable that renders the given chat colors at a specified coordinate offset.
* This is meant to be used in conjunction with [ChatColorsItemDecoration]
*/
class ChatColorsDrawable : Drawable() {
companion object {
private var maskDrawable: Drawable? = null
/**
* Binds the ChatColorsDrawable static cache to the lifecycle of the given recycler-view
*/
fun attach(recyclerView: RecyclerView) {
recyclerView.addOnLayoutChangeListener { _, left, top, right, bottom, _, _, _, _ ->
applyBounds(Rect(left, top, right, bottom))
}
recyclerView.addItemDecoration(ChatColorsItemDecoration)
recyclerView.doOnDetach {
maskDrawable = null
}
}
private fun applyBounds(bounds: Rect) {
maskDrawable?.bounds = bounds
}
}
/**
* Translation coordinates so that the mask is drawn at the right location
* on the screen.
*/
private val maskOffset = PointF()
/**
* Clipping path that includes the dimensions and corners for this view.
*/
private val path = Path()
private val rect = RectF()
private var gradientColors: ChatColors? = null
private var corners: FloatArray = floatArrayOf()
private var fillColor: Int = 0
override fun draw(canvas: Canvas) {
if (gradientColors == null && fillColor == 0) {
return
}
val mask = maskDrawable
if (gradientColors != null && mask != null) {
canvas.withTranslation(-maskOffset.x, -maskOffset.y) {
canvas.withClip(path) {
mask.draw(canvas)
}
}
} else {
path.reset()
rect.set(bounds)
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas.withClip(path) {
canvas.drawColor(fillColor)
}
}
}
override fun setAlpha(alpha: Int) = Unit
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
/**
* Applies the given [Projection] as the clipping path for the canvas on subsequent draws.
* Also applies the given [Projection]'s (x,y) (Top, Left) coordinates as the mask offset,
* which is used as a canvas translation before drawing.
*
* This is done separately from setting the corners and color, because it needs to happen
* on every frame we would normally perform a decorator onDraw, whereas setting the corners
* and color only needs to happen on bind.
*/
fun applyMaskProjection(projection: Projection) {
path.reset()
projection.applyToPath(path)
maskOffset.set(
projection.x,
projection.y
)
invalidateSelf()
}
fun isSolidColor(): Boolean {
return gradientColors == null
}
/**
* Sets the chat color and shape as specified. If the colors are a gradient,
* we will use masking to draw, and we will draw every time we're told to by
* the decorator.
*
* If a solid color is set, we can skip drawing as we move, since we haven't changed.
*/
fun setChatColors(
chatColors: ChatColors,
corners: Corners
) {
this.gradientColors = chatColors
this.corners = corners.toRadii()
if (chatColors.isGradient()) {
if (maskDrawable == null) {
maskDrawable = chatColors.chatBubbleMask
}
this.fillColor = 0
} else {
this.fillColor = chatColors.asSingleColor()
this.gradientColors = null
}
invalidateSelf()
}
private object ChatColorsItemDecoration : RecyclerView.ItemDecoration() {
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
parent.children.map { parent.getChildViewHolder(it) }.filterIsInstance<ChatColorsDrawableInvalidator>().forEach { element ->
element.invalidateChatColorsDrawable(parent)
}
}
}
interface ChatColorsDrawableInvalidator {
fun invalidateChatColorsDrawable(coordinateRoot: ViewGroup)
}
}

View file

@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.util.ProjectionList
/**
* A conversation element that a user can either swipe or snapshot
*/
interface InteractiveConversationElement {
interface InteractiveConversationElement : ChatColorsDrawable.ChatColorsDrawableInvalidator {
val conversationMessage: ConversationMessage
val root: ViewGroup

View file

@ -5,8 +5,6 @@
package org.thoughtcrime.securesms.conversation.v2.items
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import org.signal.core.util.dp
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.util.DateUtils
@ -36,11 +34,6 @@ class V2ConversationItemShape(
var corners: Projection.Corners = Projection.Corners(bigRadius)
private set
var bodyBubble: MaterialShapeDrawable = MaterialShapeDrawable(
ShapeAppearanceModel.Builder().setAllCornerSizes(bigRadius).build()
)
private set
/**
* Sets the message spacing and corners based off the given information. This
* updates the class state.
@ -93,12 +86,6 @@ class V2ConversationItemShape(
}
corners = newCorners
bodyBubble.shapeAppearanceModel = ShapeAppearanceModel.builder()
.setTopLeftCornerSize(corners.topLeft)
.setTopRightCornerSize(corners.topRight)
.setBottomLeftCornerSize(corners.bottomLeft)
.setBottomRightCornerSize(corners.bottomRight)
.build()
}
private fun isSingularMessage(

View file

@ -63,7 +63,7 @@ fun V2ConversationItemTextOnlyIncomingBinding.bridge(): V2ConversationItemTextOn
conversationItemFooterBackground = conversationItemFooterBackground,
conversationItemAlert = null,
conversationItemFooterSpace = null,
isIncoming = false
isIncoming = true
)
}

View file

@ -6,7 +6,6 @@
package org.thoughtcrime.securesms.conversation.v2.items
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
@ -64,30 +63,30 @@ class V2ConversationItemTheme(
)
}
@ColorInt
fun getBodyBubbleColor(
conversationMessage: ConversationMessage
): ColorStateList {
): Int {
if (conversationMessage.messageRecord.hasNoBubble(context)) {
return ColorStateList.valueOf(Color.TRANSPARENT)
return Color.TRANSPARENT
}
return getFooterBubbleColor(conversationMessage)
}
@ColorInt
fun getFooterBubbleColor(
conversationMessage: ConversationMessage
): ColorStateList {
return ColorStateList.valueOf(
if (conversationMessage.messageRecord.isOutgoing) {
Color.TRANSPARENT
): Int {
return if (conversationMessage.messageRecord.isOutgoing) {
Color.TRANSPARENT
} else {
if (conversationContext.hasWallpaper()) {
ContextCompat.getColor(context, R.color.signal_colorSurface)
} else {
if (conversationContext.hasWallpaper()) {
ContextCompat.getColor(context, R.color.signal_colorSurface)
} else {
ContextCompat.getColor(context, R.color.signal_colorSurface2)
}
ContextCompat.getColor(context, R.color.signal_colorSurface2)
}
)
}
}
@ColorInt

View file

@ -23,8 +23,6 @@ import android.view.ViewGroup.MarginLayoutParams
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import org.signal.core.util.StringUtil
import org.signal.core.util.dp
import org.thoughtcrime.securesms.R
@ -32,6 +30,7 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.conversation.BodyBubbleLayoutTransition
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.mutiselect.Multiselect
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.mutiselect.Multiselectable
@ -55,7 +54,6 @@ import org.thoughtcrime.securesms.util.VibrateUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
import org.thoughtcrime.securesms.util.hasExtraText
import org.thoughtcrime.securesms.util.hasNoBubble
import org.thoughtcrime.securesms.util.isScheduled
import org.thoughtcrime.securesms.util.visible
import java.util.Locale
@ -82,6 +80,9 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
companion object {
private val STYLE_FACTORY = StyleFactory { arrayOf<CharacterStyle>(BackgroundColorSpan(Color.YELLOW), ForegroundColorSpan(Color.BLACK)) }
private const val CONDENSED_MODE_MAX_LINES = 3
private val footerCorners = Projection.Corners(18f.dp)
private val transparentChatColors = ChatColors.forColor(ChatColors.Id.NotSet, Color.TRANSPARENT)
}
private var messageId: Long = Long.MAX_VALUE
@ -89,13 +90,6 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
private val projections = ProjectionList()
private val footerDelegate = V2FooterPositionDelegate(binding)
private val conversationItemFooterBackgroundCorners = Projection.Corners(18f.dp)
private val conversationItemFooterBackground = MaterialShapeDrawable(
ShapeAppearanceModel.Builder()
.setAllCornerSizes(18f.dp)
.build()
)
override lateinit var conversationMessage: ConversationMessage
override val root: ViewGroup = binding.root
@ -117,6 +111,9 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
private var reactionMeasureListener: ReactionMeasureListener = ReactionMeasureListener()
private val bodyBubbleDrawable = ChatColorsDrawable()
private val footerDrawable = ChatColorsDrawable()
init {
binding.root.addOnMeasureListener(footerDelegate)
@ -150,7 +147,15 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
binding.conversationItemBody.setMentionBackgroundTint(ContextCompat.getColor(context, R.color.transparent_black_25))
}
binding.conversationItemBodyWrapper.background = bodyBubbleDrawable
binding.conversationItemBodyWrapper.layoutTransition = BodyBubbleLayoutTransition()
binding.conversationItemFooterBackground.background = footerDrawable
}
override fun invalidateChatColorsDrawable(coordinateRoot: ViewGroup) {
invalidateBodyBubbleDrawable(coordinateRoot)
invalidateFooterDrawable(coordinateRoot)
}
override fun bind(model: Model) {
@ -164,11 +169,6 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
adapterPosition = bindingAdapterPosition
)
shapeDelegate.bodyBubble.fillColor = themeDelegate.getBodyBubbleColor(conversationMessage)
binding.conversationItemBodyWrapper.background = shapeDelegate.bodyBubble
binding.conversationItemReply.setBackgroundColor(themeDelegate.getReplyIconBackgroundColor())
presentBody()
presentDate(shape)
presentDeliveryStatus(shape)
@ -178,6 +178,19 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
presentSender()
presentReactions()
bodyBubbleDrawable.setChatColors(
if (binding.conversationItemBody.isJumbomoji) {
transparentChatColors
} else if (binding.isIncoming) {
ChatColors.forColor(ChatColors.Id.NotSet, themeDelegate.getBodyBubbleColor(conversationMessage))
} else {
conversationMessage.threadRecipient.chatColors
},
shapeDelegate.corners
)
binding.conversationItemReply.setBackgroundColor(themeDelegate.getReplyIconBackgroundColor())
itemView.updateLayoutParams<MarginLayoutParams> {
topMargin = shape.topPadding.toInt()
bottomMargin = shape.bottomPadding.toInt()
@ -208,29 +221,13 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
return projections
}
/**
* Note: This is not necessary for CFV2 Text-Only items because the background is rendered by
* [ChatColorsDrawable]
*/
override fun getColorizerProjections(coordinateRoot: ViewGroup): ProjectionList {
projections.clear()
if (conversationMessage.messageRecord.isOutgoing) {
if (!conversationMessage.messageRecord.hasNoBubble(context)) {
projections.add(
Projection.relativeToParent(
coordinateRoot,
binding.conversationItemBodyWrapper,
shapeDelegate.corners
).translateX(binding.conversationItemBodyWrapper.translationX).translateY(root.translationY)
)
} else if (conversationContext.hasWallpaper()) {
projections.add(
Projection.relativeToParent(
coordinateRoot,
binding.conversationItemFooterBackground,
conversationItemFooterBackgroundCorners
).translateX(binding.conversationItemFooterBackground.translationX).translateY(root.translationY)
)
}
}
return projections
}
@ -277,6 +274,36 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
override fun shouldProjectContent(): Boolean = false
private fun invalidateFooterDrawable(coordinateRoot: ViewGroup) {
if (footerDrawable.isSolidColor()) {
return
}
val projection = Projection.relativeToParent(
coordinateRoot,
binding.conversationItemFooterBackground,
shapeDelegate.corners
)
footerDrawable.applyMaskProjection(projection)
projection.release()
}
private fun invalidateBodyBubbleDrawable(coordinateRoot: ViewGroup) {
if (bodyBubbleDrawable.isSolidColor()) {
return
}
val projection = Projection.relativeToParent(
coordinateRoot,
binding.conversationItemBodyWrapper,
shapeDelegate.corners
)
bodyBubbleDrawable.applyMaskProjection(projection)
projection.release()
}
private fun MessageRecord.buildMessageId(): Long {
return if (isMms) -id else id
}
@ -472,8 +499,14 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
}
binding.conversationItemFooterBackground.visible = true
binding.conversationItemFooterBackground.background = conversationItemFooterBackground
conversationItemFooterBackground.fillColor = themeDelegate.getFooterBubbleColor(conversationMessage)
footerDrawable.setChatColors(
if (binding.isIncoming) {
ChatColors.forColor(ChatColors.Id.NotSet, themeDelegate.getFooterBubbleColor(conversationMessage))
} else {
conversationMessage.threadRecipient.chatColors
},
footerCorners
)
}
private fun presentDate(shape: V2ConversationItemShape.MessageShape) {