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,9 +95,11 @@ class BadgeImageView @JvmOverloads constructor(
} }
private fun clearDrawable() { private fun clearDrawable() {
if (drawable != null) {
setImageDrawable(null) setImageDrawable(null)
isClickable = false isClickable = false
} }
}
private fun getGlideRequests(): GlideRequests? { private fun getGlideRequests(): GlideRequests? {
return try { return try {

View file

@ -59,9 +59,11 @@ class BadgeSpriteTransformation(
return outBitmap return outBitmap
} }
enum class Size(val code: String, val frameMap: Map<Density, FrameSet>) { enum class Size(val code: String) {
SMALL( SMALL(
"small", "small"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf( mapOf(
Density.LDPI to FrameSet(Frame(124, 1, 12, 12), Frame(145, 31, 12, 12)), 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.MDPI to FrameSet(Frame(163, 1, 16, 16), Frame(189, 39, 16, 16)),
@ -70,9 +72,12 @@ class BadgeSpriteTransformation(
Density.XXHDPI to FrameSet(Frame(483, 1, 48, 48), Frame(557, 111, 48, 48)), 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)) Density.XXXHDPI to FrameSet(Frame(643, 1, 64, 64), Frame(741, 147, 64, 64))
) )
), }
},
MEDIUM( MEDIUM(
"medium", "medium"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf( mapOf(
Density.LDPI to FrameSet(Frame(124, 16, 18, 18), Frame(160, 31, 18, 18)), 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.MDPI to FrameSet(Frame(163, 19, 24, 24), Frame(207, 39, 24, 24)),
@ -81,9 +86,12 @@ class BadgeSpriteTransformation(
Density.XXHDPI to FrameSet(Frame(483, 51, 72, 72), Frame(607, 111, 72, 72)), 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)) Density.XXXHDPI to FrameSet(Frame(643, 67, 96, 96), Frame(807, 147, 96, 96))
) )
), }
},
LARGE( LARGE(
"large", "large"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf( mapOf(
Density.LDPI to FrameSet(Frame(145, 1, 27, 27), Frame(124, 46, 27, 27)), 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.MDPI to FrameSet(Frame(189, 1, 36, 36), Frame(163, 57, 36, 36)),
@ -92,9 +100,12 @@ class BadgeSpriteTransformation(
Density.XXHDPI to FrameSet(Frame(557, 1, 108, 108), Frame(483, 161, 108, 108)), 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)) Density.XXXHDPI to FrameSet(Frame(741, 1, 144, 144), Frame(643, 213, 144, 144))
) )
), }
},
BADGE_64( BADGE_64(
"badge_64", "badge_64"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf( mapOf(
Density.LDPI to FrameSet(Frame(124, 73, 48, 48), Frame(124, 73, 48, 48)), 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.MDPI to FrameSet(Frame(163, 97, 64, 64), Frame(163, 97, 64, 64)),
@ -103,9 +114,12 @@ class BadgeSpriteTransformation(
Density.XXHDPI to FrameSet(Frame(483, 289, 192, 192), Frame(483, 289, 192, 192)), 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)) Density.XXXHDPI to FrameSet(Frame(643, 385, 256, 256), Frame(643, 385, 256, 256))
) )
), }
},
BADGE_112( BADGE_112(
"badge_112", "badge_112"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf( mapOf(
Density.LDPI to FrameSet(Frame(181, 1, 84, 84), Frame(181, 1, 84, 84)), 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.MDPI to FrameSet(Frame(233, 1, 112, 112), Frame(233, 1, 112, 112)),
@ -114,9 +128,12 @@ class BadgeSpriteTransformation(
Density.XXHDPI to FrameSet(Frame(681, 1, 336, 336), Frame(681, 1, 336, 336)), 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)) Density.XXXHDPI to FrameSet(Frame(905, 1, 448, 448), Frame(905, 1, 448, 448))
) )
), }
},
XLARGE( XLARGE(
"xlarge", "xlarge"
) {
override val frameMap: Map<Density, FrameSet> by lazy {
mapOf( mapOf(
Density.LDPI to FrameSet(Frame(1, 1, 120, 120), Frame(1, 1, 120, 120)), 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.MDPI to FrameSet(Frame(1, 1, 160, 160), Frame(1, 1, 160, 160)),
@ -125,7 +142,10 @@ class BadgeSpriteTransformation(
Density.XXHDPI to FrameSet(Frame(1, 1, 480, 480), Frame(1, 1, 480, 480)), 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)) Density.XXXHDPI to FrameSet(Frame(1, 1, 640, 640), Frame(1, 1, 640, 640))
) )
); }
};
abstract val frameMap: Map<Density, FrameSet>
companion object { companion object {
fun fromInteger(integer: Int): Size { fun fromInteger(integer: Int): Size {

View file

@ -2303,6 +2303,11 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
return bodyBubble; return bodyBubble;
} }
@Override
public void invalidateChatColorsDrawable(@NonNull ViewGroup coordinateRoot) {
// Intentionally left blank.
}
private class SharedContactEventListener implements SharedContactView.EventListener { private class SharedContactEventListener implements SharedContactView.EventListener {
@Override @Override
public void onAddToContactsClicked(@NonNull Contact contact) { 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.ui.inlinequery.InlineQueryViewModelV2
import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupCallViewModel import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupCallViewModel
import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupViewModel 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.items.InteractiveConversationElement
import org.thoughtcrime.securesms.conversation.v2.keyboard.AttachmentKeyboardFragment import org.thoughtcrime.securesms.conversation.v2.keyboard.AttachmentKeyboardFragment
import org.thoughtcrime.securesms.database.DraftTable import org.thoughtcrime.securesms.database.DraftTable
@ -579,6 +580,8 @@ class ConversationFragment :
registerForResults() registerForResults()
inputPanel.setMediaListener(InputPanelMediaListener()) inputPanel.setMediaListener(InputPanelMediaListener())
ChatColorsDrawable.attach(binding.conversationItemRecycler)
} }
override fun onViewStateRestored(savedInstanceState: Bundle?) { 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 * A conversation element that a user can either swipe or snapshot
*/ */
interface InteractiveConversationElement { interface InteractiveConversationElement : ChatColorsDrawable.ChatColorsDrawableInvalidator {
val conversationMessage: ConversationMessage val conversationMessage: ConversationMessage
val root: ViewGroup val root: ViewGroup

View file

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

View file

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

View file

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

View file

@ -23,8 +23,6 @@ import android.view.ViewGroup.MarginLayoutParams
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView 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.StringUtil
import org.signal.core.util.dp import org.signal.core.util.dp
import org.thoughtcrime.securesms.R 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.BodyBubbleLayoutTransition
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode
import org.thoughtcrime.securesms.conversation.ConversationMessage 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.Multiselect
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.mutiselect.Multiselectable 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.MappingModel
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
import org.thoughtcrime.securesms.util.hasExtraText import org.thoughtcrime.securesms.util.hasExtraText
import org.thoughtcrime.securesms.util.hasNoBubble
import org.thoughtcrime.securesms.util.isScheduled import org.thoughtcrime.securesms.util.isScheduled
import org.thoughtcrime.securesms.util.visible import org.thoughtcrime.securesms.util.visible
import java.util.Locale import java.util.Locale
@ -82,6 +80,9 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
companion object { companion object {
private val STYLE_FACTORY = StyleFactory { arrayOf<CharacterStyle>(BackgroundColorSpan(Color.YELLOW), ForegroundColorSpan(Color.BLACK)) } private val STYLE_FACTORY = StyleFactory { arrayOf<CharacterStyle>(BackgroundColorSpan(Color.YELLOW), ForegroundColorSpan(Color.BLACK)) }
private const val CONDENSED_MODE_MAX_LINES = 3 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 private var messageId: Long = Long.MAX_VALUE
@ -89,13 +90,6 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
private val projections = ProjectionList() private val projections = ProjectionList()
private val footerDelegate = V2FooterPositionDelegate(binding) 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 lateinit var conversationMessage: ConversationMessage
override val root: ViewGroup = binding.root override val root: ViewGroup = binding.root
@ -117,6 +111,9 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
private var reactionMeasureListener: ReactionMeasureListener = ReactionMeasureListener() private var reactionMeasureListener: ReactionMeasureListener = ReactionMeasureListener()
private val bodyBubbleDrawable = ChatColorsDrawable()
private val footerDrawable = ChatColorsDrawable()
init { init {
binding.root.addOnMeasureListener(footerDelegate) 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.conversationItemBody.setMentionBackgroundTint(ContextCompat.getColor(context, R.color.transparent_black_25))
} }
binding.conversationItemBodyWrapper.background = bodyBubbleDrawable
binding.conversationItemBodyWrapper.layoutTransition = BodyBubbleLayoutTransition() binding.conversationItemBodyWrapper.layoutTransition = BodyBubbleLayoutTransition()
binding.conversationItemFooterBackground.background = footerDrawable
}
override fun invalidateChatColorsDrawable(coordinateRoot: ViewGroup) {
invalidateBodyBubbleDrawable(coordinateRoot)
invalidateFooterDrawable(coordinateRoot)
} }
override fun bind(model: Model) { override fun bind(model: Model) {
@ -164,11 +169,6 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
adapterPosition = bindingAdapterPosition adapterPosition = bindingAdapterPosition
) )
shapeDelegate.bodyBubble.fillColor = themeDelegate.getBodyBubbleColor(conversationMessage)
binding.conversationItemBodyWrapper.background = shapeDelegate.bodyBubble
binding.conversationItemReply.setBackgroundColor(themeDelegate.getReplyIconBackgroundColor())
presentBody() presentBody()
presentDate(shape) presentDate(shape)
presentDeliveryStatus(shape) presentDeliveryStatus(shape)
@ -178,6 +178,19 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
presentSender() presentSender()
presentReactions() 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> { itemView.updateLayoutParams<MarginLayoutParams> {
topMargin = shape.topPadding.toInt() topMargin = shape.topPadding.toInt()
bottomMargin = shape.bottomPadding.toInt() bottomMargin = shape.bottomPadding.toInt()
@ -208,29 +221,13 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
return projections 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 { override fun getColorizerProjections(coordinateRoot: ViewGroup): ProjectionList {
projections.clear() 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 return projections
} }
@ -277,6 +274,36 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
override fun shouldProjectContent(): Boolean = false 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 { private fun MessageRecord.buildMessageId(): Long {
return if (isMms) -id else id return if (isMms) -id else id
} }
@ -472,8 +499,14 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
} }
binding.conversationItemFooterBackground.visible = true binding.conversationItemFooterBackground.visible = true
binding.conversationItemFooterBackground.background = conversationItemFooterBackground footerDrawable.setChatColors(
conversationItemFooterBackground.fillColor = themeDelegate.getFooterBubbleColor(conversationMessage) if (binding.isIncoming) {
ChatColors.forColor(ChatColors.Id.NotSet, themeDelegate.getFooterBubbleColor(conversationMessage))
} else {
conversationMessage.threadRecipient.chatColors
},
footerCorners
)
} }
private fun presentDate(shape: V2ConversationItemShape.MessageShape) { private fun presentDate(shape: V2ConversationItemShape.MessageShape) {