Add several performance improvements to ConversationItemV2.
This commit is contained in:
parent
32ae4393e2
commit
4494d8652d
10 changed files with 342 additions and 128 deletions
|
@ -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? {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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?) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -63,7 +63,7 @@ fun V2ConversationItemTextOnlyIncomingBinding.bridge(): V2ConversationItemTextOn
|
|||
conversationItemFooterBackground = conversationItemFooterBackground,
|
||||
conversationItemAlert = null,
|
||||
conversationItemFooterSpace = null,
|
||||
isIncoming = false
|
||||
isIncoming = true
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue