Fix CI V2 layout bounds when item has a reaction.
This commit is contained in:
parent
3040b70100
commit
4590655dc5
7 changed files with 52 additions and 31 deletions
|
@ -18,20 +18,24 @@ class V2ConversationItemLayout @JvmOverloads constructor(
|
||||||
attrs: AttributeSet? = null
|
attrs: AttributeSet? = null
|
||||||
) : ConstraintLayout(context, attrs) {
|
) : ConstraintLayout(context, attrs) {
|
||||||
|
|
||||||
private var onMeasureListener: OnMeasureListener? = null
|
private var onMeasureListeners: Set<OnMeasureListener> = emptySet()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the onMeasureListener to be invoked by this view whenever onMeasure is called.
|
* Set the onMeasureListener to be invoked by this view whenever onMeasure is called.
|
||||||
*/
|
*/
|
||||||
fun setOnMeasureListener(onMeasureListener: OnMeasureListener?) {
|
fun addOnMeasureListener(onMeasureListener: OnMeasureListener) {
|
||||||
this.onMeasureListener = onMeasureListener
|
this.onMeasureListeners += onMeasureListener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeOnMeasureListener(onMeasureListener: OnMeasureListener) {
|
||||||
|
this.onMeasureListeners -= onMeasureListener
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
onMeasureListener?.onPreMeasure()
|
onMeasureListeners.forEach { it.onPreMeasure() }
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
val remeasure = onMeasureListener?.onPostMeasure() ?: false
|
val remeasure = onMeasureListeners.map { it.onPostMeasure() }.any { it }
|
||||||
if (remeasure) {
|
if (remeasure) {
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ class V2ConversationItemShape(
|
||||||
private var smallRadius: Float = 4f.dp
|
private var smallRadius: Float = 4f.dp
|
||||||
|
|
||||||
private var collapsedSpacing: Float = 1f.dp
|
private var collapsedSpacing: Float = 1f.dp
|
||||||
private var defaultSpacing: Float = 8f.dp
|
private var defaultSpacing: Float = 6f.dp
|
||||||
|
|
||||||
private val clusterTimeout = 3.minutes
|
private val clusterTimeout = 3.minutes
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,6 @@ class V2ConversationItemShape(
|
||||||
)
|
)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var spacing: Pair<Float, Float> = Pair(defaultSpacing, defaultSpacing)
|
|
||||||
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.
|
||||||
|
@ -59,25 +56,21 @@ class V2ConversationItemShape(
|
||||||
|
|
||||||
if (isSingularMessage(currentMessage, previousMessage, nextMessage, isGroupThread)) {
|
if (isSingularMessage(currentMessage, previousMessage, nextMessage, isGroupThread)) {
|
||||||
setBodyBubbleCorners(isLtr, bigRadius, bigRadius, bigRadius, bigRadius)
|
setBodyBubbleCorners(isLtr, bigRadius, bigRadius, bigRadius, bigRadius)
|
||||||
spacing = Pair(defaultSpacing, defaultSpacing)
|
|
||||||
return MessageShape.SINGLE
|
return MessageShape.SINGLE
|
||||||
} else if (isStartOfMessageCluster(currentMessage, previousMessage, isGroupThread)) {
|
} else if (isStartOfMessageCluster(currentMessage, previousMessage, isGroupThread)) {
|
||||||
val bottomEnd = if (currentMessage.isOutgoing) smallRadius else bigRadius
|
val bottomEnd = if (currentMessage.isOutgoing) smallRadius else bigRadius
|
||||||
val bottomStart = if (currentMessage.isOutgoing) bigRadius else smallRadius
|
val bottomStart = if (currentMessage.isOutgoing) bigRadius else smallRadius
|
||||||
setBodyBubbleCorners(isLtr, bigRadius, bigRadius, bottomEnd, bottomStart)
|
setBodyBubbleCorners(isLtr, bigRadius, bigRadius, bottomEnd, bottomStart)
|
||||||
spacing = Pair(defaultSpacing, collapsedSpacing)
|
|
||||||
return MessageShape.START
|
return MessageShape.START
|
||||||
} else if (isEndOfMessageCluster(currentMessage, nextMessage)) {
|
} else if (isEndOfMessageCluster(currentMessage, nextMessage)) {
|
||||||
val topStart = if (currentMessage.isOutgoing) bigRadius else smallRadius
|
val topStart = if (currentMessage.isOutgoing) bigRadius else smallRadius
|
||||||
val topEnd = if (currentMessage.isOutgoing) smallRadius else bigRadius
|
val topEnd = if (currentMessage.isOutgoing) smallRadius else bigRadius
|
||||||
setBodyBubbleCorners(isLtr, topStart, topEnd, bigRadius, bigRadius)
|
setBodyBubbleCorners(isLtr, topStart, topEnd, bigRadius, bigRadius)
|
||||||
spacing = Pair(collapsedSpacing, defaultSpacing)
|
|
||||||
return MessageShape.END
|
return MessageShape.END
|
||||||
} else {
|
} else {
|
||||||
val start = if (currentMessage.isOutgoing) bigRadius else smallRadius
|
val start = if (currentMessage.isOutgoing) bigRadius else smallRadius
|
||||||
val end = if (currentMessage.isOutgoing) smallRadius else bigRadius
|
val end = if (currentMessage.isOutgoing) smallRadius else bigRadius
|
||||||
setBodyBubbleCorners(isLtr, start, end, end, start)
|
setBodyBubbleCorners(isLtr, start, end, end, start)
|
||||||
spacing = Pair(collapsedSpacing, collapsedSpacing)
|
|
||||||
return MessageShape.MIDDLE
|
return MessageShape.MIDDLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,25 +149,28 @@ class V2ConversationItemShape(
|
||||||
return abs(currentMessage.dateSent - previousMessage.dateSent) <= clusterTimeout.inWholeMilliseconds
|
return abs(currentMessage.dateSent - previousMessage.dateSent) <= clusterTimeout.inWholeMilliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MessageShape {
|
enum class MessageShape(
|
||||||
|
val topPadding: Float,
|
||||||
|
val bottomPadding: Float
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* This message stands alone.
|
* This message stands alone.
|
||||||
*/
|
*/
|
||||||
SINGLE,
|
SINGLE(defaultSpacing, defaultSpacing),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This message is the start of a cluster
|
* This message is the start of a cluster
|
||||||
*/
|
*/
|
||||||
START,
|
START(defaultSpacing, collapsedSpacing),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This message is the end of a cluster
|
* This message is the end of a cluster
|
||||||
*/
|
*/
|
||||||
END,
|
END(collapsedSpacing, defaultSpacing),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This message is in the middle of a cluster
|
* This message is in the middle of a cluster
|
||||||
*/
|
*/
|
||||||
MIDDLE
|
MIDDLE(collapsedSpacing, collapsedSpacing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ package org.thoughtcrime.securesms.conversation.v2.items
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
|
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.MaterialShapeDrawable
|
||||||
import com.google.android.material.shape.ShapeAppearanceModel
|
import com.google.android.material.shape.ShapeAppearanceModel
|
||||||
|
@ -24,7 +26,6 @@ import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.thoughtcrime.securesms.util.Projection
|
import org.thoughtcrime.securesms.util.Projection
|
||||||
import org.thoughtcrime.securesms.util.ProjectionList
|
import org.thoughtcrime.securesms.util.ProjectionList
|
||||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
|
||||||
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.hasNoBubble
|
import org.thoughtcrime.securesms.util.hasNoBubble
|
||||||
|
@ -81,8 +82,10 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||||
override val contactPhotoHolderView: View? = binding.senderPhoto
|
override val contactPhotoHolderView: View? = binding.senderPhoto
|
||||||
override val badgeImageView: View? = binding.senderBadge
|
override val badgeImageView: View? = binding.senderBadge
|
||||||
|
|
||||||
|
private var reactionMeasureListener: ReactionMeasureListener = ReactionMeasureListener()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.root.setOnMeasureListener(footerDelegate)
|
binding.root.addOnMeasureListener(footerDelegate)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(model: Model) {
|
override fun bind(model: Model) {
|
||||||
|
@ -116,10 +119,12 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||||
presentFooterExpiry(shape)
|
presentFooterExpiry(shape)
|
||||||
presentAlert()
|
presentAlert()
|
||||||
presentSender()
|
presentSender()
|
||||||
|
presentReactions()
|
||||||
|
|
||||||
val (topPadding, bottomPadding) = shapeDelegate.spacing
|
itemView.updateLayoutParams<MarginLayoutParams> {
|
||||||
ViewUtil.setPaddingTop(itemView, topPadding.toInt())
|
topMargin = shape.topPadding.toInt()
|
||||||
ViewUtil.setPaddingBottom(itemView, bottomPadding.toInt())
|
bottomMargin = shape.bottomPadding.toInt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAdapterPosition(recyclerView: RecyclerView): Int = bindingAdapterPosition
|
override fun getAdapterPosition(recyclerView: RecyclerView): Int = bindingAdapterPosition
|
||||||
|
@ -245,6 +250,16 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun presentReactions() {
|
||||||
|
if (conversationMessage.messageRecord.reactions.isEmpty()) {
|
||||||
|
binding.conversationItemReactions.clear()
|
||||||
|
binding.root.removeOnMeasureListener(reactionMeasureListener)
|
||||||
|
} else {
|
||||||
|
reactionMeasureListener.onPostMeasure()
|
||||||
|
binding.root.addOnMeasureListener(reactionMeasureListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun presentFooterBackground(shape: V2ConversationItemShape.MessageShape) {
|
private fun presentFooterBackground(shape: V2ConversationItemShape.MessageShape) {
|
||||||
if (!binding.conversationItemBody.isJumbomoji ||
|
if (!binding.conversationItemBody.isJumbomoji ||
|
||||||
!conversationContext.hasWallpaper() ||
|
!conversationContext.hasWallpaper() ||
|
||||||
|
@ -347,4 +362,12 @@ class V2TextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||||
override fun disallowSwipe(latestDownX: Float, latestDownY: Float): Boolean {
|
override fun disallowSwipe(latestDownX: Float, latestDownY: Float): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class ReactionMeasureListener : V2ConversationItemLayout.OnMeasureListener {
|
||||||
|
override fun onPreMeasure() = Unit
|
||||||
|
|
||||||
|
override fun onPostMeasure(): Boolean {
|
||||||
|
return binding.conversationItemReactions.setReactions(conversationMessage.messageRecord.reactions, binding.conversationItemBodyWrapper.width)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,9 +63,9 @@ public class ReactionsConversationView extends LinearLayout {
|
||||||
removeAllViews();
|
removeAllViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReactions(@NonNull List<ReactionRecord> records, int bubbleWidth) {
|
public boolean setReactions(@NonNull List<ReactionRecord> records, int bubbleWidth) {
|
||||||
if (records.equals(this.records) && this.bubbleWidth == bubbleWidth) {
|
if (records.equals(this.records) && this.bubbleWidth == bubbleWidth) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.records.clear();
|
this.records.clear();
|
||||||
|
@ -102,6 +102,8 @@ public class ReactionsConversationView extends LinearLayout {
|
||||||
ViewUtil.setLeftMargin(this, OUTER_MARGIN);
|
ViewUtil.setLeftMargin(this, OUTER_MARGIN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull List<Reaction> buildSortedReactionsList(@NonNull List<ReactionRecord> records) {
|
private static @NonNull List<Reaction> buildSortedReactionsList(@NonNull List<ReactionRecord> records) {
|
||||||
|
|
|
@ -62,7 +62,6 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:cardElevation="0dp"
|
app:cardElevation="0dp"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toEndOf="@id/contact_photo"
|
app:layout_constraintStart_toEndOf="@id/contact_photo"
|
||||||
|
@ -157,7 +156,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="-4dp"
|
android:layout_marginTop="-4dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper"
|
app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper"
|
||||||
app:layout_constraintTop_toBottomOf="@id/conversation_item_body_wrapper"
|
app:layout_constraintTop_toBottomOf="@id/conversation_item_body_wrapper"
|
||||||
app:rcv_outgoing="false" />
|
app:rcv_outgoing="false" />
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
android:layout_marginStart="48dp"
|
android:layout_marginStart="48dp"
|
||||||
app:cardElevation="0dp"
|
app:cardElevation="0dp"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/conversation_item_alert"
|
app:layout_constraintEnd_toStartOf="@id/conversation_item_alert"
|
||||||
app:layout_constraintHorizontal_bias="1"
|
app:layout_constraintHorizontal_bias="1"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -152,7 +151,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="-4dp"
|
android:layout_marginTop="-4dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper"
|
app:layout_constraintEnd_toEndOf="@id/conversation_item_body_wrapper"
|
||||||
app:layout_constraintTop_toBottomOf="@id/conversation_item_body_wrapper"
|
app:layout_constraintTop_toBottomOf="@id/conversation_item_body_wrapper"
|
||||||
app:rcv_outgoing="false" />
|
app:rcv_outgoing="false" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue